File size: 6,607 Bytes
5710dd0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | /**
* App shell — selection state and session resume. There is no live event
* push anymore: the v2 socket (`/api/v2/ws`) that fed the core/session/agent
* event streams was removed server-side, so Service panels and the pending
* interactions card fetch on demand and the sidebar polls.
* Layout: header / icon rail / view. The `chat` view is a strip of the
* left sidebar (a workspace → session tree), the chat column, and the
* right dock (`RightPanel`) merging the transcript audit, the agent
* inspector, and the session pane under Audit / Agent / State / Session
* tabs;
* the `models` view is the full-width model catalog; the `services` view is
* the full-width app-scope Service reflection (`AppServicesView`); the
* `workspace` view is the workspace-scope counterpart
* (`WorkspaceServicesView`, with a workspace picker on top); the
* `bash` view is the full-width `IBashParserService` playground
* (`BashParserView`); the `di` view is the engine's Service × Effect × DI
* debug surface (`DiInspectionView`); the `search` view is the full-width
* global message search (`SearchView`) whose hits navigate back into the
* chat timeline.
*/
import { ISessionManager } from '@moonshot-ai/agent-core-v2/app/sessionManager/sessionManager';
import { useEffect, useState } from 'react';
import type { AuditTrail } from './audit/trail';
import { AppServicesView } from './components/AppServicesView';
import { BashParserView } from './components/BashParserView';
import { ChatView, type ChatJump } from './components/ChatView';
import { DiInspectionView } from './components/DiInspectionView';
import { FsSuggestView } from './components/FsSuggestView';
import { ModelCatalogView } from './components/ModelCatalogView';
import { NavRail, type AppView } from './components/NavRail';
import { RightPanel } from './components/RightPanel';
import { SearchView } from './components/SearchView';
import { ServerSwitcher } from './components/ServerSwitcher';
import { Sidebar } from './components/Sidebar';
import { WorkspaceServicesView } from './components/WorkspaceServicesView';
import { useConnection } from './connection';
import type { SearchHit } from './search/api';
import { errorMessage } from './ui';
export function App() {
const { klient, baseUrl, disconnect } = useConnection();
const [sessionId, setSessionId] = useState<string | null>(null);
const [agentId, setAgentId] = useState('main');
const [view, setView] = useState<AppView>('chat');
const [ready, setReady] = useState(false);
const [resumeError, setResumeError] = useState<unknown>(null);
/** Audit trail of the chat view's transcript channel, rendered in the right dock. */
const [trail, setTrail] = useState<AuditTrail | null>(null);
/** Pending chat navigation requested from another view (search result click). */
const [jump, setJump] = useState<ChatJump | null>(null);
// Resume (materialize) the session on the server when it is selected, so
// session / agent scoped Services become reachable. Session lifecycle lives
// on the workspace handler (Workspace scope): the index yields the
// session's workspaceId, then the handler resumes it.
useEffect(() => {
if (sessionId === null) return;
let cancelled = false;
setReady(false);
setResumeError(null);
klient
.core(ISessionManager)
.resume(sessionId)
.then((session) => {
if (session === undefined) throw new Error(`session ${sessionId} does not exist`);
})
.then(() => {
if (!cancelled) setReady(true);
})
.catch((error: unknown) => {
if (!cancelled) setResumeError(error);
});
return () => {
cancelled = true;
};
}, [klient, sessionId]);
// Switching servers invalidates every session/agent selection: sessions
// belong to the server they were listed from.
useEffect(() => {
setSessionId(null);
setAgentId('main');
setJump(null);
}, [baseUrl]);
// A search hit opens the chat view at its session / agent / turn / step.
// Title hits belong to the session (agent '') and carry no turn: switch
// over without a scroll target.
const openSearchHit = (hit: SearchHit): void => {
setSessionId(hit.sessionId);
setAgentId(hit.agentId === '' ? 'main' : hit.agentId);
setView('chat');
setJump({
turnId: hit.turn === undefined ? undefined : `t${hit.turn}`,
stepId: hit.stepId,
nonce: Date.now(),
});
};
return (
<div className="flex h-screen flex-col">
<header className="flex items-center gap-3 border-b border-neutral-800 px-4 py-1.5">
<span className="text-[12px] font-bold tracking-widest text-neutral-200">KIMI INSPECT</span>
<ServerSwitcher />
<div className="flex-1" />
<button
className="text-[11px] text-neutral-500 hover:text-neutral-300"
onClick={disconnect}
>
Disconnect
</button>
</header>
<div className="flex min-h-0 flex-1">
<NavRail view={view} onChange={setView} />
{view === 'services' ? (
<AppServicesView />
) : view === 'workspace' ? (
<WorkspaceServicesView />
) : view === 'suggest' ? (
<FsSuggestView />
) : view === 'bash' ? (
<BashParserView />
) : view === 'di' ? (
<DiInspectionView />
) : view === 'models' ? (
<ModelCatalogView
onOpenSession={(id) => {
setSessionId(id);
setView('chat');
}}
/>
) : view === 'search' ? (
<SearchView onOpenResult={openSearchHit} />
) : (
<>
<Sidebar activeSessionId={sessionId} onSelectSession={setSessionId} />
{resumeError !== null ? (
<div className="flex flex-1 items-center justify-center p-6 text-center text-[12px] text-red-400">
Failed to open session: {errorMessage(resumeError)}
</div>
) : (
<ChatView
sessionId={sessionId}
agentId={agentId}
ready={ready}
onTrailChange={setTrail}
jump={jump}
onJumpHandled={() => setJump(null)}
onOpenSearchHit={openSearchHit}
/>
)}
<RightPanel
sessionId={sessionId}
agentId={agentId}
onAgentChange={setAgentId}
ready={ready}
trail={trail}
/>
</>
)}
</div>
</div>
);
}
|