setup(frontend): rename frontend-react

This commit is contained in:
Nex Zhu
2025-11-20 09:41:20 +08:00
parent e40cdd1dee
commit 4fa5504697
195 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
import { autorun } from 'mobx';
import { GlobalStorage } from '.';
export const debug = (store: GlobalStorage) => {
autorun(() => {
const { availiableAgents } = store;
console.groupCollapsed('[LOG] GetAgent:', availiableAgents.length);
for (const agent of availiableAgents) {
// console 输出name 粗体profile 正常
console.info('%c%s', 'font-weight: bold', agent.name, agent.profile);
}
console.groupEnd();
});
autorun(() => {
const { agentCards } = store;
console.groupCollapsed('[LOG] AgentCards:', agentCards.length);
for (const agentCard of agentCards) {
console.groupCollapsed(
`agent: ${agentCard.name}`,
'inuse:',
agentCard.inuse,
'action:',
agentCard.actions.length,
);
for (const action of agentCard.actions) {
console.groupCollapsed(
'%c%s',
`font-weight: bold`,
action.type,
action.description,
'input:',
action.inputs.length,
);
for (const input of action.inputs) {
console.info(input);
}
console.groupEnd();
}
console.groupEnd();
}
console.groupEnd();
});
autorun(() => {
const { refMap } = store;
console.groupCollapsed('[LOG] RefMap');
for (const [key, map] of Object.entries(refMap)) {
console.groupCollapsed(key);
for (const [k, v] of map) {
console.info(k, v);
}
console.groupEnd();
}
console.groupEnd();
});
autorun(() => {
const { planManager } = store;
console.groupCollapsed('[LOG] planManager');
console.info(planManager);
const currentLeafId = planManager.currentStepTaskLeaf;
if (currentLeafId) {
const currentPath = planManager.stepTaskMap.get(currentLeafId)?.path;
const outline = {
initialInputs: planManager.inputs,
processes: currentPath
?.map(nodeId => planManager.stepTaskMap.get(nodeId))
.filter(node => node)
.map(node => ({
inputs: node?.inputs,
output: node?.output,
StepName: node?.name,
AgentSelection: node?.agentSelection,
})),
};
console.log(outline);
}
console.groupEnd();
});
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,114 @@
import { makeAutoObservable } from 'mobx';
import { SxProps } from '@mui/material';
import { INodeBase } from './base';
import { PlanManager } from './manager';
import { IApiAgentAction } from '@/apis/generate-base-plan';
export enum ActionType {
Propose = 'Propose',
Critique = 'Critique',
Improve = 'Improve',
Finalize = 'Finalize',
}
const AgentActionStyles = new Map<ActionType | '', SxProps>([
[ActionType.Propose, { backgroundColor: '#B9EBF9', borderColor: '#94c2dc' }],
[ActionType.Critique, { backgroundColor: '#EFF9B9', borderColor: '#c0dc94' }],
[ActionType.Improve, { backgroundColor: '#E0DEFC', borderColor: '#bbb8e5' }],
[ActionType.Finalize, { backgroundColor: '#F9C7B9', borderColor: '#dc9e94' }],
['', { backgroundColor: '#000000', borderColor: '#000000' }],
]);
export const getAgentActionStyle = (action: ActionType): SxProps => {
return AgentActionStyles.get(action) ?? AgentActionStyles.get('')!;
};
export interface IRichSentence {
who: string;
whoStyle: SxProps;
content: string;
style: SxProps;
}
export class AgentActionNode implements INodeBase {
public name: string = '';
public plan: PlanManager;
public type: ActionType = ActionType.Propose;
public agent: string = '';
public description: string = '';
public inputs: string[] = [];
public path: string[] = []; // ['A', 'B']
public children: string[] = []; // ['D', 'E']
public get id() {
return this.path[this.path.length - 1];
}
public get parent() {
return this.path[this.path.length - 2];
}
public get last() {
return this.plan.agentActionMap.get(this.parent);
}
public get renderCard(): IRichSentence {
const style = getAgentActionStyle(this.type);
return {
who: this.agent,
whoStyle: style,
// this.description 首字母小写
content: this.description[0].toLowerCase() + this.description.slice(1),
style: { ...style, backgroundColor: 'transparent' },
};
}
public get apiAgentAction(): IApiAgentAction {
return {
id: this.name,
type: this.type,
agent: this.agent,
description: this.description,
inputs: this.inputs,
};
}
public get belongingSelection() {
return this.plan.agentSelectionMap.get(
this.plan.actionSelectionMap.get(this.id) ?? '',
);
}
constructor(plan: PlanManager, json?: any) {
this.plan = plan;
if (json) {
this.name = json.name ?? '';
this.agent = json.agent ?? '';
this.description = json.description ?? '';
this.inputs = [...(json.inputs ?? [])];
this.type = json.type ?? ActionType.Propose;
this.path = [...(json.path ?? [])];
this.children = [...(json.children ?? [])];
}
makeAutoObservable(this);
}
public dump() {
return {
name: this.name,
agent: this.agent,
description: this.description,
inputs: [...this.inputs],
type: this.type,
path: [...this.path],
children: [...this.children],
};
}
}

View File

@@ -0,0 +1,13 @@
/* A
/ \
B C
/ \
D E */
export interface INodeBase {
id: string; // B
parent?: string; // A
path: string[]; // ['A', 'B']
children: string[]; // ['D', 'E']
}
export type NodeMap<T extends INodeBase> = Map<string, T>;

View File

@@ -0,0 +1,5 @@
export * from './action';
export * from './manager';
export * from './selection';
export * from './stepTask';
export * from './log';

View File

@@ -0,0 +1,115 @@
import React from 'react';
import { makeAutoObservable } from 'mobx';
import { PlanManager } from './manager';
import {
IExecuteNode,
ExecuteNodeType,
IExecuteObject,
} from '@/apis/execute-plan';
export class RehearsalLog {
public planManager: PlanManager;
public outdate: boolean = false;
oldLog: IExecuteNode[] = [];
userInputs: Record<string, string> = {};
public get logWithoutUserInput(): IExecuteNode[] {
if (this.oldLog.length > 0) {
return this.oldLog;
} else {
const log: IExecuteNode[] = [];
// build skeleton logs
for (const step of this.planManager.currentPlan) {
log.push({
type: ExecuteNodeType.Step,
id: step.name,
inputs: Array.from(step.inputs),
output: step.output,
history: (step.agentSelection?.currentTaskProcess ?? []).map(
action => ({
id: action.name,
type: action.type,
agent: action.agent,
description: action.description,
inputs: action.inputs,
result: '',
}),
),
});
// push output of the step
log.push({
type: ExecuteNodeType.Object,
id: step.output,
content: '',
});
}
return log;
}
}
public get renderingLog(): (IExecuteNode & {
ref: React.RefObject<HTMLDivElement>;
stepId: string;
})[] {
const outputTaskIdMap = new Map<string, string>();
const taskNameTaskIdMap = new Map<string, string>();
this.planManager.currentPlan.forEach(step => {
taskNameTaskIdMap.set(step.name, step.id);
outputTaskIdMap.set(step.output, step.id);
});
return this.logWithoutUserInput.map(node => {
let stepId = '';
if (node.type === ExecuteNodeType.Step) {
stepId = taskNameTaskIdMap.get(node.id) ?? '';
} else {
stepId = outputTaskIdMap.get(node.id) ?? '';
}
return {
...node,
ref: React.createRef(),
stepId,
};
});
}
constructor(plan: PlanManager, json?: any) {
this.planManager = plan;
if (json) {
this.oldLog = JSON.parse(JSON.stringify(json.oldLog));
this.userInputs = JSON.parse(JSON.stringify(json.userInputs));
}
makeAutoObservable(this);
}
public dump() {
return {
oldLog: JSON.parse(JSON.stringify(this.oldLog)),
userInputs: JSON.parse(JSON.stringify(this.userInputs)),
};
}
public updateLog(newLog: IExecuteNode[]) {
const log = [...newLog];
const userInputs: Record<string, string> = {};
while (log.length > 0 && log[0].type === ExecuteNodeType.Object) {
const userInputObject = log.shift()! as IExecuteObject;
userInputs[userInputObject.id] = userInputObject.content;
}
this.oldLog = log;
this.userInputs = userInputs;
}
public clearLog() {
this.oldLog.length = 0;
this.outdate = false;
}
public setOutdate() {
if (this.oldLog.length > 0) {
this.outdate = true;
}
}
}

View File

@@ -0,0 +1,196 @@
import { makeAutoObservable } from 'mobx';
import { NodeMap } from './base';
import { StepTaskNode } from './stepTask';
import { AgentActionNode } from './action';
import { AgentSelection } from './selection';
import { IApiStepTask, IGeneratedPlan } from '@/apis/generate-base-plan';
export class PlanManager {
public goal: string = '';
public nextStepTaskId: number = 0;
public nextAgentSelectionId: number = 0;
public nextAgentActionId: number = 0;
public stepTaskMap: NodeMap<StepTaskNode> = new Map();
public agentActionMap: NodeMap<AgentActionNode> = new Map();
public agentSelectionMap: Map<string, AgentSelection> = new Map();
public actionSelectionMap: Map<string, string> = new Map();
public selectionStepMap: Map<string, string> = new Map();
public stepTaskRoots: string[] = [];
public currentStepTaskLeaf?: string;
public previewStepTaskLeaf?: string;
public inputs: string[] = [];
public branches: Record<
string,
{ start?: string; requirement?: string; base?: string }
> = {};
public get leaves() {
return Object.keys(this.branches);
}
public get currentPlan() {
const path: StepTaskNode[] = [];
let node = this.stepTaskMap.get(
this.previewStepTaskLeaf ?? this.currentStepTaskLeaf ?? '',
);
while (node) {
path.push(node);
node = node.last;
}
return path.reverse();
}
public get currentPath() {
return this.currentPlan.map(node => node.id);
}
public get activeStepNodeIds() {
return new Set<string>(this.currentPath);
}
public get apiPlan() {
return {
goal: this.goal,
inputs: this.inputs,
process: this.currentPlan.map(node => node.apiStepTask),
};
}
constructor() {
makeAutoObservable(this);
}
public parseApiPlan(plan: IGeneratedPlan) {
this.goal = plan.goal;
this.inputs = [...plan.inputs];
const leaf = this.insertProcess(plan.process);
this.currentStepTaskLeaf = leaf;
}
public insertProcess(
process: IApiStepTask[],
start?: string,
requirement?: string,
base?: string,
): string {
if (start && !this.stepTaskMap.has(start)) {
throw new Error(`StekTask node ${start} does not exist!`);
}
let lastChidrenList =
this.stepTaskMap.get(start ?? '')?.children ?? this.stepTaskRoots;
const path = [...(this.stepTaskMap.get(start ?? '')?.path ?? [])];
for (const task of process) {
// create agentSelection
const agentSelection = new AgentSelection(this, {
id: (this.nextAgentSelectionId++).toString(),
agents: task.agents,
});
const leaf = agentSelection.insertActions(task.process);
this.agentSelectionMap.set(agentSelection.id, agentSelection);
agentSelection.currentActionLeaf = leaf;
// create stepTask
path.push((this.nextStepTaskId++).toString());
const node = new StepTaskNode(this, {
name: task.name,
content: task.content,
inputs: task.inputs,
output: task.output,
brief: task.brief,
path,
agentSelectionIds: [agentSelection.id],
currentAgentSelection: agentSelection.id,
});
lastChidrenList.push(node.id);
lastChidrenList = node.children;
this.selectionStepMap.set(agentSelection.id, node.id);
this.stepTaskMap.set(node.id, node);
}
const leaf = path[path.length - 1];
this.branches[leaf] = { start, requirement, base };
return leaf;
}
public reset() {
this.goal = '';
this.stepTaskMap.clear();
this.agentActionMap.clear();
this.agentSelectionMap.clear();
this.stepTaskRoots = [];
this.inputs = [];
this.currentStepTaskLeaf = undefined;
this.previewStepTaskLeaf = undefined;
this.nextAgentActionId = 0;
this.nextAgentSelectionId = 0;
this.nextStepTaskId = 0;
}
public dump() {
return {
goal: this.goal,
nextStepTaskId: this.nextStepTaskId,
nextAgentSelectionId: this.nextAgentSelectionId,
nextAgentActionId: this.nextAgentActionId,
stepTaskMap: Array.from(this.stepTaskMap).map(([k, v]) => [k, v.dump()]),
agentActionMap: Array.from(this.agentActionMap).map(([k, v]) => [
k,
v.dump(),
]),
agentSelectionMap: Array.from(this.agentSelectionMap).map(([k, v]) => [
k,
v.dump(),
]),
stepTaskRoots: [...this.stepTaskRoots],
inputs: [...this.inputs],
currentStepTaskLeaf: this.currentStepTaskLeaf,
previewStepTaskLeaf: this.previewStepTaskLeaf,
branch: JSON.parse(JSON.stringify(this.branches)),
};
}
public load(json: any) {
this.reset();
this.goal = json.goal;
this.nextStepTaskId = json.nextStepTaskId;
this.nextAgentSelectionId = json.nextAgentSelectionId;
this.nextAgentActionId = json.nextAgentActionId;
for (const [k, v] of json.agentActionMap) {
this.agentActionMap.set(k, new AgentActionNode(this, v));
}
for (const [k, v] of json.agentSelectionMap) {
const selection = new AgentSelection(this, v);
for (const leaf of selection.leaves) {
let node = this.agentActionMap.get(leaf);
while (node) {
this.actionSelectionMap.set(node.id, selection.id);
node = node.last;
}
}
this.agentSelectionMap.set(k, selection);
}
for (const [k, v] of json.stepTaskMap) {
const stepTask = new StepTaskNode(this, v);
for (const id of stepTask.agentSelectionIds) {
this.selectionStepMap.set(id, stepTask.id);
}
this.stepTaskMap.set(k, stepTask);
}
this.stepTaskRoots = [...json.stepTaskRoots];
this.inputs = [...json.inputs];
this.currentStepTaskLeaf = json.currentStepTaskLeaf;
this.previewStepTaskLeaf = json.previewStepTaskLeaf;
this.branches = JSON.parse(JSON.stringify(json.branch));
}
}

View File

@@ -0,0 +1,112 @@
import { makeAutoObservable } from 'mobx';
import { PlanManager } from './manager';
import { AgentActionNode } from './action';
import { IApiAgentAction } from '@/apis/generate-base-plan';
export class AgentSelection {
public id: string = '';
public plan: PlanManager;
public agents: string[] = [];
public actionRoot: string[] = [];
public currentActionLeaf?: string;
public previewActionLeaf?: string;
public branches: Record<
string,
{ start?: string; requirement?: string; base?: string }
> = {};
public get leaves() {
return Object.keys(this.branches);
}
public get currentTaskProcess() {
const path: AgentActionNode[] = [];
let node = this.plan.agentActionMap.get(
this.previewActionLeaf ?? this.currentActionLeaf ?? '',
);
while (node) {
path.push(node);
node = node.last;
}
return path.reverse();
}
public get currentTaskProcessIds() {
return this.currentTaskProcess.map(node => node.id);
}
public get activeTaskIds() {
return new Set<string>(this.currentTaskProcessIds);
}
public get belongingStepTask() {
return this.plan.stepTaskMap.get(
this.plan.selectionStepMap.get(this.id) ?? '',
);
}
constructor(plan: PlanManager, json?: any) {
this.plan = plan;
if (json) {
this.id = json.id ?? '';
this.agents = [...(json.agents ?? [])];
this.branches = JSON.parse(JSON.stringify(json.branches ?? {}));
this.actionRoot = [...(json.actionRoot ?? [])];
this.currentActionLeaf = json.currentActionLeaf;
this.previewActionLeaf = json.previewActionLeaf;
}
makeAutoObservable(this);
}
public dump() {
return {
id: this.id,
agents: [...this.agents],
branches: JSON.parse(JSON.stringify(this.branches)),
actionRoot: [...this.actionRoot],
currentActionLeaf: this.currentActionLeaf,
previewActionLeaf: this.previewActionLeaf,
};
}
public insertActions(
actions: IApiAgentAction[],
start?: string,
requirement?: string,
base?: string,
) {
if (actions.length === 0) {
return undefined;
}
if (start && !this.plan.agentActionMap.has(start)) {
throw new Error(`AgentAction node ${start} does not exist!`);
}
let lastChidrenList =
this.plan.agentActionMap.get(start ?? '')?.children ?? this.actionRoot;
const path = [...(this.plan.agentActionMap.get(start ?? '')?.path ?? [])];
for (const action of actions) {
path.push((this.plan.nextAgentActionId++).toString());
const node = new AgentActionNode(this.plan, {
name: action.id,
path,
type: action.type,
agent: action.agent,
description: action.description,
inputs: action.inputs,
});
this.plan.agentActionMap.set(node.id, node);
this.plan.actionSelectionMap.set(node.id, this.id);
lastChidrenList.push(node.id);
lastChidrenList = node.children;
}
const leaf = path[path.length - 1];
this.branches[leaf] = { start, requirement, base };
return leaf;
}
}

View File

@@ -0,0 +1,280 @@
import React from 'react';
import { SxProps } from '@mui/material';
import { makeAutoObservable } from 'mobx';
import { INodeBase } from './base';
import { PlanManager } from './manager';
import {
IRichText,
IApiStepTask,
IApiAgentAction,
} from '@/apis/generate-base-plan';
export interface IRichSpan {
text: string;
style?: SxProps;
}
const nameJoin = (names: string[]) => {
// join names with comma, and 'and' for the last one
const tmp = [...names];
const last = tmp.pop()!;
let t = tmp.join(', ');
if (t.length > 0) {
t = `${t} and ${last}`;
} else {
t = last;
}
return t;
};
export class StepTaskNode implements INodeBase {
public name: string = '';
public content: string = '';
public inputs: string[] = [];
public output: string = '';
public _brief: IRichText = {
template: '',
data: {},
};
public path: string[] = [];
public children: string[] = [];
public plan: PlanManager;
public agentSelectionIds: string[] = [];
public currentAgentSelection?: string;
public previewAgentSelection?: string;
public agentAspectScores: Record<
string,
Record<string, { score: number; reason: string }>
> = {};
public get brief(): IRichText {
if (!this.agentSelection) {
return this._brief;
}
const agents = [...this.agentSelection.agents];
if (agents.length === 0) {
return this._brief;
}
const data: IRichText['data'] = {};
let indexOffset = 0;
const inputPlaceHolders = this.inputs.map((text, index) => {
data[(index + indexOffset).toString()] = {
text,
style: { background: '#ACDBA0' },
};
return `!<${index + indexOffset}>!`;
});
const inputSentence = nameJoin(inputPlaceHolders);
indexOffset += this.inputs.length;
const namePlaceholders = agents.map((text, index) => {
data[(index + indexOffset).toString()] = {
text,
style: { background: '#E5E5E5', boxShadow: '1px 1px 4px 1px #0003' },
};
return `!<${index + indexOffset}>!`;
});
const nameSentence = nameJoin(namePlaceholders);
indexOffset += agents.length;
let actionSentence = this.content;
// delete the last '.' of actionSentence
if (actionSentence[actionSentence.length - 1] === '.') {
actionSentence = actionSentence.slice(0, -1);
}
const actionIndex = indexOffset++;
data[actionIndex.toString()] = {
text: actionSentence,
style: { background: '#DDD', border: '1.5px solid #ddd' },
};
let outputSentence = '';
if (this.output) {
data[indexOffset.toString()] = {
text: this.output,
style: { background: '#FFCA8C' },
};
outputSentence = `to obtain !<${indexOffset}>!`;
}
// Join them togeter
let content = inputSentence;
if (content) {
content = `Based on ${content}, ${nameSentence} perform the task of !<${actionIndex}>!`;
} else {
content = `${nameSentence} perform the task of !<${actionIndex}>!`;
}
if (outputSentence) {
content = `${content}, ${outputSentence}.`;
} else {
content = `${content}.`;
}
content = content.trim();
return {
template: content,
data,
};
}
public get id() {
return this.path[this.path.length - 1];
}
public get last() {
return this.plan.stepTaskMap.get(this.path[this.path.length - 2]);
}
public get next() {
return this.children
.map(id => this.plan.stepTaskMap.get(id)!)
.filter(node => node);
}
public get agentSelection() {
const id = this.previewAgentSelection ?? this.currentAgentSelection ?? '';
return this.plan.agentSelectionMap.get(id);
}
public get allSelections() {
return this.agentSelectionIds
.map(id => this.plan.agentSelectionMap.get(id)!)
.filter(node => node);
}
public get apiStepTask(): IApiStepTask {
const actionsProcess: IApiAgentAction[] = [];
const actions = this.agentSelection?.currentTaskProcess ?? [];
for (const action of actions) {
actionsProcess.push({
id: action.name,
type: action.type,
agent: action.agent,
description: action.description,
inputs: [...action.inputs],
});
}
return {
name: this.name,
content: this.content,
inputs: [...this.inputs],
output: this.output,
agents: this.agentSelection?.agents ?? [],
brief: JSON.parse(JSON.stringify(this.brief)) as IRichText,
process: actionsProcess,
};
}
public get descriptionCard() {
const briefSpan: IRichSpan[] = [];
for (const substring of this.brief.template.split(/(!<[^>]+>!)/)) {
if (substring[0] === '!') {
const key = substring.slice(2, -2);
const { text, style } = this.brief.data[key];
briefSpan.push({ text, style });
} else {
briefSpan.push({ text: substring });
}
}
const actions = this.agentSelection?.currentTaskProcess ?? [];
const detailParagraph = actions.map(action => [
action.renderCard,
action.id,
]);
return {
id: this.id,
name: this.name,
content: this.content,
ref: React.createRef<HTMLElement>(),
brief: briefSpan,
detail: detailParagraph,
};
}
public get outlineCard() {
return {
id: this.id,
name: this.name,
inputs: this.inputs,
output: this.output,
agents: this.agentSelection?.agents ?? [],
content: this.content,
ref: React.createRef<HTMLElement>(),
};
}
public get heatmap() {
return Object.fromEntries(
Object.entries(this.agentAspectScores).map(([aspect, scores]) => [
aspect,
Object.fromEntries(
Object.entries(scores).map(([agent, score]) => [agent, score]),
),
]),
);
}
constructor(plan: PlanManager, json?: any) {
this.plan = plan;
if (json) {
this.name = json.name ?? '';
this.content = json.content ?? '';
this.inputs = [...(json.inputs ?? [])];
this.output = json.output;
this._brief = JSON.parse(
JSON.stringify(json.brief ?? '{ template: "", data: {} }'),
) as IRichText;
this.path = [...(json.path ?? [])];
this.children = [...(json.children ?? [])];
this.agentAspectScores = JSON.parse(
JSON.stringify(json.agentAspectScores ?? {}),
);
this.agentSelectionIds = [...(json.agentSelectionIds ?? [])];
this.currentAgentSelection = json.currentAgentSelection;
this.previewAgentSelection = json.previewAgentSelection;
}
makeAutoObservable(this);
}
public dump() {
return {
name: this.name,
content: this.content,
inputs: [...this.inputs],
output: this.output,
brief: JSON.parse(JSON.stringify(this.brief)),
path: [...this.path],
children: [...this.children],
agentSelectionIds: [...this.agentSelectionIds],
agentAspectScores: JSON.parse(JSON.stringify(this.agentAspectScores)),
currentAgentSelection: this.currentAgentSelection,
previewAgentSelection: this.previewAgentSelection,
};
}
public appendAspectScore(
aspect: string,
agentScores: Record<string, { score: number; reason: string }>,
) {
if (this.agentAspectScores[aspect]) {
for (const [agent, score] of Object.entries(agentScores)) {
if (this.agentAspectScores[aspect][agent]) {
this.agentAspectScores[aspect][agent].score = score.score;
this.agentAspectScores[aspect][agent].reason = score.reason;
} else {
this.agentAspectScores[aspect][agent] = score;
}
}
} else {
this.agentAspectScores[aspect] = agentScores;
}
}
}