ForcePilot/web/src/apis/agent_api.js
zhizhizhii 9e75ac478b Merge remote-tracking branch 'refs/remotes/1165506270/feat/sandbox-provisioner-shared-storage' into feature/sandbox
# Conflicts:
#	server/routers/chat_router.py
#	src/config/app.py
#	src/services/chat_stream_service.py
#	uv.lock
#	web/src/components/AgentChatComponent.vue
#	web/src/components/AgentPanel.vue
2026-03-14 11:10:32 +08:00

411 lines
12 KiB
JavaScript
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 {
apiGet,
apiPost,
apiDelete,
apiPut,
apiAdminGet,
apiAdminPost,
apiAdminDelete,
apiRequest
} from './base'
import { useUserStore } from '@/stores/user'
/**
* 智能体API模块
* 包含智能体管理、聊天、配置等功能
* 权限要求: 任何已登录用户(普通用户、管理员、超级管理员)
*/
// =============================================================================
// === 智能体聊天分组 ===
// =============================================================================
export const agentApi = {
/**
* 发送聊天消息到指定智能体(流式响应)
* @param {string} agentId - 智能体ID
* @param {Object} data - 聊天数据
* @returns {Promise} - 聊天响应流
*/
sendAgentMessage: (agentId, data, options = {}) => {
const { signal, headers: extraHeaders, ...restOptions } = options || {}
const baseHeaders = {
'Content-Type': 'application/json',
...useUserStore().getAuthHeaders()
}
return fetch(`/api/chat/agent/${agentId}`, {
method: 'POST',
body: JSON.stringify(data),
signal,
headers: {
...baseHeaders,
...(extraHeaders || {})
},
...restOptions
})
},
/**
* 简单聊天调用(非流式)
* @param {string} query - 查询内容
* @returns {Promise} - 聊天响应
*/
simpleCall: (query) => apiPost('/api/chat/call', { query }),
/**
* 获取默认智能体
* @returns {Promise} - 默认智能体信息
*/
getDefaultAgent: () => apiGet('/api/chat/default_agent'),
/**
* 获取智能体列表
* @returns {Promise} - 智能体列表
*/
getAgents: () => apiGet('/api/chat/agent'),
/**
* 获取单个智能体详情
* @param {string} agentId - 智能体ID
* @returns {Promise} - 智能体详情
*/
getAgentDetail: (agentId) => apiGet(`/api/chat/agent/${agentId}`),
/**
* 获取智能体历史消息
* @param {string} agentId - 智能体ID
* @param {string} threadId - 会话ID
* @returns {Promise} - 历史消息
*/
getAgentHistory: (agentId, threadId) =>
apiGet(`/api/chat/agent/${agentId}/history?thread_id=${threadId}`),
/**
* 获取指定会话的 AgentState
* @param {string} agentId - 智能体ID
* @param {string} threadId - 会话ID
* @returns {Promise} - AgentState
*/
getAgentState: (agentId, threadId) =>
apiGet(`/api/chat/agent/${agentId}/state?thread_id=${threadId}`),
/**
* Submit feedback for a message
* @param {number} messageId - Message ID
* @param {string} rating - 'like' or 'dislike'
* @param {string|null} reason - Optional reason for dislike
* @returns {Promise} - Feedback response
*/
submitMessageFeedback: (messageId, rating, reason = null) =>
apiPost(`/api/chat/message/${messageId}/feedback`, { rating, reason }),
/**
* Get feedback status for a message
* @param {number} messageId - Message ID
* @returns {Promise} - Feedback status
*/
getMessageFeedback: (messageId) => apiGet(`/api/chat/message/${messageId}/feedback`),
/**
* 获取模型提供商的模型列表
* @param {string} provider - 模型提供商
* @returns {Promise} - 模型列表
*/
getProviderModels: (provider) => apiGet(`/api/chat/models?model_provider=${provider}`),
/**
* 更新模型提供商的模型列表
* @param {string} provider - 模型提供商
* @param {Array} models - 选中的模型列表
* @returns {Promise} - 更新结果
*/
updateProviderModels: (provider, models) =>
apiPost(`/api/chat/models/update?model_provider=${provider}`, models),
/**
* 获取智能体配置
* @param {string} agentName - 智能体名称
* @returns {Promise} - 智能体配置
*/
getAgentConfig: async (agentName) => {
return apiAdminGet(`/api/chat/agent/${agentName}/config`)
},
/**
* 保存智能体配置
* @param {string} agentName - 智能体名称
* @param {Object} config - 配置对象
* @param {Object} options - 额外参数 (e.g., { reload_graph: true })
* @returns {Promise} - 保存结果
*/
saveAgentConfig: async (agentName, config, options = {}) => {
const queryParams = new URLSearchParams(options).toString()
const url = `/api/chat/agent/${agentName}/config` + (queryParams ? `?${queryParams}` : '')
return apiAdminPost(url, config)
},
getAgentConfigs: (agentId) => apiGet(`/api/chat/agent/${agentId}/configs`),
getAgentConfigProfile: (agentId, configId) =>
apiGet(`/api/chat/agent/${agentId}/configs/${configId}`),
createAgentConfigProfile: (agentId, payload) =>
apiAdminPost(`/api/chat/agent/${agentId}/configs`, payload),
updateAgentConfigProfile: (agentId, configId, payload) =>
apiPut(`/api/chat/agent/${agentId}/configs/${configId}`, payload),
setAgentConfigDefault: (agentId, configId) =>
apiAdminPost(`/api/chat/agent/${agentId}/configs/${configId}/set_default`, {}),
deleteAgentConfigProfile: (agentId, configId) =>
apiAdminDelete(`/api/chat/agent/${agentId}/configs/${configId}`),
/**
* 设置默认智能体
* @param {string} agentId - 智能体ID
* @returns {Promise} - 设置结果
*/
setDefaultAgent: async (agentId) => {
return apiAdminPost('/api/chat/set_default_agent', { agent_id: agentId })
},
/**
* 恢复被人工审批中断的对话(流式响应)
* @param {string} agentId - 智能体ID
* @param {Object} data - 恢复数据 { thread_id, approved }
* @param {Object} options - 可选参数signal, headers等
* @returns {Promise} - 恢复响应流
*/
resumeAgentChat: (agentId, data, options = {}) => {
const { signal, headers: extraHeaders, ...restOptions } = options || {}
const baseHeaders = {
'Content-Type': 'application/json',
...useUserStore().getAuthHeaders()
}
return fetch(`/api/chat/agent/${agentId}/resume`, {
method: 'POST',
body: JSON.stringify(data),
signal,
headers: {
...baseHeaders,
...(extraHeaders || {})
},
...restOptions
})
},
/**
* 创建异步运行任务Run
* @param {string} agentId - 智能体ID
* @param {Object} data - run 请求体
* @returns {Promise<Object>}
*/
createAgentRun: (agentId, data) =>
apiPost(`/api/chat/agent/${agentId}/runs`, {
query: data.query,
config: data.config || {},
image_content: data.image_content || null
}),
/**
* 获取 Run 状态
* @param {string} runId - run ID
* @returns {Promise<Object>}
*/
getAgentRun: (runId) => apiGet(`/api/chat/runs/${runId}`),
/**
* 取消 Run
* @param {string} runId - run ID
* @returns {Promise<Object>}
*/
cancelAgentRun: (runId) => apiPost(`/api/chat/runs/${runId}/cancel`, {}),
/**
* 获取线程活跃 Run
* @param {string} threadId - 线程ID
* @returns {Promise<Object>}
*/
getThreadActiveRun: (threadId) => apiGet(`/api/chat/thread/${threadId}/active_run`),
/**
* 打开 Run 事件 SSE 连接(调用方负责关闭)
* @param {string} runId - run ID
* @param {string|number} afterSeq - 起始 seq/cursor
* @param {Object} options - { signal }
* @returns {Promise<Response>}
*/
streamAgentRunEvents: (runId, afterSeq = '0', options = {}) => {
const { signal } = options
return fetch(
`/api/chat/runs/${runId}/events?after_seq=${encodeURIComponent(String(afterSeq))}`,
{
method: 'GET',
headers: {
...useUserStore().getAuthHeaders()
},
signal
}
)
}
}
// =============================================================================
// === 多模态图片支持分组 ===
// =============================================================================
export const multimodalApi = {
/**
* 上传图片并获取base64编码
* @param {File} file - 图片文件
* @returns {Promise} - 上传结果
*/
uploadImage: (file) => {
const formData = new FormData()
formData.append('file', file)
return apiRequest(
'/api/chat/image/upload',
{
method: 'POST',
body: formData
},
true
)
}
}
// =============================================================================
// === 对话线程分组 ===
// =============================================================================
export const threadApi = {
/**
* 获取对话线程列表
* @param {string} agentId - 智能体ID
* @param {number} limit - 返回数量限制默认100
* @param {number} offset - 偏移量默认0
* @returns {Promise} - 对话线程列表
*/
getThreads: (agentId, limit = 100, offset = 0) => {
const url = `/api/chat/threads?agent_id=${agentId}&limit=${limit}&offset=${offset}`
return apiGet(url)
},
/**
* 创建新对话线程
* @param {string} agentId - 智能体ID
* @param {string} title - 对话标题
* @param {Object} metadata - 元数据
* @returns {Promise} - 创建结果
*/
createThread: (agentId, title, metadata) =>
apiPost('/api/chat/thread', {
agent_id: agentId,
title: title || '新的对话',
metadata: metadata || {}
}),
/**
* 更新对话线程
* @param {string} threadId - 对话线程ID
* @param {string} title - 对话标题
* @param {boolean} is_pinned - 是否置顶
* @returns {Promise} - 更新结果
*/
updateThread: (threadId, title, is_pinned) =>
apiPut(`/api/chat/thread/${threadId}`, {
title,
is_pinned
}),
/**
* 删除对话线程
* @param {string} threadId - 对话线程ID
* @returns {Promise} - 删除结果
*/
deleteThread: (threadId) => apiDelete(`/api/chat/thread/${threadId}`),
/**
* 获取线程附件列表
* @param {string} threadId - 对话线程ID
* @returns {Promise}
*/
getThreadAttachments: (threadId) => apiGet(`/api/chat/thread/${threadId}/attachments`),
/**
* 列出线程文件(目录)
* @param {string} threadId
* @param {string} path
* @returns {Promise}
*/
listThreadFiles: (threadId, path = '/mnt/user-data') =>
apiGet(`/api/chat/thread/${threadId}/files?path=${encodeURIComponent(path)}`),
/**
* 读取线程文本文件内容(分页)
* @param {string} threadId
* @param {string} path
* @param {number} offset
* @param {number} limit
* @returns {Promise}
*/
readThreadFile: (threadId, path, offset = 0, limit = 2000) =>
apiGet(
`/api/chat/thread/${threadId}/files/content?path=${encodeURIComponent(path)}&offset=${offset}&limit=${limit}`
),
/**
* 获取线程文件下载/预览 URL
* @param {string} threadId
* @param {string} path
* @param {boolean} download
* @returns {string}
*/
getThreadArtifactUrl: (threadId, path, download = false) => {
const encodedPath = path
.split('/')
.filter(Boolean)
.map((segment) => encodeURIComponent(segment))
.join('/')
const query = download ? '?download=true' : ''
return `/api/chat/thread/${threadId}/artifacts/${encodedPath}${query}`
},
/**
* 下载线程文件(带鉴权)
* @param {string} threadId
* @param {string} path
* @returns {Promise<Response>}
*/
downloadThreadArtifact: (threadId, path) =>
apiGet(threadApi.getThreadArtifactUrl(threadId, path, true), {}, true, 'blob'),
/**
* 上传附件
* @param {string} threadId
* @param {File} file
* @returns {Promise}
*/
uploadThreadAttachment: (threadId, file) => {
const formData = new FormData()
formData.append('file', file)
return apiRequest(`/api/chat/thread/${threadId}/attachments`, {
method: 'POST',
body: formData
})
},
/**
* 删除附件
* @param {string} threadId
* @param {string} fileId
* @returns {Promise}
*/
deleteThreadAttachment: (threadId, fileId) =>
apiDelete(`/api/chat/thread/${threadId}/attachments/${fileId}`)
}