mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-19 04:14:46 +08:00
feat: support subtasks
This commit is contained in:
@@ -209,6 +209,16 @@ export const enUS: Translations = {
|
||||
skillInstallTooltip: "Install skill and make it available to DeerFlow",
|
||||
},
|
||||
|
||||
// Subtasks
|
||||
subtasks: {
|
||||
subtask: "Subtask",
|
||||
executing: (count: number) =>
|
||||
`Executing ${count} subtask${count === 1 ? "" : "s"} in parallel`,
|
||||
running: "Running subtask",
|
||||
completed: "Subtask completed",
|
||||
failed: "Subtask failed",
|
||||
},
|
||||
|
||||
// Settings
|
||||
settings: {
|
||||
title: "Settings",
|
||||
|
||||
@@ -155,6 +155,15 @@ export interface Translations {
|
||||
skillInstallTooltip: string;
|
||||
};
|
||||
|
||||
// Subtasks
|
||||
subtasks: {
|
||||
subtask: string;
|
||||
executing: (count: number) => string;
|
||||
running: string;
|
||||
completed: string;
|
||||
failed: string;
|
||||
};
|
||||
|
||||
// Settings
|
||||
settings: {
|
||||
title: string;
|
||||
|
||||
@@ -78,7 +78,8 @@ export const zhCN: Translations = {
|
||||
proMode: "专业",
|
||||
proModeDescription: "思考、计划再执行,获得更精准的结果,可能需要更多时间",
|
||||
ultraMode: "超级",
|
||||
ultraModeDescription: "专业模式加子代理,适用于复杂的多步骤任务,功能最强大",
|
||||
ultraModeDescription:
|
||||
"专业模式加子代理,适用于复杂的多步骤任务,功能最强大",
|
||||
searchModels: "搜索模型...",
|
||||
surpriseMe: "小惊喜",
|
||||
surpriseMePrompt: "给我一个小惊喜吧",
|
||||
@@ -203,6 +204,14 @@ export const zhCN: Translations = {
|
||||
skillInstallTooltip: "安装技能并使其可在 DeerFlow 中使用",
|
||||
},
|
||||
|
||||
subtasks: {
|
||||
subtask: "子任务",
|
||||
executing: (count: number) => `并行执行 ${count} 个子任务`,
|
||||
running: "子任务运行中",
|
||||
completed: "子任务已完成",
|
||||
failed: "子任务失败",
|
||||
},
|
||||
|
||||
// Settings
|
||||
settings: {
|
||||
title: "设置",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Message } from "@langchain/langgraph-sdk";
|
||||
import type { AIMessage, Message } from "@langchain/langgraph-sdk";
|
||||
|
||||
interface GenericMessageGroup<T = string> {
|
||||
type: T;
|
||||
@@ -16,12 +16,15 @@ interface AssistantPresentFilesGroup extends GenericMessageGroup<"assistant:pres
|
||||
|
||||
interface AssistantClarificationGroup extends GenericMessageGroup<"assistant:clarification"> {}
|
||||
|
||||
interface AssistantSubagentGroup extends GenericMessageGroup<"assistant:subagent"> {}
|
||||
|
||||
type MessageGroup =
|
||||
| HumanMessageGroup
|
||||
| AssistantProcessingGroup
|
||||
| AssistantMessageGroup
|
||||
| AssistantPresentFilesGroup
|
||||
| AssistantClarificationGroup;
|
||||
| AssistantClarificationGroup
|
||||
| AssistantSubagentGroup;
|
||||
|
||||
export function groupMessages<T>(
|
||||
messages: Message[],
|
||||
@@ -78,6 +81,12 @@ export function groupMessages<T>(
|
||||
type: "assistant:present-files",
|
||||
messages: [message],
|
||||
});
|
||||
} else if (hasSubagent(message)) {
|
||||
groups.push({
|
||||
id: message.id,
|
||||
type: "assistant:subagent",
|
||||
messages: [message],
|
||||
});
|
||||
} else {
|
||||
if (lastGroup?.type !== "assistant:processing") {
|
||||
groups.push({
|
||||
@@ -232,6 +241,15 @@ export function extractPresentFilesFromMessage(message: Message) {
|
||||
return files;
|
||||
}
|
||||
|
||||
export function hasSubagent(message: AIMessage) {
|
||||
for (const toolCall of message.tool_calls ?? []) {
|
||||
if (toolCall.name === "task") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function findToolCallResult(toolCallId: string, messages: Message[]) {
|
||||
for (const message of messages) {
|
||||
if (message.type === "tool" && message.tool_call_id === toolCallId) {
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { createContext, useContext } from "react";
|
||||
|
||||
import type { SubagentState } from "../threads/types";
|
||||
|
||||
export const SubagentContext = createContext<Map<string, SubagentState>>(new Map());
|
||||
|
||||
export function useSubagentContext() {
|
||||
const context = useContext(SubagentContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useSubagentContext must be used within a SubagentContext.Provider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
import type { SubagentProgressEvent, SubagentState } from "../threads/types";
|
||||
|
||||
export function useSubagentStates() {
|
||||
const [subagents, setSubagents] = useState<Map<string, SubagentState>>(new Map());
|
||||
const subagentsRef = useRef<Map<string, SubagentState>>(new Map());
|
||||
|
||||
// 保持 ref 与 state 同步
|
||||
useEffect(() => {
|
||||
subagentsRef.current = subagents;
|
||||
}, [subagents]);
|
||||
|
||||
const handleSubagentProgress = useCallback((event: SubagentProgressEvent) => {
|
||||
console.log('[SubagentProgress] Received event:', event);
|
||||
|
||||
const { task_id, trace_id, subagent_type, event_type, result, error } = event;
|
||||
|
||||
setSubagents(prev => {
|
||||
const newSubagents = new Map(prev);
|
||||
const existingState = newSubagents.get(task_id) || {
|
||||
task_id,
|
||||
trace_id,
|
||||
subagent_type,
|
||||
status: "running" as const,
|
||||
};
|
||||
|
||||
let newState = { ...existingState };
|
||||
|
||||
switch (event_type) {
|
||||
case "started":
|
||||
newState = {
|
||||
...newState,
|
||||
status: "running",
|
||||
};
|
||||
break;
|
||||
|
||||
case "completed":
|
||||
newState = {
|
||||
...newState,
|
||||
status: "completed",
|
||||
result,
|
||||
};
|
||||
break;
|
||||
|
||||
case "failed":
|
||||
newState = {
|
||||
...newState,
|
||||
status: "failed",
|
||||
error,
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
newSubagents.set(task_id, newState);
|
||||
return newSubagents;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const clearSubagents = useCallback(() => {
|
||||
setSubagents(new Map());
|
||||
}, []);
|
||||
|
||||
return {
|
||||
subagents,
|
||||
handleSubagentProgress,
|
||||
clearSubagents,
|
||||
};
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export { useSubagentStates } from "./hooks";
|
||||
export { SubagentContext, useSubagentContext } from "./context";
|
||||
46
frontend/src/core/tasks/context.tsx
Normal file
46
frontend/src/core/tasks/context.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { createContext, useCallback, useContext, useState } from "react";
|
||||
|
||||
import type { Subtask } from "./types";
|
||||
|
||||
export interface SubtaskContextValue {
|
||||
tasks: Map<string, Subtask>;
|
||||
}
|
||||
|
||||
export const SubtaskContext = createContext<SubtaskContextValue>({
|
||||
tasks: new Map(),
|
||||
});
|
||||
|
||||
export function SubtasksProvider({ children }: { children: React.ReactNode }) {
|
||||
const [tasks] = useState<Map<string, Subtask>>(new Map());
|
||||
return (
|
||||
<SubtaskContext.Provider value={{ tasks }}>
|
||||
{children}
|
||||
</SubtaskContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useSubtaskContext() {
|
||||
const context = useContext(SubtaskContext);
|
||||
if (context === undefined) {
|
||||
throw new Error(
|
||||
"useSubtaskContext must be used within a SubtaskContext.Provider",
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
export function useSubtask(id: string) {
|
||||
const { tasks } = useSubtaskContext();
|
||||
return tasks.get(id);
|
||||
}
|
||||
|
||||
export function useUpdateSubtask() {
|
||||
const { tasks } = useSubtaskContext();
|
||||
const updateSubtask = useCallback(
|
||||
(task: Partial<Subtask> & { id: string }) => {
|
||||
tasks.set(task.id, { ...tasks.get(task.id), ...task } as Subtask);
|
||||
},
|
||||
[tasks],
|
||||
);
|
||||
return updateSubtask;
|
||||
}
|
||||
1
frontend/src/core/tasks/index.ts
Normal file
1
frontend/src/core/tasks/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./types";
|
||||
9
frontend/src/core/tasks/types.ts
Normal file
9
frontend/src/core/tasks/types.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export interface Subtask {
|
||||
id: string;
|
||||
status: "in_progress" | "completed" | "failed";
|
||||
subagent_type: string;
|
||||
description: string;
|
||||
prompt: string;
|
||||
result?: string;
|
||||
error?: string;
|
||||
}
|
||||
@@ -31,6 +31,9 @@ export function useThreadStream({
|
||||
threadId: isNewThread ? undefined : threadId,
|
||||
reconnectOnMount: true,
|
||||
fetchStateHistory: true,
|
||||
onCustomEvent(event) {
|
||||
console.info(event);
|
||||
},
|
||||
onFinish(state) {
|
||||
onFinish?.(state.values);
|
||||
// void queryClient.invalidateQueries({ queryKey: ["threads", "search"] });
|
||||
|
||||
Reference in New Issue
Block a user