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

336
frontend/node_modules/react-smooth/src/Animate.js generated vendored Normal file
View File

@@ -0,0 +1,336 @@
import React, { PureComponent, cloneElement, Children } from 'react';
import PropTypes from 'prop-types';
import { deepEqual } from 'fast-equals';
import createAnimateManager from './AnimateManager';
import { configEasing } from './easing';
import configUpdate from './configUpdate';
import { getTransitionVal, identity } from './util';
class Animate extends PureComponent {
constructor(props, context) {
super(props, context);
const { isActive, attributeName, from, to, steps, children, duration } = this.props;
this.handleStyleChange = this.handleStyleChange.bind(this);
this.changeStyle = this.changeStyle.bind(this);
if (!isActive || duration <= 0) {
this.state = { style: {} };
// if children is a function and animation is not active, set style to 'to'
if (typeof children === 'function') {
this.state = { style: to };
}
return;
}
if (steps && steps.length) {
this.state = { style: steps[0].style };
} else if (from) {
if (typeof children === 'function') {
this.state = {
style: from,
};
return;
}
this.state = {
style: attributeName ? { [attributeName]: from } : from,
};
} else {
this.state = { style: {} };
}
}
componentDidMount() {
const { isActive, canBegin } = this.props;
this.mounted = true;
if (!isActive || !canBegin) {
return;
}
this.runAnimation(this.props);
}
componentDidUpdate(prevProps) {
const { isActive, canBegin, attributeName, shouldReAnimate, to, from: currentFrom } = this.props;
const { style } = this.state;
if (!canBegin) {
return;
}
if (!isActive) {
const newState = {
style: attributeName ? { [attributeName]: to } : to,
};
if (this.state && style) {
if ((attributeName && style[attributeName] !== to) || (!attributeName && style !== to)) {
// eslint-disable-next-line react/no-did-update-set-state
this.setState(newState);
}
}
return;
}
if (deepEqual(prevProps.to, to) && prevProps.canBegin && prevProps.isActive) {
return;
}
const isTriggered = !prevProps.canBegin || !prevProps.isActive;
if (this.manager) {
this.manager.stop();
}
if (this.stopJSAnimation) {
this.stopJSAnimation();
}
const from = isTriggered || shouldReAnimate ? currentFrom : prevProps.to;
if (this.state && style) {
const newState = {
style: attributeName ? { [attributeName]: from } : from,
};
if ((attributeName && style[attributeName] !== from) || (!attributeName && style !== from)) {
// eslint-disable-next-line react/no-did-update-set-state
this.setState(newState);
}
}
this.runAnimation({
...this.props,
from,
begin: 0,
});
}
componentWillUnmount() {
this.mounted = false;
const { onAnimationEnd } = this.props;
if (this.unSubscribe) {
this.unSubscribe();
}
if (this.manager) {
this.manager.stop();
this.manager = null;
}
if (this.stopJSAnimation) {
this.stopJSAnimation();
}
if (onAnimationEnd) {
onAnimationEnd();
}
}
handleStyleChange(style) {
this.changeStyle(style);
}
changeStyle(style) {
if (this.mounted) {
this.setState({
style,
});
}
}
runJSAnimation(props) {
const { from, to, duration, easing, begin, onAnimationEnd, onAnimationStart } = props;
const startAnimation = configUpdate(from, to, configEasing(easing), duration, this.changeStyle);
const finalStartAnimation = () => {
this.stopJSAnimation = startAnimation();
};
this.manager.start([onAnimationStart, begin, finalStartAnimation, duration, onAnimationEnd]);
}
runStepAnimation(props) {
const { steps, begin, onAnimationStart } = props;
const { style: initialStyle, duration: initialTime = 0 } = steps[0];
const addStyle = (sequence, nextItem, index) => {
if (index === 0) {
return sequence;
}
const { duration, easing = 'ease', style, properties: nextProperties, onAnimationEnd } = nextItem;
const preItem = index > 0 ? steps[index - 1] : nextItem;
const properties = nextProperties || Object.keys(style);
if (typeof easing === 'function' || easing === 'spring') {
return [
...sequence,
this.runJSAnimation.bind(this, {
from: preItem.style,
to: style,
duration,
easing,
}),
duration,
];
}
const transition = getTransitionVal(properties, duration, easing);
const newStyle = {
...preItem.style,
...style,
transition,
};
return [...sequence, newStyle, duration, onAnimationEnd].filter(identity);
};
return this.manager.start([
onAnimationStart,
...steps.reduce(addStyle, [initialStyle, Math.max(initialTime, begin)]),
props.onAnimationEnd,
]);
}
runAnimation(props) {
if (!this.manager) {
this.manager = createAnimateManager();
}
const {
begin,
duration,
attributeName,
to: propsTo,
easing,
onAnimationStart,
onAnimationEnd,
steps,
children,
} = props;
const manager = this.manager;
this.unSubscribe = manager.subscribe(this.handleStyleChange);
if (typeof easing === 'function' || typeof children === 'function' || easing === 'spring') {
this.runJSAnimation(props);
return;
}
if (steps.length > 1) {
this.runStepAnimation(props);
return;
}
const to = attributeName ? { [attributeName]: propsTo } : propsTo;
const transition = getTransitionVal(Object.keys(to), duration, easing);
manager.start([onAnimationStart, begin, { ...to, transition }, duration, onAnimationEnd]);
}
render() {
const {
children,
begin,
duration,
attributeName,
easing,
isActive,
steps,
from,
to,
canBegin,
onAnimationEnd,
shouldReAnimate,
onAnimationReStart,
...others
} = this.props;
const count = Children.count(children);
// eslint-disable-next-line react/destructuring-assignment
const stateStyle = this.state.style;
if (typeof children === 'function') {
return children(stateStyle);
}
if (!isActive || count === 0 || duration <= 0) {
return children;
}
const cloneContainer = container => {
const { style = {}, className } = container.props;
const res = cloneElement(container, {
...others,
style: {
...style,
...stateStyle,
},
className,
});
return res;
};
if (count === 1) {
return cloneContainer(Children.only(children));
}
return <div>{Children.map(children, child => cloneContainer(child))}</div>;
}
}
Animate.displayName = 'Animate';
Animate.defaultProps = {
begin: 0,
duration: 1000,
from: '',
to: '',
attributeName: '',
easing: 'ease',
isActive: true,
canBegin: true,
steps: [],
onAnimationEnd: () => {},
onAnimationStart: () => {},
};
Animate.propTypes = {
from: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
to: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
attributeName: PropTypes.string,
// animation duration
duration: PropTypes.number,
begin: PropTypes.number,
easing: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
steps: PropTypes.arrayOf(
PropTypes.shape({
duration: PropTypes.number.isRequired,
style: PropTypes.object.isRequired,
easing: PropTypes.oneOfType([
PropTypes.oneOf(['ease', 'ease-in', 'ease-out', 'ease-in-out', 'linear']),
PropTypes.func,
]),
// transition css properties(dash case), optional
properties: PropTypes.arrayOf('string'),
onAnimationEnd: PropTypes.func,
}),
),
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
isActive: PropTypes.bool,
canBegin: PropTypes.bool,
onAnimationEnd: PropTypes.func,
// decide if it should reanimate with initial from style when props change
shouldReAnimate: PropTypes.bool,
onAnimationStart: PropTypes.func,
onAnimationReStart: PropTypes.func,
};
export default Animate;

37
frontend/node_modules/react-smooth/src/AnimateGroup.js generated vendored Normal file
View File

@@ -0,0 +1,37 @@
import React, { Children } from 'react';
import { TransitionGroup } from 'react-transition-group';
import PropTypes from 'prop-types';
import AnimateGroupChild from './AnimateGroupChild';
function AnimateGroup(props) {
const { component, children, appear, enter, leave } = props;
return (
<TransitionGroup component={component}>
{Children.map(children, (child, index) => (
<AnimateGroupChild
appearOptions={appear}
enterOptions={enter}
leaveOptions={leave}
key={`child-${index}`} // eslint-disable-line
>
{child}
</AnimateGroupChild>
))}
</TransitionGroup>
);
}
AnimateGroup.propTypes = {
appear: PropTypes.object,
enter: PropTypes.object,
leave: PropTypes.object,
children: PropTypes.oneOfType([PropTypes.array, PropTypes.element]),
component: PropTypes.any,
};
AnimateGroup.defaultProps = {
component: 'span',
};
export default AnimateGroup;

View File

@@ -0,0 +1,86 @@
import React, { Component, Children } from 'react';
import { Transition } from 'react-transition-group';
import PropTypes from 'prop-types';
import Animate from './Animate';
const parseDurationOfSingleTransition = (options = {}) => {
const { steps, duration } = options;
if (steps && steps.length) {
return steps.reduce(
(result, entry) => result + (Number.isFinite(entry.duration) && entry.duration > 0 ? entry.duration : 0),
0,
);
}
if (Number.isFinite(duration)) {
return duration;
}
return 0;
};
class AnimateGroupChild extends Component {
constructor() {
super();
this.state = {
isActive: false,
};
}
handleStyleActive(style) {
if (style) {
const onAnimationEnd = style.onAnimationEnd
? () => {
style.onAnimationEnd();
}
: null;
this.setState({
...style,
onAnimationEnd,
isActive: true,
});
}
}
handleEnter = (node, isAppearing) => {
const { appearOptions, enterOptions } = this.props;
this.handleStyleActive(isAppearing ? appearOptions : enterOptions);
};
handleExit = () => {
const { leaveOptions } = this.props;
this.handleStyleActive(leaveOptions);
};
parseTimeout() {
const { appearOptions, enterOptions, leaveOptions } = this.props;
return (
parseDurationOfSingleTransition(appearOptions) +
parseDurationOfSingleTransition(enterOptions) +
parseDurationOfSingleTransition(leaveOptions)
);
}
render() {
const { children, appearOptions, enterOptions, leaveOptions, ...props } = this.props;
return (
<Transition {...props} onEnter={this.handleEnter} onExit={this.handleExit} timeout={this.parseTimeout()}>
{() => <Animate {...this.state}>{Children.only(children)}</Animate>}
</Transition>
);
}
}
AnimateGroupChild.propTypes = {
appearOptions: PropTypes.object,
enterOptions: PropTypes.object,
leaveOptions: PropTypes.object,
children: PropTypes.element,
};
export default AnimateGroupChild;

View File

@@ -0,0 +1,58 @@
import setRafTimeout from './setRafTimeout';
export default function createAnimateManager() {
let currStyle = {};
let handleChange = () => null;
let shouldStop = false;
const setStyle = _style => {
if (shouldStop) {
return;
}
if (Array.isArray(_style)) {
if (!_style.length) {
return;
}
const styles = _style;
const [curr, ...restStyles] = styles;
if (typeof curr === 'number') {
setRafTimeout(setStyle.bind(null, restStyles), curr);
return;
}
setStyle(curr);
setRafTimeout(setStyle.bind(null, restStyles));
return;
}
if (typeof _style === 'object') {
currStyle = _style;
handleChange(currStyle);
}
if (typeof _style === 'function') {
_style();
}
};
return {
stop: () => {
shouldStop = true;
},
start: style => {
shouldStop = false;
setStyle(style);
},
subscribe: _handleChange => {
handleChange = _handleChange;
return () => {
handleChange = () => null;
};
},
};
}

134
frontend/node_modules/react-smooth/src/configUpdate.js generated vendored Normal file
View File

@@ -0,0 +1,134 @@
import { getIntersectionKeys, mapObject } from './util';
const alpha = (begin, end, k) => begin + (end - begin) * k;
const needContinue = ({ from, to }) => from !== to;
/*
* @description: cal new from value and velocity in each stepper
* @return: { [styleProperty]: { from, to, velocity } }
*/
const calStepperVals = (easing, preVals, steps) => {
const nextStepVals = mapObject((key, val) => {
if (needContinue(val)) {
const [newX, newV] = easing(val.from, val.to, val.velocity);
return {
...val,
from: newX,
velocity: newV,
};
}
return val;
}, preVals);
if (steps < 1) {
return mapObject((key, val) => {
if (needContinue(val)) {
return {
...val,
velocity: alpha(val.velocity, nextStepVals[key].velocity, steps),
from: alpha(val.from, nextStepVals[key].from, steps),
};
}
return val;
}, preVals);
}
return calStepperVals(easing, nextStepVals, steps - 1);
};
// configure update function
export default (from, to, easing, duration, render) => {
const interKeys = getIntersectionKeys(from, to);
const timingStyle = interKeys.reduce(
(res, key) => ({
...res,
[key]: [from[key], to[key]],
}),
{},
);
let stepperStyle = interKeys.reduce(
(res, key) => ({
...res,
[key]: {
from: from[key],
velocity: 0,
to: to[key],
},
}),
{},
);
let cafId = -1;
let preTime;
let beginTime;
let update = () => null;
const getCurrStyle = () => mapObject((key, val) => val.from, stepperStyle);
const shouldStopAnimation = () => !Object.values(stepperStyle).filter(needContinue).length;
// stepper timing function like spring
const stepperUpdate = now => {
if (!preTime) {
preTime = now;
}
const deltaTime = now - preTime;
const steps = deltaTime / easing.dt;
stepperStyle = calStepperVals(easing, stepperStyle, steps);
// get union set and add compatible prefix
render({
...from,
...to,
...getCurrStyle(stepperStyle),
});
preTime = now;
if (!shouldStopAnimation()) {
cafId = requestAnimationFrame(update);
}
};
// t => val timing function like cubic-bezier
const timingUpdate = now => {
if (!beginTime) {
beginTime = now;
}
const t = (now - beginTime) / duration;
const currStyle = mapObject((key, val) => alpha(...val, easing(t)), timingStyle);
// get union set and add compatible prefix
render({
...from,
...to,
...currStyle,
});
if (t < 1) {
cafId = requestAnimationFrame(update);
} else {
const finalStyle = mapObject((key, val) => alpha(...val, easing(1)), timingStyle);
render({
...from,
...to,
...finalStyle,
});
}
};
update = easing.isStepper ? stepperUpdate : timingUpdate;
// return start animation method
return () => {
requestAnimationFrame(update);
// return stop animation method
return () => {
cancelAnimationFrame(cafId);
};
};
};

159
frontend/node_modules/react-smooth/src/easing.js generated vendored Normal file
View File

@@ -0,0 +1,159 @@
import { warn } from './util';
const ACCURACY = 1e-4;
const cubicBezierFactor = (c1, c2) => [0, 3 * c1, 3 * c2 - 6 * c1, 3 * c1 - 3 * c2 + 1];
const multyTime = (params, t) => params.map((param, i) => param * t ** i).reduce((pre, curr) => pre + curr);
const cubicBezier = (c1, c2) => t => {
const params = cubicBezierFactor(c1, c2);
return multyTime(params, t);
};
const derivativeCubicBezier = (c1, c2) => t => {
const params = cubicBezierFactor(c1, c2);
const newParams = [...params.map((param, i) => param * i).slice(1), 0];
return multyTime(newParams, t);
};
// calculate cubic-bezier using Newton's method
export const configBezier = (...args) => {
let [x1, y1, x2, y2] = args;
if (args.length === 1) {
switch (args[0]) {
case 'linear':
[x1, y1, x2, y2] = [0.0, 0.0, 1.0, 1.0];
break;
case 'ease':
[x1, y1, x2, y2] = [0.25, 0.1, 0.25, 1.0];
break;
case 'ease-in':
[x1, y1, x2, y2] = [0.42, 0.0, 1.0, 1.0];
break;
case 'ease-out':
[x1, y1, x2, y2] = [0.42, 0.0, 0.58, 1.0];
break;
case 'ease-in-out':
[x1, y1, x2, y2] = [0.0, 0.0, 0.58, 1.0];
break;
default: {
const easing = args[0].split('(');
if (easing[0] === 'cubic-bezier' && easing[1].split(')')[0].split(',').length === 4) {
[x1, y1, x2, y2] = easing[1]
.split(')')[0]
.split(',')
.map(x => parseFloat(x));
} else {
warn(
false,
'[configBezier]: arguments should be one of ' +
"oneOf 'linear', 'ease', 'ease-in', 'ease-out', " +
"'ease-in-out','cubic-bezier(x1,y1,x2,y2)', instead received %s",
args,
);
}
}
}
}
warn(
[x1, x2, y1, y2].every(num => typeof num === 'number' && num >= 0 && num <= 1),
'[configBezier]: arguments should be x1, y1, x2, y2 of [0, 1] instead received %s',
args,
);
const curveX = cubicBezier(x1, x2);
const curveY = cubicBezier(y1, y2);
const derCurveX = derivativeCubicBezier(x1, x2);
const rangeValue = value => {
if (value > 1) {
return 1;
}
if (value < 0) {
return 0;
}
return value;
};
const bezier = _t => {
const t = _t > 1 ? 1 : _t;
let x = t;
for (let i = 0; i < 8; ++i) {
const evalT = curveX(x) - t;
const derVal = derCurveX(x);
if (Math.abs(evalT - t) < ACCURACY || derVal < ACCURACY) {
return curveY(x);
}
x = rangeValue(x - evalT / derVal);
}
return curveY(x);
};
bezier.isStepper = false;
return bezier;
};
export const configSpring = (config = {}) => {
const { stiff = 100, damping = 8, dt = 17 } = config;
const stepper = (currX, destX, currV) => {
const FSpring = -(currX - destX) * stiff;
const FDamping = currV * damping;
const newV = currV + ((FSpring - FDamping) * dt) / 1000;
const newX = (currV * dt) / 1000 + currX;
if (Math.abs(newX - destX) < ACCURACY && Math.abs(newV) < ACCURACY) {
return [destX, 0];
}
return [newX, newV];
};
stepper.isStepper = true;
stepper.dt = dt;
return stepper;
};
export const configEasing = (...args) => {
const [easing] = args;
if (typeof easing === 'string') {
switch (easing) {
case 'ease':
case 'ease-in-out':
case 'ease-out':
case 'ease-in':
case 'linear':
return configBezier(easing);
case 'spring':
return configSpring();
default:
if (easing.split('(')[0] === 'cubic-bezier') {
return configBezier(easing);
}
warn(
false,
"[configEasing]: first argument should be one of 'ease', 'ease-in', " +
"'ease-out', 'ease-in-out','cubic-bezier(x1,y1,x2,y2)', 'linear' and 'spring', instead received %s",
args,
);
}
}
if (typeof easing === 'function') {
return easing;
}
warn(false, '[configEasing]: first argument type should be function or string, instead received %s', args);
return null;
};

7
frontend/node_modules/react-smooth/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,7 @@
import Animate from './Animate';
import { configBezier, configSpring } from './easing';
import AnimateGroup from './AnimateGroup';
export { configSpring, configBezier, AnimateGroup };
export default Animate;

View File

@@ -0,0 +1,22 @@
function safeRequestAnimationFrame(callback) {
if (typeof requestAnimationFrame !== 'undefined') requestAnimationFrame(callback);
}
export default function setRafTimeout(callback, timeout = 0) {
let currTime = -1;
const shouldUpdate = now => {
if (currTime < 0) {
currTime = now;
}
if (now - currTime > timeout) {
callback(now);
currTime = -1;
} else {
safeRequestAnimationFrame(shouldUpdate);
}
};
requestAnimationFrame(shouldUpdate);
}

82
frontend/node_modules/react-smooth/src/util.js generated vendored Normal file
View File

@@ -0,0 +1,82 @@
/* eslint no-console: 0 */
export const getIntersectionKeys = (preObj, nextObj) =>
[Object.keys(preObj), Object.keys(nextObj)].reduce((a, b) => a.filter(c => b.includes(c)));
export const identity = param => param;
/*
* @description: convert camel case to dash case
* string => string
*/
export const getDashCase = name => name.replace(/([A-Z])/g, v => `-${v.toLowerCase()}`);
export const log = (...args) => {
console.log(...args);
};
/*
* @description: log the value of a varible
* string => any => any
*/
export const debug = name => item => {
log(name, item);
return item;
};
/*
* @description: log name, args, return value of a function
* function => function
*/
export const debugf =
(tag, f) =>
(...args) => {
const res = f(...args);
const name = tag || f.name || 'anonymous function';
const argNames = `(${args.map(JSON.stringify).join(', ')})`;
log(`${name}: ${argNames} => ${JSON.stringify(res)}`);
return res;
};
/*
* @description: map object on every element in this object.
* (function, object) => object
*/
export const mapObject = (fn, obj) =>
Object.keys(obj).reduce(
(res, key) => ({
...res,
[key]: fn(key, obj[key]),
}),
{},
);
export const getTransitionVal = (props, duration, easing) =>
props.map(prop => `${getDashCase(prop)} ${duration}ms ${easing}`).join(',');
const isDev = process.env.NODE_ENV !== 'production';
export const warn = (condition, format, a, b, c, d, e, f) => {
if (isDev && typeof console !== 'undefined' && console.warn) {
if (format === undefined) {
console.warn('LogUtils requires an error message argument');
}
if (!condition) {
if (format === undefined) {
console.warn(
'Minified exception occurred; use the non-minified dev environment ' +
'for the full error message and additional helpful warnings.',
);
} else {
const args = [a, b, c, d, e, f];
let argIndex = 0;
console.warn(format.replace(/%s/g, () => args[argIndex++]));
}
}
}
};