代码修改
This commit is contained in:
1 parent
8f5af46ec2
commit
8f75e12871
168 files changed
+76575
No files matched your search
@@ -0,0 +1,349 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import { useSession } from '@/hooks/useSession';
|
||||
import { useTheme } from '../../contexts/ThemeContext';
|
||||
import ConfigPanel from './components/ConfigPanel';
|
||||
import Sidebar from './components/Sidebar';
|
||||
import Timeline from './components/Timeline';
|
||||
import ProgressSidebar from './components/ProgressSidebar';
|
||||
import ScenarioConfigPage from './components/ScenarioConfigPage';
|
||||
import './MopeChat.css';
|
||||
import { useChatContext } from '@/contexts/ChatContext';
|
||||
import { requestCrudService } from '@/service/request-axios-service';
|
||||
|
||||
type StartConfig = {
|
||||
task_context?: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
type SessionError = {
|
||||
agent_id?: string;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
type SessionHistoryPayload = {
|
||||
typeName: string;
|
||||
templateName: string;
|
||||
resultJson: Record<string, any>;
|
||||
};
|
||||
|
||||
const MOPE_HISTORY_API_BASE = '/api/mope/history';
|
||||
const backendUrls = import.meta.env.VITE_AIPI_MOPECHAT_URL || '';
|
||||
const zncmUrl = import.meta.env.VITE_API_BASE_URL || '';
|
||||
const service = requestCrudService;
|
||||
/**
|
||||
* 保存模型完整生成结果。
|
||||
*
|
||||
* 接口暂未提供,先把调用方法和请求体结构固定下来:
|
||||
* - typeName:类型名字,目前使用用户输入的任务背景。
|
||||
* - templateName:用户选择的生成模板名称。
|
||||
* - resultJson:完整结果 JSON,包含 timeline、agentStates、reportSchema、finalReport 等恢复页面所需数据。
|
||||
*/
|
||||
async function saveMopeHistory(payload: SessionHistoryPayload) {
|
||||
console.log('保存了吗',payload )
|
||||
const response = await fetch(MOPE_HISTORY_API_BASE, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`保存 MOPE 历史失败:HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json().catch(() => null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户最近一次 MOPE 生成历史。
|
||||
*
|
||||
* 接口暂未提供,先约定返回值就是保存时的 resultJson。
|
||||
* 如果接口没有历史数据,应返回 null / 空对象 / 404,页面会保持当前未生成状态。
|
||||
*/
|
||||
async function fetchMopeHistory(): Promise<Record<string, any> | null> {
|
||||
|
||||
const response = await fetch(MOPE_HISTORY_API_BASE, { method: 'GET' });
|
||||
|
||||
if (response.status === 404 || response.status === 204) return null;
|
||||
if (!response.ok) {
|
||||
throw new Error(`获取 MOPE 历史失败:HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data?.resultJson || data || null;
|
||||
}
|
||||
|
||||
export default function MopeChat() {
|
||||
const session = useSession() as any;
|
||||
const { theme } = useTheme() as { theme?: string };
|
||||
const { startConfig } = useChatContext();
|
||||
const isDark = theme === 'dark';
|
||||
const [taskContext, setTaskContext] = useState<string>('');
|
||||
const [idlePage, setIdlePage] = useState<string>('home');
|
||||
const [isLeftSidebarCollapsed, setIsLeftSidebarCollapsed] = useState(false);
|
||||
const [isRightSidebarCollapsed, setIsRightSidebarCollapsed] = useState(false);
|
||||
|
||||
const {
|
||||
status,
|
||||
phase,
|
||||
coordinatorMode,
|
||||
currentRound,
|
||||
divergeRounds,
|
||||
reportSchema,
|
||||
agents,
|
||||
agentStates,
|
||||
timeline,
|
||||
sharedMemory,
|
||||
finalReportPartial,
|
||||
currentFinalField,
|
||||
pendingReview,
|
||||
reviewSubmitting,
|
||||
reviewRevising,
|
||||
errors,
|
||||
startSession,
|
||||
stopSession,
|
||||
approveCoordinator,
|
||||
reviseCoordinator,
|
||||
hydrateFromSnapshot,
|
||||
reset,
|
||||
} = session;
|
||||
|
||||
const historyLoadedRef = useRef(false);
|
||||
const savedHistoryKeyRef = useRef<string | null>(null);
|
||||
const currentConfigRef = useRef<StartConfig | null>(null);
|
||||
|
||||
const buildHistorySnapshot = useCallback(() => ({
|
||||
status,
|
||||
phase,
|
||||
coordinatorMode,
|
||||
currentRound,
|
||||
divergeRounds,
|
||||
reportSchema,
|
||||
agents,
|
||||
agentStates,
|
||||
timeline,
|
||||
sharedMemory,
|
||||
finalReportPartial,
|
||||
currentFinalField,
|
||||
config: currentConfigRef.current,
|
||||
}), [
|
||||
status,
|
||||
phase,
|
||||
coordinatorMode,
|
||||
currentRound,
|
||||
divergeRounds,
|
||||
reportSchema,
|
||||
agents,
|
||||
agentStates,
|
||||
timeline,
|
||||
sharedMemory,
|
||||
finalReportPartial,
|
||||
currentFinalField,
|
||||
]);
|
||||
|
||||
const handleStart = (config: StartConfig, backendUrl: string) => {
|
||||
currentConfigRef.current = config;
|
||||
savedHistoryKeyRef.current = null;
|
||||
setTaskContext(config.task_context || '');
|
||||
console.log('config', config)
|
||||
startSession(config, backendUrl);
|
||||
};
|
||||
|
||||
const handleStop = () => {
|
||||
console.log('state', )
|
||||
stopSession();
|
||||
const snapshot = buildHistorySnapshot();
|
||||
const templateName = currentConfigRef.current?.template_name || '未命名模板';
|
||||
const typeName = taskContext || currentConfigRef.current?.task_context || '未命名任务';
|
||||
const saveKey = `${typeName}::${templateName}::${timeline.length}`;
|
||||
if (savedHistoryKeyRef.current === saveKey) return;
|
||||
savedHistoryKeyRef.current = saveKey;
|
||||
|
||||
void saveMopeHistory({
|
||||
typeName,
|
||||
templateName,
|
||||
resultJson: {
|
||||
...snapshot,
|
||||
typeName,
|
||||
templateName,
|
||||
savedAt: new Date().toISOString(),
|
||||
},
|
||||
}).catch((error) => {
|
||||
console.warn('MOPE 历史记录保存失败:', error);
|
||||
savedHistoryKeyRef.current = null;
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
reset();
|
||||
currentConfigRef.current = null;
|
||||
savedHistoryKeyRef.current = null;
|
||||
setTaskContext('');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (startConfig) {
|
||||
handleStart({
|
||||
...startConfig,
|
||||
coordinator_mode: 'human_review',
|
||||
}, backendUrls);
|
||||
}
|
||||
console.log('startConfig', startConfig)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (historyLoadedRef.current) return;
|
||||
historyLoadedRef.current = true;
|
||||
|
||||
void fetchMopeHistory()
|
||||
.then((snapshot) => {
|
||||
if (!snapshot || !Array.isArray(snapshot.timeline) || snapshot.timeline.length === 0) return;
|
||||
currentConfigRef.current = snapshot.config || null;
|
||||
setTaskContext(snapshot.config?.task_context || snapshot.typeName || '');
|
||||
hydrateFromSnapshot(snapshot);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('MOPE 历史记录获取失败,保持默认空状态:', error);
|
||||
});
|
||||
}, [hydrateFromSnapshot]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('status', status)
|
||||
if (status !== 'done' || !timeline?.length) return;
|
||||
const snapshot = buildHistorySnapshot();
|
||||
const templateName = currentConfigRef.current?.template_name || '未命名模板';
|
||||
const typeName = taskContext || currentConfigRef.current?.task_context || '未命名任务';
|
||||
const saveKey = `${typeName}::${templateName}::${timeline.length}`;
|
||||
if (savedHistoryKeyRef.current === saveKey) return;
|
||||
savedHistoryKeyRef.current = saveKey;
|
||||
|
||||
void saveMopeHistory({
|
||||
typeName,
|
||||
templateName,
|
||||
resultJson: {
|
||||
...snapshot,
|
||||
typeName,
|
||||
templateName,
|
||||
savedAt: new Date().toISOString(),
|
||||
},
|
||||
}).catch((error) => {
|
||||
console.warn('MOPE 历史记录保存失败:', error);
|
||||
savedHistoryKeyRef.current = null;
|
||||
});
|
||||
}, [status, timeline, taskContext, buildHistorySnapshot]);
|
||||
|
||||
if (status === 'idle') {
|
||||
if (idlePage === 'scenario_config') {
|
||||
return (
|
||||
<div className={`mope-chat app-idle app-idle-scroll ${isDark ? 'mope-chat-dark' : 'mope-chat-light'}`}>
|
||||
<ScenarioConfigPage onBack={() => setIdlePage('home')} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`mope-chat app-idle ${isDark ? 'mope-chat-dark' : 'mope-chat-light'}`}>
|
||||
<ConfigPanel
|
||||
onStart={handleStart}
|
||||
isRunning={false}
|
||||
onStop={handleStop}
|
||||
onOpenScenarioConfig={() => setIdlePage('scenario_config')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`mope-chat app-running ${isDark ? 'mope-chat-dark' : 'mope-chat-light'}`}>
|
||||
<header className="app-header">
|
||||
<span className="app-header-logo">MOPE</span>
|
||||
<span className="app-header-task">
|
||||
{taskContext ? `"${taskContext.slice(0, 80)}${taskContext.length > 80 ? '…' : ''}"` : '分析中…'}
|
||||
</span>
|
||||
<span className="badge badge-default">
|
||||
{coordinatorMode === 'human_review' ? '人工确认' : '全自动'}
|
||||
</span>
|
||||
{status === 'waiting_human' && (
|
||||
<span className="badge badge-diverging">等待人工确认</span>
|
||||
)}
|
||||
<div className="app-header-actions">
|
||||
{(status === 'running' || status === 'waiting_human') && (
|
||||
<button className="btn-danger" onClick={handleStop}>
|
||||
停止
|
||||
</button>
|
||||
)}
|
||||
{(status === 'done' || status === 'error') && (
|
||||
<button className="btn-secondary" onClick={handleReset}>
|
||||
重新开始
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="app-body">
|
||||
<aside className={`app-sidebar app-side-panel${isLeftSidebarCollapsed ? ' collapsed' : ''}`}>
|
||||
<button
|
||||
type="button"
|
||||
className="app-side-toggle app-side-toggle-left"
|
||||
onClick={() => setIsLeftSidebarCollapsed(v => !v)}
|
||||
aria-label={isLeftSidebarCollapsed ? '展开左侧栏' : '收起左侧栏'}
|
||||
title={isLeftSidebarCollapsed ? '展开左侧栏' : '收起左侧栏'}
|
||||
>
|
||||
{isLeftSidebarCollapsed ? <ChevronRight size={16} /> : <ChevronLeft size={16} />}
|
||||
</button>
|
||||
{!isLeftSidebarCollapsed && (
|
||||
<Sidebar
|
||||
agents={agents}
|
||||
agentStates={agentStates}
|
||||
phase={phase}
|
||||
currentRound={currentRound}
|
||||
divergeRounds={divergeRounds}
|
||||
/>
|
||||
)}
|
||||
</aside>
|
||||
|
||||
<main className="app-main">
|
||||
{errors.length > 0 && errors.map((e: SessionError, i: number) => (
|
||||
<div key={i} className="error-banner">
|
||||
{e.agent_id ? `[${e.agent_id}] ` : ''}{e.error}
|
||||
</div>
|
||||
))}
|
||||
<Timeline
|
||||
timeline={timeline}
|
||||
agentStates={agentStates}
|
||||
reportSchema={reportSchema}
|
||||
pendingReview={pendingReview}
|
||||
onApproveReview={approveCoordinator}
|
||||
onReviseReview={reviseCoordinator}
|
||||
reviewSubmitting={reviewSubmitting}
|
||||
reviewRevising={reviewRevising}
|
||||
/>
|
||||
</main>
|
||||
|
||||
<aside className={`app-progress-sidebar app-side-panel${isRightSidebarCollapsed ? ' collapsed' : ''}`}>
|
||||
<button
|
||||
type="button"
|
||||
className="app-side-toggle app-side-toggle-right"
|
||||
onClick={() => setIsRightSidebarCollapsed(v => !v)}
|
||||
aria-label={isRightSidebarCollapsed ? '展开右侧栏' : '收起右侧栏'}
|
||||
title={isRightSidebarCollapsed ? '展开右侧栏' : '收起右侧栏'}
|
||||
>
|
||||
{isRightSidebarCollapsed ? <ChevronLeft size={16} /> : <ChevronRight size={16} />}
|
||||
</button>
|
||||
{!isRightSidebarCollapsed && (
|
||||
<ProgressSidebar
|
||||
phase={phase}
|
||||
status={status}
|
||||
currentRound={currentRound}
|
||||
divergeRounds={divergeRounds}
|
||||
reportSchema={reportSchema}
|
||||
sharedMemory={sharedMemory}
|
||||
finalReportPartial={finalReportPartial}
|
||||
currentFinalField={currentFinalField}
|
||||
/>
|
||||
)}
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user