feat: 前后端分离架构 — FastAPI SSE后端 + Vue 3前端
将单体 Streamlit 应用拆分为三层架构: - api_server.py: FastAPI SSE 流式后端 (端口 8000) - frontend/: Vue 3 + Vite + Pinia 聊天前端 (端口 5173) - agent/graph.py: 新增 node_start 回调支持 - 更新启动脚本为三服务模式 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
/** Pinia store — chat messages + streaming state. */
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export interface Message {
|
||||
id: string
|
||||
role: 'user' | 'assistant'
|
||||
content: string
|
||||
type?: 'text' | 'jrxml' | 'error' | 'success' | 'consult'
|
||||
timestamp: string
|
||||
}
|
||||
|
||||
export interface NodeProgress {
|
||||
node: string
|
||||
label: string
|
||||
detail?: string
|
||||
status: 'running' | 'done'
|
||||
}
|
||||
|
||||
export interface AgentSummary {
|
||||
intent: string
|
||||
status: string
|
||||
jrxml_length: number
|
||||
error_msg: string
|
||||
natural_explanation: string
|
||||
retry_count: number
|
||||
}
|
||||
|
||||
export const useChatStore = defineStore('chat', () => {
|
||||
const messages = ref<Message[]>([])
|
||||
const streaming = ref(false)
|
||||
const streamText = ref('')
|
||||
const nodes = ref<NodeProgress[]>([])
|
||||
const error = ref<string>('')
|
||||
const ocrResult = ref<any>(null)
|
||||
const summary = ref<AgentSummary>({
|
||||
intent: '', status: '', jrxml_length: 0,
|
||||
error_msg: '', natural_explanation: '', retry_count: 0,
|
||||
})
|
||||
|
||||
function addMessage(msg: Omit<Message, 'id' | 'timestamp'>) {
|
||||
messages.value.push({
|
||||
...msg,
|
||||
id: crypto.randomUUID(),
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
|
||||
function startStreaming() {
|
||||
streaming.value = true
|
||||
streamText.value = ''
|
||||
nodes.value = []
|
||||
error.value = ''
|
||||
summary.value = {
|
||||
intent: '', status: '', jrxml_length: 0,
|
||||
error_msg: '', natural_explanation: '', retry_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
function appendStreamToken(text: string) {
|
||||
streamText.value += text
|
||||
}
|
||||
|
||||
function addNode(node: { node: string; label: string }) {
|
||||
nodes.value.push({ ...node, status: 'running' })
|
||||
}
|
||||
|
||||
function completeNode(node: { node: string; label: string; detail: string }) {
|
||||
const existing = nodes.value.find(n => n.node === node.node)
|
||||
if (existing) {
|
||||
existing.status = 'done'
|
||||
existing.detail = node.detail
|
||||
}
|
||||
}
|
||||
|
||||
function finishStreaming(data?: {
|
||||
intent?: string; status?: string; jrxml_length?: number
|
||||
error_msg?: string; natural_explanation?: string; retry_count?: number
|
||||
ocr_extraction_result?: any
|
||||
}) {
|
||||
streaming.value = false
|
||||
nodes.value.forEach(n => { n.status = 'done' })
|
||||
if (data) {
|
||||
summary.value = {
|
||||
intent: data.intent || '',
|
||||
status: data.status || '',
|
||||
jrxml_length: data.jrxml_length || 0,
|
||||
error_msg: data.error_msg || '',
|
||||
natural_explanation: data.natural_explanation || '',
|
||||
retry_count: data.retry_count || 0,
|
||||
}
|
||||
if (data.ocr_extraction_result) {
|
||||
ocrResult.value = data.ocr_extraction_result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setError(err: string) {
|
||||
error.value = err
|
||||
streaming.value = false
|
||||
}
|
||||
|
||||
function reset() {
|
||||
messages.value = []
|
||||
streamText.value = ''
|
||||
nodes.value = []
|
||||
error.value = ''
|
||||
streaming.value = false
|
||||
ocrResult.value = null
|
||||
summary.value = {
|
||||
intent: '', status: '', jrxml_length: 0,
|
||||
error_msg: '', natural_explanation: '', retry_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
messages, streaming, streamText, nodes, error, ocrResult, summary,
|
||||
addMessage, startStreaming, appendStreamToken, addNode, completeNode,
|
||||
finishStreaming, setError, reset,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user