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:
@@ -6,11 +6,16 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap');
|
||||
|
||||
/* ============================================ */
|
||||
/* 全局样式 */
|
||||
/* 全局样式 - 全屏覆盖(与ChatWorkspace一致) */
|
||||
/* ============================================ */
|
||||
.protocol-agent-page {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
font-family: 'Inter', 'Noto Sans SC', sans-serif;
|
||||
background: #FFFFFF;
|
||||
@@ -445,8 +450,252 @@
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
line-height: 1.8;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* Markdown 格式支持 */
|
||||
.chat-bubble h1,
|
||||
.chat-bubble h2,
|
||||
.chat-bubble h3 {
|
||||
margin: 16px 0 8px 0;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.chat-bubble h1 { font-size: 1.25em; }
|
||||
.chat-bubble h2 { font-size: 1.15em; }
|
||||
.chat-bubble h3 { font-size: 1.05em; }
|
||||
|
||||
.chat-bubble p {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.chat-bubble ul,
|
||||
.chat-bubble ol {
|
||||
margin: 8px 0;
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.chat-bubble li {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.chat-bubble strong,
|
||||
.chat-bubble b {
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.chat-bubble em,
|
||||
.chat-bubble i {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.chat-bubble code {
|
||||
background: #F3F4F6;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Monaco', 'Consolas', monospace;
|
||||
font-size: 0.9em;
|
||||
color: #6366F1;
|
||||
}
|
||||
|
||||
.chat-bubble pre {
|
||||
background: #1F2937;
|
||||
color: #F9FAFB;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.chat-bubble pre code {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.chat-bubble blockquote {
|
||||
border-left: 4px solid #6366F1;
|
||||
margin: 12px 0;
|
||||
padding: 8px 16px;
|
||||
background: #F9FAFB;
|
||||
color: #4B5563;
|
||||
}
|
||||
|
||||
/* MarkdownContent 组件样式 */
|
||||
.markdown-content {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.markdown-content p {
|
||||
margin: 0 0 12px 0;
|
||||
}
|
||||
|
||||
.markdown-content p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.markdown-content h1,
|
||||
.markdown-content h2,
|
||||
.markdown-content h3 {
|
||||
margin: 16px 0 8px 0;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.markdown-content h1:first-child,
|
||||
.markdown-content h2:first-child,
|
||||
.markdown-content h3:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.markdown-content ul,
|
||||
.markdown-content ol {
|
||||
margin: 8px 0 12px 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.markdown-content li {
|
||||
margin: 6px 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.markdown-content li::marker {
|
||||
color: #6366F1;
|
||||
}
|
||||
|
||||
.markdown-content strong {
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.markdown-content em {
|
||||
font-style: italic;
|
||||
color: #4B5563;
|
||||
}
|
||||
|
||||
.markdown-content code {
|
||||
background: rgba(99, 102, 241, 0.1);
|
||||
color: #6366F1;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Monaco', 'Consolas', 'Courier New', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* ============================================ */
|
||||
/* 阶段标签 */
|
||||
/* ============================================ */
|
||||
|
||||
.message-meta .stage-tag {
|
||||
display: inline-block;
|
||||
background: linear-gradient(135deg, #6366F1, #8B5CF6);
|
||||
color: white;
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
margin-left: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.message-meta .timestamp {
|
||||
color: #9CA3AF;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
/* ============================================ */
|
||||
/* 动作卡片 */
|
||||
/* ============================================ */
|
||||
|
||||
.action-cards {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.action-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 14px 18px;
|
||||
background: linear-gradient(135deg, #F8FAFC, #F1F5F9);
|
||||
border: 1px solid #E2E8F0;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
min-width: 200px;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.action-card:hover {
|
||||
background: linear-gradient(135deg, #EEF2FF, #E0E7FF);
|
||||
border-color: #6366F1;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.15);
|
||||
}
|
||||
|
||||
.action-card-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1F2937;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.action-card-desc {
|
||||
font-size: 12px;
|
||||
color: #6B7280;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.action-card-icon {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
right: 14px;
|
||||
color: #9CA3AF;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.action-card:hover .action-card-icon {
|
||||
color: #6366F1;
|
||||
}
|
||||
|
||||
/* ============================================ */
|
||||
/* 加载状态 */
|
||||
/* ============================================ */
|
||||
|
||||
.chat-bubble.loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 流式输出光标 */
|
||||
.streaming-cursor {
|
||||
display: inline-block;
|
||||
animation: blink 0.8s step-end infinite;
|
||||
color: #6366F1;
|
||||
margin-left: 2px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
|
||||
.assistant-bubble {
|
||||
@@ -1190,6 +1439,201 @@
|
||||
background: #94A3B8;
|
||||
}
|
||||
|
||||
/* ============================================ */
|
||||
/* 阶段编辑弹窗 */
|
||||
/* ============================================ */
|
||||
.stage-edit-modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
.stage-edit-modal {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
width: 480px;
|
||||
max-width: 90%;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.stage-edit-modal .modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #E5E7EB;
|
||||
background: #F9FAFB;
|
||||
}
|
||||
|
||||
.stage-edit-modal .modal-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.stage-edit-modal .close-btn {
|
||||
padding: 4px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
color: #6B7280;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.stage-edit-modal .close-btn:hover {
|
||||
background: #E5E7EB;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.stage-edit-modal .modal-body {
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
max-height: 50vh;
|
||||
}
|
||||
|
||||
.stage-edit-modal .form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.stage-edit-modal .form-group label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.stage-edit-modal .form-group input,
|
||||
.stage-edit-modal .form-group textarea {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #D1D5DB;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
color: #111827;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.stage-edit-modal .form-group input:focus,
|
||||
.stage-edit-modal .form-group textarea:focus {
|
||||
outline: none;
|
||||
border-color: #3B82F6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.stage-edit-modal .form-group textarea {
|
||||
resize: vertical;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.stage-edit-modal .form-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.stage-edit-modal .form-group.half {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stage-edit-modal .modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid #E5E7EB;
|
||||
background: #F9FAFB;
|
||||
}
|
||||
|
||||
.stage-edit-modal .cancel-btn {
|
||||
padding: 8px 16px;
|
||||
background: white;
|
||||
border: 1px solid #D1D5DB;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
color: #374151;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.stage-edit-modal .cancel-btn:hover {
|
||||
background: #F3F4F6;
|
||||
}
|
||||
|
||||
.stage-edit-modal .save-btn {
|
||||
padding: 8px 16px;
|
||||
background: #3B82F6;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.stage-edit-modal .save-btn:hover:not(:disabled) {
|
||||
background: #2563EB;
|
||||
}
|
||||
|
||||
.stage-edit-modal .save-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 编辑按钮样式 */
|
||||
.stage-header .stage-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.stage-header .edit-btn {
|
||||
padding: 4px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
color: #9CA3AF;
|
||||
transition: all 0.2s;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.stage-card:hover .edit-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.stage-header .edit-btn:hover {
|
||||
background: #E5E7EB;
|
||||
color: #3B82F6;
|
||||
}
|
||||
|
||||
/* 流式输出光标 */
|
||||
.streaming-cursor {
|
||||
display: inline-block;
|
||||
animation: blink 1s step-end infinite;
|
||||
color: #3B82F6;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 50% { opacity: 1; }
|
||||
51%, 100% { opacity: 0; }
|
||||
}
|
||||
|
||||
/* ============================================ */
|
||||
/* 响应式 */
|
||||
/* ============================================ */
|
||||
|
||||
Reference in New Issue
Block a user