/** * rag.js — 智能问答页(RAG + SSE 流式) */ const RagPage = { state: { question: '', template: 'simple', answer: '', citations: [], streaming: false, error: null, thinking: '', // thinking 内容 thinkingEnabled: true, // thinking 开关(从 API 加载) }, render() { const templates = [ { value: 'simple', label: '通俗解释' }, { value: 'professional', label: '专业分析' }, { value: 'compare', label: '对比条文' }, ]; return `

智能问答

${templates.map(t => ` `).join('')}
${this.state.question.length}/1000
${UI.emptyState('提问开始对话', '系统将检索相关法规条文并由 AI 生成带引用的答案')}
`; }, init() { // 加载 LLM 参数(用于 thinking 状态显示) this.loadLLMParams(); }, async loadLLMParams() { try { const resp = await API.get('/api/prompts'); this.state.thinkingEnabled = resp.data.llm_params.thinking_enabled; } catch (e) { console.error('加载 LLM 参数失败:', e); } }, onInput() { const input = document.getElementById('rag-input'); if (input) this.state.question = input.value; }, setTemplate(t) { this.state.template = t; const content = document.getElementById('content'); content.innerHTML = this.render(); }, async submit() { const input = document.getElementById('rag-input'); if (input) this.state.question = input.value.trim(); if (!this.state.question || this.state.streaming) return; this.state.answer = ''; this.state.thinking = ''; this.state.citations = []; this.state.streaming = true; this.state.error = null; this.renderAnswer(); try { const resp = await fetch('/api/rag', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ question: this.state.question, template: this.state.template, }), }); if (!resp.ok) { const err = await resp.json().catch(() => ({ detail: resp.statusText })); throw new Error(err.detail || `HTTP ${resp.status}`); } // SSE 流式读取 const reader = resp.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { 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.startsWith('data: ')) continue; try { const evt = JSON.parse(line.slice(6)); if (evt.type === 'answer') { this.state.answer += evt.content; this.renderAnswer(); } else if (evt.type === 'thinking') { this.state.thinking += evt.content; this.renderAnswer(); } else if (evt.type === 'params') { this.state.thinkingEnabled = evt.thinking_enabled; } else if (evt.type === 'citations') { this.state.citations = evt.clauses || []; this.renderAnswer(); } else if (evt.type === 'no_result') { this.state.error = evt.message; this.renderAnswer(); } else if (evt.type === 'error') { this.state.error = evt.message; this.renderAnswer(); } else if (evt.type === 'done') { // 完成 } } catch (e) { /* 忽略解析错误 */ } } } this.state.streaming = false; this.renderAnswer(); } catch (e) { this.state.streaming = false; this.state.error = e.message; this.renderAnswer(); } }, renderAnswer() { const el = document.getElementById('rag-answer'); if (!el) return; if (this.state.error) { el.innerHTML = UI.errorState(this.state.error, 'RagPage.submit()'); return; } if (!this.state.answer && !this.state.citations.length && this.state.streaming) { el.innerHTML = `
${UI.spinner(20)}检索法规并生成答案中...
`; return; } if (!this.state.answer && !this.state.thinking && !this.state.citations.length) { el.innerHTML = UI.emptyState('提问开始对话', '系统将检索相关法规条文并由 AI 生成带引用的答案'); return; } // thinking 折叠区(仅 thinking 开启且有内容时显示) const thinkingHtml = this.state.thinking ? `
Thinking 思考过程 ${this.state.thinking.length} 字 ${this.state.streaming && !this.state.answer ? '思考中...' : ''}
${this._escapeHtml(this.state.thinking)}
` : ''; const answerHtml = this.state.answer ? `
${marked.parse(this.state.answer)}
` : (this.state.streaming && this.state.thinking ? '
等待生成答案...
' : ''); const citationsHtml = this.state.citations.length ? `
引用条文(${this.state.citations.length})
${this.state.citations.map(c => UI.clauseCard(c)).join('')}
` : ''; el.innerHTML = `
AI 回答
${thinkingHtml} ${answerHtml} ${this.state.streaming ? `
${UI.spinner(14)} 生成中...
` : ''} ${citationsHtml}
`; }, _escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }, };