Add comprehensive frontend UI and distributed infrastructure

Frontend Enhancements:
- Complete React TypeScript frontend with modern UI components
- Distributed workflows management interface with real-time updates
- Socket.IO integration for live agent status monitoring
- Agent management dashboard with cluster visualization
- Project management interface with metrics and task tracking
- Responsive design with proper error handling and loading states

Backend Infrastructure:
- Distributed coordinator for multi-agent workflow orchestration
- Cluster management API with comprehensive agent operations
- Enhanced database models for agents and projects
- Project service for filesystem-based project discovery
- Performance monitoring and metrics collection
- Comprehensive API documentation and error handling

Documentation:
- Complete distributed development guide (README_DISTRIBUTED.md)
- Comprehensive development report with architecture insights
- System configuration templates and deployment guides

The platform now provides a complete web interface for managing the distributed AI cluster
with real-time monitoring, workflow orchestration, and agent coordination capabilities.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
anthonyrawlins
2025-07-10 08:41:59 +10:00
parent fc0eec91ef
commit 85bf1341f3
28348 changed files with 2646896 additions and 69 deletions

View File

@@ -0,0 +1,61 @@
import { styled, keyframes } from 'goober';
const circleAnimation = keyframes`
from {
transform: scale(0) rotate(45deg);
opacity: 0;
}
to {
transform: scale(1) rotate(45deg);
opacity: 1;
}`;
const checkmarkAnimation = keyframes`
0% {
height: 0;
width: 0;
opacity: 0;
}
40% {
height: 0;
width: 6px;
opacity: 1;
}
100% {
opacity: 1;
height: 10px;
}`;
export interface CheckmarkTheme {
primary?: string;
secondary?: string;
}
export const CheckmarkIcon = styled('div')<CheckmarkTheme>`
width: 20px;
opacity: 0;
height: 20px;
border-radius: 10px;
background: ${(p) => p.primary || '#61d345'};
position: relative;
transform: rotate(45deg);
animation: ${circleAnimation} 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275)
forwards;
animation-delay: 100ms;
&:after {
content: '';
box-sizing: border-box;
animation: ${checkmarkAnimation} 0.2s ease-out forwards;
opacity: 0;
animation-delay: 200ms;
position: absolute;
border-right: 2px solid;
border-bottom: 2px solid;
border-color: ${(p) => p.secondary || '#fff'};
bottom: 6px;
left: 6px;
height: 10px;
width: 6px;
}
`;

View File

@@ -0,0 +1,71 @@
import { styled, keyframes } from 'goober';
const circleAnimation = keyframes`
from {
transform: scale(0) rotate(45deg);
opacity: 0;
}
to {
transform: scale(1) rotate(45deg);
opacity: 1;
}`;
const firstLineAnimation = keyframes`
from {
transform: scale(0);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}`;
const secondLineAnimation = keyframes`
from {
transform: scale(0) rotate(90deg);
opacity: 0;
}
to {
transform: scale(1) rotate(90deg);
opacity: 1;
}`;
export interface ErrorTheme {
primary?: string;
secondary?: string;
}
export const ErrorIcon = styled('div')<ErrorTheme>`
width: 20px;
opacity: 0;
height: 20px;
border-radius: 10px;
background: ${(p) => p.primary || '#ff4b4b'};
position: relative;
transform: rotate(45deg);
animation: ${circleAnimation} 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275)
forwards;
animation-delay: 100ms;
&:after,
&:before {
content: '';
animation: ${firstLineAnimation} 0.15s ease-out forwards;
animation-delay: 150ms;
position: absolute;
border-radius: 3px;
opacity: 0;
background: ${(p) => p.secondary || '#fff'};
bottom: 9px;
left: 4px;
height: 2px;
width: 12px;
}
&:before {
animation: ${secondLineAnimation} 0.15s ease-out forwards;
animation-delay: 180ms;
transform: rotate(90deg);
}
`;

View File

@@ -0,0 +1,26 @@
import { styled, keyframes } from 'goober';
const rotate = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`;
export interface LoaderTheme {
primary?: string;
secondary?: string;
}
export const LoaderIcon = styled('div')<LoaderTheme>`
width: 12px;
height: 12px;
box-sizing: border-box;
border: 2px solid;
border-radius: 100%;
border-color: ${(p) => p.secondary || '#e0e0e0'};
border-right-color: ${(p) => p.primary || '#616161'};
animation: ${rotate} 1s linear infinite;
`;

View File

@@ -0,0 +1,111 @@
import * as React from 'react';
import { styled, keyframes } from 'goober';
import { Toast, ToastPosition, resolveValue, Renderable } from '../core/types';
import { ToastIcon } from './toast-icon';
import { prefersReducedMotion } from '../core/utils';
const enterAnimation = (factor: number) => `
0% {transform: translate3d(0,${factor * -200}%,0) scale(.6); opacity:.5;}
100% {transform: translate3d(0,0,0) scale(1); opacity:1;}
`;
const exitAnimation = (factor: number) => `
0% {transform: translate3d(0,0,-1px) scale(1); opacity:1;}
100% {transform: translate3d(0,${factor * -150}%,-1px) scale(.6); opacity:0;}
`;
const fadeInAnimation = `0%{opacity:0;} 100%{opacity:1;}`;
const fadeOutAnimation = `0%{opacity:1;} 100%{opacity:0;}`;
const ToastBarBase = styled('div')`
display: flex;
align-items: center;
background: #fff;
color: #363636;
line-height: 1.3;
will-change: transform;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1), 0 3px 3px rgba(0, 0, 0, 0.05);
max-width: 350px;
pointer-events: auto;
padding: 8px 10px;
border-radius: 8px;
`;
const Message = styled('div')`
display: flex;
justify-content: center;
margin: 4px 10px;
color: inherit;
flex: 1 1 auto;
white-space: pre-line;
`;
interface ToastBarProps {
toast: Toast;
position?: ToastPosition;
style?: React.CSSProperties;
children?: (components: {
icon: Renderable;
message: Renderable;
}) => Renderable;
}
const getAnimationStyle = (
position: ToastPosition,
visible: boolean
): React.CSSProperties => {
const top = position.includes('top');
const factor = top ? 1 : -1;
const [enter, exit] = prefersReducedMotion()
? [fadeInAnimation, fadeOutAnimation]
: [enterAnimation(factor), exitAnimation(factor)];
return {
animation: visible
? `${keyframes(enter)} 0.35s cubic-bezier(.21,1.02,.73,1) forwards`
: `${keyframes(exit)} 0.4s forwards cubic-bezier(.06,.71,.55,1)`,
};
};
export const ToastBar: React.FC<ToastBarProps> = React.memo(
({ toast, position, style, children }) => {
const animationStyle: React.CSSProperties = toast.height
? getAnimationStyle(
toast.position || position || 'top-center',
toast.visible
)
: { opacity: 0 };
const icon = <ToastIcon toast={toast} />;
const message = (
<Message {...toast.ariaProps}>
{resolveValue(toast.message, toast)}
</Message>
);
return (
<ToastBarBase
className={toast.className}
style={{
...animationStyle,
...style,
...toast.style,
}}
>
{typeof children === 'function' ? (
children({
icon,
message,
})
) : (
<>
{icon}
{message}
</>
)}
</ToastBarBase>
);
}
);

View File

@@ -0,0 +1,77 @@
import * as React from 'react';
import { styled, keyframes } from 'goober';
import { Toast } from '../core/types';
import { ErrorIcon, ErrorTheme } from './error';
import { LoaderIcon, LoaderTheme } from './loader';
import { CheckmarkIcon, CheckmarkTheme } from './checkmark';
const StatusWrapper = styled('div')`
position: absolute;
`;
const IndicatorWrapper = styled('div')`
position: relative;
display: flex;
justify-content: center;
align-items: center;
min-width: 20px;
min-height: 20px;
`;
const enter = keyframes`
from {
transform: scale(0.6);
opacity: 0.4;
}
to {
transform: scale(1);
opacity: 1;
}`;
export const AnimatedIconWrapper = styled('div')`
position: relative;
transform: scale(0.6);
opacity: 0.4;
min-width: 20px;
animation: ${enter} 0.3s 0.12s cubic-bezier(0.175, 0.885, 0.32, 1.275)
forwards;
`;
export type IconThemes = Partial<{
success: CheckmarkTheme;
error: ErrorTheme;
loading: LoaderTheme;
}>;
export const ToastIcon: React.FC<{
toast: Toast;
}> = ({ toast }) => {
const { icon, type, iconTheme } = toast;
if (icon !== undefined) {
if (typeof icon === 'string') {
return <AnimatedIconWrapper>{icon}</AnimatedIconWrapper>;
} else {
return icon;
}
}
if (type === 'blank') {
return null;
}
return (
<IndicatorWrapper>
<LoaderIcon {...iconTheme} />
{type !== 'loading' && (
<StatusWrapper>
{type === 'error' ? (
<ErrorIcon {...iconTheme} />
) : (
<CheckmarkIcon {...iconTheme} />
)}
</StatusWrapper>
)}
</IndicatorWrapper>
);
};

View File

@@ -0,0 +1,142 @@
import { css, setup } from 'goober';
import * as React from 'react';
import {
resolveValue,
ToasterProps,
ToastPosition,
ToastWrapperProps,
} from '../core/types';
import { useToaster } from '../core/use-toaster';
import { prefersReducedMotion } from '../core/utils';
import { ToastBar } from './toast-bar';
setup(React.createElement);
const ToastWrapper = ({
id,
className,
style,
onHeightUpdate,
children,
}: ToastWrapperProps) => {
const ref = React.useCallback(
(el: HTMLElement | null) => {
if (el) {
const updateHeight = () => {
const height = el.getBoundingClientRect().height;
onHeightUpdate(id, height);
};
updateHeight();
new MutationObserver(updateHeight).observe(el, {
subtree: true,
childList: true,
characterData: true,
});
}
},
[id, onHeightUpdate]
);
return (
<div ref={ref} className={className} style={style}>
{children}
</div>
);
};
const getPositionStyle = (
position: ToastPosition,
offset: number
): React.CSSProperties => {
const top = position.includes('top');
const verticalStyle: React.CSSProperties = top ? { top: 0 } : { bottom: 0 };
const horizontalStyle: React.CSSProperties = position.includes('center')
? {
justifyContent: 'center',
}
: position.includes('right')
? {
justifyContent: 'flex-end',
}
: {};
return {
left: 0,
right: 0,
display: 'flex',
position: 'absolute',
transition: prefersReducedMotion()
? undefined
: `all 230ms cubic-bezier(.21,1.02,.73,1)`,
transform: `translateY(${offset * (top ? 1 : -1)}px)`,
...verticalStyle,
...horizontalStyle,
};
};
const activeClass = css`
z-index: 9999;
> * {
pointer-events: auto;
}
`;
const DEFAULT_OFFSET = 16;
export const Toaster: React.FC<ToasterProps> = ({
reverseOrder,
position = 'top-center',
toastOptions,
gutter,
children,
containerStyle,
containerClassName,
}) => {
const { toasts, handlers } = useToaster(toastOptions);
return (
<div
id="_rht_toaster"
style={{
position: 'fixed',
zIndex: 9999,
top: DEFAULT_OFFSET,
left: DEFAULT_OFFSET,
right: DEFAULT_OFFSET,
bottom: DEFAULT_OFFSET,
pointerEvents: 'none',
...containerStyle,
}}
className={containerClassName}
onMouseEnter={handlers.startPause}
onMouseLeave={handlers.endPause}
>
{toasts.map((t) => {
const toastPosition = t.position || position;
const offset = handlers.calculateOffset(t, {
reverseOrder,
gutter,
defaultPosition: position,
});
const positionStyle = getPositionStyle(toastPosition, offset);
return (
<ToastWrapper
id={t.id}
key={t.id}
onHeightUpdate={handlers.updateHeight}
className={t.visible ? activeClass : ''}
style={positionStyle}
>
{t.type === 'custom' ? (
resolveValue(t.message, t)
) : children ? (
children(t)
) : (
<ToastBar toast={t} position={toastPosition} />
)}
</ToastWrapper>
);
})}
</div>
);
};

186
frontend/node_modules/react-hot-toast/src/core/store.ts generated vendored Normal file
View File

@@ -0,0 +1,186 @@
import { useEffect, useState, useRef } from 'react';
import { DefaultToastOptions, Toast, ToastType } from './types';
const TOAST_LIMIT = 20;
export enum ActionType {
ADD_TOAST,
UPDATE_TOAST,
UPSERT_TOAST,
DISMISS_TOAST,
REMOVE_TOAST,
START_PAUSE,
END_PAUSE,
}
type Action =
| {
type: ActionType.ADD_TOAST;
toast: Toast;
}
| {
type: ActionType.UPSERT_TOAST;
toast: Toast;
}
| {
type: ActionType.UPDATE_TOAST;
toast: Partial<Toast>;
}
| {
type: ActionType.DISMISS_TOAST;
toastId?: string;
}
| {
type: ActionType.REMOVE_TOAST;
toastId?: string;
}
| {
type: ActionType.START_PAUSE;
time: number;
}
| {
type: ActionType.END_PAUSE;
time: number;
};
interface State {
toasts: Toast[];
pausedAt: number | undefined;
}
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case ActionType.ADD_TOAST:
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
};
case ActionType.UPDATE_TOAST:
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t
),
};
case ActionType.UPSERT_TOAST:
const { toast } = action;
return reducer(state, {
type: state.toasts.find((t) => t.id === toast.id)
? ActionType.UPDATE_TOAST
: ActionType.ADD_TOAST,
toast,
});
case ActionType.DISMISS_TOAST:
const { toastId } = action;
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
dismissed: true,
visible: false,
}
: t
),
};
case ActionType.REMOVE_TOAST:
if (action.toastId === undefined) {
return {
...state,
toasts: [],
};
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
};
case ActionType.START_PAUSE:
return {
...state,
pausedAt: action.time,
};
case ActionType.END_PAUSE:
const diff = action.time - (state.pausedAt || 0);
return {
...state,
pausedAt: undefined,
toasts: state.toasts.map((t) => ({
...t,
pauseDuration: t.pauseDuration + diff,
})),
};
}
};
const listeners: Array<(state: State) => void> = [];
let memoryState: State = { toasts: [], pausedAt: undefined };
export const dispatch = (action: Action) => {
memoryState = reducer(memoryState, action);
listeners.forEach((listener) => {
listener(memoryState);
});
};
export const defaultTimeouts: {
[key in ToastType]: number;
} = {
blank: 4000,
error: 4000,
success: 2000,
loading: Infinity,
custom: 4000,
};
export const useStore = (toastOptions: DefaultToastOptions = {}): State => {
const [state, setState] = useState<State>(memoryState);
const initial = useRef(memoryState);
// TODO: Switch to useSyncExternalStore when targeting React 18+
useEffect(() => {
if (initial.current !== memoryState) {
setState(memoryState);
}
listeners.push(setState);
return () => {
const index = listeners.indexOf(setState);
if (index > -1) {
listeners.splice(index, 1);
}
};
}, []);
const mergedToasts = state.toasts.map((t) => ({
...toastOptions,
...toastOptions[t.type],
...t,
removeDelay:
t.removeDelay ||
toastOptions[t.type]?.removeDelay ||
toastOptions?.removeDelay,
duration:
t.duration ||
toastOptions[t.type]?.duration ||
toastOptions?.duration ||
defaultTimeouts[t.type],
style: {
...toastOptions.style,
...toastOptions[t.type]?.style,
...t.style,
},
}));
return {
...state,
toasts: mergedToasts,
};
};

111
frontend/node_modules/react-hot-toast/src/core/toast.ts generated vendored Normal file
View File

@@ -0,0 +1,111 @@
import {
Renderable,
Toast,
ToastOptions,
ToastType,
DefaultToastOptions,
ValueOrFunction,
resolveValue,
} from './types';
import { genId } from './utils';
import { dispatch, ActionType } from './store';
type Message = ValueOrFunction<Renderable, Toast>;
type ToastHandler = (message: Message, options?: ToastOptions) => string;
const createToast = (
message: Message,
type: ToastType = 'blank',
opts?: ToastOptions
): Toast => ({
createdAt: Date.now(),
visible: true,
dismissed: false,
type,
ariaProps: {
role: 'status',
'aria-live': 'polite',
},
message,
pauseDuration: 0,
...opts,
id: opts?.id || genId(),
});
const createHandler =
(type?: ToastType): ToastHandler =>
(message, options) => {
const toast = createToast(message, type, options);
dispatch({ type: ActionType.UPSERT_TOAST, toast });
return toast.id;
};
const toast = (message: Message, opts?: ToastOptions) =>
createHandler('blank')(message, opts);
toast.error = createHandler('error');
toast.success = createHandler('success');
toast.loading = createHandler('loading');
toast.custom = createHandler('custom');
toast.dismiss = (toastId?: string) => {
dispatch({
type: ActionType.DISMISS_TOAST,
toastId,
});
};
toast.remove = (toastId?: string) =>
dispatch({ type: ActionType.REMOVE_TOAST, toastId });
toast.promise = <T>(
promise: Promise<T> | (() => Promise<T>),
msgs: {
loading: Renderable;
success?: ValueOrFunction<Renderable, T>;
error?: ValueOrFunction<Renderable, any>;
},
opts?: DefaultToastOptions
) => {
const id = toast.loading(msgs.loading, { ...opts, ...opts?.loading });
if (typeof promise === 'function') {
promise = promise();
}
promise
.then((p) => {
const successMessage = msgs.success
? resolveValue(msgs.success, p)
: undefined;
if (successMessage) {
toast.success(successMessage, {
id,
...opts,
...opts?.success,
});
} else {
toast.dismiss(id);
}
return p;
})
.catch((e) => {
const errorMessage = msgs.error ? resolveValue(msgs.error, e) : undefined;
if (errorMessage) {
toast.error(errorMessage, {
id,
...opts,
...opts?.error,
});
} else {
toast.dismiss(id);
}
});
return promise;
};
export { toast };

View File

@@ -0,0 +1,94 @@
import { CSSProperties } from 'react';
export type ToastType = 'success' | 'error' | 'loading' | 'blank' | 'custom';
export type ToastPosition =
| 'top-left'
| 'top-center'
| 'top-right'
| 'bottom-left'
| 'bottom-center'
| 'bottom-right';
export type Renderable = React.ReactElement | string | null;
export interface IconTheme {
primary: string;
secondary: string;
}
export type ValueFunction<TValue, TArg> = (arg: TArg) => TValue;
export type ValueOrFunction<TValue, TArg> =
| TValue
| ValueFunction<TValue, TArg>;
const isFunction = <TValue, TArg>(
valOrFunction: ValueOrFunction<TValue, TArg>
): valOrFunction is ValueFunction<TValue, TArg> =>
typeof valOrFunction === 'function';
export const resolveValue = <TValue, TArg>(
valOrFunction: ValueOrFunction<TValue, TArg>,
arg: TArg
): TValue => (isFunction(valOrFunction) ? valOrFunction(arg) : valOrFunction);
export interface Toast {
type: ToastType;
id: string;
message: ValueOrFunction<Renderable, Toast>;
icon?: Renderable;
duration?: number;
pauseDuration: number;
position?: ToastPosition;
removeDelay?: number;
ariaProps: {
role: 'status' | 'alert';
'aria-live': 'assertive' | 'off' | 'polite';
};
style?: CSSProperties;
className?: string;
iconTheme?: IconTheme;
createdAt: number;
visible: boolean;
dismissed: boolean;
height?: number;
}
export type ToastOptions = Partial<
Pick<
Toast,
| 'id'
| 'icon'
| 'duration'
| 'ariaProps'
| 'className'
| 'style'
| 'position'
| 'iconTheme'
| 'removeDelay'
>
>;
export type DefaultToastOptions = ToastOptions & {
[key in ToastType]?: ToastOptions;
};
export interface ToasterProps {
position?: ToastPosition;
toastOptions?: DefaultToastOptions;
reverseOrder?: boolean;
gutter?: number;
containerStyle?: React.CSSProperties;
containerClassName?: string;
children?: (toast: Toast) => React.ReactElement;
}
export interface ToastWrapperProps {
id: string;
className?: string;
style?: React.CSSProperties;
onHeightUpdate: (id: string, height: number) => void;
children?: React.ReactNode;
}

View File

@@ -0,0 +1,132 @@
import { useEffect, useCallback } from 'react';
import { dispatch, ActionType, useStore } from './store';
import { toast } from './toast';
import { DefaultToastOptions, Toast, ToastPosition } from './types';
const updateHeight = (toastId: string, height: number) => {
dispatch({
type: ActionType.UPDATE_TOAST,
toast: { id: toastId, height },
});
};
const startPause = () => {
dispatch({
type: ActionType.START_PAUSE,
time: Date.now(),
});
};
const toastTimeouts = new Map<Toast['id'], ReturnType<typeof setTimeout>>();
export const REMOVE_DELAY = 1000;
const addToRemoveQueue = (toastId: string, removeDelay = REMOVE_DELAY) => {
if (toastTimeouts.has(toastId)) {
return;
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId);
dispatch({
type: ActionType.REMOVE_TOAST,
toastId: toastId,
});
}, removeDelay);
toastTimeouts.set(toastId, timeout);
};
export const useToaster = (toastOptions?: DefaultToastOptions) => {
const { toasts, pausedAt } = useStore(toastOptions);
useEffect(() => {
if (pausedAt) {
return;
}
const now = Date.now();
const timeouts = toasts.map((t) => {
if (t.duration === Infinity) {
return;
}
const durationLeft =
(t.duration || 0) + t.pauseDuration - (now - t.createdAt);
if (durationLeft < 0) {
if (t.visible) {
toast.dismiss(t.id);
}
return;
}
return setTimeout(() => toast.dismiss(t.id), durationLeft);
});
return () => {
timeouts.forEach((timeout) => timeout && clearTimeout(timeout));
};
}, [toasts, pausedAt]);
const endPause = useCallback(() => {
if (pausedAt) {
dispatch({ type: ActionType.END_PAUSE, time: Date.now() });
}
}, [pausedAt]);
const calculateOffset = useCallback(
(
toast: Toast,
opts?: {
reverseOrder?: boolean;
gutter?: number;
defaultPosition?: ToastPosition;
}
) => {
const { reverseOrder = false, gutter = 8, defaultPosition } = opts || {};
const relevantToasts = toasts.filter(
(t) =>
(t.position || defaultPosition) ===
(toast.position || defaultPosition) && t.height
);
const toastIndex = relevantToasts.findIndex((t) => t.id === toast.id);
const toastsBefore = relevantToasts.filter(
(toast, i) => i < toastIndex && toast.visible
).length;
const offset = relevantToasts
.filter((t) => t.visible)
.slice(...(reverseOrder ? [toastsBefore + 1] : [0, toastsBefore]))
.reduce((acc, t) => acc + (t.height || 0) + gutter, 0);
return offset;
},
[toasts]
);
useEffect(() => {
// Add dismissed toasts to remove queue
toasts.forEach((toast) => {
if (toast.dismissed) {
addToRemoveQueue(toast.id, toast.removeDelay);
} else {
// If toast becomes visible again, remove it from the queue
const timeout = toastTimeouts.get(toast.id);
if (timeout) {
clearTimeout(timeout);
toastTimeouts.delete(toast.id);
}
}
});
}, [toasts]);
return {
toasts,
handlers: {
updateHeight,
startPause,
endPause,
calculateOffset,
},
};
};

View File

@@ -0,0 +1,19 @@
export const genId = (() => {
let count = 0;
return () => {
return (++count).toString();
};
})();
export const prefersReducedMotion = (() => {
// Cache result
let shouldReduceMotion: boolean | undefined = undefined;
return () => {
if (shouldReduceMotion === undefined && typeof window !== 'undefined') {
const mediaQuery = matchMedia('(prefers-reduced-motion: reduce)');
shouldReduceMotion = !mediaQuery || mediaQuery.matches;
}
return shouldReduceMotion;
};
})();

View File

@@ -0,0 +1,21 @@
import { toast } from '../core/toast';
export type {
DefaultToastOptions,
IconTheme,
Renderable,
Toast,
ToasterProps,
ToastOptions,
ToastPosition,
ToastType,
ValueFunction,
ValueOrFunction,
} from '../core/types';
export { resolveValue } from '../core/types';
export { useToaster } from '../core/use-toaster';
export { useStore as useToasterStore } from '../core/store';
export { toast };
export default toast;

13
frontend/node_modules/react-hot-toast/src/index.ts generated vendored Normal file
View File

@@ -0,0 +1,13 @@
import { toast } from './core/toast';
export * from './headless';
export { ToastBar } from './components/toast-bar';
export { ToastIcon } from './components/toast-icon';
export { Toaster } from './components/toaster';
export { CheckmarkIcon } from './components/checkmark';
export { ErrorIcon } from './components/error';
export { LoaderIcon } from './components/loader';
export { toast };
export default toast;