ForcePilot/web/src/stores/agent.js
Wenjie Zhang 221f4af213 fix(chatbot): 修复智能体工具获取和权限问题
- 修改工具获取逻辑,使用智能体实例的工具列表
- 将工具获取接口的权限从管理员改为普通用户
- 更新日志格式,添加行号信息
- 修复前端工具获取时未传递智能体ID的问题
2025-08-31 10:27:58 +08:00

573 lines
17 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 { defineStore } from 'pinia';
import { agentApi, threadApi } from '@/apis/agent_api';
import { MessageProcessor } from '@/utils/messageProcessor';
import { handleChatError } from '@/utils/errorHandler';
export const useAgentStore = defineStore('agent', {
state: () => ({
// 智能体相关状态
agents: {}, // 以ID为键的智能体对象
selectedAgentId: null, // 当前选中的智能体ID
defaultAgentId: null, // 默认智能体ID
// 智能体配置相关状态
agentConfig: {}, // 当前智能体的配置
originalAgentConfig: {}, // 原始配置,用于重置
// 工具相关状态
availableTools: [], // 所有可用工具列表
// 线程相关状态
threads: {}, // 以智能体ID为键的线程列表
currentThreadId: null, // 当前选中的线程ID
threadMessages: {}, // 以线程ID为键的消息列表
// 对话状态
onGoingConv: { msgChunks: {} }, // 正在进行的对话(流式)
isStreaming: false, // 是否正在接收流式响应
streamAbortController: null, // 流式对话取消控制器
// 加载状态
isLoadingAgents: false,
isLoadingConfig: false,
isLoadingTools: false,
isLoadingThreads: false,
isLoadingMessages: false,
// 错误状态
error: null,
// 初始化状态
isInitialized: false
}),
getters: {
// --- 智能体相关 Getters ---
selectedAgent: (state) => state.selectedAgentId ? state.agents[state.selectedAgentId] : null,
defaultAgent: (state) => state.defaultAgentId ? state.agents[state.defaultAgentId] : state.agents[Object.keys(state.agents)[0]],
agentsList: (state) => Object.values(state.agents),
isDefaultAgent: (state) => state.selectedAgentId === state.defaultAgentId,
configurableItems: (state) => {
const agent = state.selectedAgentId ? state.agents[state.selectedAgentId] : null;
if (!agent || !agent.configurable_items) return {};
const agentConfigurableItems = agent.configurable_items;
const items = { ...agentConfigurableItems };
Object.keys(items).forEach(key => {
const item = items[key];
if (item && item.x_oap_ui_config) {
items[key] = { ...item, ...item.x_oap_ui_config };
delete items[key].x_oap_ui_config;
}
});
return items;
},
hasConfigChanges: (state) => JSON.stringify(state.agentConfig) !== JSON.stringify(state.originalAgentConfig),
// --- 线程与消息相关 Getters ---
currentAgentThreads: (state) => state.selectedAgentId ? (state.threads[state.selectedAgentId] || []) : [],
currentThread: (state) => {
if (!state.currentThreadId || !state.selectedAgentId) return null;
const agentThreads = state.threads[state.selectedAgentId] || [];
return agentThreads.find(thread => thread.id === state.currentThreadId);
},
currentThreadMessages: (state) => state.currentThreadId ? (state.threadMessages[state.currentThreadId] || []) : [],
// --- 对话UI Getters ---
onGoingConvMessages: (state) => {
const msgs = Object.values(state.onGoingConv.msgChunks).map(MessageProcessor.mergeMessageChunk);
return msgs.length > 0
? MessageProcessor.convertToolResultToMessages(msgs).filter(msg => msg.type !== 'tool')
: [];
},
conversations: (state) => {
const historyConvs = MessageProcessor.convertServerHistoryToMessages(state.currentThreadMessages);
// Compute ongoing messages directly from state to avoid circular reference
const msgs = Object.values(state.onGoingConv.msgChunks).map(MessageProcessor.mergeMessageChunk);
const onGoingMessages = msgs.length > 0
? MessageProcessor.convertToolResultToMessages(msgs).filter(msg => msg.type !== 'tool')
: [];
if (onGoingMessages.length > 0) {
// Create a new conversation object for the ongoing messages
const onGoingConv = {
messages: onGoingMessages,
status: 'streaming'
};
return [...historyConvs, onGoingConv];
}
return historyConvs;
},
},
actions: {
// 初始化store
async initialize() {
if (this.isInitialized) return;
try {
// 首先加载智能体列表
await this.fetchAgents();
// 然后设置默认智能体
await this.fetchDefaultAgent();
// 最后加载工具
await this.fetchTools();
this.isInitialized = true;
} catch (error) {
console.error('Failed to initialize agent store:', error);
handleChatError(error, 'initialize');
this.error = error.message;
}
},
// 获取智能体列表
async fetchAgents() {
this.isLoadingAgents = true;
this.error = null;
try {
const response = await agentApi.getAgents();
// 将数组转换为以ID为键的对象
this.agents = response.agents.reduce((acc, agent) => {
acc[agent.id] = agent;
return acc;
}, {});
} catch (error) {
console.error('Failed to fetch agents:', error);
handleChatError(error, 'fetch');
this.error = error.message;
throw error;
} finally {
this.isLoadingAgents = false;
}
},
// 获取默认智能体
async fetchDefaultAgent() {
try {
const response = await agentApi.getDefaultAgent();
this.defaultAgentId = response.default_agent_id;
// 如果没有选中的智能体且默认智能体存在于智能体列表中,则选择默认智能体
if (!this.selectedAgentId && this.defaultAgentId && this.agents[this.defaultAgentId]) {
this.selectedAgentId = this.defaultAgentId;
}
} catch (error) {
console.error('Failed to fetch default agent:', error);
handleChatError(error, 'fetch');
this.error = error.message;
}
},
// 设置默认智能体
async setDefaultAgent(agentId) {
try {
await agentApi.setDefaultAgent(agentId);
this.defaultAgentId = agentId;
} catch (error) {
console.error('Failed to set default agent:', error);
handleChatError(error, 'save');
this.error = error.message;
throw error;
}
},
// 选择智能体
selectAgent(agentId) {
if (this.agents[agentId]) {
this.selectedAgentId = agentId;
// 清空之前的配置
this.agentConfig = {};
this.originalAgentConfig = {};
}
},
// 加载智能体配置
async loadAgentConfig(agentId = null) {
const targetAgentId = agentId || this.selectedAgentId;
if (!targetAgentId) return;
this.isLoadingConfig = true;
this.error = null;
try {
const response = await agentApi.getAgentConfig(targetAgentId);
this.agentConfig = { ...response.config };
this.originalAgentConfig = { ...response.config };
} catch (error) {
console.error('Failed to load agent config:', error);
handleChatError(error, 'load');
this.error = error.message;
throw error;
} finally {
this.isLoadingConfig = false;
}
},
// 保存智能体配置
async saveAgentConfig(agentId = null) {
const targetAgentId = agentId || this.selectedAgentId;
if (!targetAgentId) return;
try {
await agentApi.saveAgentConfig(targetAgentId, this.agentConfig);
this.originalAgentConfig = { ...this.agentConfig };
} catch (error) {
console.error('Failed to save agent config:', error);
handleChatError(error, 'save');
this.error = error.message;
throw error;
}
},
// 重置智能体配置
resetAgentConfig() {
this.agentConfig = { ...this.originalAgentConfig };
},
// 更新配置项
updateConfigItem(key, value) {
this.agentConfig[key] = value;
},
// 更新智能体配置(支持批量更新)
updateAgentConfig(updates) {
Object.assign(this.agentConfig, updates);
},
// 获取工具列表
async fetchTools() {
this.isLoadingTools = true;
this.error = null;
try {
const response = await agentApi.getTools(this.selectedAgentId);
this.availableTools = response.tools;
} catch (error) {
console.error('Failed to fetch tools:', error);
handleChatError(error, 'fetch');
this.error = error.message;
throw error;
} finally {
this.isLoadingTools = false;
}
},
// 清除错误状态
clearError() {
this.error = null;
},
// ==================== 线程管理方法 ====================
// 获取智能体的线程列表
async fetchThreads(agentId = null) {
const targetAgentId = agentId || this.selectedAgentId;
if (!targetAgentId) return;
this.isLoadingThreads = true;
this.error = null;
try {
const threads = await threadApi.getThreads(targetAgentId);
this.threads[targetAgentId] = threads || [];
} catch (error) {
console.error('Failed to fetch threads:', error);
handleChatError(error, 'fetch');
this.error = error.message;
throw error;
} finally {
this.isLoadingThreads = false;
}
},
// 创建新线程
async createThread(agentId, title = '新的对话') {
if (!agentId) return null;
try {
const thread = await threadApi.createThread(agentId, title);
if (thread) {
// 更新线程列表
if (!this.threads[agentId]) {
this.threads[agentId] = [];
}
this.threads[agentId].unshift(thread);
// 设置为当前线程
this.currentThreadId = thread.id;
// 初始化消息列表
this.threadMessages[thread.id] = [];
}
return thread;
} catch (error) {
console.error('Failed to create thread:', error);
handleChatError(error, 'create');
this.error = error.message;
throw error;
}
},
// 删除线程
async deleteThread(threadId) {
if (!threadId) return;
try {
await threadApi.deleteThread(threadId);
// 从所有智能体的线程列表中移除
Object.keys(this.threads).forEach(agentId => {
this.threads[agentId] = this.threads[agentId].filter(thread => thread.id !== threadId);
});
// 清理消息
delete this.threadMessages[threadId];
// 如果删除的是当前线程重置当前线程ID
if (this.currentThreadId === threadId) {
this.currentThreadId = null;
}
} catch (error) {
console.error('Failed to delete thread:', error);
handleChatError(error, 'delete');
this.error = error.message;
throw error;
}
},
// 更新线程标题
async updateThread(threadId, title) {
if (!threadId || !title) return;
try {
await threadApi.updateThread(threadId, title);
// 更新本地线程列表中的标题
Object.keys(this.threads).forEach(agentId => {
const thread = this.threads[agentId].find(t => t.id === threadId);
if (thread) {
thread.title = title;
}
});
} catch (error) {
console.error('Failed to update thread:', error);
handleChatError(error, 'update');
this.error = error.message;
throw error;
}
},
// 选择线程
selectThread(threadId) {
this.currentThreadId = threadId;
this.resetOnGoingConv();
// 如果没有该线程的消息,初始化空数组
if (threadId && !this.threadMessages[threadId]) {
this.threadMessages[threadId] = [];
}
},
// 获取线程消息
async fetchThreadMessages(threadId) {
if (!threadId) return;
this.isLoadingMessages = true;
this.error = null;
this.resetOnGoingConv();
try {
const response = await agentApi.getAgentHistory(this.selectedAgentId, threadId);
this.threadMessages[threadId] = response.history || [];
} catch (error) {
handleChatError(error, 'load');
throw error;
} finally {
this.isLoadingMessages = false;
}
},
// --- 流式对话 Actions ---
resetOnGoingConv() {
// 取消之前的请求(如果存在)
if (this.streamAbortController) {
this.streamAbortController.abort();
this.streamAbortController = null;
}
this.onGoingConv = { msgChunks: {} };
},
_processStreamChunk(chunk) {
const { status, msg, request_id, message } = chunk;
switch (status) {
case 'init':
this.onGoingConv.msgChunks[request_id] = [msg];
break;
case 'loading':
if (msg.id) {
if (!this.onGoingConv.msgChunks[msg.id]) {
this.onGoingConv.msgChunks[msg.id] = [];
}
this.onGoingConv.msgChunks[msg.id].push(msg);
}
break;
case 'error':
handleChatError({ message }, 'stream');
break;
case 'finished':
this.fetchThreadMessages(this.currentThreadId);
break;
}
},
// 取消流式对话
cancelStreaming() {
if (this.streamAbortController && this.isStreaming) {
this.streamAbortController.abort();
this.streamAbortController = null;
this.isStreaming = false;
this.resetOnGoingConv();
}
},
// 发送消息并处理流式响应
async sendMessage(text) {
if (!this.selectedAgentId || !this.currentThreadId || !text) {
handleChatError({ message: "Missing agent, thread, or message text" }, 'send');
return;
}
this.isStreaming = true;
this.resetOnGoingConv();
// 创建新的 AbortController
this.streamAbortController = new AbortController();
// 如果是新对话,用消息内容作为标题
if (this.currentThreadMessages.length === 0) {
this.updateThread(this.currentThreadId, text);
}
const requestData = {
query: text,
config: {
thread_id: this.currentThreadId,
},
};
try {
const response = await agentApi.sendAgentMessage(this.selectedAgentId, requestData);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
// 检查是否被取消
if (this.streamAbortController && this.streamAbortController.signal.aborted) {
break;
}
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.trim() && (!this.streamAbortController || !this.streamAbortController.signal.aborted)) {
try {
const chunk = JSON.parse(line.trim());
this._processStreamChunk(chunk);
} catch (e) {
console.warn('Failed to parse stream chunk JSON:', e);
}
}
}
}
// Process any remaining data in the buffer
if (buffer.trim() && (!this.streamAbortController || !this.streamAbortController.signal.aborted)) {
try {
const chunk = JSON.parse(buffer.trim());
this._processStreamChunk(chunk);
} catch (e) {
console.warn('Failed to parse final stream chunk JSON:', e);
}
}
} catch (error) {
// 如果是取消错误,不显示错误信息
if (error.name === 'AbortError') {
console.log('Stream was cancelled');
} else {
handleChatError(error, 'send');
}
} finally {
this.isStreaming = false;
this.streamAbortController = null;
}
},
// 添加消息到线程
addMessageToThread(threadId, message) {
if (!threadId || !message) return;
if (!this.threadMessages[threadId]) {
this.threadMessages[threadId] = [];
}
this.threadMessages[threadId].push(message);
},
// 更新线程中的消息
updateMessageInThread(threadId, messageIndex, updatedMessage) {
if (!threadId || messageIndex < 0 || !this.threadMessages[threadId]) return;
if (messageIndex < this.threadMessages[threadId].length) {
this.threadMessages[threadId][messageIndex] = updatedMessage;
}
},
// 重置store状态
reset() {
// 取消正在进行的流式对话
if (this.streamAbortController) {
this.streamAbortController.abort();
this.streamAbortController = null;
}
this.agents = {};
this.selectedAgentId = null;
this.defaultAgentId = null;
this.agentConfig = {};
this.originalAgentConfig = {};
this.availableTools = [];
this.threads = {};
this.currentThreadId = null;
this.threadMessages = {};
this.onGoingConv = { msgChunks: {} };
this.isStreaming = false;
this.isLoadingAgents = false;
this.isLoadingConfig = false;
this.isLoadingTools = false;
this.isLoadingThreads = false;
this.isLoadingMessages = false;
this.error = null;
this.isInitialized = false;
}
},
// 持久化配置
persist: {
key: 'agent-store',
storage: localStorage,
paths: ['selectedAgentId', 'defaultAgentId'] // 只持久化关键状态
}
});