feat(aia): Protocol Agent streaming + editable state panel + protocol generation plan
Day 2 Development (2026-01-24): Backend Enhancements: - Implement SSE streaming in ProtocolAgentController using createStreamingService - Add data condensation via LLM in ProtocolOrchestrator.handleProtocolSync - Support stage editing without resetting progress - Add explicit JSON output format for each stage in system prompt - Create independent seed script for Protocol Agent (seed-protocol-agent.ts) Frontend Improvements: - Integrate useAIStream hook for typewriter effect in ChatArea - Add MarkdownContent component for basic Markdown rendering - Implement StageEditModal for editing stage data (scientific question, PICO, etc.) - Add edit button to StageCard (visible on hover) - Fix routing paths from /aia to /ai-qa - Enhance CSS with full-screen layout and Markdown styles New Documentation: - One-click protocol generation development plan (v1.1) - Editor selection evaluation (Novel vs BlockNote vs Tiptap) - Novel fork strategy for AI-native editing Technical Decisions: - Choose Novel (Fork) as protocol editor for AI-first design - Two-stage progressive generation: summary in chat, full protocol in editor - 10-day development plan for protocol generation feature Code Stats: - Backend: 3 files modified, 1 new file - Frontend: 9 files modified, 2 new files - Docs: 3 new files Status: Streaming and editable features working, protocol generation pending
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
* 三栏布局:可折叠侧边栏 + 聊天区 + 状态面板
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import {
|
||||
Bot, Settings, ChevronLeft, Menu, Plus,
|
||||
@@ -15,6 +15,7 @@ import { ChatArea } from './components/ChatArea';
|
||||
import { StatePanel } from './components/StatePanel';
|
||||
import { useProtocolContext } from './hooks/useProtocolContext';
|
||||
import { useProtocolConversations } from './hooks/useProtocolConversations';
|
||||
import { getAccessToken } from '../../../framework/auth/api';
|
||||
import './styles/protocol-agent.css';
|
||||
|
||||
export const ProtocolAgentPage: React.FC = () => {
|
||||
@@ -36,27 +37,53 @@ export const ProtocolAgentPage: React.FC = () => {
|
||||
// 上下文状态
|
||||
const { context, refreshContext } = useProtocolContext(currentConversation?.id);
|
||||
|
||||
// 首次进入且无conversationId时,自动创建新对话
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
// 处理阶段数据编辑更新
|
||||
const handleStageUpdate = useCallback(async (stageCode: string, data: Record<string, unknown>) => {
|
||||
if (!currentConversation?.id) return;
|
||||
|
||||
const token = getAccessToken();
|
||||
const response = await fetch('/api/v1/aia/protocol-agent/sync', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
conversationId: currentConversation.id,
|
||||
stageCode,
|
||||
data,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('更新失败');
|
||||
}
|
||||
|
||||
// 刷新上下文
|
||||
await refreshContext();
|
||||
}, [currentConversation?.id, refreshContext]);
|
||||
|
||||
// 使用ref避免无限循环
|
||||
const hasTriedCreate = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!conversationId && !currentConversation && !isCreating) {
|
||||
// 只在首次进入且无conversationId时尝试创建一次
|
||||
if (!conversationId && !currentConversation && !hasTriedCreate.current) {
|
||||
hasTriedCreate.current = true;
|
||||
console.log('[ProtocolAgentPage] 自动创建新对话...');
|
||||
setIsCreating(true);
|
||||
|
||||
createConversation().then(newConv => {
|
||||
if (newConv) {
|
||||
console.log('[ProtocolAgentPage] 新对话创建成功:', newConv.id);
|
||||
navigate(`/aia/protocol-agent/${newConv.id}`, { replace: true });
|
||||
navigate(`/ai-qa/protocol-agent/${newConv.id}`, { replace: true });
|
||||
} else {
|
||||
console.error('[ProtocolAgentPage] 创建对话失败');
|
||||
setIsCreating(false);
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('[ProtocolAgentPage] 创建对话异常:', err);
|
||||
setIsCreating(false);
|
||||
});
|
||||
}
|
||||
}, [conversationId, currentConversation, isCreating, createConversation, navigate]);
|
||||
}, [conversationId, currentConversation, navigate]); // 移除createConversation依赖
|
||||
|
||||
// 获取当前阶段信息
|
||||
const currentStageName = context?.stageName || '科学问题梳理';
|
||||
@@ -66,7 +93,7 @@ export const ProtocolAgentPage: React.FC = () => {
|
||||
const handleNewConversation = async () => {
|
||||
const newConv = await createConversation();
|
||||
if (newConv) {
|
||||
navigate(`/aia/protocol-agent/${newConv.id}`);
|
||||
navigate(`/ai-qa/protocol-agent/${newConv.id}`);
|
||||
setSidebarCollapsed(true);
|
||||
}
|
||||
};
|
||||
@@ -74,17 +101,17 @@ export const ProtocolAgentPage: React.FC = () => {
|
||||
// 选择对话
|
||||
const handleSelectConversation = (id: string) => {
|
||||
selectConversation(id);
|
||||
navigate(`/aia/protocol-agent/${id}`);
|
||||
navigate(`/ai-qa/protocol-agent/${id}`);
|
||||
setSidebarCollapsed(true);
|
||||
};
|
||||
|
||||
// 返回AgentHub
|
||||
const handleBack = () => {
|
||||
navigate('/aia');
|
||||
navigate('/ai-qa');
|
||||
};
|
||||
|
||||
// 加载状态
|
||||
if (isCreating || (!conversationId && !currentConversation)) {
|
||||
// 如果没有conversationId,显示等待状态
|
||||
if (!conversationId) {
|
||||
return (
|
||||
<div className="protocol-agent-page">
|
||||
<div style={{
|
||||
@@ -103,7 +130,7 @@ export const ProtocolAgentPage: React.FC = () => {
|
||||
borderRadius: '50%',
|
||||
animation: 'spin 1s linear infinite'
|
||||
}} />
|
||||
<p style={{ color: '#6B7280', fontSize: '14px' }}>正在创建新对话...</p>
|
||||
<p style={{ color: '#6B7280', fontSize: '14px' }}>正在初始化...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -234,7 +261,7 @@ export const ProtocolAgentPage: React.FC = () => {
|
||||
/>
|
||||
|
||||
{/* 状态面板 */}
|
||||
<StatePanel context={context} />
|
||||
<StatePanel context={context} onStageUpdate={handleStageUpdate} />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user