gaza/src/App.tsx
Ubuntu d7e49d54f3 修复桌面交互并统一标签视觉
Co-authored-by: multica-agent <github@multica.ai>
2026-08-03 20:21:06 +00:00

36 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useEffect, useReducer, useState, type ReactNode } from "react";
import { blankConnection, ConnectionCenter, ConnectionEditor, MemoryMockConnectionAdapter } from "./features/connections";
import { ArtifactsPage, AuditPage, EventsPage, OverviewPage, SessionDetailPage, TasksPage } from "./features/workbench";
import { createSessionTableViewState, SessionQueryIntent, SessionTable } from "./SessionTable";
import { demoSessions } from "./protocol/fixtures";
import { initializeSessionTableStates, updateSessionTableState } from "./sessionTableState";
import { toSessionRow } from "./sessionPresentation";
import { WorkspaceLayout } from "./shell";
import { Dialog, Toast } from "./ui";
import { createInitialWorkspaceState, selectActiveTab, selectActiveWorkspace, workspaceReducer } from "./workspace";
const fixtureNow = new Date("2026-08-03T19:12:02Z");
const sessions = demoSessions.map((session, index) => ({ ...toSessionRow(session, fixtureNow), capabilities: index === 0 ? ["session.processes", "session.files"] : [] }));
const connectionAdapter = new MemoryMockConnectionAdapter();
export function App() {
useEffect(() => { const disableNativeMenu = (event: globalThis.MouseEvent) => event.preventDefault(); document.addEventListener("contextmenu", disableNativeMenu); return () => document.removeEventListener("contextmenu", disableNativeMenu); }, []);
const [workspace, dispatch] = useReducer(workspaceReducer, undefined, createInitialWorkspaceState);
const [tableStates, setTableStates] = useState(() => initializeSessionTableStates(workspace.serverOrder));
const [lastQueryIntent, setLastQueryIntent] = useState<Record<string, SessionQueryIntent | undefined>>({});
const [addOpen, setAddOpen] = useState(false);
const [connectionAddresses, setConnectionAddresses] = useState<string[]>([]);
const [notice, setNotice] = useState("");
const server = selectActiveWorkspace(workspace); const activeTab = selectActiveTab(server);
const tableState = { ...(tableStates[server.id] ?? createSessionTableViewState()), filter: server.filter };
const navigate = (page: string) => {
const title = page === "Sessions" ? "Sessions" : page;
dispatch({ type: "navigate", serverId: server.id, project: server.activeProject, navigation: page });
dispatch({ type: "openTab", serverId: server.id, tab: { id: `${server.activeProject.toLowerCase()}-${page.toLowerCase().replaceAll(" ", "-")}`, title, context: { serverId: server.id, project: server.activeProject, session: "Project" } } });
};
const sessionsPage = <><div className="content-heading"><div><h2>Sessions</h2><p>{activeTab.context.project} 248 Session · 4 </p></div></div><p className="demo-boundary" role="status"> · adapter · Teamserver</p><SessionTable rows={sessions} state={tableState} capabilities={{ filtering: true, sortableColumns: ["status", "id", "seen"], columnVisibility: true, pagination: true }} page={{ start: 1, end: 50, total: 248, hasPrevious: false, hasNext: true }} onStateChange={(state) => { if (state.filter !== server.filter) dispatch({ type: "setFilter", serverId: server.id, filter: state.filter }); setTableStates((current) => updateSessionTableState(current, server.id, state)); }} onOpenSession={(session) => dispatch({ type: "openTab", serverId: server.id, tab: { id: `${server.id}-${activeTab.context.project}-${session.id}-overview`, title: "Session Overview", context: { serverId: server.id, project: activeTab.context.project, session: session.id } } })} onOpenOperation={(session, operation) => dispatch({ type: "openTab", serverId: server.id, tab: { id: `${server.id}-${activeTab.context.project}-${session.id}-${operation}`, title: operation === "processes" ? "进程" : operation === "files" ? "文件" : "打开操作", context: { serverId: server.id, project: activeTab.context.project, session: session.id }, dirty: operation === "operation" } })} onQueryIntent={(intent) => setLastQueryIntent((current) => ({ ...current, [server.id]: intent }))} /><output className="query-intent" aria-live="polite">{lastQueryIntent[server.id] ? `查询意图:${lastQueryIntent[server.id]?.type}` : "等待服务端查询意图"}</output></>;
const pages: Record<string, ReactNode> = { Overview: <OverviewPage />, Sessions: sessionsPage, "Session Overview": <SessionDetailPage sessionId={activeTab.context.session} />, Tasks: <TasksPage />, "Task Output": <TasksPage />, Events: <EventsPage />, Artifacts: <ArtifactsPage />, Audit: <AuditPage />, "连接中心": <ConnectionCenter adapter={connectionAdapter} developmentMode={import.meta.env.DEV} /> };
const operationBoundary = ["进程", "文件", "打开操作"].includes(activeTab.title) ? <section className="operation-boundary"><h2>{activeTab.title}</h2><p>{activeTab.context.serverId} / {activeTab.context.project} / {activeTab.context.session}</p><p className="demo-boundary">Mock Teamserver adapter </p></section> : undefined;
return <><WorkspaceLayout servers={workspace.serverOrder.map((id) => workspace.servers[id])} server={server} activeTab={activeTab} activePage={server.navigation} onNavigate={navigate} onSwitchServer={(serverId) => dispatch({ type: "switchServer", serverId })} onAddServer={() => setAddOpen(true)} onActivateTab={(tabId) => dispatch({ type: "activateTab", serverId: server.id, tabId })} onCloseTab={(tabId) => dispatch({ type: "closeTab", serverId: server.id, tabId })} onTogglePin={(tabId) => dispatch({ type: "togglePinTab", serverId: server.id, tabId })}><article className="content" aria-label={`${activeTab.title} 页面`}>{pages[activeTab.title] ?? operationBoundary ?? <OverviewPage />}</article></WorkspaceLayout><Dialog open={addOpen} onOpenChange={setAddOpen} title="添加 Teamserver"><ConnectionEditor initial={blankConnection()} existingAddresses={connectionAddresses} developmentMode={import.meta.env.DEV} onCancel={() => setAddOpen(false)} onSave={async (config) => { await connectionAdapter.save(config); setConnectionAddresses((current) => [...current, config.address.trim()]); const tab = { id: `${config.id}-connections`, title: "连接中心", context: { serverId: config.id, project: "尚未选择 Project", session: "Project" } }; dispatch({ type: "addServer", server: { id: config.id, name: config.name, shortName: config.name.trim().slice(0, 2).toUpperCase(), connection: "DEGRADED", unread: 0, activeProject: "尚未选择 Project", navigation: "连接中心", filter: "", activeTabId: tab.id, tabs: [tab] } }); setAddOpen(false); setNotice("连接配置已创建,尚未建立真实连接。"); }} /></Dialog>{notice && <Toast tone="info" onDismiss={() => setNotice("")}>{notice}</Toast>}</>;
}