From 48b11b24803bf40e00dfcdec7b9075619340515a Mon Sep 17 00:00:00 2001 From: pan-liwei <1987884467@qq.com> Date: Wed, 1 Apr 2026 19:27:10 +0800 Subject: [PATCH 1/2] assistent --- .../workbench/workbench.component.tsx | 182 +- .../conversation/conversation.component.tsx | 615 ++++- .../conversation/conversation.props.ts | 46 +- packages/conversation/src/style.scss | 2459 +++++++++++++---- packages/conversation/vite.config.ts | 8 + 5 files changed, 2656 insertions(+), 654 deletions(-) diff --git a/packages/conversation/demo/components/workbench/workbench.component.tsx b/packages/conversation/demo/components/workbench/workbench.component.tsx index 22b0d500..652bf290 100644 --- a/packages/conversation/demo/components/workbench/workbench.component.tsx +++ b/packages/conversation/demo/components/workbench/workbench.component.tsx @@ -1,10 +1,9 @@ -import { defineComponent, ref } from 'vue'; +import { defineComponent, ref, onMounted } from 'vue'; import { Conversation } from '../../..'; import type { NavActionItem, Message, ConversationData, - HistoryItem, ChatAgent, MessageContentFileOperation, MessageContentTodo, @@ -13,6 +12,7 @@ import type { MessageContentAppPreview, AssistiveTool, } from '../../..'; +import { getAgentList, getCategories } from '../../../src/components/conversation/api/agentApi'; const NAV_ACTIONS: NavActionItem[] = [ { id: 'new', icon: 'f-icon f-icon-message_round', label: '新对话', fixed: true } @@ -445,52 +445,152 @@ const DEFAULT_CONVERSATION = createConversation('创建企业通信录', [ export default defineComponent({ name: 'WorkbenchComponent', setup() { + // 历史会话列表(所有会话,包含已关闭标签页的会话) const conversations = ref([DEFAULT_CONVERSATION]); + // 当前打开的标签页 id 列表 + const openTabIds = ref([DEFAULT_CONVERSATION.id]); + // 当前激活的标签页 id const activeConversationId = ref(DEFAULT_CONVERSATION.id); + const agents = ref([]); + const agentsLoading = ref(false); + // 分类 ID -> 分类名称 映射 + const categoryIdToName: Record = {}; + + // 加载专家列表 + async function loadAgents() { + agentsLoading.value = true; + try { + // 先加载分类映射 + const categories = await getCategories(); + categories.forEach(cat => { + categoryIdToName[String(cat.id)] = cat.name; + }); + // 加载专家列表 + const result = await getAgentList({ page: 1, size: 100 }); + // 转换为 ChatAgent 格式 + agents.value = result.records.map((item): ChatAgent => { + return { + id: String(item.id), + name: item.name || item.title || '', + title: item.title, + category: categoryIdToName[String(item.categoryId)] || '未分类', + description: item.description, + avatar: item.avatarUrl || undefined, + views: item.views, + likes: item.likeCount, + creator: item.author, + createdDate: item.createTime ? new Date(item.createTime).toLocaleDateString('zh-CN') : undefined, + systemPrompt: item.systemPrompt, + skills: item.skills ? JSON.parse(item.skills) : [], + }; + }); + } catch (error) { + console.error('加载专家列表失败:', error); + // 失败时使用默认数据 + agents.value = DEMO_AGENTS; + } finally { + agentsLoading.value = false; + } + } + + onMounted(() => { + loadAgents(); + }); function openOrActivateHistory(convTitle: string, initialMessages: Message[]) { const existing = conversations.value.find((c) => c.title === convTitle); if (existing) { + // 如果会话已存在,打开其标签页并激活 + if (!openTabIds.value.includes(existing.id)) { + openTabIds.value = [...openTabIds.value, existing.id]; + } activeConversationId.value = existing.id; } else { + // 创建新会话 const conv = createConversation(convTitle, initialMessages); conversations.value = [...conversations.value, conv]; + openTabIds.value = [...openTabIds.value, conv.id]; activeConversationId.value = conv.id; } } - const historyItems: HistoryItem[] = [ - { - title: '创建企业通信录', - onClick: () => openOrActivateHistory('创建企业通信录', [ENTERPRISE_CONTACTS_USER_MESSAGE]) - }, - { - title: '创建项目数据字典', - onClick: () => openOrActivateHistory('创建项目数据字典', [PROJECT_DATA_DICT_USER_MESSAGE]) - } - ]; - function handleCloseConversation(id: string) { - const idx = conversations.value.findIndex((c) => c.id === id); - if (idx < 0) return; - const nextConversations = conversations.value.filter((c) => c.id !== id); - conversations.value = nextConversations; + // 只从 openTabIds 中移除,不影响 conversations 历史记录 + if (!openTabIds.value.includes(id)) return; + openTabIds.value = openTabIds.value.filter(tabId => tabId !== id); + // 如果关闭的是当前激活的标签页,切换到其他标签页 if (activeConversationId.value === id) { - activeConversationId.value = idx > 0 ? nextConversations[idx - 1].id : (nextConversations[0]?.id ?? null); + if (openTabIds.value.length > 0) { + activeConversationId.value = openTabIds.value[openTabIds.value.length - 1]; + } else { + activeConversationId.value = null; + } } } function handleSwitchConversation(id: string) { - activeConversationId.value = id; + // 如果标签页已打开,切换激活状态 + if (openTabIds.value.includes(id)) { + activeConversationId.value = id; + } else { + // 如果标签页未打开,打开它并激活 + openTabIds.value = [...openTabIds.value, id]; + activeConversationId.value = id; + } } - function handleSendMessage(content: string) { + function handleCreateDerivedChat(data: { + sessionId: string; + agentId: number; + agentName: string; + agentAvatar?: string; + systemPrompt?: string; + }) { + const newConv: ConversationData = { + id: data.sessionId, + title: data.agentName, + messages: [], + agentId: String(data.agentId), + agentConfig: { + agentId: data.agentId, + name: data.agentName, + avatar: data.agentAvatar || '', + systemPrompt: data.systemPrompt || '', + }, + }; + conversations.value = [...conversations.value, newConv]; + openTabIds.value = [...openTabIds.value, newConv.id]; + activeConversationId.value = newConv.id; + } + + function handleCreateConversation(conv: ConversationData) { + conversations.value = [...conversations.value, conv]; + openTabIds.value = [...openTabIds.value, conv.id]; + activeConversationId.value = conv.id; + } + + function handleSendMessage(content: string, msg?: Message) { const activeId = activeConversationId.value; - if (!activeId) return; - const idx = conversations.value.findIndex((c) => c.id === activeId); + let targetId = activeId; + + // 如果没有激活的会话,创建一个新会话 + if (!targetId) { + const newConv: ConversationData = { + id: `conv-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`, + title: '新对话', + messages: [], + }; + conversations.value = [...conversations.value, newConv]; + openTabIds.value = [...openTabIds.value, newConv.id]; + targetId = newConv.id; + activeConversationId.value = targetId; + } + + const idx = conversations.value.findIndex((c) => c.id === targetId); if (idx < 0) return; const conv = conversations.value[idx]; - const newMsg: Message = { + // 如果传入了消息对象,直接使用;否则创建 + const newMsg: Message = msg || { id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`, name: 'Sagi', role: 'user', @@ -503,25 +603,16 @@ export default defineComponent({ conversations.value = updated; } - const agents: ChatAgent[] = [ - { - id: 'haiyue', - name: '海岳大模型(70B)', - skills: ['海岳-Text','海岳-Coder','海岳-Math'], - }, - { - id: 'deepseek', - name: 'DeepSeek(R1)', - skills: ['DeepSeek-Coder', 'DeepSeek-Math', 'DeepSeek-VL'], - icon: '../../../public/assets/icon/deep-seek-logo.svg', - }, - { - id: 'qianwen', - name: 'Qwen-qwq(32B)', - skills: ['Qwen-Text', 'Qwen-Coder','Qwen-Omni'], - icon: '../../../public/assets/icon/qwen-logo.svg', - }, - ]; + function handleAssistantMessage(msg: Message) { + const activeId = activeConversationId.value; + if (!activeId) return; + const idx = conversations.value.findIndex((c) => c.id === activeId); + if (idx < 0) return; + const conv = conversations.value[idx]; + const updated = [...conversations.value]; + updated[idx] = { ...conv, messages: [...conv.messages, msg] }; + conversations.value = updated; + } const assistiveTools: AssistiveTool[] = [ { @@ -629,16 +720,19 @@ export default defineComponent({ ); diff --git a/packages/conversation/src/components/conversation/conversation.component.tsx b/packages/conversation/src/components/conversation/conversation.component.tsx index 3e0b3fe4..5e165f4f 100644 --- a/packages/conversation/src/components/conversation/conversation.component.tsx +++ b/packages/conversation/src/components/conversation/conversation.component.tsx @@ -1,12 +1,14 @@ import { defineComponent, ref, computed, onMounted, onUnmounted, watch, nextTick } from "vue"; import { FLayout, FLayoutPane, FSplitter, FSplitterPane, FPopover, FNav, FListView } from "@farris/ui-vue"; -import { conversationProps, ConversationProps, Message, ChatAgent, AssistiveTool, SlotConfig } from "./conversation.props"; +import { conversationProps, ConversationProps, Message, ChatAgent, AssistiveTool, SlotConfig, ModelInfo } from "./conversation.props"; import type { PreviewConfig } from "../app-preview/types"; import type { ContentAreaContext, LayoutStrategy } from "./composition/types"; import type { VNode } from "vue"; import { renderMessageContent } from "../../utils/message-renderers"; import ChatPreview from "../chat-preview/chat-preview.component"; import { renderSlotConfig } from '../../utils/slot-config-renderers'; +import { getModelList, sendMessage as sendMessageApi, uploadFile, getAgentVersions, createConversation as createConversationApi } from "./api/agentApi"; +import type { AgentVersion } from "./types/agent"; /** 紧凑模式布局策略 */ function createCompactLayoutStrategy(): LayoutStrategy { @@ -29,7 +31,7 @@ function createNormalLayoutStrategy( showPreviewPane: () => boolean, renderPreview: () => VNode, chatPreviewPaneWidth: () => number, - chatPreviewPaneRef: { value: unknown } + chatPreviewPaneRef: any ): LayoutStrategy { return { isCompact: false, @@ -73,7 +75,7 @@ function createNormalLayoutStrategy( export default defineComponent({ name: 'Conversation', props: conversationProps, - emits: ['sendMessage', 'closeConversation', 'switchConversation'], + emits: ['sendMessage', 'closeConversation', 'switchConversation', 'assistantMessage', 'createConversation'], setup(props: ConversationProps, context) { const chatNavPaneWidth = ref(260); const chatNavPaneCollapsed = ref(props.navPaneCollapsed); @@ -118,6 +120,31 @@ export default defineComponent({ const agentDetailAgent = ref(null); const agentDetailActiveTab = ref('overview'); const agentDetailDescExpanded = ref(true); + const agentVersions = ref([]); + + // 头像加载失败状态追踪 + const avatarLoadFailed = ref>({}); + + // 运行时配置:模型选择、联网搜索、记忆功能 + const selectedModel = ref(null); + const modelList = ref([]); + const enableWebSearch = ref(false); + const memoryEnabled = ref(true); + const memoryLevel = ref<'high' | 'medium' | 'low'>('medium'); + const uploadedFileIds = ref([]); + + // 加载模型列表 + async function loadModelList() { + try { + const models = await getModelList(); + modelList.value = models; + if (models.length > 0 && !selectedModel.value) { + selectedModel.value = models[0]; + } + } catch (error) { + console.error('加载模型列表失败:', error); + } + } const agentDetailTabData = computed(() => { const agent = agentDetailAgent.value; @@ -170,10 +197,20 @@ export default defineComponent({ agentDetailAgent.value = agent; agentDetailActiveTab.value = 'overview'; agentDetailDescExpanded.value = true; + // 加载版本历史 + if (agent.id) { + getAgentVersions(agent.id).then(versions => { + agentVersions.value = versions; + }).catch(error => { + console.error('加载版本历史失败:', error); + agentVersions.value = []; + }); + } } function closeAgentDetail() { agentDetailAgent.value = null; + agentVersions.value = []; } // 消息区域滚动条:鼠标移入或滚动时显示,默认隐藏 @@ -204,8 +241,29 @@ export default defineComponent({ const hoveredInputBarTimerId = ref(null); const inputBarPopoverRef = ref(); const inputBarPopoverHostRef = ref(); + const isSendingMessage = ref(false); const speechInput = ref(false); + const fileInputRef = ref(); + + // 文件上传处理 + function handleFileSelect(event: Event) { + const input = event.target as HTMLInputElement; + const files = input.files; + if (!files || files.length === 0) return; + + Array.from(files).forEach(async (file) => { + try { + const result = await uploadFile(file); + uploadedFileIds.value = [...uploadedFileIds.value, result.fileId]; + console.log('文件上传成功:', result.fileName); + } catch (error) { + console.error('文件上传失败:', error); + } + }); + // 清空 input 以便选择相同文件 + input.value = ''; + } function showMessageScrollbar() { messageScrollbarVisible.value = true; @@ -299,6 +357,8 @@ export default defineComponent({ } if ((isTabMode.value ? displayedMessages.value.length : messages.value.length) > 0) focusInputEditor(); }); + // 加载模型列表 + loadModelList(); }); onUnmounted(() => { @@ -366,6 +426,8 @@ export default defineComponent({ showPreviewPane.value = false; activePreviewConfig.value = null; } + // 确保编辑器中有常驻的 agent 标签 + nextTick(() => ensurePermanentAgentTag()); }, { immediate: true }); watch(showPreviewPane, (visible) => { @@ -424,28 +486,137 @@ export default defineComponent({ showNavPopover.value = false; } - function sendMessage() { + async function sendMessage() { const content = serializeEditorToText(); message.value = content; if (!content) return; - if (!isTabMode.value) { - messages.value = [ - ...messages.value, - { - id: `${Date.now()}_${Math.random().toString(16).slice(2)}`, - name: 'Sagi', - role: 'user', - agentId: 'user', - content, - timestamp: Date.now(), - }, - ]; + + // 正在发送中,禁止重复发送 + if (isSendingMessage.value) return; + isSendingMessage.value = true; + + // 获取当前会话和助手配置 + const activeConv = isTabMode.value + ? props.conversations?.find(c => c.id === props.activeConversationId) + : null; + const sessionId = activeConv?.id || props.conversations?.[0]?.id; + const agentConfig = activeConv?.agentConfig; + + // 构造用户消息 + const userMessage: Message = { + id: `${Date.now()}_${Math.random().toString(16).slice(2)}`, + name: 'Sagi', + role: 'user', + agentId: 'user', + content, + timestamp: Date.now(), + }; + + // 如果没有 sessionId,先创建会话 + let currentSessionId = sessionId; + let currentAgentConfig = agentConfig; + if (!currentSessionId) { + // 从 agents 中获取第一个助手作为默认 + const defaultAgent = props.agents[0]; + if (!defaultAgent) { + isSendingMessage.value = false; + return; + } + currentAgentConfig = { + agentId: Number(defaultAgent.id), + name: defaultAgent.name, + avatar: defaultAgent.avatar || '', + systemPrompt: defaultAgent.systemPrompt || '', + }; + // 创建新会话 + try { + const convResponse = await createConversationApi({ agentId: Number(defaultAgent.id) }); + currentSessionId = convResponse.sessionId; + // 通知父组件创建了新会话 + context.emit('createConversation', { + id: currentSessionId, + title: currentAgentConfig.name, + messages: [], + agentId: String(currentAgentConfig.agentId), + agentConfig: currentAgentConfig, + }); + } catch (error) { + console.error('创建会话失败:', error); + isSendingMessage.value = false; + return; + } + } else if (!currentAgentConfig) { + // 如果有 sessionId 但没有 agentConfig,使用默认助手(不创建新会话) + const defaultAgent = props.agents[0]; + if (defaultAgent) { + currentAgentConfig = { + agentId: Number(defaultAgent.id), + name: defaultAgent.name, + avatar: defaultAgent.avatar || '', + systemPrompt: defaultAgent.systemPrompt || '', + }; + } } + + // 清空编辑器 if (editorRef.value) editorRef.value.innerHTML = ''; if (slotEditorRef.value) displayedSlotConfigs.value = []; selectedSkills.value = []; message.value = ''; - context.emit('sendMessage', content); + + // 先添加用户消息到列表 + if (!isTabMode.value) { + messages.value = [...messages.value, userMessage]; + } else { + // 页签模式下通知父组件添加用户消息 + context.emit('sendMessage', content, userMessage); + } + + // 调用 API 发送消息 + if (!currentAgentConfig) { + console.error('发送消息失败: 缺少助手配置'); + isSendingMessage.value = false; + return; + } + try { + const response = await sendMessageApi({ + sessionId: currentSessionId, + message: content, + agentConfig: currentAgentConfig, + runtimeConfig: { + model: selectedModel.value?.modelId || 'gpt-4o', + enableWebSearch: enableWebSearch.value, + memoryEnabled: memoryEnabled.value, + memoryLevel: memoryLevel.value, + }, + fileIds: uploadedFileIds.value, + }); + + // 添加助手回复到消息列表 + const assistantMessage: Message = { + id: `${Date.now()}_${Math.random().toString(16).slice(2)}`, + name: currentAgentConfig.name, + role: 'assistant', + agentId: String(currentAgentConfig.agentId), + content: response.content, + timestamp: Date.now(), + }; + + if (!isTabMode.value) { + messages.value = [...messages.value, assistantMessage]; + } else { + context.emit('assistantMessage', assistantMessage); + } + } catch (error) { + console.error('发送消息失败:', error); + // 可以在这里添加错误提示 + } finally { + isSendingMessage.value = false; + // 清空已上传文件 + uploadedFileIds.value = []; + // 重新确保常驻标签存在 + ensurePermanentAgentTag(); + } } function collapseChatNavPane() { @@ -597,6 +768,52 @@ export default defineComponent({ updateMessageFromEditor(); } + /** 确保编辑器中有常驻的 agent 标签(不可删除) */ + function ensurePermanentAgentTag() { + const editor = editorRef.value; + if (!editor) return; + const agentConfig = activeConversation.value?.agentConfig; + if (!agentConfig) return; + const agentName = agentConfig.name; + // 检查是否已有常驻标签 + const existingTag = editor.querySelector('.f-chat-agent-tag[data-permanent="true"]'); + if (existingTag) { + // 已有标签,检查 agent 名称是否匹配 + const existingAgentName = existingTag.getAttribute('data-agent'); + if (existingAgentName === agentName) { + // 名称相同,不需要更新 + return; + } + // 名称不同,移除旧标签 + existingTag.remove(); + } + // 创建常驻标签(无删除按钮) + const tagEl = document.createElement('span'); + tagEl.className = 'f-chat-agent-tag'; + tagEl.setAttribute('data-agent', agentName); + tagEl.setAttribute('data-permanent', 'true'); + (tagEl as any).contentEditable = 'false'; + const textEl = document.createElement('span'); + textEl.className = 'f-chat-agent-tag-text'; + textEl.textContent = `@${agentName}`; + tagEl.appendChild(textEl); + // 插入到编辑器开头 + if (editor.firstChild) { + editor.insertBefore(tagEl, editor.firstChild); + } else { + editor.appendChild(tagEl); + } + // 在标签后添加空格 + const firstChild = editor.firstChild; + if (firstChild && firstChild.nextSibling) { + const space = document.createTextNode(' '); + editor.insertBefore(space, firstChild.nextSibling); + } else { + const space = document.createTextNode(' '); + editor.appendChild(space); + } + } + function handleAgentDblClick(agent: ChatAgent) { insertAgentMention(agent.name); } @@ -770,37 +987,53 @@ export default defineComponent({
{shouldShowAgentButton.value && !selectedAssistiveTool.value && (
- - -
    - {props.agents.map((item: ChatAgent) => { - return ( -
  • { - selectedAgent.value = item; - hideAgentListPanel(); - }}> -
    - - {item.name} -
    - -
  • - ); - })} -
-
+ {/* 模型选择 */} + {modelList.value.length > 0 && ( + + )} + {/* 联网搜索开关 */} + + {/* 记忆功能开关 */} + + {/* 记忆级别选择 */} + {memoryEnabled.value && ( + + )} {shouldShowAssistiveTool.value && (
)}
+ {agentDetailActiveTab.value !== 'overview' && (agent.systemPrompt || agent.description) && ( @@ -1351,16 +1719,31 @@ export default defineComponent({
相关助理 - 查看更多 › + 查看更多 ›