Files
AgentCoord/frontend/src/api/index.ts
2026-03-05 11:00:21 +08:00

981 lines
26 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import websocket from '@/utils/websocket'
import type { Agent, IApiStepTask, IRawPlanResponse, IRawStepTask } from '@/stores'
import { withRetry } from '@/utils/retry'
import request from '@/utils/request'
import { useConfigStoreHook } from '@/stores'
export interface ActionHistory {
ID: string
ActionType: string
AgentName: string
Description: string
ImportantInput: string[]
Action_Result: string
}
export interface BranchAction {
ID: string
ActionType: string
AgentName: string
Description: string
ImportantInput: string[]
}
export type IExecuteRawResponse = {
LogNodeType: string
NodeId: string
InputName_List?: string[] | null
OutputName?: string
content?: string
ActionHistory: ActionHistory[]
}
/**
* WebSocket 流式事件类型
*/
export type StreamingEvent =
| {
type: 'step_start'
step_index: number
total_steps: number
step_name: string
task_description?: string
}
| {
type: 'action_complete'
step_index: number
step_name: string
action_index: number
total_actions: number
completed_actions: number
action_result: ActionHistory
batch_info?: {
batch_index: number
batch_size: number
is_parallel: boolean
}
}
| {
type: 'step_complete'
step_index: number
step_name: string
step_log_node: any
object_log_node: any
}
| {
type: 'execution_complete'
total_steps: number
}
| {
type: 'error'
message: string
}
export interface IFillAgentSelectionRequest {
goal: string
stepTask: IApiStepTask
agents: string[]
}
class Api {
// 提取响应数据的公共方法
private extractResponse<T>(raw: any): T {
return (raw.data || raw) as T
}
// 颜色向量转 HSL 字符串
private vec2Hsl = (color: number[]): string => {
const [h, s, l] = color
return `hsl(${h}, ${s}%, ${l}%)`
}
setAgents = (data: Pick<Agent, 'Name' | 'Profile' | 'apiUrl' | 'apiKey' | 'apiModel'>[]) =>
websocket.send('set_agents', data)
getAgents = (user_id: string = 'default_user') => websocket.send('get_agents', { user_id })
generateBasePlan = (data: {
goal: string
inputs: string[]
apiUrl?: string
apiKey?: string
apiModel?: string
onProgress?: (progress: {
status: string
stage?: string
message?: string
[key: string]: any
}) => void
}) =>
websocket.send(
'generate_base_plan',
{
'General Goal': data.goal,
'Initial Input Object': data.inputs,
apiUrl: data.apiUrl,
apiKey: data.apiKey,
apiModel: data.apiModel,
},
undefined,
data.onProgress,
)
/**
* 优化版流式执行计划(支持动态追加步骤)
* 步骤级流式 + 动作级智能并行 + 动态追加步骤
*/
executePlanOptimized = (
plan: IRawPlanResponse,
onMessage: (event: StreamingEvent) => void,
onError?: (error: Error) => void,
onComplete?: () => void,
_useWebSocket?: boolean,
existingKeyObjects?: Record<string, any>,
enableDynamic?: boolean,
onExecutionStarted?: (executionId: string) => void,
executionId?: string,
restartFromStepIndex?: number,
rehearsalLog?: any[],
TaskID?: string,
) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
void _useWebSocket // 保留参数位置以保持兼容性
const data = {
RehearsalLog: rehearsalLog || [], // 使用传递的 RehearsalLog
num_StepToRun: null,
existingKeyObjects: existingKeyObjects || {},
enable_dynamic: enableDynamic || false,
execution_id: executionId || null,
restart_from_step_index: restartFromStepIndex ?? null, // 新增:传递重新执行索引
task_id: TaskID || null, // 任务唯一标识,用于写入数据库
plan: {
'Initial Input Object': plan['Initial Input Object'],
'General Goal': plan['General Goal'],
'Collaboration Process': plan['Collaboration Process']?.map((step) => ({
StepName: step.StepName,
TaskContent: step.TaskContent,
InputObject_List: step.InputObject_List,
OutputObject: step.OutputObject,
AgentSelection: step.AgentSelection,
Collaboration_Brief_frontEnd: step.Collaboration_Brief_frontEnd,
TaskProcess: step.TaskProcess.map((action) => ({
ActionType: action.ActionType,
AgentName: action.AgentName,
Description: action.Description,
ID: action.ID,
ImportantInput: action.ImportantInput,
})),
})),
},
}
websocket.subscribe(
'execute_plan_optimized',
data,
// onProgress
(progressData) => {
try {
let event: StreamingEvent
// 处理不同类型的progress数据
if (typeof progressData === 'string') {
event = JSON.parse(progressData)
} else {
event = progressData as StreamingEvent
}
// 处理特殊事件类型
if (event && typeof event === 'object') {
// 检查是否是execution_started事件
if ('status' in event && event.status === 'execution_started') {
if ('execution_id' in event && onExecutionStarted) {
onExecutionStarted(event.execution_id as string)
}
return
}
}
onMessage(event)
} catch (e) {
// Failed to parse WebSocket data
}
},
// onComplete
() => {
onComplete?.()
},
// onError
(error) => {
onError?.(error)
},
)
}
/**
* 分支任务大纲
*/
branchPlanOutline = (data: {
branch_Number: number
Modification_Requirement: string
Existing_Steps: IRawStepTask[]
Baseline_Completion: number
initialInputs: string[]
goal: string
onProgress?: (progress: {
status: string
stage?: string
message?: string
[key: string]: any
}) => void
}) =>
websocket.send(
'branch_plan_outline',
{
branch_Number: data.branch_Number,
Modification_Requirement: data.Modification_Requirement,
Existing_Steps: data.Existing_Steps,
Baseline_Completion: data.Baseline_Completion,
'Initial Input Object': data.initialInputs,
'General Goal': data.goal,
},
undefined,
data.onProgress,
)
/**
* 分支任务流程
*/
branchTaskProcess = (data: {
branch_Number: number
Modification_Requirement: string
Existing_Steps: BranchAction[]
Baseline_Completion: number
stepTaskExisting: any
goal: string
onProgress?: (progress: {
status: string
stage?: string
message?: string
[key: string]: any
}) => void
}) =>
websocket.send(
'branch_task_process',
{
branch_Number: data.branch_Number,
Modification_Requirement: data.Modification_Requirement,
Existing_Steps: data.Existing_Steps,
Baseline_Completion: data.Baseline_Completion,
stepTaskExisting: data.stepTaskExisting,
'General Goal': data.goal,
},
undefined,
data.onProgress,
)
fillStepTask = async (data: {
goal: string
stepTask: any
generation_id?: string
TaskID?: string
onProgress?: (progress: {
status: string
stage?: string
message?: string
[key: string]: any
}) => void
}): Promise<IRawStepTask> => {
const rawResponse = await withRetry(
() =>
websocket.send(
'fill_step_task',
{
'General Goal': data.goal,
stepTask: data.stepTask,
generation_id: data.generation_id || '',
task_id: data.TaskID || '',
},
undefined,
data.onProgress,
),
{
maxRetries: 3,
initialDelayMs: 2000,
onRetry: (error, attempt, delay) => {
console.warn(` [fillStepTask] 第${attempt}次重试,等待 ${delay}ms...`, error?.message)
},
},
)
let response = this.extractResponse<any>(rawResponse)
if (response?.filled_stepTask) {
response = response.filled_stepTask
}
const briefData: Record<string, { text: string; style?: Record<string, string> }> = {}
if (response.Collaboration_Brief_FrontEnd?.data) {
for (const [key, value] of Object.entries(response.Collaboration_Brief_FrontEnd.data)) {
briefData[key] = {
text: (value as { text: string; color: number[] }).text,
style: {
background: this.vec2Hsl((value as { text: string; color: number[] }).color),
},
}
}
}
return {
StepName: response.StepName || '',
TaskContent: response.TaskContent || '',
InputObject_List: response.InputObject_List || [],
OutputObject: response.OutputObject || '',
AgentSelection: response.AgentSelection || [],
Collaboration_Brief_frontEnd: {
template: response.Collaboration_Brief_FrontEnd?.template || '',
data: briefData,
},
TaskProcess: response.TaskProcess || [],
}
}
fillStepTaskTaskProcess = async (data: {
goal: string
stepTask: IApiStepTask
agents: string[]
TaskID?: string
onProgress?: (progress: {
status: string
stage?: string
message?: string
[key: string]: any
}) => void
}): Promise<IApiStepTask> => {
const rawResponse = await withRetry(
() =>
websocket.send(
'fill_step_task_process',
{
'General Goal': data.goal,
task_id: data.TaskID || undefined,
stepTask_lackTaskProcess: {
StepName: data.stepTask.name,
TaskContent: data.stepTask.content,
InputObject_List: data.stepTask.inputs,
OutputObject: data.stepTask.output,
AgentSelection: data.agents,
},
agents: data.agents,
},
undefined,
data.onProgress,
),
{
maxRetries: 3,
initialDelayMs: 2000,
onRetry: (error, attempt, delay) => {
console.warn(
`[fillStepTaskTaskProcess] 第${attempt}次重试,等待 ${delay}ms...`,
error?.message,
)
},
},
)
let response = this.extractResponse<any>(rawResponse)
if (response?.filled_stepTask) {
response = response.filled_stepTask
}
const briefData: Record<string, { text: string; style: { background: string } }> = {}
if (response.Collaboration_Brief_FrontEnd?.data) {
for (const [key, value] of Object.entries(response.Collaboration_Brief_FrontEnd.data)) {
briefData[key] = {
text: (value as { text: string; color: number[] }).text,
style: {
background: this.vec2Hsl((value as { text: string; color: number[] }).color),
},
}
}
}
const process = (response.TaskProcess || []).map((action: any) => ({
id: action.ID,
type: action.ActionType,
agent: action.AgentName,
description: action.Description,
inputs: action.ImportantInput,
}))
return {
name: response.StepName || '',
content: response.TaskContent || '',
inputs: response.InputObject_List || [],
output: response.OutputObject || '',
agents: response.AgentSelection || [],
brief: {
template: response.Collaboration_Brief_FrontEnd?.template || '',
data: briefData,
},
process,
}
}
/**
* 为每个智能体评分(带重试机制)
*/
agentSelectModifyInit = async (data: {
goal: string
stepTask: any
TaskID?: string
onProgress?: (progress: {
status: string
stage?: string
message?: string
[key: string]: any
}) => void
}): Promise<Record<string, Record<string, { reason: string; score: number }>>> => {
const requestPayload = {
'General Goal': data.goal,
stepTask: {
Id: data.stepTask.Id || data.stepTask.id,
StepName: data.stepTask.StepName || data.stepTask.name,
TaskContent: data.stepTask.TaskContent || data.stepTask.content,
InputObject_List: data.stepTask.InputObject_List || data.stepTask.inputs,
OutputObject: data.stepTask.OutputObject || data.stepTask.output,
},
task_id: data.TaskID || '',
}
const rawResponse = await withRetry(
() =>
websocket.send(
'agent_select_modify_init',
requestPayload,
undefined,
data.onProgress,
),
{
maxRetries: 3,
initialDelayMs: 2000,
onRetry: (error, attempt, delay) => {
console.warn(
`[agentSelectModifyInit] 第${attempt}次重试,等待 ${delay}ms...`,
error?.message,
)
},
},
)
let response = this.extractResponse<any>(rawResponse)
if (response?.scoreTable) {
response = response.scoreTable
}
const transformedData: Record<string, Record<string, { reason: string; score: number }>> = {}
if (!response || typeof response !== 'object' || Array.isArray(response)) {
console.warn('[agentSelectModifyInit] 后端返回数据格式异常:', response)
return transformedData
}
for (const [aspect, agents] of Object.entries(response)) {
for (const [agentName, scoreInfo] of Object.entries(
(agents as Record<string, { Reason: string; Score: number }>) || {},
)) {
if (!transformedData[agentName]) {
transformedData[agentName] = {}
}
transformedData[agentName][aspect] = {
reason: scoreInfo.Reason,
score: scoreInfo.Score,
}
}
}
return transformedData
}
/**
* 添加新的评估维度
*/
agentSelectModifyAddAspect = async (data: {
aspectList: string[]
stepTask?: {
Id?: string
StepName?: string
TaskContent?: string
InputObject_List?: string[]
OutputObject?: string
}
TaskID?: string
onProgress?: (progress: {
status: string
stage?: string
message?: string
[key: string]: any
}) => void
}): Promise<{
aspectName: string
agentScores: Record<string, { score: number; reason: string }>
}> => {
const rawResponse = await websocket.send(
'agent_select_modify_add_aspect',
{
aspectList: data.aspectList,
stepTask: data.stepTask,
task_id: data.TaskID || '',
},
undefined,
data.onProgress,
)
const response = this.extractResponse<any>(rawResponse)
const newAspect = data.aspectList[data.aspectList.length - 1]
if (!newAspect) {
throw new Error('aspectList is empty')
}
const scoreTable = response.scoreTable || response
const newAspectAgents = scoreTable[newAspect]
const agentScores: Record<string, { score: number; reason: string }> = {}
if (newAspectAgents) {
for (const [agentName, scoreInfo] of Object.entries(newAspectAgents as Record<string, { Score?: number; score?: number; Reason?: string; reason?: string }>)) {
agentScores[agentName] = {
score: scoreInfo.Score || scoreInfo.score || 0,
reason: scoreInfo.Reason || scoreInfo.reason || '',
}
}
}
return {
aspectName: newAspect,
agentScores,
}
}
/**
* 删除评估维度
* @param taskId 任务ID
* @param stepId 步骤ID可选不传则删除所有步骤中的该维度
* @param aspectName 要删除的维度名称
* @returns 是否删除成功
*/
agentSelectModifyDeleteAspect = async (
taskId: string,
aspectName: string,
stepId?: string
): Promise<boolean> => {
if (!websocket.connected) {
throw new Error('WebSocket未连接')
}
try {
const rawResponse = await websocket.send(
'agent_select_modify_delete_aspect',
{
task_id: taskId,
step_id: stepId || '',
aspect_name: aspectName,
},
undefined,
undefined
)
const response = this.extractResponse<{ status: string }>(rawResponse)
return response?.status === 'success' || false
} catch (error) {
console.error('删除维度失败:', error)
return false
}
}
/**
* 向正在执行的任务追加新步骤
* @param executionId 执行ID
* @param newSteps 新步骤列表
* @returns 追加的步骤数量
*/
addStepsToExecution = async (executionId: string, newSteps: IRawStepTask[]): Promise<number> => {
if (!websocket.connected) {
throw new Error('WebSocket未连接')
}
const rawResponse = await websocket.send('add_steps_to_execution', {
execution_id: executionId,
new_steps: newSteps.map((step) => ({
StepName: step.StepName,
TaskContent: step.TaskContent,
InputObject_List: step.InputObject_List,
OutputObject: step.OutputObject,
AgentSelection: step.AgentSelection,
Collaboration_Brief_frontEnd: step.Collaboration_Brief_frontEnd,
TaskProcess: step.TaskProcess.map((action) => ({
ActionType: action.ActionType,
AgentName: action.AgentName,
Description: action.Description,
ID: action.ID,
ImportantInput: action.ImportantInput,
})),
})),
})
const response = this.extractResponse<{ added_count: number }>(rawResponse)
return response?.added_count || 0
}
/**
* 保存任务分支数据
* @param taskId 任务ID
* @param branches 分支数据数组
* @returns 是否保存成功
*/
saveBranches = async (taskId: string, branches: any[]): Promise<boolean> => {
if (!websocket.connected) {
throw new Error('WebSocket未连接')
}
try {
const rawResponse = await websocket.send('save_branches', {
task_id: taskId,
branches,
})
const response = this.extractResponse<{ status: string }>(rawResponse)
return response?.status === 'success' || false
} catch (error) {
console.error('保存分支数据失败:', error)
return false
}
}
/**
* 保存任务过程分支数据
* @param TaskID 大任务ID数据库主键
* @param branches 任务过程分支数据Map结构转对象
* @returns 是否保存成功
*/
saveTaskProcessBranches = async (
TaskID: string,
branches: Record<string, Record<string, any[]>>,
): Promise<boolean> => {
if (!websocket.connected) {
throw new Error('WebSocket未连接')
}
try {
const rawResponse = await websocket.send('save_task_process_branches', {
task_id: TaskID,
branches,
})
const response = this.extractResponse<{ status: string }>(rawResponse)
return response?.status === 'success' || false
} catch (error) {
console.error('保存任务过程分支数据失败:', error)
return false
}
}
/**
* 删除任务过程分支数据
* @param TaskID 大任务ID
* @param stepId 小任务ID
* @param branchId 要删除的分支ID
* @returns 是否删除成功
*/
deleteTaskProcessBranch = async (
TaskID: string,
stepId: string,
branchId: string,
): Promise<boolean> => {
if (!websocket.connected) {
throw new Error('WebSocket未连接')
}
try {
const rawResponse = await websocket.send('delete_task_process_branch', {
task_id: TaskID,
stepId,
branchId,
})
const response = this.extractResponse<{ status: string }>(rawResponse)
return response?.status === 'success' || false
} catch (error) {
console.error('删除任务过程分支数据失败:', error)
return false
}
}
/**
* 更新任务大纲数据
* @param taskId 任务ID
* @param taskOutline 完整的大纲数据
* @returns 是否更新成功
*/
updateTaskOutline = async (taskId: string, taskOutline: any): Promise<boolean> => {
if (!websocket.connected) {
throw new Error('WebSocket未连接')
}
try {
const rawResponse = await websocket.send('save_task_outline', {
task_id: taskId,
task_outline: taskOutline,
})
const response = this.extractResponse<{ status: string }>(rawResponse)
return response?.status === 'success' || false
} catch (error) {
console.error('更新任务大纲失败:', error)
return false
}
}
/**
* 更新指定步骤的 assigned_agents
* @param params 参数对象
* @returns 是否更新成功
*/
updateAssignedAgents = async (params: {
task_id: string // 大任务ID数据库主键
step_id: string // 步骤级ID小任务UUID
agents: string[] // 选中的 agent 列表
confirmed_groups?: string[][] // 可选:确认的 agent 组合列表
agent_combinations?: Record<string, { process: any; brief: any }> // 可选agent 组合的 TaskProcess 数据
}): Promise<boolean> => {
if (!websocket.connected) {
throw new Error('WebSocket未连接')
}
try {
const rawResponse = await websocket.send('update_assigned_agents', {
task_id: params.task_id,
step_id: params.step_id,
agents: params.agents,
confirmed_groups: params.confirmed_groups,
agent_combinations: params.agent_combinations,
})
const response = this.extractResponse<{ status: string; error?: string }>(rawResponse)
if (response?.status === 'success') {
console.log('更新 assigned_agents 成功:', params)
return true
}
console.warn('更新 assigned_agents 失败:', response?.error)
return false
} catch (error) {
console.error('更新 assigned_agents 失败:', error)
return false
}
}
// ==================== 导出功能 ====================
/**
* 导出任务为指定格式
*/
exportTask = async (params: {
task_id: string
export_type: string
user_id?: string
}): Promise<{
record_id: number
file_name: string
file_url: string
file_size: number
export_type: string
}> => {
if (!websocket.connected) {
throw new Error('WebSocket未连接')
}
const rawResponse = await websocket.send('export', {
task_id: params.task_id,
export_type: params.export_type,
user_id: params.user_id || 'anonymous',
})
const response = this.extractResponse<{
record_id: number
file_name: string
file_url: string
file_size: number
export_type: string
}>(rawResponse)
if (response) {
console.log('导出成功:', response)
return response
}
throw new Error('导出失败')
}
/**
* 获取导出记录列表
*/
getExportList = async (params: {
task_id: string
}): Promise<{
list: Array<{
id: number
task_id: string
user_id: string
export_type: string
file_name: string
file_path: string
file_url: string
file_size: number
created_at: string
}>
total: number
}> => {
if (!websocket.connected) {
throw new Error('WebSocket未连接')
}
const rawResponse = await websocket.send('get_export_list', {
task_id: params.task_id,
})
const response = this.extractResponse<{
list: Array<{
id: number
task_id: string
user_id: string
export_type: string
file_name: string
file_path: string
file_url: string
file_size: number
created_at: string
}>
total: number
}>(rawResponse)
if (response) {
return response
}
return { list: [], total: 0 }
}
/**
* 下载导出文件
*/
downloadExport = async (recordId: number): Promise<void> => {
const configStore = useConfigStoreHook()
const baseURL = configStore.config.apiBaseUrl || ''
const url = `${baseURL}/api/export/${recordId}/download`
try {
const response = await fetch(url, {
method: 'GET',
})
if (!response.ok) {
throw new Error('下载失败')
}
// 获取文件名从 Content-Disposition 头
const contentDisposition = response.headers.get('Content-Disposition')
let fileName = 'download'
if (contentDisposition) {
const match = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)
if (match) {
fileName = match[1].replace(/['"]/g, '')
}
}
// 创建 Blob 并下载
const blob = await response.blob()
const downloadUrl = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = downloadUrl
link.download = fileName
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(downloadUrl)
} catch (error) {
console.error('下载失败:', error)
throw error
}
}
/**
* 预览导出文件
*/
previewExport = async (recordId: number): Promise<{
content?: string
file_url?: string
file_name?: string
type: string
}> => {
const configStore = useConfigStoreHook()
const baseURL = configStore.config.apiBaseUrl || ''
const url = `${baseURL}/api/export/${recordId}/preview`
const response = await request<{
content?: string
file_url?: string
file_name?: string
type: string
}>({
url,
method: 'GET',
})
return response.data
}
/**
* 生成分享链接
*/
shareExport = async (recordId: number): Promise<{
share_url: string
file_name: string
expired_at: string | null
}> => {
const configStore = useConfigStoreHook()
const baseURL = configStore.config.apiBaseUrl || ''
const url = `${baseURL}/api/export/${recordId}/share`
const response = await request<{
share_url: string
file_name: string
expired_at: string | null
}>({
url,
method: 'GET',
})
return response.data
}
/**
* 删除导出记录
*/
deleteExport = async (recordId: number): Promise<boolean> => {
const configStore = useConfigStoreHook()
const baseURL = configStore.config.apiBaseUrl || ''
const url = `${baseURL}/api/export/${recordId}`
try {
await request({
url,
method: 'DELETE',
})
console.log('删除导出记录成功:', recordId)
return true
} catch (error) {
console.error('删除导出记录失败:', error)
return false
}
}
}
export default new Api()