diff --git a/prompto-lab-ui/package.json b/prompto-lab-ui/package.json index 25d5393..b942967 100644 --- a/prompto-lab-ui/package.json +++ b/prompto-lab-ui/package.json @@ -10,7 +10,8 @@ "build-only": "vite build", "type-check": "vue-tsc --build", "lint": "eslint . --fix", - "format": "prettier --write src/" + "format": "prettier --write src/", + "serve": "vite preview --port 4173 --host" }, "dependencies": { "lodash-es": "^4.17.21", diff --git a/prompto-lab-ui/src/App.vue b/prompto-lab-ui/src/App.vue index 4a75f6e..590fbbf 100644 --- a/prompto-lab-ui/src/App.vue +++ b/prompto-lab-ui/src/App.vue @@ -2,7 +2,7 @@ import { RouterView } from 'vue-router' import { useRoute } from 'vue-router' import { ref,onMounted,onUnmounted,computed } from 'vue' -import AppHeader from '@/components/AppHeader.vue' +import AppHeader from '@/components/layout/AppHeader.vue' const route = useRoute() const appRef = ref() diff --git a/prompto-lab-ui/src/components/AIChatPage.vue b/prompto-lab-ui/src/components/Chat/AIChatPage.vue similarity index 92% rename from prompto-lab-ui/src/components/AIChatPage.vue rename to prompto-lab-ui/src/components/Chat/AIChatPage.vue index 6275380..631d338 100644 --- a/prompto-lab-ui/src/components/AIChatPage.vue +++ b/prompto-lab-ui/src/components/Chat/AIChatPage.vue @@ -39,6 +39,7 @@ @@ -113,6 +114,10 @@ const isResizing = ref(false) const startX = ref(0) const startRightWidth = ref(0) +// 添加流式消息状态管理 +const streamingNodeId = ref('') +const streamingContent = ref('') + // 会话状态 const session = ref(null) const eventSource = ref(null) @@ -179,28 +184,70 @@ const initializeSession = async () => { const handleSSEMessage = (response: MessageResponse) => { console.log('收到SSE消息:', response) - // 根据消息类型处理 switch (response.type) { case 'AI_QUESTION': - case 'AI_ANSWER': // 添加对AI_ANSWER类型的处理 + case 'AI_ANSWER': addAIMessage(response.nodeId, response.content) break - case 'AI_SELECTION_QUESTION': - addAISelectionMessage(response.nodeId, response.content, response.options || []) - break - case 'USER_ANSWER': - // 用户消息确认,通常不需要特殊处理 + case 'AI_STREAM_START': + // 开始流式响应 + startStreamingMessage(response.nodeId) break - case 'SYSTEM_INFO': - toast.info({ - title: '系统消息', - message: response.content, - duration: 3000 - }) + case 'AI_STREAM_CHUNK': + // 接收流式内容片段 + appendStreamingContent(response.nodeId, response.content) break - default: - console.warn('未知的消息类型:', response.type, response) + case 'AI_STREAM_END': + // 结束流式响应 + finishStreamingMessage(response.nodeId) break + // ... 其他case保持不变 + } +} + +// 开始流式消息 +const startStreamingMessage = (nodeId: string) => { + streamingNodeId.value = nodeId + streamingContent.value = '' + + const aiNode: ConversationNode = { + id: nodeId, + content: '', + type: 'assistant', + timestamp: new Date(), + parentId: currentNodeId.value, + children: [], + isActive: true + } + + const currentNode = conversationTree.value.get(currentNodeId.value) + if (currentNode) { + currentNode.children.push(nodeId) + } + + conversationTree.value.set(nodeId, aiNode) + currentNodeId.value = nodeId + isLoading.value = false +} + +// 追加流式内容 +const appendStreamingContent = (nodeId: string, chunk: string) => { + if (streamingNodeId.value === nodeId) { + streamingContent.value += chunk + + // 更新节点内容 + const node = conversationTree.value.get(nodeId) + if (node) { + node.content = streamingContent.value + } + } +} + +// 完成流式消息 +const finishStreamingMessage = (nodeId: string) => { + if (streamingNodeId.value === nodeId) { + streamingNodeId.value = '' + streamingContent.value = '' } } @@ -228,7 +275,13 @@ const handleSSEError = (error: Event) => { } // 添加AI消息到对话树 -const addAIMessage = (nodeId: string, content: string) => { +const addAIMessage = (nodeId: string, content: string, isStreaming = false) => { + const existingNode = conversationTree.value.get(nodeId) + if (existingNode && isStreaming) { + // 如果是流式更新,只更新内容 + existingNode.content = content + return + } const aiNode: ConversationNode = { id: nodeId, content, diff --git a/prompto-lab-ui/src/components/AiNodeConfig.vue b/prompto-lab-ui/src/components/Chat/AiNodeConfig.vue similarity index 99% rename from prompto-lab-ui/src/components/AiNodeConfig.vue rename to prompto-lab-ui/src/components/Chat/AiNodeConfig.vue index e00337f..13c931c 100644 --- a/prompto-lab-ui/src/components/AiNodeConfig.vue +++ b/prompto-lab-ui/src/components/Chat/AiNodeConfig.vue @@ -112,7 +112,7 @@ :class="{ 'configured': operation.modelName, 'disabled': !operation.enabled, - 'testing': testing === operationType + 'testing': testing === String(operationType) }" > @@ -183,10 +183,10 @@ v-if="operation.modelName" @click="testOperation(String(operationType))" class="action-btn test large" - :disabled="testing === operationType" - :title="testing === operationType ? '测试中...' : '测试操作'" + :disabled="testing === String(operationType)" + :title="testing === String(operationType) ? '测试中...' : '测试操作'" > - + diff --git a/prompto-lab-ui/src/components/ChatMain.vue b/prompto-lab-ui/src/components/Chat/ChatMain.vue similarity index 71% rename from prompto-lab-ui/src/components/ChatMain.vue rename to prompto-lab-ui/src/components/Chat/ChatMain.vue index 8c23a6b..0ad4033 100644 --- a/prompto-lab-ui/src/components/ChatMain.vue +++ b/prompto-lab-ui/src/components/Chat/ChatMain.vue @@ -6,7 +6,7 @@
- +
@@ -30,90 +30,118 @@
- +
-
-
-
-
- - {{ message.type === 'user' ? '👤' : '🤖' }} - -
-
-
-
-
-
{{ message.content }}
-
{{ formatTime(message.timestamp) }}
-
-
-
- -
-
-
-
- 🤖 -
-
-
-
-
-
-
- - - -
-
AI正在思考...
+ + + + +
+
+
+
+ +
+ +
- -
-
-
-
- -
- - -
-
-
-
+ + \ No newline at end of file diff --git a/prompto-lab-ui/src/components/Chat/MessageItem.vue b/prompto-lab-ui/src/components/Chat/MessageItem.vue new file mode 100644 index 0000000..3019c31 --- /dev/null +++ b/prompto-lab-ui/src/components/Chat/MessageItem.vue @@ -0,0 +1,291 @@ + + + + + \ No newline at end of file diff --git a/prompto-lab-ui/src/components/MindMapTree.vue b/prompto-lab-ui/src/components/Chat/MindMapTree.vue similarity index 100% rename from prompto-lab-ui/src/components/MindMapTree.vue rename to prompto-lab-ui/src/components/Chat/MindMapTree.vue diff --git a/prompto-lab-ui/src/components/TreeNode.vue b/prompto-lab-ui/src/components/Chat/TreeNode.vue similarity index 100% rename from prompto-lab-ui/src/components/TreeNode.vue rename to prompto-lab-ui/src/components/Chat/TreeNode.vue diff --git a/prompto-lab-ui/src/components/HelloWorld.vue b/prompto-lab-ui/src/components/HelloWorld.vue deleted file mode 100644 index d174cf8..0000000 --- a/prompto-lab-ui/src/components/HelloWorld.vue +++ /dev/null @@ -1,41 +0,0 @@ - - - - - diff --git a/prompto-lab-ui/src/components/AICallLogViewer.vue b/prompto-lab-ui/src/components/SFChain/AICallLogViewer.vue similarity index 97% rename from prompto-lab-ui/src/components/AICallLogViewer.vue rename to prompto-lab-ui/src/components/SFChain/AICallLogViewer.vue index c09b73b..df1f54c 100644 --- a/prompto-lab-ui/src/components/AICallLogViewer.vue +++ b/prompto-lab-ui/src/components/SFChain/AICallLogViewer.vue @@ -204,11 +204,11 @@ const selectedLog = ref(null) // 计算属性 const uniqueOperations = computed(() => { - return [...new Set(logs.value.map(log => log.operationType))] + return [...new Set(logs.value.map((log:AICallLogSummary) => log.operationType))] }) const uniqueModels = computed(() => { - return [...new Set(logs.value.map(log => log.modelName))] + return [...new Set(logs.value.map((log:AICallLogSummary) => log.modelName))] }) const filteredLogs = computed(() => { @@ -217,7 +217,7 @@ const filteredLogs = computed(() => { // 搜索过滤 if (searchQuery.value) { const query = searchQuery.value.toLowerCase() - filtered = filtered.filter(log => + filtered = filtered.filter((log:AICallLogSummary) => log.callId.toLowerCase().includes(query) || log.operationType.toLowerCase().includes(query) || log.modelName.toLowerCase().includes(query) || @@ -227,21 +227,21 @@ const filteredLogs = computed(() => { // 操作类型过滤 if (filterOperation.value) { - filtered = filtered.filter(log => log.operationType === filterOperation.value) + filtered = filtered.filter((log:AICallLogSummary) => log.operationType === filterOperation.value) } // 模型过滤 if (filterModel.value) { - filtered = filtered.filter(log => log.modelName === filterModel.value) + filtered = filtered.filter((log:AICallLogSummary) => log.modelName === filterModel.value) } // 状态过滤 if (filterStatus.value) { - filtered = filtered.filter(log => log.status === filterStatus.value) + filtered = filtered.filter((log:AICallLogSummary) => log.status === filterStatus.value) } // 按时间倒序排列 - return filtered.sort((a, b) => new Date(b.callTime).getTime() - new Date(a.callTime).getTime()) + return filtered.sort((a:AICallLogSummary, b:AICallLogSummary) => new Date(b.callTime).getTime() - new Date(a.callTime).getTime()) }) const totalPages = computed(() => { diff --git a/prompto-lab-ui/src/components/ApiInfoConfig.vue b/prompto-lab-ui/src/components/SFChain/ApiInfoConfig.vue similarity index 100% rename from prompto-lab-ui/src/components/ApiInfoConfig.vue rename to prompto-lab-ui/src/components/SFChain/ApiInfoConfig.vue diff --git a/prompto-lab-ui/src/components/JsonViewer.vue b/prompto-lab-ui/src/components/SFChain/JsonViewer.vue similarity index 98% rename from prompto-lab-ui/src/components/JsonViewer.vue rename to prompto-lab-ui/src/components/SFChain/JsonViewer.vue index e6092ca..88cc099 100644 --- a/prompto-lab-ui/src/components/JsonViewer.vue +++ b/prompto-lab-ui/src/components/SFChain/JsonViewer.vue @@ -248,10 +248,10 @@ const countItems = (obj: JsonValue): number => { if (obj === null || obj === undefined || typeof obj !== 'object') return 1 if (Array.isArray(obj)) { - return obj.reduce((sum, item) => sum + countItems(item), 0) + return obj.reduce((sum:number, item) => sum + countItems(item), 0) } - return Object.values(obj as JsonObject).reduce((sum, value) => sum + countItems(value), 0) + return Object.values(obj as JsonObject).reduce((sum:number, value) => sum + countItems(value), 0) } // 事件处理 diff --git a/prompto-lab-ui/src/components/LogDetailModal.vue b/prompto-lab-ui/src/components/SFChain/LogDetailModal.vue similarity index 100% rename from prompto-lab-ui/src/components/LogDetailModal.vue rename to prompto-lab-ui/src/components/SFChain/LogDetailModal.vue diff --git a/prompto-lab-ui/src/components/SystemManagement.vue b/prompto-lab-ui/src/components/SFChain/SystemManagement.vue similarity index 100% rename from prompto-lab-ui/src/components/SystemManagement.vue rename to prompto-lab-ui/src/components/SFChain/SystemManagement.vue diff --git a/prompto-lab-ui/src/components/Toast.vue b/prompto-lab-ui/src/components/Toast.vue index 481e87a..d2266e6 100644 --- a/prompto-lab-ui/src/components/Toast.vue +++ b/prompto-lab-ui/src/components/Toast.vue @@ -116,7 +116,7 @@ const emit = defineEmits() const visible = ref(false) const progressWidth = ref(100) const isPaused = ref(false) -let timer: NodeJS.Timeout | null = null +let timer: number | null = null let startTime: number = 0 let remainingTime: number = 0 diff --git a/prompto-lab-ui/src/components/AppHeader.vue b/prompto-lab-ui/src/components/layout/AppHeader.vue similarity index 100% rename from prompto-lab-ui/src/components/AppHeader.vue rename to prompto-lab-ui/src/components/layout/AppHeader.vue diff --git a/prompto-lab-ui/src/services/conversationApi.ts b/prompto-lab-ui/src/services/conversationApi.ts index a5a5d59..5c718e8 100644 --- a/prompto-lab-ui/src/services/conversationApi.ts +++ b/prompto-lab-ui/src/services/conversationApi.ts @@ -13,9 +13,10 @@ export interface MessageRequest { export interface MessageResponse { nodeId: string content: string - type: 'USER_ANSWER' | 'AI_QUESTION' | 'AI_SELECTION_QUESTION' | 'SYSTEM_INFO' + type: 'USER_ANSWER' | 'AI_QUESTION' | 'AI_SELECTION_QUESTION' | 'SYSTEM_INFO' | 'AI_STREAM_START' | 'AI_STREAM_CHUNK' | 'AI_STREAM_END' | 'AI_ANSWER' options?: string[] timestamp: number + isComplete?: boolean // 标识消息是否完整 } export interface ConversationSession { @@ -66,10 +67,14 @@ export const getSession = async (sessionId: string): Promise void, onError?: (error: Event) => void): EventSource => { const url = `${API_BASE}/sse/${sessionId}` const eventSource = new EventSource(url) + // 监听默认message事件 eventSource.addEventListener('message', (event: MessageEvent) => { try { const response: MessageResponse = JSON.parse(event.data) @@ -79,6 +84,39 @@ export const connectSSE = (sessionId: string, onMessage: (response: MessageRespo } }) + // 监听流式开始事件 + eventSource.addEventListener('stream_start', (event: MessageEvent) => { + try { + const response: MessageResponse = JSON.parse(event.data) + response.type = 'AI_STREAM_START' + onMessage(response) + } catch (error) { + console.error('解析流式开始消息失败:', error) + } + }) + + // 监听流式数据块事件 + eventSource.addEventListener('stream_chunk', (event: MessageEvent) => { + try { + const response: MessageResponse = JSON.parse(event.data) + response.type = 'AI_STREAM_CHUNK' + onMessage(response) + } catch (error) { + console.error('解析流式数据块失败:', error) + } + }) + + // 监听流式结束事件 + eventSource.addEventListener('stream_end', (event: MessageEvent) => { + try { + const response: MessageResponse = JSON.parse(event.data) + response.type = 'AI_STREAM_END' + onMessage(response) + } catch (error) { + console.error('解析流式结束消息失败:', error) + } + }) + eventSource.onerror = (error: Event) => { console.error('SSE连接错误:', error) if (onError) { diff --git a/prompto-lab-ui/src/types/system.ts b/prompto-lab-ui/src/types/system.ts index 1febf9a..35636b6 100644 --- a/prompto-lab-ui/src/types/system.ts +++ b/prompto-lab-ui/src/types/system.ts @@ -1,8 +1,11 @@ +import type { property } from "lodash-es" + export interface SystemOverview { totalModels: number enabledModels: number totalOperations: number enabledOperations: number + [property: string]: number } export interface ApiResponse { diff --git a/prompto-lab-ui/src/views/ApiConfigView.vue b/prompto-lab-ui/src/views/ApiConfigView.vue index ebd14cb..f08be12 100644 --- a/prompto-lab-ui/src/views/ApiConfigView.vue +++ b/prompto-lab-ui/src/views/ApiConfigView.vue @@ -121,10 +121,10 @@