feat(aia): Implement Protocol Agent MVP with reusable Agent framework
Sprint 1-3 Completed (Backend + Frontend): Backend (Sprint 1-2): - Implement 5-layer Agent framework (Query->Planner->Executor->Tools->Reflection) - Create agent_schema with 6 tables (agent_definitions, stages, prompts, sessions, traces, reflexion_rules) - Create protocol_schema with 2 tables (protocol_contexts, protocol_generations) - Implement Protocol Agent core services (Orchestrator, ContextService, PromptBuilder) - Integrate LLM service adapter (DeepSeek/Qwen/GPT-5/Claude) - 6 API endpoints with full authentication - 10/10 API tests passed Frontend (Sprint 3): - Add Protocol Agent entry in AgentHub (indigo theme card) - Implement ProtocolAgentPage with 3-column layout - Collapsible sidebar (Gemini style, 48px <-> 280px) - StatePanel with 5 stage cards (scientific_question, pico, study_design, sample_size, endpoints) - ChatArea with sync button and action cards integration - 100% prototype design restoration (608 lines CSS) - Detailed endpoints structure: baseline, exposure, outcomes, confounders Features: - 5-stage dialogue flow for research protocol design - Conversation-driven interaction with sync-to-protocol button - Real-time context state management - One-click protocol generation button (UI ready, backend pending) Database: - agent_schema: 6 tables for reusable Agent framework - protocol_schema: 2 tables for Protocol Agent - Seed data: 1 agent + 5 stages + 9 prompts + 4 reflexion rules Code Stats: - Backend: 13 files, 4338 lines - Frontend: 14 files, 2071 lines - Total: 27 files, 6409 lines Status: MVP core functionality completed, pending frontend-backend integration testing Next: Sprint 4 - One-click protocol generation + Word export
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Protocol Agent Hooks Export
|
||||
*/
|
||||
|
||||
export { useProtocolContext } from './useProtocolContext';
|
||||
export { useProtocolConversations } from './useProtocolConversations';
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* useProtocolContext Hook
|
||||
* 管理Protocol Agent的上下文状态
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import type { ProtocolContext } from '../types';
|
||||
import { getAccessToken } from '../../../../framework/auth/api';
|
||||
|
||||
const API_BASE = '/api/v1/aia/protocol-agent';
|
||||
|
||||
export function useProtocolContext(conversationId?: string) {
|
||||
const [context, setContext] = useState<ProtocolContext | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
/**
|
||||
* 获取上下文状态
|
||||
*/
|
||||
const fetchContext = useCallback(async () => {
|
||||
if (!conversationId) {
|
||||
setContext(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const token = getAccessToken();
|
||||
const response = await fetch(`${API_BASE}/context/${conversationId}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.status === 404) {
|
||||
// 上下文不存在,返回空
|
||||
setContext(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch context: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
setContext(result.data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[useProtocolContext] fetchContext error:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to load context');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [conversationId]);
|
||||
|
||||
/**
|
||||
* 刷新上下文
|
||||
*/
|
||||
const refreshContext = useCallback(() => {
|
||||
fetchContext();
|
||||
}, [fetchContext]);
|
||||
|
||||
// 初次加载和conversationId变化时获取上下文
|
||||
useEffect(() => {
|
||||
fetchContext();
|
||||
}, [fetchContext]);
|
||||
|
||||
return {
|
||||
context,
|
||||
loading,
|
||||
error,
|
||||
refreshContext,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* useProtocolConversations Hook
|
||||
* 管理Protocol Agent的会话列表
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import type { ProtocolConversation } from '../types';
|
||||
import { getAccessToken } from '../../../../framework/auth/api';
|
||||
|
||||
const API_BASE = '/api/v1/aia';
|
||||
|
||||
export function useProtocolConversations(initialConversationId?: string) {
|
||||
const [conversations, setConversations] = useState<ProtocolConversation[]>([]);
|
||||
const [currentConversation, setCurrentConversation] = useState<ProtocolConversation | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
/**
|
||||
* 获取会话列表
|
||||
*/
|
||||
const fetchConversations = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const token = getAccessToken();
|
||||
const response = await fetch(`${API_BASE}/conversations`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch conversations');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
const allConversations = result.data || result;
|
||||
|
||||
// 过滤出Protocol Agent的对话(agentId为PROTOCOL_AGENT)
|
||||
const protocolConversations = Array.isArray(allConversations)
|
||||
? allConversations.filter((conv: any) =>
|
||||
conv.agentId === 'PROTOCOL_AGENT' || conv.agent_id === 'PROTOCOL_AGENT'
|
||||
)
|
||||
: [];
|
||||
|
||||
setConversations(protocolConversations);
|
||||
|
||||
// 如果有initialConversationId,设置为当前对话
|
||||
if (initialConversationId) {
|
||||
const current = protocolConversations.find((c: any) => c.id === initialConversationId);
|
||||
if (current) {
|
||||
setCurrentConversation(current);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[useProtocolConversations] fetchConversations error:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [initialConversationId]);
|
||||
|
||||
/**
|
||||
* 创建新对话
|
||||
*/
|
||||
const createConversation = useCallback(async (): Promise<ProtocolConversation | null> => {
|
||||
try {
|
||||
const token = getAccessToken();
|
||||
const response = await fetch(`${API_BASE}/conversations`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
agentId: 'PROTOCOL_AGENT',
|
||||
title: `研究方案-${new Date().toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })}`,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to create conversation');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
const newConv = result.data || result;
|
||||
|
||||
setConversations(prev => [newConv, ...prev]);
|
||||
setCurrentConversation(newConv);
|
||||
|
||||
return newConv;
|
||||
} catch (err) {
|
||||
console.error('[useProtocolConversations] createConversation error:', err);
|
||||
return null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* 选择对话
|
||||
*/
|
||||
const selectConversation = useCallback((id: string) => {
|
||||
const conv = conversations.find(c => c.id === id);
|
||||
if (conv) {
|
||||
setCurrentConversation(conv);
|
||||
}
|
||||
}, [conversations]);
|
||||
|
||||
/**
|
||||
* 删除对话
|
||||
*/
|
||||
const deleteConversation = useCallback(async (id: string) => {
|
||||
try {
|
||||
const token = getAccessToken();
|
||||
await fetch(`${API_BASE}/conversations/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
setConversations(prev => prev.filter(c => c.id !== id));
|
||||
|
||||
if (currentConversation?.id === id) {
|
||||
setCurrentConversation(null);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[useProtocolConversations] deleteConversation error:', err);
|
||||
}
|
||||
}, [currentConversation]);
|
||||
|
||||
// 初次加载
|
||||
useEffect(() => {
|
||||
fetchConversations();
|
||||
}, [fetchConversations]);
|
||||
|
||||
return {
|
||||
conversations,
|
||||
currentConversation,
|
||||
loading,
|
||||
createConversation,
|
||||
selectConversation,
|
||||
deleteConversation,
|
||||
refreshConversations: fetchConversations,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user