# 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
411 lines
12 KiB
JavaScript
411 lines
12 KiB
JavaScript
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}`)
|
||
}
|