File size: 4,081 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
/**
 * Right dock — the single right-hand column of the chat view. Merges what
 * used to be three separate columns (the transcript audit panel docked
 * inside the chat view, the agent inspector on the far right, and the
 * session pane next to the session list) into one tabbed column: `Audit`
 * replays how the visible transcript store was built, entry by entry;
 * `Agent` hosts the agent switcher, the Plan lookup card, and the agent
 * Service panels; `State` reads the active agent's registered plain-data
 * state through `IAgentStateService.snapshot()`; `Session` embeds
 * `SessionPane` (session Services / State tabs). Tabs switch with `hidden`
 * instead of unmounting, so panel-local state (the audit timeline position,
 * Plan lookup input/results, expanded Service panels, the state tree's open
 * rows) survives tab switches.
 */

import { IAgentStateService } from '@moonshot-ai/agent-core-v2/agent/state/agentState';
import { useState } from 'react';

import type { AuditTrail } from '../audit/trail';
import { useConnection } from '../connection';
import { Badge } from '../ui';
import { AuditPanel } from './audit/AuditPanel';
import { Inspector } from './Inspector';
import { SessionPane } from './SessionPane';
import { StateCard } from './StateCard';

type Tab = 'audit' | 'agent' | 'state' | 'session';

export function RightPanel({
  sessionId,
  agentId,
  onAgentChange,
  ready,
  trail,
}: {
  sessionId: string | null;
  agentId: string;
  onAgentChange: (agentId: string) => void;
  ready: boolean;
  /** The chat view's audit trail; null until its transcript channel exists. */
  trail: AuditTrail | null;
}) {
  const { klient } = useConnection();
  const [tab, setTab] = useState<Tab>('audit');

  return (
    <div className="flex h-full w-[440px] shrink-0 flex-col border-l border-neutral-800 bg-neutral-900/30">
      <div className="flex border-b border-neutral-800 text-[11px]">
        {(['audit', 'agent', 'state', 'session'] as const).map((t) => (
          <button
            key={t}
            className={`flex-1 px-2 py-2 font-medium uppercase tracking-wider ${
              tab === t ? 'bg-neutral-800 text-sky-400' : 'text-neutral-500 hover:text-neutral-300'
            }`}
            onClick={() => setTab(t)}
          >
            {t === 'audit' ? 'Audit' : t === 'agent' ? 'Agent' : t === 'state' ? 'State' : 'Session'}
          </button>
        ))}
      </div>
      <div className={tab === 'audit' ? 'flex min-h-0 flex-1 flex-col' : 'hidden'}>
        {trail !== null ? (
          <AuditPanel trail={trail} />
        ) : (
          <div className="p-3 text-[12px] text-neutral-600">
            {sessionId === null
              ? 'No session selected.'
              : 'Loading transcript — the audit trail appears once the channel is up.'}
          </div>
        )}
      </div>
      <div className={tab === 'agent' ? 'flex min-h-0 flex-1 flex-col' : 'hidden'}>
        <Inspector
          sessionId={sessionId}
          agentId={agentId}
          onAgentChange={onAgentChange}
          ready={ready}
        />
      </div>
      <div className={tab === 'state' ? 'min-h-0 flex-1 overflow-y-auto' : 'hidden'}>
        <div className="p-3">
          {sessionId === null || !ready ? (
            <div className="text-[12px] text-neutral-600">
              {sessionId === null ? 'No session selected.' : 'Loading session…'}
            </div>
          ) : (
            <StateCard
              id={`${sessionId}/${agentId}`}
              queryKey={['agentState', sessionId, agentId]}
              title="Agent state"
              label="agentStateService"
              badge={<Badge tone="sky">{agentId}</Badge>}
              fetchSnapshot={() =>
                klient.session(sessionId).agent(agentId).service(IAgentStateService).snapshot()
              }
            />
          )}
        </div>
      </div>
      <div className={tab === 'session' ? 'flex min-h-0 flex-1 flex-col' : 'hidden'}>
        <SessionPane sessionId={sessionId} ready={ready} />
      </div>
    </div>
  );
}