diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index e739ea1..2a27be5 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -1,94 +1,207 @@ -// App 主入口 - -import React, { useState, useCallback } from 'react'; -import { TaskBoard } from './pages/TaskBoard'; -import { Monitor } from './pages/Monitor'; -import { Config } from './pages/Config'; -import { Briefing } from './pages/Briefing'; -import { TaskModal } from './components/TaskModal'; -import { useProjects } from './hooks/useApi'; -import type { Task } from './types'; - -type Page = 'tasks' | 'monitor' | 'config' | 'briefing'; +import { useEffect, useState } from 'react'; +import { useStore, TAB_DEFS, startPolling, stopPolling, isEdict, isArchived } from './store'; +import { api } from './api'; +import EdictBoard from './components/EdictBoard'; +import MonitorPanel from './components/MonitorPanel'; +import OfficialPanel from './components/OfficialPanel'; +import ModelConfig from './components/ModelConfig'; +import SkillsConfig from './components/SkillsConfig'; +import SessionsPanel from './components/SessionsPanel'; +import MemorialPanel from './components/MemorialPanel'; +import TemplatePanel from './components/TemplatePanel'; +import TaskModal from './components/TaskModal'; +import Toaster from './components/Toaster'; +import CourtCeremony from './components/CourtCeremony'; +import CourtDiscussion from './components/CourtDiscussion'; +import UsagePanel from './components/UsagePanel'; +import SettingsPanel from './components/SettingsPanel'; export default function App() { - const [page, setPage] = useState('tasks'); - const [selectedProject, setSelectedProject] = useState(null); - const [selectedTask, setSelectedTask] = useState(null); - const { projects } = useProjects(); + const [showCreateModal, setShowCreateModal] = useState(false); + const activeTab = useStore((s) => s.activeTab); + const setActiveTab = useStore((s) => s.setActiveTab); + const liveStatus = useStore((s) => s.liveStatus); + const countdown = useStore((s) => s.countdown); + const loadAll = useStore((s) => s.loadAll); + const toast = useStore((s) => s.toast); - const handleSelectTask = useCallback((task: Task) => { - setSelectedTask(task); + useEffect(() => { + startPolling(); + return () => stopPolling(); }, []); + const tasks = liveStatus?.tasks || []; + const moziTasks = tasks.filter(isEdict); + const activeTasks = moziTasks.filter((t) => !isArchived(t)); + const syncOk = !!liveStatus; + + const tabBadge = (key: string): string => { + if (key === 'tasks') return String(activeTasks.length); + if (key === 'sessions') return String(tasks.filter((t) => !isEdict(t)).length); + if (key === 'archives') return String(moziTasks.filter((t) => ['completed', 'cancelled'].includes(t.state)).length); + if (key === 'monitor') { + const executing = tasks.filter((t) => isEdict(t) && t.state === 'executing').length; + return executing + '活跃'; + } + return ''; + }; + + // Tab → Component mapping (moziplus keys → Edict components) + const panels: Record = { + tasks: , + court: , + monitor: , + agents: , + models: , + skills: , + sessions: , + archives: , + templates: , + usage: , + settings: , + }; + return ( -
- {/* Sidebar */} - +
+ + {syncOk ? '✅ 同步正常' : syncOk === false ? '❌ 服务器未启动' : '⏳ 连接中…'} + + {activeTasks.length} 个任务 + + + ⟳ {countdown}s +
+
- {/* Main content */} -
- {page === 'tasks' && ( - - )} - {page === 'monitor' && } - {page === 'briefing' && } - {page === 'config' && } -
+ {/* Tabs */} +
+ {TAB_DEFS.map((t) => ( +
setActiveTab(t.key)} + > + {t.icon} {t.label} + {tabBadge(t.key) && {tabBadge(t.key)}} +
+ ))} +
- {/* Task detail modal */} - setSelectedTask(null)} /> + {/* Panel */} + {panels[activeTab] || null} + + {/* Create Task Modal */} + {showCreateModal && setShowCreateModal(false)} + onSubmit={async (data) => { + try { + const r = await api.createTask({...data, title: data.title || ''}); + const rAny = r as unknown as Record; + if (r.ok || r.taskId || rAny.id) { + const tid = r.taskId || String(rAny.id || ''); + toast(`📜 ${tid} 军令已创建`, 'ok'); + setShowCreateModal(false); + loadAll(); + } else { + toast(r.error || '创建失败', 'err'); + } + } catch (e: any) { + toast(e.message || '⚠️ 创建失败', 'err'); + } + }} + />} + + {/* Overlays */} + + + + + ); +} + +/* ── Create Task Modal ── */ +function CreateTaskModal({ onClose, onSubmit }: { + onClose: () => void; + onSubmit: (data: { requirement: string; project_root: string; project_type: string; title?: string }) => Promise; +}) { + const [requirement, setRequirement] = useState(''); + const [title, setTitle] = useState(''); + const [projectType, setProjectType] = useState('general'); + const [loading, setLoading] = useState(false); + + const handleSubmit = () => { + if (!requirement.trim()) return; + setLoading(true); + onSubmit({ + requirement: requirement.trim(), + title: title.trim() || undefined, + project_root: '/tmp', + project_type: projectType, + }).finally(() => setLoading(false)); + }; + + return ( +
e.target === e.currentTarget && onClose()}> +
+
+ 📜 新建军令 + +
+ + {/* Title */} +
+ + setTitle(e.target.value)} + placeholder="简要概括任务..." + style={{ width: '100%', padding: '8px 12px', borderRadius: 6, border: '1px solid var(--line)', background: 'var(--panel)', color: 'var(--fg)', fontSize: 13 }} /> +
+ + {/* Requirement */} +
+ +