refactor(agent): 重构智能体相关逻辑至Pinia store
将智能体管理、配置、线程和消息等逻辑从组件中抽离,统一管理至Pinia store 优化代码结构,提升可维护性和复用性 修复多处直接调用API的问题,统一通过store管理状态
This commit is contained in:
parent
b7c54d80c0
commit
3bca00a864
@ -1,5 +1,13 @@
|
||||
<script setup>
|
||||
import { themeConfig } from '@/assets/theme'
|
||||
import { useAgentStore } from '@/stores/agent'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
const agentStore = useAgentStore();
|
||||
|
||||
onMounted(async () => {
|
||||
await agentStore.initialize();
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<a-config-provider :theme="themeConfig">
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
:is-initial-render="state.isInitialRender"
|
||||
:single-mode="props.singleMode"
|
||||
:agents="agents"
|
||||
:selected-agent-id="props.agentId"
|
||||
:selected-agent-id="agentStore.selectedAgentId"
|
||||
@create-chat="createNewChat"
|
||||
@select-chat="selectChat"
|
||||
@delete-chat="deleteChat"
|
||||
@ -34,8 +34,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="header__center" @mouseenter="showRenameButton = true" @mouseleave="showRenameButton = false">
|
||||
<div @click="console.log(currentChat)" class="center-title">
|
||||
{{ currentChat?.title }}
|
||||
<div @click="console.log(agentStore.currentThread)" class="center-title">
|
||||
{{ agentStore.currentThread?.title }}
|
||||
</div>
|
||||
<div class="rename-button" v-if="currentChatId" :class="{ 'visible': showRenameButton }" @click="handleRenameChat">
|
||||
<EditOutlined style="font-size: 14px; color: var(--gray-600);"/>
|
||||
@ -54,7 +54,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isLoading" class="chat-loading">
|
||||
<div v-if="isLoadingThreads || isLoadingMessages" class="chat-loading">
|
||||
<LoadingOutlined />
|
||||
<span>正在加载历史记录...</span>
|
||||
</div>
|
||||
@ -147,19 +147,12 @@ import MessageInputComponent from '@/components/MessageInputComponent.vue'
|
||||
import AgentMessageComponent from '@/components/AgentMessageComponent.vue'
|
||||
import ChatSidebarComponent from '@/components/ChatSidebarComponent.vue'
|
||||
import RefsComponent from '@/components/RefsComponent.vue'
|
||||
import { agentApi, threadApi } from '@/apis/agent'
|
||||
import { PanelLeftOpen, MessageSquarePlus } from 'lucide-vue-next';
|
||||
import { useAgentStore } from '@/stores/agent';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
// 新增props属性,允许父组件传入agentId
|
||||
const props = defineProps({
|
||||
agentId: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
state: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
@ -172,6 +165,19 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['open-config', 'open-agent-modal']);
|
||||
|
||||
// ==================== Store 管理 ====================
|
||||
const agentStore = useAgentStore();
|
||||
const {
|
||||
agents,
|
||||
currentAgentThreads,
|
||||
currentThread,
|
||||
currentThreadMessages,
|
||||
isLoadingThreads,
|
||||
isLoadingMessages,
|
||||
selectedAgent,
|
||||
configSchema
|
||||
} = storeToRefs(agentStore);
|
||||
|
||||
// ==================== 状态管理 ====================
|
||||
|
||||
// UI状态
|
||||
@ -272,15 +278,17 @@ const getLastMessage = (conv) => {
|
||||
const messagesContainer = ref(null);
|
||||
|
||||
// 数据状态
|
||||
const agents = ref({}); // 智能体列表
|
||||
const userInput = ref(''); // 用户输入
|
||||
const currentChatId = ref(null); // 当前对话ID
|
||||
const chatsList = ref([]); // 对话列表
|
||||
const isLoading = ref(false); // 是否处于加载状态
|
||||
|
||||
const convs = ref([]);
|
||||
const currentAgent = computed(() => agents.value[props.agentId]);
|
||||
const currentChat = computed(() => chatsList.value.find(chat => chat.id === currentChatId.value));
|
||||
const currentAgent = computed(() => {
|
||||
if (!agentStore.selectedAgentId || !agents.value) return null;
|
||||
return agents.value[agentStore.selectedAgentId];
|
||||
});
|
||||
|
||||
// 使用 agentStore 的线程相关计算属性
|
||||
const currentChatId = computed(() => agentStore.currentThreadId);
|
||||
const chatsList = computed(() => currentAgentThreads.value || []);
|
||||
|
||||
const onGoingConv = reactive({
|
||||
msgChunks: {},
|
||||
@ -308,7 +316,7 @@ const expandedToolCalls = ref(new Set()); // 展开的工具调用集合
|
||||
// 创建新对话
|
||||
const createNewChat = async () => {
|
||||
// 确保有AgentID
|
||||
if (!props.agentId) {
|
||||
if (!agentStore.selectedAgentId) {
|
||||
console.warn("未指定AgentID,无法创建对话");
|
||||
return;
|
||||
}
|
||||
@ -319,24 +327,20 @@ const createNewChat = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentChatId.value &&convs.value.length === 0) {
|
||||
if (currentChatId.value && convs.value.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 调用API创建新对话
|
||||
// 使用 agentStore 创建新对话
|
||||
state.creatingNewChat = true;
|
||||
const response = await threadApi.createThread(props.agentId, '新的对话');
|
||||
if (!response || !response.id) {
|
||||
const thread = await agentStore.createThread(agentStore.selectedAgentId, '新的对话');
|
||||
if (!thread || !thread.id) {
|
||||
throw new Error('创建对话失败');
|
||||
}
|
||||
|
||||
// 切换到新对话
|
||||
currentChatId.value = response.id;
|
||||
// 重置线程状态
|
||||
resetThread();
|
||||
|
||||
// 刷新对话列表
|
||||
loadChatsList();
|
||||
} catch (error) {
|
||||
console.error('创建对话失败:', error);
|
||||
message.error('创建对话失败');
|
||||
@ -348,37 +352,33 @@ const createNewChat = async () => {
|
||||
// 选择已有对话
|
||||
const selectChat = async (chatId) => {
|
||||
// 确保有AgentID
|
||||
if (!props.agentId) {
|
||||
if (!agentStore.selectedAgentId) {
|
||||
console.warn("未指定AgentID,无法选择对话");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("选择对话:", chatId);
|
||||
|
||||
// 切换到选中的对话
|
||||
currentChatId.value = chatId;
|
||||
// 使用 agentStore 选择线程
|
||||
agentStore.selectThread(chatId);
|
||||
await getAgentHistory();
|
||||
};
|
||||
|
||||
// 删除对话
|
||||
const deleteChat = async (chatId) => {
|
||||
if (!props.agentId) {
|
||||
if (!agentStore.selectedAgentId) {
|
||||
console.warn("未指定AgentID,无法删除对话");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 调用API删除对话
|
||||
await threadApi.deleteThread(chatId);
|
||||
// 使用 agentStore 删除对话
|
||||
await agentStore.deleteThread(chatId);
|
||||
|
||||
// 如果删除的是当前对话,重置状态
|
||||
if (chatId === currentChatId.value) {
|
||||
resetThread();
|
||||
currentChatId.value = null;
|
||||
}
|
||||
|
||||
// 刷新对话列表
|
||||
loadChatsList();
|
||||
} catch (error) {
|
||||
console.error('删除对话失败:', error);
|
||||
message.error('删除对话失败');
|
||||
@ -395,7 +395,7 @@ const renameChat = async (data) => {
|
||||
}
|
||||
|
||||
// 确保有AgentID
|
||||
if (!props.agentId) {
|
||||
if (!agentStore.selectedAgentId) {
|
||||
console.warn("未指定AgentID,无法重命名对话");
|
||||
return;
|
||||
}
|
||||
@ -406,11 +406,8 @@ const renameChat = async (data) => {
|
||||
}
|
||||
|
||||
try {
|
||||
// 调用API更新对话
|
||||
await threadApi.updateThread(chatId, title);
|
||||
|
||||
// 刷新对话列表
|
||||
loadChatsList();
|
||||
// 使用 agentStore 更新对话
|
||||
await agentStore.updateThread(chatId, title);
|
||||
} catch (error) {
|
||||
console.error('重命名对话失败:', error);
|
||||
message.error('重命名对话失败');
|
||||
@ -419,12 +416,12 @@ const renameChat = async (data) => {
|
||||
|
||||
// 处理重命名对话点击事件
|
||||
const handleRenameChat = () => {
|
||||
if (!currentChatId.value || !currentChat.value) {
|
||||
if (!currentChatId.value || !currentThread.value) {
|
||||
message.warning('请先选择对话');
|
||||
return;
|
||||
}
|
||||
|
||||
let newTitle = currentChat.value.title;
|
||||
let newTitle = currentThread.value.title;
|
||||
|
||||
Modal.confirm({
|
||||
title: '重命名对话',
|
||||
@ -473,7 +470,7 @@ const shareChat = () => {
|
||||
const link = document.createElement('a');
|
||||
|
||||
// 生成文件名
|
||||
const chatTitle = currentChat.value?.title || '新对话';
|
||||
const chatTitle = currentThread.value?.title || '新对话';
|
||||
const timestamp = new Date().toLocaleString('zh-CN').replace(/[:/\s]/g, '-');
|
||||
const filename = `${chatTitle}-${timestamp}.html`;
|
||||
|
||||
@ -497,7 +494,7 @@ const shareChat = () => {
|
||||
|
||||
// 生成对话的HTML内容
|
||||
const generateChatHTML = () => {
|
||||
const chatTitle = currentChat.value?.title || '新对话';
|
||||
const chatTitle = agentStore.currentThread.value?.title || '新对话';
|
||||
const agentName = currentAgent.value?.name || '智能助手';
|
||||
const agentDescription = currentAgent.value?.description || '';
|
||||
const exportTime = new Date().toLocaleString('zh-CN');
|
||||
@ -939,7 +936,7 @@ const sendMessageToServer = async (text) => {
|
||||
const requestData = {
|
||||
query: text.trim(),
|
||||
config: {
|
||||
...props.config,
|
||||
...agentStore.agentConfig,
|
||||
thread_id: currentChatId.value
|
||||
},
|
||||
meta: {
|
||||
@ -950,7 +947,7 @@ const sendMessageToServer = async (text) => {
|
||||
try {
|
||||
state.waitingServerResponse = true;
|
||||
|
||||
const response = await agentApi.sendAgentMessage(currentAgent.value.id, requestData);
|
||||
const response = await agentStore.sendMessage(currentAgent.value.id, requestData);
|
||||
if (!response.ok) {
|
||||
throw new Error('请求失败');
|
||||
}
|
||||
@ -1049,52 +1046,37 @@ const processResponseChunk = async (data) => {
|
||||
// 初始化所有数据
|
||||
const initAll = async () => {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
// 获取智能体列表
|
||||
// 使用 agentStore 的加载状态
|
||||
// 初始化agent store(如果还未初始化)
|
||||
if (!agentStore.agents || Object.keys(agentStore.agents).length === 0) {
|
||||
await agentStore.initialize();
|
||||
}
|
||||
// 加载对话列表以及对话历史
|
||||
setTimeout(async () => {
|
||||
await fetchAgents(); // 加载智能体数据
|
||||
await loadChatsList(); // 加载对话列表以及对话历史
|
||||
isLoading.value = false;
|
||||
await loadChatsList();
|
||||
}, 100);
|
||||
|
||||
} catch (error) {
|
||||
console.error("组件挂载出错:", error);
|
||||
message.error(`加载数据失败: ${error}`);
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取智能体列表
|
||||
const fetchAgents = async () => {
|
||||
try {
|
||||
const data = await agentApi.getAgents();
|
||||
// 将数组转换为对象
|
||||
agents.value = data.agents.reduce((acc, agent) => {
|
||||
acc[agent.id] = agent;
|
||||
return acc;
|
||||
}, {});
|
||||
console.log("agents", agents.value);
|
||||
} catch (error) {
|
||||
console.error('获取智能体错误:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 从服务器对话列表
|
||||
const loadChatsList = async () => {
|
||||
try {
|
||||
if (!props.agentId) {
|
||||
if (!agentStore.selectedAgentId) {
|
||||
console.warn("未指定AgentID,无法加载状态");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取对话列表
|
||||
const threads = await threadApi.getThreads(props.agentId);
|
||||
// 使用 agentStore 获取对话列表
|
||||
await agentStore.fetchThreads(agentStore.selectedAgentId);
|
||||
|
||||
if (threads && Array.isArray(threads) && threads.length > 0) {
|
||||
// 如果有对话,则加载最近的一个
|
||||
chatsList.value = threads;
|
||||
currentChatId.value = threads[0].id; // 假设已按更新时间排序
|
||||
await getAgentHistory()
|
||||
if (currentAgentThreads.value && currentAgentThreads.value.length > 0) {
|
||||
// 如果有对话,则选择最近的一个
|
||||
await agentStore.selectThread(currentAgentThreads.value[0].id);
|
||||
await getAgentHistory();
|
||||
} else {
|
||||
// 如果没有对话,创建新对话
|
||||
await createNewChat();
|
||||
@ -1106,21 +1088,22 @@ const loadChatsList = async () => {
|
||||
|
||||
// 获取智能体历史记录
|
||||
const getAgentHistory = async () => {
|
||||
if (!props.agentId || !currentChatId.value) {
|
||||
if (!agentStore.selectedAgentId || !currentChatId.value) {
|
||||
console.warn('未选择智能体或对话ID');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.debug(`正在获取智能体[${props.agentId}]的历史记录,对话ID: ${currentChatId.value}`);
|
||||
const response = await agentApi.getAgentHistory(props.agentId, currentChatId.value);
|
||||
console.debug('智能体历史记录:', response);
|
||||
console.debug(`正在获取智能体[${agentStore.selectedAgentId}]的历史记录,对话ID: ${currentChatId.value}`);
|
||||
|
||||
// 如果成功获取历史记录并且是数组
|
||||
if (response && Array.isArray(response.history)) {
|
||||
|
||||
// 使用 agentStore 获取线程消息
|
||||
await agentStore.fetchThreadMessages(currentChatId.value);
|
||||
// 从 store 获取消息并转换格式
|
||||
if (currentThreadMessages.value && Array.isArray(currentThreadMessages.value)) {
|
||||
// 将服务器格式的历史记录转换为组件格式
|
||||
onGoingConv.msgChunks = {};
|
||||
convs.value = convertServerHistoryToMessages(response.history);
|
||||
convs.value = convertServerHistoryToMessages(currentThreadMessages.value);
|
||||
|
||||
// 加载历史记录后,启用自动滚动并滚动到底部
|
||||
shouldAutoScroll.value = true;
|
||||
@ -1202,7 +1185,7 @@ onMounted(async () => {
|
||||
|
||||
// 监听agentId变化
|
||||
onMounted(() => {
|
||||
watch(() => props.agentId, async (newAgentId, oldAgentId) => {
|
||||
watch(() => agentStore.selectedAgentId, async (newAgentId, oldAgentId) => {
|
||||
try {
|
||||
console.debug("智能体ID变化", oldAgentId, "->", newAgentId);
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
|
||||
<!-- 侧边栏内容 -->
|
||||
<div class="sidebar-content">
|
||||
<div class="agent-info">
|
||||
<div class="agent-info" v-if="selectedAgent">
|
||||
<div class="agent-basic-info">
|
||||
<h4>{{ selectedAgent.name || '未选择智能体' }}</h4>
|
||||
<p class="agent-description">{{ selectedAgent.description }}</p>
|
||||
@ -70,7 +70,8 @@
|
||||
<!-- 系统提示词 -->
|
||||
<a-textarea
|
||||
v-else-if="key === 'system_prompt'"
|
||||
v-model:value="agentConfig[key]"
|
||||
:value="agentConfig[key]"
|
||||
@update:value="(val) => agentStore.updateAgentConfig({ [key]: val })"
|
||||
:rows="3"
|
||||
:placeholder="getPlaceholder(key, value)"
|
||||
class="system-prompt-input"
|
||||
@ -116,13 +117,15 @@
|
||||
<!-- 布尔类型 -->
|
||||
<a-switch
|
||||
v-else-if="typeof agentConfig[key] === 'boolean'"
|
||||
v-model:checked="agentConfig[key]"
|
||||
:checked="agentConfig[key]"
|
||||
@update:checked="(val) => agentStore.updateAgentConfig({ [key]: val })"
|
||||
/>
|
||||
|
||||
<!-- 单选 -->
|
||||
<a-select
|
||||
v-else-if="value?.options && (value?.type === 'str' || value?.type === 'select')"
|
||||
v-model:value="agentConfig[key]"
|
||||
:value="agentConfig[key]"
|
||||
@update:value="(val) => agentStore.updateAgentConfig({ [key]: val })"
|
||||
class="config-select"
|
||||
>
|
||||
<a-select-option v-for="option in value.options" :key="option" :value="option">
|
||||
@ -168,7 +171,8 @@
|
||||
<!-- 数字 -->
|
||||
<a-input-number
|
||||
v-else-if="value?.type === 'number'"
|
||||
v-model:value="agentConfig[key]"
|
||||
:value="agentConfig[key]"
|
||||
@update:value="(val) => agentStore.updateAgentConfig({ [key]: val })"
|
||||
:placeholder="getPlaceholder(key, value)"
|
||||
class="config-input-number"
|
||||
/>
|
||||
@ -176,7 +180,8 @@
|
||||
<!-- 滑块 -->
|
||||
<a-slider
|
||||
v-else-if="value?.type === 'slider'"
|
||||
v-model:value="agentConfig[key]"
|
||||
:value="agentConfig[key]"
|
||||
@update:value="(val) => agentStore.updateAgentConfig({ [key]: val })"
|
||||
:min="value.min"
|
||||
:max="value.max"
|
||||
:step="value.step"
|
||||
@ -186,7 +191,8 @@
|
||||
<!-- 其他类型 -->
|
||||
<a-input
|
||||
v-else
|
||||
v-model:value="agentConfig[key]"
|
||||
:value="agentConfig[key]"
|
||||
@update:value="(val) => agentStore.updateAgentConfig({ [key]: val })"
|
||||
:placeholder="getPlaceholder(key, value)"
|
||||
class="config-input"
|
||||
/>
|
||||
@ -279,45 +285,41 @@ import {
|
||||
} from '@ant-design/icons-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue';
|
||||
import { agentApi } from '@/apis/agent';
|
||||
import { useAgentStore } from '@/stores/agent';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
isOpen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
selectedAgent: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
selectedAgentId: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
agentConfig: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits([
|
||||
'close',
|
||||
'update:agentConfig',
|
||||
'config-saved',
|
||||
'config-reset'
|
||||
'close'
|
||||
]);
|
||||
|
||||
// Store 管理
|
||||
const agentStore = useAgentStore();
|
||||
const {
|
||||
availableTools,
|
||||
selectedAgent,
|
||||
selectedAgentId,
|
||||
agentConfig
|
||||
} = storeToRefs(agentStore);
|
||||
|
||||
// console.log(availableTools.value)
|
||||
|
||||
// 本地状态
|
||||
const debugMode = ref(false);
|
||||
const toolsModalOpen = ref(false);
|
||||
const availableTools = ref([]);
|
||||
const selectedTools = ref([]);
|
||||
const toolsSearchText = ref('');
|
||||
|
||||
// 计算属性
|
||||
const configSchema = computed(() => props.selectedAgent.config_schema || {});
|
||||
const configSchema = computed(() => selectedAgent.value?.config_schema || {});
|
||||
|
||||
const configurableItems = computed(() => {
|
||||
const items = configSchema.value.configurable_items || {};
|
||||
@ -333,15 +335,16 @@ const configurableItems = computed(() => {
|
||||
});
|
||||
|
||||
const isEmptyConfig = computed(() => {
|
||||
return !props.selectedAgentId || Object.keys(configurableItems.value).length === 0;
|
||||
return !selectedAgentId.value || Object.keys(configurableItems.value).length === 0;
|
||||
});
|
||||
|
||||
const filteredTools = computed(() => {
|
||||
const toolsList = availableTools.value ? Object.values(availableTools.value) : [];
|
||||
if (!toolsSearchText.value) {
|
||||
return availableTools.value;
|
||||
return toolsList;
|
||||
}
|
||||
const searchLower = toolsSearchText.value.toLowerCase();
|
||||
return availableTools.value.filter(tool =>
|
||||
return toolsList.filter(tool =>
|
||||
tool.name.toLowerCase().includes(searchLower) ||
|
||||
tool.description.toLowerCase().includes(searchLower)
|
||||
);
|
||||
@ -357,6 +360,7 @@ const toggleDebugMode = () => {
|
||||
};
|
||||
|
||||
const getConfigLabel = (key, value) => {
|
||||
// console.log(configurableItems)
|
||||
if (value.description && value.name !== key) {
|
||||
return `${value.name}(${key})`;
|
||||
}
|
||||
@ -368,33 +372,32 @@ const getPlaceholder = (key, value) => {
|
||||
};
|
||||
|
||||
const handleModelChange = (data) => {
|
||||
const newConfig = { ...props.agentConfig };
|
||||
newConfig.model = `${data.provider}/${data.name}`;
|
||||
emit('update:agentConfig', newConfig);
|
||||
agentStore.updateAgentConfig({
|
||||
model: `${data.provider}/${data.name}`
|
||||
});
|
||||
};
|
||||
|
||||
// 多选相关方法
|
||||
const ensureArray = (key) => {
|
||||
const config = { ...props.agentConfig };
|
||||
const config = agentConfig.value || {};
|
||||
if (!config[key] || !Array.isArray(config[key])) {
|
||||
config[key] = [];
|
||||
return [];
|
||||
}
|
||||
return config;
|
||||
return config[key];
|
||||
};
|
||||
|
||||
const isOptionSelected = (key, option) => {
|
||||
const config = ensureArray(key);
|
||||
return config[key].includes(option);
|
||||
const currentOptions = ensureArray(key);
|
||||
return currentOptions.includes(option);
|
||||
};
|
||||
|
||||
const getSelectedCount = (key) => {
|
||||
const config = ensureArray(key);
|
||||
return config[key].length;
|
||||
const currentOptions = ensureArray(key);
|
||||
return currentOptions.length;
|
||||
};
|
||||
|
||||
const toggleOption = (key, option) => {
|
||||
const config = ensureArray(key);
|
||||
const currentOptions = [...config[key]];
|
||||
const currentOptions = [...ensureArray(key)];
|
||||
const index = currentOptions.indexOf(option);
|
||||
|
||||
if (index > -1) {
|
||||
@ -403,26 +406,27 @@ const toggleOption = (key, option) => {
|
||||
currentOptions.push(option);
|
||||
}
|
||||
|
||||
config[key] = currentOptions;
|
||||
emit('update:agentConfig', config);
|
||||
agentStore.updateAgentConfig({
|
||||
[key]: currentOptions
|
||||
});
|
||||
};
|
||||
|
||||
const clearSelection = (key) => {
|
||||
const config = { ...props.agentConfig };
|
||||
config[key] = [];
|
||||
emit('update:agentConfig', config);
|
||||
agentStore.updateAgentConfig({
|
||||
[key]: []
|
||||
});
|
||||
};
|
||||
|
||||
// 工具相关方法
|
||||
const getToolNameById = (toolId) => {
|
||||
const tool = availableTools.value.find(t => t.id === toolId);
|
||||
const toolsList = availableTools.value ? Object.values(availableTools.value) : [];
|
||||
const tool = toolsList.find(t => t.id === toolId);
|
||||
return tool ? tool.name : toolId;
|
||||
};
|
||||
|
||||
const loadAvailableTools = async () => {
|
||||
try {
|
||||
const response = await agentApi.getTools();
|
||||
availableTools.value = Object.values(response.tools || {});
|
||||
await agentStore.fetchTools();
|
||||
} catch (error) {
|
||||
console.error('加载工具列表失败:', error);
|
||||
}
|
||||
@ -430,10 +434,10 @@ const loadAvailableTools = async () => {
|
||||
|
||||
const openToolsModal = async () => {
|
||||
try {
|
||||
if (availableTools.value.length === 0) {
|
||||
if (!availableTools.value || Object.keys(availableTools.value).length === 0) {
|
||||
await loadAvailableTools();
|
||||
}
|
||||
selectedTools.value = [...(props.agentConfig.tools || [])];
|
||||
selectedTools.value = [...(agentConfig.value?.tools || [])];
|
||||
toolsModalOpen.value = true;
|
||||
} catch (error) {
|
||||
console.error('打开工具选择弹窗失败:', error);
|
||||
@ -451,18 +455,20 @@ const toggleToolSelection = (toolId) => {
|
||||
};
|
||||
|
||||
const removeSelectedTool = (toolId) => {
|
||||
const config = { ...props.agentConfig };
|
||||
const index = config.tools.indexOf(toolId);
|
||||
const currentTools = [...(agentConfig.value?.tools || [])];
|
||||
const index = currentTools.indexOf(toolId);
|
||||
if (index > -1) {
|
||||
config.tools.splice(index, 1);
|
||||
emit('update:agentConfig', config);
|
||||
currentTools.splice(index, 1);
|
||||
agentStore.updateAgentConfig({
|
||||
tools: currentTools
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const confirmToolsSelection = () => {
|
||||
const config = { ...props.agentConfig };
|
||||
config.tools = [...selectedTools.value];
|
||||
emit('update:agentConfig', config);
|
||||
agentStore.updateAgentConfig({
|
||||
tools: [...selectedTools.value]
|
||||
});
|
||||
toolsModalOpen.value = false;
|
||||
toolsSearchText.value = '';
|
||||
};
|
||||
@ -475,15 +481,14 @@ const cancelToolsSelection = () => {
|
||||
|
||||
// 配置保存和重置
|
||||
const saveConfig = async () => {
|
||||
if (!props.selectedAgentId) {
|
||||
if (!selectedAgentId.value) {
|
||||
message.error('没有选择智能体');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await agentApi.saveAgentConfig(props.selectedAgentId, props.agentConfig);
|
||||
await agentStore.saveAgentConfig();
|
||||
message.success('配置已保存到服务器');
|
||||
emit('config-saved');
|
||||
} catch (error) {
|
||||
console.error('保存配置到服务器出错:', error);
|
||||
message.error('保存配置到服务器失败');
|
||||
@ -491,14 +496,13 @@ const saveConfig = async () => {
|
||||
};
|
||||
|
||||
const resetConfig = async () => {
|
||||
if (!props.selectedAgentId) {
|
||||
if (!selectedAgentId.value) {
|
||||
message.error('没有选择智能体');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await agentApi.saveAgentConfig(props.selectedAgentId, {});
|
||||
emit('config-reset');
|
||||
agentStore.resetAgentConfig();
|
||||
message.info('配置已重置');
|
||||
} catch (error) {
|
||||
console.error('重置配置出错:', error);
|
||||
@ -508,7 +512,7 @@ const resetConfig = async () => {
|
||||
|
||||
// 监听器
|
||||
watch(() => props.isOpen, (newVal) => {
|
||||
if (newVal && availableTools.value.length === 0) {
|
||||
if (newVal && (!availableTools.value || Object.keys(availableTools.value).length === 0)) {
|
||||
loadAvailableTools();
|
||||
}
|
||||
});
|
||||
|
||||
@ -35,10 +35,10 @@
|
||||
<span v-if="!toolCall.tool_call_result">
|
||||
<span><Loader size="16" class="tool-loader rotate" /></span>
|
||||
<span>正在调用工具: </span>
|
||||
<span class="tool-name">{{ toolCall.name || toolCall.function.name }}</span>
|
||||
<span class="tool-name">{{ getToolNameByToolCall(toolCall) }}</span>
|
||||
</span>
|
||||
<span v-else>
|
||||
<span><CircleCheckBig size="16" class="tool-loader" /></span> 工具 <span class="tool-name">{{ toolCall.name || toolCall.function.name }}</span> 执行完成
|
||||
<span><CircleCheckBig size="16" class="tool-loader" /></span> 工具 <span class="tool-name">{{ getToolNameByToolCall(toolCall) }}</span> 执行完成
|
||||
</span>
|
||||
</div>
|
||||
<div class="tool-content" v-show="expandedToolCalls.has(toolCall.id)">
|
||||
@ -85,6 +85,8 @@ import { CaretRightOutlined, ThunderboltOutlined, LoadingOutlined } from '@ant-d
|
||||
import RefsComponent from '@/components/RefsComponent.vue'
|
||||
import { Loader, CircleCheckBig } from 'lucide-vue-next';
|
||||
import { ToolResultRenderer } from '@/components/ToolCallingResult'
|
||||
import { useAgentStore } from '@/stores/agent'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
|
||||
import { MdPreview } from 'md-editor-v3'
|
||||
@ -138,6 +140,18 @@ const isEmptyAndLoading = computed(() => {
|
||||
return isEmpty && isLoading;
|
||||
});
|
||||
|
||||
// 引入智能体 store
|
||||
const agentStore = useAgentStore();
|
||||
const { availableTools } = storeToRefs(agentStore);
|
||||
|
||||
// 工具相关方法
|
||||
const getToolNameByToolCall = (toolCall) => {
|
||||
const toolId = toolCall.name || toolCall.function.name;
|
||||
const toolsList = availableTools.value ? Object.values(availableTools.value) : [];
|
||||
const tool = toolsList.find(t => t.id === toolId);
|
||||
return tool ? tool.name : toolId;
|
||||
};
|
||||
|
||||
const parsedMessage = computed(() => {
|
||||
const message = props.message;
|
||||
if (message.content) {
|
||||
|
||||
@ -2,7 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
|
||||
import AppLayout from '@/layouts/AppLayout.vue';
|
||||
import BlankLayout from '@/layouts/BlankLayout.vue';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { agentApi } from '@/apis/agent';
|
||||
import { useAgentStore } from '@/stores/agent';
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
@ -121,21 +121,24 @@ router.beforeEach(async (to, from, next) => {
|
||||
if (requiresAdmin && !isAdmin) {
|
||||
// 如果是普通用户,跳转到默认智能体页面
|
||||
try {
|
||||
// 先尝试获取默认智能体
|
||||
const data = await agentApi.getDefaultAgent();
|
||||
if (data && data.default_agent_id) {
|
||||
// 如果存在默认智能体,直接跳转
|
||||
next(`/agent/${data.default_agent_id}`);
|
||||
return;
|
||||
const agentStore = useAgentStore();
|
||||
// 等待 store 初始化完成
|
||||
if (!agentStore.isInitialized) {
|
||||
await agentStore.initialize();
|
||||
}
|
||||
|
||||
// 如果没有默认智能体,则获取第一个可用智能体
|
||||
const agentData = await agentApi.getAgents();
|
||||
if (agentData && agentData.agents && agentData.agents.length > 0) {
|
||||
const firstAgentId = agentData.agents[0].name;
|
||||
next(`/agent/${firstAgentId}`);
|
||||
|
||||
const defaultAgent = agentStore.defaultAgent;
|
||||
if (defaultAgent && defaultAgent.id) {
|
||||
next(`/agent/${defaultAgent.id}`);
|
||||
} else {
|
||||
next('/');
|
||||
// 如果没有默认智能体,可以考虑跳转到第一个可用的智能体,或者一个特定的页面
|
||||
const agentIds = Object.keys(agentStore.agents);
|
||||
if (agentIds.length > 0) {
|
||||
next(`/agent/${agentIds[0]}`);
|
||||
} else {
|
||||
// 没有可用的智能体,跳转到首页
|
||||
next('/');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取智能体信息失败:', error);
|
||||
|
||||
445
web/src/stores/agent.js
Normal file
445
web/src/stores/agent.js
Normal file
@ -0,0 +1,445 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { agentApi, threadApi } from '@/apis/agent';
|
||||
|
||||
export const useAgentStore = defineStore('agent', {
|
||||
state: () => ({
|
||||
// 智能体相关状态
|
||||
agents: {}, // 以ID为键的智能体对象
|
||||
selectedAgentId: null, // 当前选中的智能体ID
|
||||
defaultAgentId: null, // 默认智能体ID
|
||||
|
||||
// 智能体配置相关状态
|
||||
agentConfig: {}, // 当前智能体的配置
|
||||
originalAgentConfig: {}, // 原始配置,用于重置
|
||||
|
||||
// 工具相关状态
|
||||
availableTools: [], // 所有可用工具列表
|
||||
|
||||
// 线程相关状态
|
||||
threads: {}, // 以智能体ID为键的线程列表
|
||||
currentThreadId: null, // 当前选中的线程ID
|
||||
threadMessages: {}, // 以线程ID为键的消息列表
|
||||
|
||||
// 加载状态
|
||||
isLoadingAgents: false,
|
||||
isLoadingConfig: false,
|
||||
isLoadingTools: false,
|
||||
isLoadingThreads: false,
|
||||
isLoadingMessages: false,
|
||||
|
||||
// 错误状态
|
||||
error: null,
|
||||
|
||||
// 初始化状态
|
||||
isInitialized: false
|
||||
}),
|
||||
|
||||
getters: {
|
||||
// 获取当前选中的智能体
|
||||
selectedAgent() {
|
||||
return this.selectedAgentId ? this.agents[this.selectedAgentId] : null;
|
||||
},
|
||||
|
||||
// 获取默认智能体,默认取第一个
|
||||
defaultAgent() {
|
||||
return this.defaultAgentId ? this.agents[this.defaultAgentId] : this.agents[Object.keys(this.agents)[0]];
|
||||
},
|
||||
|
||||
// 获取智能体列表(数组形式)
|
||||
agentsList() {
|
||||
return Object.values(this.agents);
|
||||
},
|
||||
|
||||
// 判断当前智能体是否为默认智能体
|
||||
isDefaultAgent() {
|
||||
return this.selectedAgentId === this.defaultAgentId;
|
||||
},
|
||||
|
||||
// 获取当前智能体的配置schema
|
||||
configSchema() {
|
||||
const agent = this.selectedAgentId ? this.agents[this.selectedAgentId] : null;
|
||||
return agent?.config_schema || {};
|
||||
},
|
||||
|
||||
// 获取可配置项(处理x_oap_ui_config)
|
||||
configurableItems() {
|
||||
const schema = this.configSchema || {};
|
||||
if (!schema || !schema.configurable_items) return {};
|
||||
|
||||
const items = { ...schema.configurable_items };
|
||||
|
||||
// 处理x_oap_ui_config,将其提升到上一层
|
||||
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() {
|
||||
return JSON.stringify(this.agentConfig) !== JSON.stringify(this.originalAgentConfig);
|
||||
},
|
||||
|
||||
// 获取当前智能体的线程列表
|
||||
currentAgentThreads() {
|
||||
return this.selectedAgentId ? (this.threads[this.selectedAgentId] || []) : [];
|
||||
},
|
||||
|
||||
// 获取当前线程
|
||||
currentThread() {
|
||||
if (!this.currentThreadId || !this.selectedAgentId) return null;
|
||||
const agentThreads = this.threads[this.selectedAgentId] || [];
|
||||
return agentThreads.find(thread => thread.id === this.currentThreadId);
|
||||
},
|
||||
|
||||
// 获取当前线程的消息
|
||||
currentThreadMessages() {
|
||||
return this.currentThreadId ? (this.threadMessages[this.currentThreadId] || []) : [];
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
// 初始化store
|
||||
async initialize() {
|
||||
if (this.isInitialized) return;
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
this.fetchAgents(),
|
||||
this.fetchDefaultAgent(),
|
||||
this.fetchTools()
|
||||
]);
|
||||
this.isInitialized = true;
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize agent store:', error);
|
||||
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);
|
||||
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.selectedAgentId = this.defaultAgentId;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch default agent:', error);
|
||||
this.error = error.message;
|
||||
}
|
||||
},
|
||||
|
||||
// 设置默认智能体
|
||||
async setDefaultAgent(agentId) {
|
||||
try {
|
||||
await agentConfigApi.setDefaultAgent(agentId);
|
||||
this.defaultAgentId = agentId;
|
||||
} catch (error) {
|
||||
console.error('Failed to set default agent:', error);
|
||||
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);
|
||||
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);
|
||||
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.availableTools = response.tools;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch tools:', error);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
this.error = error.message;
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// 选择线程
|
||||
selectThread(threadId) {
|
||||
this.currentThreadId = threadId;
|
||||
|
||||
// 如果没有该线程的消息,初始化空数组
|
||||
if (threadId && !this.threadMessages[threadId]) {
|
||||
this.threadMessages[threadId] = [];
|
||||
}
|
||||
},
|
||||
|
||||
// 获取线程消息
|
||||
async fetchThreadMessages(threadId) {
|
||||
if (!threadId) return;
|
||||
|
||||
this.isLoadingMessages = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const response = await agentApi.getAgentHistory(this.selectedAgentId, threadId);
|
||||
this.threadMessages[threadId] = response.history || [];
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch thread messages:', error);
|
||||
this.error = error.message;
|
||||
throw error;
|
||||
} finally {
|
||||
this.isLoadingMessages = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 发送消息
|
||||
async sendMessage(agentId, requestData) {
|
||||
if (!agentId) return null;
|
||||
|
||||
try {
|
||||
const response = await agentApi.sendAgentMessage(agentId, requestData);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Failed to send message:', error);
|
||||
this.error = error.message;
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// 添加消息到线程
|
||||
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() {
|
||||
this.agents = {};
|
||||
this.selectedAgentId = null;
|
||||
this.defaultAgentId = null;
|
||||
this.agentConfig = {};
|
||||
this.originalAgentConfig = {};
|
||||
this.availableTools = [];
|
||||
this.threads = {};
|
||||
this.currentThreadId = null;
|
||||
this.threadMessages = {};
|
||||
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'] // 只持久化关键状态
|
||||
}
|
||||
});
|
||||
@ -5,7 +5,7 @@
|
||||
<div class="header-item">
|
||||
<a-button class="header-button" @click="openAgentModal">
|
||||
<Bot size="18" stroke-width="1.75" />
|
||||
选择:{{ selectedAgent.name || '选择智能体' }}
|
||||
选择:{{ selectedAgent?.name || '选择智能体' }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
@ -62,8 +62,6 @@
|
||||
<!-- 中间内容区域 -->
|
||||
<div class="content">
|
||||
<AgentChatComponent
|
||||
:agent-id="selectedAgentId"
|
||||
:config="agentConfig"
|
||||
:state="state"
|
||||
:single-mode="false"
|
||||
@open-config="toggleConf"
|
||||
@ -76,13 +74,7 @@
|
||||
<!-- 配置侧边栏 -->
|
||||
<AgentConfigSidebar
|
||||
:isOpen="state.isConfigSidebarOpen"
|
||||
:selectedAgent="selectedAgent"
|
||||
:selectedAgentId="selectedAgentId"
|
||||
:agentConfig="agentConfig"
|
||||
@close="() => state.isConfigSidebarOpen = false"
|
||||
@update:agentConfig="(config) => agentConfig = config"
|
||||
@config-saved="loadAgentConfig"
|
||||
@config-reset="loadAgentConfig"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -92,72 +84,53 @@
|
||||
import { ref, onMounted, reactive, watch, computed, h } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import {
|
||||
CloseOutlined,
|
||||
SettingOutlined,
|
||||
LinkOutlined,
|
||||
StarOutlined,
|
||||
StarFilled,
|
||||
CheckCircleOutlined,
|
||||
PlusCircleOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { Bot } from 'lucide-vue-next';
|
||||
import AgentChatComponent from '@/components/AgentChatComponent.vue';
|
||||
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue';
|
||||
import AgentConfigSidebar from '@/components/AgentConfigSidebar.vue';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { agentApi } from '@/apis/agent';
|
||||
import { useAgentStore } from '@/stores/agent';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
// 路由
|
||||
// 路由和stores
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const agentStore = useAgentStore();
|
||||
|
||||
// 状态
|
||||
const agents = ref({});
|
||||
const selectedAgentId = ref(null);
|
||||
const defaultAgentId = ref(null); // 存储默认智能体ID
|
||||
// 从store中获取响应式状态
|
||||
const {
|
||||
agents,
|
||||
selectedAgentId,
|
||||
defaultAgentId,
|
||||
agentConfig,
|
||||
selectedAgent,
|
||||
configurableItems,
|
||||
} = storeToRefs(agentStore);
|
||||
const state = reactive({
|
||||
debug_mode: false,
|
||||
agentModalOpen: false,
|
||||
isConfigSidebarOpen: false,
|
||||
isEmptyConfig: computed(() =>
|
||||
!selectedAgentId.value ||
|
||||
Object.keys(configurableItems.value).length === 0
|
||||
Object.keys(configurableItems.value || {}).length === 0
|
||||
)
|
||||
});
|
||||
|
||||
|
||||
|
||||
const selectedAgent = computed(() => agents.value[selectedAgentId.value] || {});
|
||||
const configSchema = computed(() => selectedAgent.value.config_schema || {});
|
||||
const configurableItems = computed(() => {
|
||||
const items = configSchema.value.configurable_items || {};
|
||||
// 遍历所有的配置项,将所有的 x_oap_ui_config 的层级提升到上一层
|
||||
Object.keys(items).forEach(key => {
|
||||
const item = items[key];
|
||||
if (item.x_oap_ui_config) {
|
||||
items[key] = { ...item, ...item.x_oap_ui_config };
|
||||
delete items[key].x_oap_ui_config;
|
||||
}
|
||||
});
|
||||
return items;
|
||||
});
|
||||
|
||||
// 配置状态
|
||||
const agentConfig = ref({});
|
||||
|
||||
// 检查是否为默认智能体
|
||||
const isDefaultAgent = computed(() => {
|
||||
return selectedAgentId.value === defaultAgentId.value;
|
||||
});
|
||||
// 本地状态(仅UI相关)
|
||||
|
||||
// 设置为默认智能体
|
||||
const setAsDefaultAgent = async () => {
|
||||
if (!selectedAgentId.value || !userStore.isAdmin) return;
|
||||
|
||||
try {
|
||||
await agentApi.setDefaultAgent(selectedAgentId.value);
|
||||
defaultAgentId.value = selectedAgentId.value;
|
||||
await agentStore.setDefaultAgent(selectedAgentId.value);
|
||||
message.success('已将当前智能体设为默认');
|
||||
} catch (error) {
|
||||
console.error('设置默认智能体错误:', error);
|
||||
@ -169,97 +142,15 @@ const setAsDefaultAgent = async () => {
|
||||
|
||||
|
||||
|
||||
// 获取默认智能体ID
|
||||
const fetchDefaultAgent = async () => {
|
||||
try {
|
||||
const data = await agentApi.getDefaultAgent();
|
||||
defaultAgentId.value = data.default_agent_id;
|
||||
console.debug("Default agent ID:", defaultAgentId.value);
|
||||
} catch (error) {
|
||||
console.error('获取默认智能体错误:', error);
|
||||
}
|
||||
};
|
||||
// 这些方法现在由agentStore处理,无需在组件中定义
|
||||
|
||||
// 获取智能体列表
|
||||
const fetchAgents = async () => {
|
||||
try {
|
||||
const data = await agentApi.getAgents();
|
||||
// 将数组转换为对象
|
||||
agents.value = data.agents.reduce((acc, agent) => {
|
||||
acc[agent.id] = agent;
|
||||
return acc;
|
||||
}, {});
|
||||
// console.log("agents", agents.value);
|
||||
|
||||
// 加载当前选中智能体的配置
|
||||
if (selectedAgentId.value) {
|
||||
loadAgentConfig();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取智能体错误:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 根据选中的智能体加载配置
|
||||
// 加载智能体配置(使用store方法)
|
||||
const loadAgentConfig = async () => {
|
||||
// BUG: 目前消息重置有问题,需要重置消息
|
||||
if (!selectedAgentId.value || !agents.value[selectedAgentId.value]) return;
|
||||
|
||||
const agent = agents.value[selectedAgentId.value];
|
||||
const schema = agent.config_schema || {};
|
||||
const items = schema.configurable_items || {};
|
||||
|
||||
// 重置配置
|
||||
agentConfig.value = {};
|
||||
|
||||
// 初始化基础配置项
|
||||
if (schema.system_prompt) {
|
||||
agentConfig.value.system_prompt = schema.system_prompt;
|
||||
}
|
||||
|
||||
if (schema.model) {
|
||||
agentConfig.value.model = schema.model;
|
||||
}
|
||||
|
||||
if (schema.tools && schema.tools.length > 0 && schema.tools[0] != 'undefined') {
|
||||
agentConfig.value.tools = schema.tools;
|
||||
}
|
||||
|
||||
// 初始化可配置项
|
||||
Object.keys(items).forEach(key => {
|
||||
const item = items[key];
|
||||
|
||||
// 根据类型设置默认值
|
||||
if (typeof item.default === 'boolean') {
|
||||
agentConfig.value[key] = item.default;
|
||||
} else if (item.type === 'list') {
|
||||
// 对于 list 类型,确保初始化为数组
|
||||
agentConfig.value[key] = Array.isArray(item.default) ? item.default : [];
|
||||
} else {
|
||||
agentConfig.value[key] = item.default || '';
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
// 从服务器加载配置
|
||||
const response = await agentApi.getAgentConfig(selectedAgentId.value);
|
||||
if (response.success && response.config) {
|
||||
// 合并服务器配置
|
||||
Object.keys(response.config).forEach(key => {
|
||||
if (key in agentConfig.value) {
|
||||
const item = items[key];
|
||||
// 对于 list 类型,确保是数组
|
||||
if (item && item.type === 'list') {
|
||||
agentConfig.value[key] = Array.isArray(response.config[key]) ? response.config[key] : [];
|
||||
} else {
|
||||
agentConfig.value[key] = response.config[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
// console.log(`从服务器加载 ${selectedAgentId.value} 配置成功, ${JSON.stringify(agentConfig.value)}`);
|
||||
}
|
||||
await agentStore.loadAgentConfig();
|
||||
} catch (error) {
|
||||
console.error('从服务器加载配置出错:', error);
|
||||
console.error('加载配置出错:', error);
|
||||
message.error('加载配置失败');
|
||||
}
|
||||
};
|
||||
|
||||
@ -267,43 +158,6 @@ const handleModelChange = (data) => {
|
||||
agentConfig.value.model = `${data.provider}/${data.name}`;
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
const saveConfig = async () => {
|
||||
if (!selectedAgentId.value) {
|
||||
message.error('没有选择智能体');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 保存配置到服务器
|
||||
await agentApi.saveAgentConfig(selectedAgentId.value, agentConfig.value);
|
||||
// 提示保存成功
|
||||
message.success('配置已保存到服务器');
|
||||
console.log("保存配置:", agentConfig.value);
|
||||
} catch (error) {
|
||||
console.error('保存配置到服务器出错:', error);
|
||||
message.error('保存配置到服务器失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 重置配置
|
||||
const resetConfig = async () => {
|
||||
if (!selectedAgentId.value) {
|
||||
message.error('没有选择智能体');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 保存空配置到服务器,相当于重置
|
||||
await agentApi.saveAgentConfig(selectedAgentId.value, {});
|
||||
// 重新加载默认配置
|
||||
await loadAgentConfig();
|
||||
message.info('配置已重置');
|
||||
} catch (error) {
|
||||
console.error('重置配置出错:', error);
|
||||
message.error('重置配置失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 监听智能体选择变化
|
||||
watch(
|
||||
@ -313,11 +167,9 @@ watch(
|
||||
}
|
||||
);
|
||||
|
||||
// 选择智能体
|
||||
// 选择智能体(使用store方法)
|
||||
const selectAgent = (agentId) => {
|
||||
selectedAgentId.value = agentId;
|
||||
// 保存选择到本地存储
|
||||
localStorage.setItem('last-selected-agent', agentId);
|
||||
agentStore.selectAgent(agentId);
|
||||
// 加载该智能体的配置
|
||||
loadAgentConfig();
|
||||
};
|
||||
@ -335,29 +187,27 @@ const selectAgentFromModal = (agentId) => {
|
||||
|
||||
|
||||
|
||||
// 初始化
|
||||
// 初始化(使用store方法)
|
||||
onMounted(async () => {
|
||||
// 获取默认智能体
|
||||
await fetchDefaultAgent();
|
||||
// 获取智能体列表
|
||||
await fetchAgents();
|
||||
// 加载工具列表
|
||||
try {
|
||||
// 恢复上次选择的智能体
|
||||
const lastSelectedAgent = localStorage.getItem('last-selected-agent');
|
||||
if (lastSelectedAgent && agents.value[lastSelectedAgent]) {
|
||||
agentStore.selectAgent(lastSelectedAgent);
|
||||
} else if (defaultAgentId.value && agents.value[defaultAgentId.value]) {
|
||||
// 如果有默认智能体,优先选择默认智能体
|
||||
agentStore.selectAgent(defaultAgentId.value);
|
||||
} else if (Object.keys(agents.value).length > 0) {
|
||||
// 默认选择第一个智能体
|
||||
agentStore.selectAgent(Object.keys(agents.value)[0]);
|
||||
}
|
||||
|
||||
|
||||
// 恢复上次选择的智能体
|
||||
const lastSelectedAgent = localStorage.getItem('last-selected-agent');
|
||||
if (lastSelectedAgent && agents.value[lastSelectedAgent]) {
|
||||
selectedAgentId.value = lastSelectedAgent;
|
||||
} else if (defaultAgentId.value && agents.value[defaultAgentId.value]) {
|
||||
// 如果有默认智能体,优先选择默认智能体
|
||||
selectedAgentId.value = defaultAgentId.value;
|
||||
} else if (Object.keys(agents.value).length > 0) {
|
||||
// 默认选择第一个智能体
|
||||
selectedAgentId.value = Object.keys(agents.value)[0];
|
||||
// 加载配置
|
||||
await loadAgentConfig();
|
||||
} catch (error) {
|
||||
console.error('初始化失败:', error);
|
||||
message.error('初始化失败');
|
||||
}
|
||||
|
||||
// 加载配置
|
||||
loadAgentConfig();
|
||||
});
|
||||
|
||||
// 获取配置标签
|
||||
@ -445,6 +295,35 @@ const toggleConf = () => {
|
||||
// padding: var(--gap-radius);
|
||||
// gap: var(--gap-radius);
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.no-agent-selected {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--bg-content);
|
||||
}
|
||||
|
||||
.no-agent-content {
|
||||
text-align: center;
|
||||
color: var(--text-secondary);
|
||||
|
||||
svg {
|
||||
margin-bottom: 16px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 16px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
// .content {
|
||||
// border-radius: var(--gap-radius);
|
||||
// border: 1px solid var(--gray-300);
|
||||
|
||||
@ -64,12 +64,13 @@ import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useInfoStore } from '@/stores/info'
|
||||
import { agentApi } from '@/apis/agent'
|
||||
import { useAgentStore } from '@/stores/agent'
|
||||
import UserInfoComponent from '@/components/UserInfoComponent.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const infoStore = useInfoStore()
|
||||
const agentStore = useAgentStore()
|
||||
|
||||
const goToChat = async () => {
|
||||
// 检查用户是否登录
|
||||
@ -90,23 +91,11 @@ const goToChat = async () => {
|
||||
// 普通用户跳转到默认智能体
|
||||
try {
|
||||
// 获取默认智能体
|
||||
const data = await agentApi.getDefaultAgent();
|
||||
if (data.default_agent_id) {
|
||||
// 使用后端设置的默认智能体
|
||||
router.push(`/agent/${data.default_agent_id}`);
|
||||
} else {
|
||||
// 如果没有设置默认智能体,则获取智能体列表选择第一个
|
||||
const agentData = await agentApi.getAgents();
|
||||
if (agentData.agents && agentData.agents.length > 0) {
|
||||
router.push(`/agent/${agentData.agents[0].id}`);
|
||||
} else {
|
||||
// 没有可用智能体,回退到chat页面
|
||||
router.push("/chat");
|
||||
}
|
||||
}
|
||||
const defaultAgent = agentStore.defaultAgent;
|
||||
router.push(`/agent/${defaultAgent.id}`);
|
||||
} catch (error) {
|
||||
console.error('跳转到智能体页面失败:', error);
|
||||
router.push("/chat");
|
||||
router.push("/");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -170,13 +170,14 @@ import { ref, reactive, onMounted, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { useInfoStore } from '@/stores/info';
|
||||
import { useAgentStore } from '@/stores/agent';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { agentApi } from '@/apis/agent';
|
||||
import { healthApi } from '@/apis/system_api';
|
||||
import { UserOutlined, LockOutlined, WechatOutlined, QrcodeOutlined, ThunderboltOutlined, ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const infoStore = useInfoStore();
|
||||
const agentStore = useAgentStore();
|
||||
|
||||
// 从infoStore获取登录背景图片
|
||||
const loginBgImage = computed(() => {
|
||||
@ -248,18 +249,20 @@ const handleLogin = async () => {
|
||||
|
||||
// 普通用户跳转到默认智能体
|
||||
try {
|
||||
// 初始化agentStore并获取智能体信息
|
||||
await agentStore.initialize();
|
||||
|
||||
// 尝试获取默认智能体
|
||||
const data = await agentApi.getDefaultAgent();
|
||||
if (data.default_agent_id) {
|
||||
if (agentStore.defaultAgentId) {
|
||||
// 如果存在默认智能体,直接跳转
|
||||
router.push(`/agent/${data.default_agent_id}`);
|
||||
router.push(`/agent/${agentStore.defaultAgentId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 没有默认智能体,获取第一个可用智能体
|
||||
const agentData = await agentApi.getAgents();
|
||||
if (agentData.agents && agentData.agents.length > 0) {
|
||||
router.push(`/agent/${agentData.agents[0].id}`);
|
||||
const agentIds = Object.keys(agentStore.agents);
|
||||
if (agentIds.length > 0) {
|
||||
router.push(`/agent/${agentIds[0]}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user