@agimon-ai/doompi-web 0.0.1-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +29 -0
- package/README.md +196 -0
- package/dist/_virtual/_rolldown/runtime.cjs +1 -0
- package/dist/bin/serve.cjs +3 -0
- package/dist/bin/serve.cjs.map +1 -0
- package/dist/bin/serve.d.cts +1 -0
- package/dist/bin/serve.d.mts +1 -0
- package/dist/bin/serve.mjs +3 -0
- package/dist/bin/serve.mjs.map +1 -0
- package/dist/bundler.cjs +1 -0
- package/dist/bundler.d.cts +2 -0
- package/dist/bundler.d.mts +2 -0
- package/dist/bundler.mjs +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +7 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.mjs +1 -0
- package/dist/src/adapters/authRoutes.cjs +2 -0
- package/dist/src/adapters/authRoutes.cjs.map +1 -0
- package/dist/src/adapters/authRoutes.mjs +2 -0
- package/dist/src/adapters/authRoutes.mjs.map +1 -0
- package/dist/src/adapters/bundledServer.cjs +2 -0
- package/dist/src/adapters/bundledServer.cjs.map +1 -0
- package/dist/src/adapters/bundledServer.mjs +2 -0
- package/dist/src/adapters/bundledServer.mjs.map +1 -0
- package/dist/src/adapters/directoryListing.cjs +2 -0
- package/dist/src/adapters/directoryListing.cjs.map +1 -0
- package/dist/src/adapters/directoryListing.mjs +2 -0
- package/dist/src/adapters/directoryListing.mjs.map +1 -0
- package/dist/src/adapters/directorySearch.cjs +3 -0
- package/dist/src/adapters/directorySearch.cjs.map +1 -0
- package/dist/src/adapters/directorySearch.mjs +3 -0
- package/dist/src/adapters/directorySearch.mjs.map +1 -0
- package/dist/src/adapters/gitStatus.cjs +2 -0
- package/dist/src/adapters/gitStatus.cjs.map +1 -0
- package/dist/src/adapters/gitStatus.mjs +2 -0
- package/dist/src/adapters/gitStatus.mjs.map +1 -0
- package/dist/src/adapters/httpServer.cjs +2 -0
- package/dist/src/adapters/httpServer.cjs.map +1 -0
- package/dist/src/adapters/httpServer.d.cts +14 -0
- package/dist/src/adapters/httpServer.d.cts.map +1 -0
- package/dist/src/adapters/httpServer.d.mts +14 -0
- package/dist/src/adapters/httpServer.d.mts.map +1 -0
- package/dist/src/adapters/httpServer.mjs +2 -0
- package/dist/src/adapters/httpServer.mjs.map +1 -0
- package/dist/src/adapters/hubProbe.cjs +2 -0
- package/dist/src/adapters/hubProbe.cjs.map +1 -0
- package/dist/src/adapters/hubProbe.mjs +2 -0
- package/dist/src/adapters/hubProbe.mjs.map +1 -0
- package/dist/src/adapters/packageApiProxy.cjs +2 -0
- package/dist/src/adapters/packageApiProxy.cjs.map +1 -0
- package/dist/src/adapters/packageApiProxy.mjs +2 -0
- package/dist/src/adapters/packageApiProxy.mjs.map +1 -0
- package/dist/src/adapters/piHubService.cjs +2 -0
- package/dist/src/adapters/piHubService.cjs.map +1 -0
- package/dist/src/adapters/piHubService.mjs +2 -0
- package/dist/src/adapters/piHubService.mjs.map +1 -0
- package/dist/src/adapters/piWebSocketListener.cjs +2 -0
- package/dist/src/adapters/piWebSocketListener.cjs.map +1 -0
- package/dist/src/adapters/piWebSocketListener.mjs +2 -0
- package/dist/src/adapters/piWebSocketListener.mjs.map +1 -0
- package/dist/src/adapters/providerAuth.cjs +2 -0
- package/dist/src/adapters/providerAuth.cjs.map +1 -0
- package/dist/src/adapters/providerAuth.mjs +2 -0
- package/dist/src/adapters/providerAuth.mjs.map +1 -0
- package/dist/src/adapters/registryWatcher.cjs +2 -0
- package/dist/src/adapters/registryWatcher.cjs.map +1 -0
- package/dist/src/adapters/registryWatcher.mjs +2 -0
- package/dist/src/adapters/registryWatcher.mjs.map +1 -0
- package/dist/src/adapters/serverSpawner.cjs +2 -0
- package/dist/src/adapters/serverSpawner.cjs.map +1 -0
- package/dist/src/adapters/serverSpawner.mjs +2 -0
- package/dist/src/adapters/serverSpawner.mjs.map +1 -0
- package/dist/src/adapters/sessionFiles.cjs +3 -0
- package/dist/src/adapters/sessionFiles.cjs.map +1 -0
- package/dist/src/adapters/sessionFiles.mjs +3 -0
- package/dist/src/adapters/sessionFiles.mjs.map +1 -0
- package/dist/src/adapters/sessionHub.cjs +2 -0
- package/dist/src/adapters/sessionHub.cjs.map +1 -0
- package/dist/src/adapters/sessionHub.mjs +2 -0
- package/dist/src/adapters/sessionHub.mjs.map +1 -0
- package/dist/src/adapters/sessionSocketClient.cjs +2 -0
- package/dist/src/adapters/sessionSocketClient.cjs.map +1 -0
- package/dist/src/adapters/sessionSocketClient.mjs +2 -0
- package/dist/src/adapters/sessionSocketClient.mjs.map +1 -0
- package/dist/src/adapters/syncGuard.cjs +2 -0
- package/dist/src/adapters/syncGuard.cjs.map +1 -0
- package/dist/src/adapters/syncGuard.mjs +2 -0
- package/dist/src/adapters/syncGuard.mjs.map +1 -0
- package/dist/src/adapters/threadJournals.cjs +2 -0
- package/dist/src/adapters/threadJournals.cjs.map +1 -0
- package/dist/src/adapters/threadJournals.mjs +2 -0
- package/dist/src/adapters/threadJournals.mjs.map +1 -0
- package/dist/src/adapters/webBundler.cjs +2 -0
- package/dist/src/adapters/webBundler.cjs.map +1 -0
- package/dist/src/adapters/webBundler.d.cts +28 -0
- package/dist/src/adapters/webBundler.d.cts.map +1 -0
- package/dist/src/adapters/webBundler.d.mts +28 -0
- package/dist/src/adapters/webBundler.d.mts.map +1 -0
- package/dist/src/adapters/webBundler.mjs +2 -0
- package/dist/src/adapters/webBundler.mjs.map +1 -0
- package/dist/src/adapters/webHubPluginLoader.cjs +2 -0
- package/dist/src/adapters/webHubPluginLoader.cjs.map +1 -0
- package/dist/src/adapters/webHubPluginLoader.mjs +2 -0
- package/dist/src/adapters/webHubPluginLoader.mjs.map +1 -0
- package/dist/src/adapters/webHubPlugins.generated.cjs +2 -0
- package/dist/src/adapters/webHubPlugins.generated.cjs.map +1 -0
- package/dist/src/adapters/webHubPlugins.generated.mjs +2 -0
- package/dist/src/adapters/webHubPlugins.generated.mjs.map +1 -0
- package/dist/src/adapters/webPluginGenerate.cjs +4 -0
- package/dist/src/adapters/webPluginGenerate.cjs.map +1 -0
- package/dist/src/adapters/webPluginGenerate.mjs +4 -0
- package/dist/src/adapters/webPluginGenerate.mjs.map +1 -0
- package/dist/src/adapters/webPluginScan.cjs +2 -0
- package/dist/src/adapters/webPluginScan.cjs.map +1 -0
- package/dist/src/adapters/webPluginScan.mjs +2 -0
- package/dist/src/adapters/webPluginScan.mjs.map +1 -0
- package/dist/src/adapters/webPluginVite.cjs +2 -0
- package/dist/src/adapters/webPluginVite.cjs.map +1 -0
- package/dist/src/adapters/webPluginVite.mjs +2 -0
- package/dist/src/adapters/webPluginVite.mjs.map +1 -0
- package/dist/src/services/directoryMatch.cjs +2 -0
- package/dist/src/services/directoryMatch.cjs.map +1 -0
- package/dist/src/services/directoryMatch.mjs +2 -0
- package/dist/src/services/directoryMatch.mjs.map +1 -0
- package/dist/src/services/fileMatch.cjs +2 -0
- package/dist/src/services/fileMatch.cjs.map +1 -0
- package/dist/src/services/fileMatch.mjs +2 -0
- package/dist/src/services/fileMatch.mjs.map +1 -0
- package/dist/src/services/fileMedia.cjs +2 -0
- package/dist/src/services/fileMedia.cjs.map +1 -0
- package/dist/src/services/fileMedia.mjs +2 -0
- package/dist/src/services/fileMedia.mjs.map +1 -0
- package/dist/src/services/frameRing.cjs +2 -0
- package/dist/src/services/frameRing.cjs.map +1 -0
- package/dist/src/services/frameRing.mjs +2 -0
- package/dist/src/services/frameRing.mjs.map +1 -0
- package/dist/src/services/journalTail.cjs +3 -0
- package/dist/src/services/journalTail.cjs.map +1 -0
- package/dist/src/services/journalTail.mjs +3 -0
- package/dist/src/services/journalTail.mjs.map +1 -0
- package/dist/src/services/loginFlow.cjs +2 -0
- package/dist/src/services/loginFlow.cjs.map +1 -0
- package/dist/src/services/loginFlow.mjs +2 -0
- package/dist/src/services/loginFlow.mjs.map +1 -0
- package/dist/src/services/registryStore.cjs +2 -0
- package/dist/src/services/registryStore.cjs.map +1 -0
- package/dist/src/services/registryStore.mjs +2 -0
- package/dist/src/services/registryStore.mjs.map +1 -0
- package/dist/src/services/retryPolicy.cjs +2 -0
- package/dist/src/services/retryPolicy.cjs.map +1 -0
- package/dist/src/services/retryPolicy.mjs +2 -0
- package/dist/src/services/retryPolicy.mjs.map +1 -0
- package/dist/src/services/serveOptions.cjs +2 -0
- package/dist/src/services/serveOptions.cjs.map +1 -0
- package/dist/src/services/serveOptions.d.cts +21 -0
- package/dist/src/services/serveOptions.d.cts.map +1 -0
- package/dist/src/services/serveOptions.d.mts +21 -0
- package/dist/src/services/serveOptions.d.mts.map +1 -0
- package/dist/src/services/serveOptions.mjs +2 -0
- package/dist/src/services/serveOptions.mjs.map +1 -0
- package/dist/src/services/sessionFraming.cjs +4 -0
- package/dist/src/services/sessionFraming.cjs.map +1 -0
- package/dist/src/services/sessionFraming.mjs +4 -0
- package/dist/src/services/sessionFraming.mjs.map +1 -0
- package/dist/src/services/sessionPresence.cjs +2 -0
- package/dist/src/services/sessionPresence.cjs.map +1 -0
- package/dist/src/services/sessionPresence.mjs +2 -0
- package/dist/src/services/sessionPresence.mjs.map +1 -0
- package/dist/src/services/staticAssets.cjs +2 -0
- package/dist/src/services/staticAssets.cjs.map +1 -0
- package/dist/src/services/staticAssets.mjs +2 -0
- package/dist/src/services/staticAssets.mjs.map +1 -0
- package/dist/src/services/webDevRoots.cjs +2 -0
- package/dist/src/services/webDevRoots.cjs.map +1 -0
- package/dist/src/services/webDevRoots.mjs +2 -0
- package/dist/src/services/webDevRoots.mjs.map +1 -0
- package/dist/src/services/webPluginManifest.cjs +2 -0
- package/dist/src/services/webPluginManifest.cjs.map +1 -0
- package/dist/src/services/webPluginManifest.mjs +2 -0
- package/dist/src/services/webPluginManifest.mjs.map +1 -0
- package/dist/src/types/auth.cjs +2 -0
- package/dist/src/types/auth.cjs.map +1 -0
- package/dist/src/types/auth.d.cts +94 -0
- package/dist/src/types/auth.d.cts.map +1 -0
- package/dist/src/types/auth.d.mts +94 -0
- package/dist/src/types/auth.d.mts.map +1 -0
- package/dist/src/types/auth.mjs +2 -0
- package/dist/src/types/auth.mjs.map +1 -0
- package/dist/src/types/bridge.d.cts +31 -0
- package/dist/src/types/bridge.d.cts.map +1 -0
- package/dist/src/types/bridge.d.mts +31 -0
- package/dist/src/types/bridge.d.mts.map +1 -0
- package/dist/src/types/hub.cjs +2 -0
- package/dist/src/types/hub.cjs.map +1 -0
- package/dist/src/types/hub.d.cts +58 -0
- package/dist/src/types/hub.d.cts.map +1 -0
- package/dist/src/types/hub.d.mts +58 -0
- package/dist/src/types/hub.d.mts.map +1 -0
- package/dist/src/types/hub.mjs +2 -0
- package/dist/src/types/hub.mjs.map +1 -0
- package/dist/src/types/media.cjs +2 -0
- package/dist/src/types/media.cjs.map +1 -0
- package/dist/src/types/media.mjs +2 -0
- package/dist/src/types/media.mjs.map +1 -0
- package/dist/src/types/registry.cjs +1 -0
- package/dist/src/types/registry.d.cts +2 -0
- package/dist/src/types/registry.d.mts +2 -0
- package/dist/src/types/registry.mjs +1 -0
- package/dist/src/types/session.cjs +2 -0
- package/dist/src/types/session.cjs.map +1 -0
- package/dist/src/types/session.d.cts +24 -0
- package/dist/src/types/session.d.cts.map +1 -0
- package/dist/src/types/session.d.mts +24 -0
- package/dist/src/types/session.d.mts.map +1 -0
- package/dist/src/types/session.mjs +2 -0
- package/dist/src/types/session.mjs.map +1 -0
- package/dist/web/apple-touch-icon.png +0 -0
- package/dist/web/assets/index-Cg52ngKv.css +2 -0
- package/dist/web/assets/index-DQO7NTIP.js +90 -0
- package/dist/web/assets/index-DQO7NTIP.js.map +1 -0
- package/dist/web/favicon-16.png +0 -0
- package/dist/web/favicon-32.png +0 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/favicon.svg +16 -0
- package/dist/web/icon-512.png +0 -0
- package/dist/web/index.html +17 -0
- package/llms.txt +8 -0
- package/package.json +106 -0
- package/src/adapters/authRoutes.ts +95 -0
- package/src/adapters/bundledServer.ts +64 -0
- package/src/adapters/directoryListing.ts +127 -0
- package/src/adapters/directorySearch.ts +124 -0
- package/src/adapters/gitStatus.ts +27 -0
- package/src/adapters/httpServer.ts +556 -0
- package/src/adapters/hubProbe.ts +27 -0
- package/src/adapters/packageApiProxy.ts +60 -0
- package/src/adapters/piHubService.ts +129 -0
- package/src/adapters/piWebSocketListener.ts +98 -0
- package/src/adapters/providerAuth.ts +181 -0
- package/src/adapters/registryWatcher.ts +112 -0
- package/src/adapters/serverSpawner.ts +181 -0
- package/src/adapters/sessionFiles.ts +99 -0
- package/src/adapters/sessionHub.ts +567 -0
- package/src/adapters/sessionSocketClient.ts +105 -0
- package/src/adapters/syncGuard.ts +119 -0
- package/src/adapters/threadJournals.ts +184 -0
- package/src/adapters/webBundler.ts +83 -0
- package/src/adapters/webHubPluginLoader.ts +67 -0
- package/src/adapters/webHubPlugins.generated.ts +4 -0
- package/src/adapters/webPluginGenerate.ts +172 -0
- package/src/adapters/webPluginScan.ts +62 -0
- package/src/adapters/webPluginVite.ts +51 -0
- package/src/bin/serve.ts +64 -0
- package/src/exports/bundler.ts +1 -0
- package/src/exports/index.ts +9 -0
- package/src/services/directoryMatch.ts +71 -0
- package/src/services/fileMatch.ts +37 -0
- package/src/services/fileMedia.ts +27 -0
- package/src/services/frameRing.ts +33 -0
- package/src/services/journalTail.ts +47 -0
- package/src/services/loginFlow.ts +144 -0
- package/src/services/registryStore.ts +83 -0
- package/src/services/retryPolicy.ts +15 -0
- package/src/services/serveOptions.ts +63 -0
- package/src/services/sessionFraming.ts +32 -0
- package/src/services/sessionPresence.ts +131 -0
- package/src/services/staticAssets.ts +37 -0
- package/src/services/webDevRoots.ts +36 -0
- package/src/services/webPluginManifest.ts +149 -0
- package/src/types/auth.ts +118 -0
- package/src/types/bridge.ts +41 -0
- package/src/types/hub.ts +290 -0
- package/src/types/media.ts +38 -0
- package/src/types/registry.ts +2 -0
- package/src/types/session.ts +36 -0
- package/src/web/app/Providers.tsx +46 -0
- package/src/web/app/protocolRuntime.ts +106 -0
- package/src/web/app/sessionRuntime.ts +189 -0
- package/src/web/app/webPlugins.generated.ts +4 -0
- package/src/web/components/MascotMark.tsx +46 -0
- package/src/web/components/PluginSurface.tsx +10 -0
- package/src/web/components/ToolRendererBoundary.tsx +32 -0
- package/src/web/features/activity/ActivityDock.tsx +125 -0
- package/src/web/features/connection/RefusedCard.tsx +63 -0
- package/src/web/features/dialogs/DialogOverlay.tsx +176 -0
- package/src/web/features/leader/CommandPalette.tsx +386 -0
- package/src/web/features/selection/SelectionBar.tsx +529 -0
- package/src/web/features/session/Composer.tsx +368 -0
- package/src/web/features/session/MentionPreviews.tsx +58 -0
- package/src/web/features/session/ThreadView.tsx +35 -0
- package/src/web/features/session/Timeline.tsx +382 -0
- package/src/web/features/session/ToolCard.tsx +79 -0
- package/src/web/features/sessions/NewSessionDialog.tsx +222 -0
- package/src/web/features/sessions/SessionRail.tsx +411 -0
- package/src/web/features/settings/AppearanceSettings.tsx +88 -0
- package/src/web/features/settings/LoginFlowDialog.tsx +221 -0
- package/src/web/features/settings/PluginSettings.tsx +92 -0
- package/src/web/features/settings/ProviderSettings.tsx +238 -0
- package/src/web/features/settings/SettingsMenu.tsx +32 -0
- package/src/web/features/status/TopBar.tsx +208 -0
- package/src/web/index.html +16 -0
- package/src/web/lib/authApi.ts +90 -0
- package/src/web/lib/commands.ts +61 -0
- package/src/web/lib/composition.ts +225 -0
- package/src/web/lib/fileMentions.ts +32 -0
- package/src/web/lib/hubApi.ts +86 -0
- package/src/web/lib/leaderTree.ts +159 -0
- package/src/web/lib/piTransport.ts +61 -0
- package/src/web/lib/pluginRegistry.ts +488 -0
- package/src/web/lib/pluginSlotProps.ts +50 -0
- package/src/web/lib/promptFocus.ts +25 -0
- package/src/web/lib/protocolTimeline.ts +73 -0
- package/src/web/lib/sessionModel.ts +673 -0
- package/src/web/lib/sessionSummary.ts +58 -0
- package/src/web/lib/settingsSections.ts +21 -0
- package/src/web/lib/statusLine.ts +90 -0
- package/src/web/lib/threadRenderer.ts +25 -0
- package/src/web/lib/toolMessageProps.ts +21 -0
- package/src/web/lib/transport.ts +30 -0
- package/src/web/lib/wsClient.ts +73 -0
- package/src/web/main.tsx +17 -0
- package/src/web/public/apple-touch-icon.png +0 -0
- package/src/web/public/favicon-16.png +0 -0
- package/src/web/public/favicon-32.png +0 -0
- package/src/web/public/favicon.ico +0 -0
- package/src/web/public/favicon.svg +16 -0
- package/src/web/public/icon-512.png +0 -0
- package/src/web/routes/CockpitPage.tsx +98 -0
- package/src/web/routes/SettingsPage.tsx +60 -0
- package/src/web/routes/routeTree.tsx +49 -0
- package/src/web/stores/menuStore.ts +78 -0
- package/src/web/stores/paletteStore.ts +25 -0
- package/src/web/stores/sessionStore.ts +303 -0
- package/src/web/stores/sessionsStore.ts +155 -0
- package/src/web/stores/themeStore.ts +52 -0
- package/src/web/stores/threadStore.ts +68 -0
- package/src/web/stores/transientTabsStore.ts +63 -0
- package/src/web/stores/uiStore.ts +28 -0
- package/src/web/stores/useOpenTab.ts +19 -0
- package/src/web/stores/usePluginSlotProps.ts +22 -0
- package/src/web/styles/app.css +15 -0
- package/src/web/styles/webPluginSources.generated.css +1 -0
- package/src/web/vite-env.d.ts +1 -0
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
buttonVariants,
|
|
4
|
+
ChevronDownIcon,
|
|
5
|
+
cn,
|
|
6
|
+
Dot,
|
|
7
|
+
EmptyState,
|
|
8
|
+
handleOptionListKey,
|
|
9
|
+
Input,
|
|
10
|
+
Kbd,
|
|
11
|
+
OptionLabel,
|
|
12
|
+
OptionList,
|
|
13
|
+
optionListHint,
|
|
14
|
+
OptionRow,
|
|
15
|
+
Popover,
|
|
16
|
+
PopoverAnchor,
|
|
17
|
+
PopoverContent,
|
|
18
|
+
PopoverFooter,
|
|
19
|
+
PopoverHeader,
|
|
20
|
+
PopoverTrigger,
|
|
21
|
+
SectionLabel,
|
|
22
|
+
Spinner,
|
|
23
|
+
} from '@agimon-ai/doompi-web-components';
|
|
24
|
+
import { useStore } from '@tanstack/react-store';
|
|
25
|
+
import { useEffect, useState } from 'react';
|
|
26
|
+
import { PluginSurface } from '../../components/PluginSurface.tsx';
|
|
27
|
+
import { HOST_SLOTS } from '../../lib/pluginRegistry.ts';
|
|
28
|
+
import { minorModes, type MinorMode, selectionAxes } from '../../lib/composition.ts';
|
|
29
|
+
import type { AgentInfo, DialogRequest, ModelChoice } from '../../lib/sessionModel.ts';
|
|
30
|
+
import { focusPrompt } from '../../lib/promptFocus.ts';
|
|
31
|
+
import { parseSelection } from '../../lib/statusLine.ts';
|
|
32
|
+
import { menuStore, setPendingMenu } from '../../stores/menuStore.ts';
|
|
33
|
+
import {
|
|
34
|
+
answerDialogValue,
|
|
35
|
+
cancelDialog,
|
|
36
|
+
loadModelChoices,
|
|
37
|
+
runCommand,
|
|
38
|
+
selectModel,
|
|
39
|
+
selectThinkingLevel,
|
|
40
|
+
useActiveSession,
|
|
41
|
+
} from '../../stores/sessionStore.ts';
|
|
42
|
+
import { sessionsStore } from '../../stores/sessionsStore.ts';
|
|
43
|
+
|
|
44
|
+
// The known axes keep their mockup styling; a plugin-declared axis the host
|
|
45
|
+
// has no entry for reads in the neutral tone.
|
|
46
|
+
const AXIS_TONE: Readonly<Record<string, string>> = {
|
|
47
|
+
profile: 'text-doom-green',
|
|
48
|
+
domains: 'text-doom-violet',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const MENU_TITLE: Readonly<Record<string, string>> = {
|
|
52
|
+
mode: 'MAJOR MODE',
|
|
53
|
+
profile: 'PROFILE',
|
|
54
|
+
domains: 'DOMAINS',
|
|
55
|
+
minor: 'MINOR MODES',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const MENU_ACCENT: Readonly<Record<string, { border: string; title: string }>> = {
|
|
59
|
+
mode: { border: 'border-doom-edge-blue', title: 'text-doom-blue' },
|
|
60
|
+
profile: { border: 'border-doom-edge-green', title: 'text-doom-green' },
|
|
61
|
+
domains: { border: 'border-doom-edge-violet', title: 'text-doom-violet' },
|
|
62
|
+
minor: { border: 'border-doom-edge-magenta', title: 'text-doom-magenta' },
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const DEFAULT_ACCENT = { border: 'border-doom-border', title: 'text-doom-hi' };
|
|
66
|
+
|
|
67
|
+
const AVAILABILITY_TONE: Readonly<Record<MinorMode['availability'], string>> = {
|
|
68
|
+
on: 'text-doom-hi',
|
|
69
|
+
off: 'text-doom-dim',
|
|
70
|
+
unavailable: 'text-doom-faint/60',
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* A select the user asked for from this bar, rendered as the button's own
|
|
75
|
+
* popover rather than a centered modal: the question came from here, so the
|
|
76
|
+
* answer belongs here. It is the same dialog and the same replies; only the
|
|
77
|
+
* frame differs.
|
|
78
|
+
*/
|
|
79
|
+
function AxisMenu({ menu, dialog }: { menu: string; dialog: DialogRequest }) {
|
|
80
|
+
const accent = MENU_ACCENT[menu] ?? DEFAULT_ACCENT;
|
|
81
|
+
const [cursor, setCursor] = useState(0);
|
|
82
|
+
return (
|
|
83
|
+
<PopoverContent
|
|
84
|
+
side="top"
|
|
85
|
+
align="start"
|
|
86
|
+
data-testid="dialog"
|
|
87
|
+
data-dialog-method={dialog.method}
|
|
88
|
+
data-dialog-menu={menu}
|
|
89
|
+
onCloseAutoFocus={(event) => {
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
focusPrompt();
|
|
92
|
+
}}
|
|
93
|
+
onKeyDown={(event) =>
|
|
94
|
+
handleOptionListKey(event, {
|
|
95
|
+
options: dialog.options,
|
|
96
|
+
cursor,
|
|
97
|
+
onCursorChange: setCursor,
|
|
98
|
+
onSelect: (option) => answerDialogValue(dialog.id, option),
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
className={`w-[420px] ${accent.border}`}
|
|
102
|
+
>
|
|
103
|
+
<PopoverHeader>
|
|
104
|
+
<span data-testid="dialog-title" className={`text-[10px] font-bold tracking-wide ${accent.title}`}>
|
|
105
|
+
{MENU_TITLE[menu] ?? menu.toUpperCase()}
|
|
106
|
+
</span>
|
|
107
|
+
<span className="truncate text-[9px] text-doom-faint">{dialog.title}</span>
|
|
108
|
+
</PopoverHeader>
|
|
109
|
+
<div className="flex max-h-[320px] min-h-0 flex-col">
|
|
110
|
+
<OptionList
|
|
111
|
+
options={dialog.options}
|
|
112
|
+
cursor={cursor}
|
|
113
|
+
onCursorChange={setCursor}
|
|
114
|
+
density="compact"
|
|
115
|
+
testIdPrefix="dialog-option"
|
|
116
|
+
onSelect={(option) => answerDialogValue(dialog.id, option)}
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
<PopoverFooter>
|
|
120
|
+
<span data-testid="dialog-hints" className="flex items-center gap-1.5">
|
|
121
|
+
{optionListHint(dialog.options.length)} · <Kbd>esc</Kbd> closes
|
|
122
|
+
</span>
|
|
123
|
+
<Button variant="ghost" size="xs" data-testid="dialog-cancel" onClick={() => cancelDialog(dialog.id)}>
|
|
124
|
+
cancel
|
|
125
|
+
</Button>
|
|
126
|
+
</PopoverFooter>
|
|
127
|
+
</PopoverContent>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* One selection axis: a button that asks the agent, and the menu the agent
|
|
133
|
+
* answers with. While the question is in flight the button says so, because
|
|
134
|
+
* the round trip runs through the session and can take a moment.
|
|
135
|
+
*/
|
|
136
|
+
function AxisButton({
|
|
137
|
+
name,
|
|
138
|
+
command,
|
|
139
|
+
pending,
|
|
140
|
+
claimedDialog,
|
|
141
|
+
children,
|
|
142
|
+
className,
|
|
143
|
+
}: {
|
|
144
|
+
name: string;
|
|
145
|
+
command: string;
|
|
146
|
+
pending: boolean;
|
|
147
|
+
claimedDialog: DialogRequest | null;
|
|
148
|
+
children: React.ReactNode;
|
|
149
|
+
className: string;
|
|
150
|
+
}) {
|
|
151
|
+
const open = claimedDialog !== null;
|
|
152
|
+
return (
|
|
153
|
+
<Popover
|
|
154
|
+
open={open}
|
|
155
|
+
onOpenChange={(next) => {
|
|
156
|
+
if (!next && claimedDialog) cancelDialog(claimedDialog.id);
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
<PopoverAnchor asChild>
|
|
160
|
+
<Button
|
|
161
|
+
data-testid={`axis-${name}`}
|
|
162
|
+
data-pending={pending}
|
|
163
|
+
aria-haspopup="menu"
|
|
164
|
+
aria-expanded={open}
|
|
165
|
+
title={pending ? `asking the session for ${name}…` : `change ${name}`}
|
|
166
|
+
onClick={() => {
|
|
167
|
+
setPendingMenu(name);
|
|
168
|
+
runCommand(command);
|
|
169
|
+
}}
|
|
170
|
+
className={className}
|
|
171
|
+
>
|
|
172
|
+
{children}
|
|
173
|
+
{pending ? <Spinner className="h-[10px] w-[10px]" /> : <ChevronDownIcon className="h-[10px] w-[10px]" />}
|
|
174
|
+
</Button>
|
|
175
|
+
</PopoverAnchor>
|
|
176
|
+
{claimedDialog ? <AxisMenu menu={name} dialog={claimedDialog} /> : null}
|
|
177
|
+
</Popover>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* The minor-modes popup: state is derived from the statuses the session
|
|
183
|
+
* already published, and every row is a button that hands the mode to the
|
|
184
|
+
* runtime's /minor command. The runtime owns what happens next: a mode with
|
|
185
|
+
* one opt-in toggles outright, one with several opens its action picker as a
|
|
186
|
+
* dialog, and modes compose, so any number can be on at once.
|
|
187
|
+
*/
|
|
188
|
+
function MinorModesPopup({ modes, onClose }: { modes: MinorMode[]; onClose: () => void }) {
|
|
189
|
+
const on = modes.filter((mode) => mode.availability === 'on').length;
|
|
190
|
+
return (
|
|
191
|
+
<PopoverContent side="top" align="start" data-testid="minor-popup" className="w-[430px] border-doom-edge-magenta">
|
|
192
|
+
<PopoverHeader>
|
|
193
|
+
<SectionLabel className="tracking-wide text-doom-magenta">minor modes</SectionLabel>
|
|
194
|
+
<span className="text-[9px] text-doom-faint">{on} on</span>
|
|
195
|
+
</PopoverHeader>
|
|
196
|
+
<div
|
|
197
|
+
role="listbox"
|
|
198
|
+
aria-label="minor modes"
|
|
199
|
+
className="flex max-h-[320px] flex-col gap-0.5 overflow-y-auto p-1.5"
|
|
200
|
+
>
|
|
201
|
+
{modes.map((mode) => (
|
|
202
|
+
<OptionRow
|
|
203
|
+
key={mode.name}
|
|
204
|
+
density="compact"
|
|
205
|
+
active={mode.availability === 'on'}
|
|
206
|
+
data-testid={`minor-${mode.name}`}
|
|
207
|
+
data-availability={mode.availability}
|
|
208
|
+
// A mode that cannot run here says so on hover instead of taking a
|
|
209
|
+
// click and answering with a notice after the round trip.
|
|
210
|
+
disabled={mode.availability === 'unavailable'}
|
|
211
|
+
title={mode.availability === 'unavailable' ? mode.unavailableReason : `drive ${mode.name}`}
|
|
212
|
+
onClick={() => {
|
|
213
|
+
// The runtime may answer with an opt-in picker; claiming the menu
|
|
214
|
+
// keeps that question on this chip, where it was asked, instead
|
|
215
|
+
// of throwing it to the middle of the screen.
|
|
216
|
+
setPendingMenu('minor');
|
|
217
|
+
runCommand(`/minor ${mode.id}`);
|
|
218
|
+
onClose();
|
|
219
|
+
}}
|
|
220
|
+
className={cn(
|
|
221
|
+
'w-full gap-2.5 py-1.5 transition-colors hover:bg-doom-deep focus-visible:bg-doom-deep disabled:cursor-not-allowed',
|
|
222
|
+
mode.availability === 'on' && 'bg-doom-tint-magenta hover:brightness-125',
|
|
223
|
+
)}
|
|
224
|
+
>
|
|
225
|
+
<Kbd
|
|
226
|
+
className={`w-8 justify-center ${
|
|
227
|
+
mode.availability === 'on' ? 'bg-doom-magenta/25 text-doom-magenta' : ''
|
|
228
|
+
}`}
|
|
229
|
+
>
|
|
230
|
+
{mode.keys}
|
|
231
|
+
</Kbd>
|
|
232
|
+
<OptionLabel density="compact" className={AVAILABILITY_TONE[mode.availability]}>
|
|
233
|
+
{mode.name}
|
|
234
|
+
</OptionLabel>
|
|
235
|
+
{mode.detail ? (
|
|
236
|
+
<span data-testid={`minor-detail-${mode.name}`} className="truncate text-[9px] text-doom-magenta">
|
|
237
|
+
{mode.detail}
|
|
238
|
+
</span>
|
|
239
|
+
) : null}
|
|
240
|
+
{mode.availability === 'unavailable' && mode.unavailableReason ? (
|
|
241
|
+
<span
|
|
242
|
+
data-testid={`minor-reason-${mode.name}`}
|
|
243
|
+
className="min-w-0 flex-1 truncate text-right text-[9px] text-doom-faint/70"
|
|
244
|
+
>
|
|
245
|
+
{mode.unavailableReason}
|
|
246
|
+
</span>
|
|
247
|
+
) : null}
|
|
248
|
+
{mode.availability === 'unavailable' ? (
|
|
249
|
+
<span className="shrink-0 text-[8px] text-doom-faint/60">n/a</span>
|
|
250
|
+
) : (
|
|
251
|
+
<Dot tone={mode.availability === 'on' ? 'magenta' : 'muted'} />
|
|
252
|
+
)}
|
|
253
|
+
</OptionRow>
|
|
254
|
+
))}
|
|
255
|
+
</div>
|
|
256
|
+
<PopoverFooter>
|
|
257
|
+
<span>click drives a mode · one with several opt-ins asks · esc closes</span>
|
|
258
|
+
</PopoverFooter>
|
|
259
|
+
</PopoverContent>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* The model popup: the models Pi can switch to and the thinking levels the
|
|
265
|
+
* current model accepts, both asked for on open because Pi only reports them
|
|
266
|
+
* on request. A pick goes straight to the RPC verbs; the chip updates from the
|
|
267
|
+
* get_state that follows, so it never shows a choice the agent refused.
|
|
268
|
+
*/
|
|
269
|
+
function ModelPopup({
|
|
270
|
+
agent,
|
|
271
|
+
models,
|
|
272
|
+
levels,
|
|
273
|
+
onClose,
|
|
274
|
+
}: {
|
|
275
|
+
agent: AgentInfo | null;
|
|
276
|
+
models: ModelChoice[];
|
|
277
|
+
levels: string[];
|
|
278
|
+
onClose: () => void;
|
|
279
|
+
}) {
|
|
280
|
+
const [filter, setFilter] = useState('');
|
|
281
|
+
|
|
282
|
+
useEffect(() => {
|
|
283
|
+
loadModelChoices();
|
|
284
|
+
}, []);
|
|
285
|
+
|
|
286
|
+
const needle = filter.trim().toLowerCase();
|
|
287
|
+
const shown = needle
|
|
288
|
+
? models.filter((model) => `${model.provider} ${model.id} ${model.name}`.toLowerCase().includes(needle))
|
|
289
|
+
: models;
|
|
290
|
+
|
|
291
|
+
return (
|
|
292
|
+
<PopoverContent side="top" align="end" data-testid="model-popup" className="w-[400px] border-doom-edge-yellow">
|
|
293
|
+
<PopoverHeader>
|
|
294
|
+
<SectionLabel className="tracking-wide text-doom-yellow">model</SectionLabel>
|
|
295
|
+
<span className="truncate text-[9px] text-doom-faint">{agent ? `${agent.provider}/${agent.model}` : ''}</span>
|
|
296
|
+
</PopoverHeader>
|
|
297
|
+
<div className="border-b border-doom-border-soft p-1.5">
|
|
298
|
+
<Input
|
|
299
|
+
data-testid="model-filter"
|
|
300
|
+
value={filter}
|
|
301
|
+
autoFocus
|
|
302
|
+
placeholder="filter models…"
|
|
303
|
+
onChange={(event) => setFilter(event.target.value)}
|
|
304
|
+
className="w-full px-2 py-1 text-[11px]"
|
|
305
|
+
/>
|
|
306
|
+
</div>
|
|
307
|
+
<div role="listbox" aria-label="models" className="flex max-h-[260px] flex-col gap-0.5 overflow-y-auto p-1.5">
|
|
308
|
+
{models.length === 0 ? (
|
|
309
|
+
<span className="flex items-center gap-2 px-2 py-1.5 text-[10px] text-doom-faint">
|
|
310
|
+
<Spinner label="asking the session for its models" />
|
|
311
|
+
asking the session for its models…
|
|
312
|
+
</span>
|
|
313
|
+
) : shown.length === 0 ? (
|
|
314
|
+
<EmptyState className="py-4" title="no model matches" />
|
|
315
|
+
) : null}
|
|
316
|
+
{shown.map((model) => {
|
|
317
|
+
const current = agent?.model === model.id && agent.provider === model.provider;
|
|
318
|
+
return (
|
|
319
|
+
<OptionRow
|
|
320
|
+
key={`${model.provider}/${model.id}`}
|
|
321
|
+
density="compact"
|
|
322
|
+
active={current}
|
|
323
|
+
data-testid={`model-${model.provider}-${model.id}`}
|
|
324
|
+
data-current={current}
|
|
325
|
+
onClick={() => {
|
|
326
|
+
selectModel(model.provider, model.id);
|
|
327
|
+
onClose();
|
|
328
|
+
}}
|
|
329
|
+
className={cn(
|
|
330
|
+
'w-full gap-2.5 py-1.5 transition-colors hover:bg-doom-deep focus-visible:bg-doom-deep',
|
|
331
|
+
current && 'bg-doom-tint-yellow hover:brightness-125',
|
|
332
|
+
)}
|
|
333
|
+
>
|
|
334
|
+
<span className="w-20 shrink-0 truncate text-[9px] text-doom-faint">{model.provider}</span>
|
|
335
|
+
<OptionLabel density="compact" className={current ? 'text-doom-yellow' : 'text-doom-hi'}>
|
|
336
|
+
{model.id}
|
|
337
|
+
</OptionLabel>
|
|
338
|
+
{model.reasoning ? <span className="text-[8px] text-doom-faint">thinks</span> : null}
|
|
339
|
+
<Dot tone={current ? 'yellow' : 'muted'} />
|
|
340
|
+
</OptionRow>
|
|
341
|
+
);
|
|
342
|
+
})}
|
|
343
|
+
</div>
|
|
344
|
+
<PopoverFooter className="flex-wrap justify-start gap-1 py-1.5">
|
|
345
|
+
<SectionLabel className="mr-1 tracking-wide">thinking</SectionLabel>
|
|
346
|
+
{levels.length === 0 ? <span className="text-[9px] text-doom-faint">…</span> : null}
|
|
347
|
+
{levels.map((level) => {
|
|
348
|
+
const current = agent?.thinkingLevel === level;
|
|
349
|
+
return (
|
|
350
|
+
<Button
|
|
351
|
+
key={level}
|
|
352
|
+
variant="ghost"
|
|
353
|
+
size="xs"
|
|
354
|
+
data-testid={`thinking-${level}`}
|
|
355
|
+
data-current={current}
|
|
356
|
+
onClick={() => {
|
|
357
|
+
selectThinkingLevel(level);
|
|
358
|
+
onClose();
|
|
359
|
+
}}
|
|
360
|
+
className={cn(
|
|
361
|
+
'text-[10px]',
|
|
362
|
+
current && 'bg-doom-yellow/25 font-bold text-doom-yellow hover:bg-doom-yellow/25',
|
|
363
|
+
)}
|
|
364
|
+
>
|
|
365
|
+
{level}
|
|
366
|
+
</Button>
|
|
367
|
+
);
|
|
368
|
+
})}
|
|
369
|
+
</PopoverFooter>
|
|
370
|
+
</PopoverContent>
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* The mockup's bottom bar: the DoomPi selection axes as buttons, the model,
|
|
376
|
+
* and the context gauge. Buttons route through the same slash commands the
|
|
377
|
+
* TUI uses; the agent's select dialog then opens as that button's popover.
|
|
378
|
+
*/
|
|
379
|
+
export function SelectionBar() {
|
|
380
|
+
const activeId = useStore(sessionsStore, (state) => state.activeId);
|
|
381
|
+
const raw = useActiveSession((state) => state.statuses['doom-major-mode'] ?? '');
|
|
382
|
+
const statuses = useActiveSession((state) => state.statuses);
|
|
383
|
+
const widgets = useActiveSession((state) => state.widgets);
|
|
384
|
+
const catalog = useActiveSession((state) => state.minorModes);
|
|
385
|
+
const agent = useActiveSession((state) => state.agent);
|
|
386
|
+
const stats = useActiveSession((state) => state.stats);
|
|
387
|
+
const models = useActiveSession((state) => state.models);
|
|
388
|
+
const thinkingLevels = useActiveSession((state) => state.thinkingLevels);
|
|
389
|
+
const dialog = useActiveSession((state) => state.dialog);
|
|
390
|
+
const pendingMenu = useStore(menuStore, (state) => state.pending);
|
|
391
|
+
const claimed = useStore(menuStore, (state) => state.claimed);
|
|
392
|
+
const [minorOpen, setMinorOpen] = useState(false);
|
|
393
|
+
const [modelOpen, setModelOpen] = useState(false);
|
|
394
|
+
|
|
395
|
+
const selection = parseSelection(raw);
|
|
396
|
+
const axes = selectionAxes(statuses);
|
|
397
|
+
const modes = minorModes(statuses, widgets, catalog);
|
|
398
|
+
const activeMinors = modes.filter((mode) => mode.availability === 'on');
|
|
399
|
+
|
|
400
|
+
/** The dialog this axis asked for, when the claim named it and it is still open. */
|
|
401
|
+
const dialogFor = (name: string): DialogRequest | null =>
|
|
402
|
+
dialog !== null && claimed !== null && claimed.dialogId === dialog.id && claimed.menu === name ? dialog : null;
|
|
403
|
+
const minorDialog = dialogFor('minor');
|
|
404
|
+
|
|
405
|
+
const modeClass = cn(
|
|
406
|
+
buttonVariants({ variant: 'primary', size: 'sm' }),
|
|
407
|
+
'h-[21px] rounded-[3px] px-2 text-doom-rail',
|
|
408
|
+
selection.pending && 'bg-doom-yellow',
|
|
409
|
+
);
|
|
410
|
+
const axisClass = cn(buttonVariants({ variant: 'outline', size: 'sm' }), 'h-[21px] min-w-0 rounded-[3px] px-2');
|
|
411
|
+
|
|
412
|
+
return (
|
|
413
|
+
<footer
|
|
414
|
+
data-testid="selection-bar"
|
|
415
|
+
data-pending={selection.pending}
|
|
416
|
+
className="relative flex h-[34px] shrink-0 items-center gap-2 border-t border-doom-border bg-doom-rail px-3.5"
|
|
417
|
+
>
|
|
418
|
+
<AxisButton
|
|
419
|
+
name="mode"
|
|
420
|
+
command="mode"
|
|
421
|
+
pending={pendingMenu === 'mode'}
|
|
422
|
+
claimedDialog={dialogFor('mode')}
|
|
423
|
+
className={modeClass}
|
|
424
|
+
>
|
|
425
|
+
<span data-testid="selection-mode" className="text-[10px] font-bold tracking-[0.08em]">
|
|
426
|
+
{(selection.majorMode || 'mode').toUpperCase()}
|
|
427
|
+
</span>
|
|
428
|
+
</AxisButton>
|
|
429
|
+
|
|
430
|
+
{axes.map((axis) => (
|
|
431
|
+
<AxisButton
|
|
432
|
+
key={axis.name}
|
|
433
|
+
name={axis.name}
|
|
434
|
+
command={axis.command}
|
|
435
|
+
pending={pendingMenu === axis.name}
|
|
436
|
+
claimedDialog={dialogFor(axis.name)}
|
|
437
|
+
className={axisClass}
|
|
438
|
+
>
|
|
439
|
+
<span
|
|
440
|
+
data-testid={`selection-${axis.name}`}
|
|
441
|
+
className={`truncate text-[10px] ${axis.multi ? '' : 'font-bold'} ${
|
|
442
|
+
axis.values.length > 0 ? (AXIS_TONE[axis.name] ?? 'text-doom-hi') : 'text-doom-faint'
|
|
443
|
+
}`}
|
|
444
|
+
>
|
|
445
|
+
{axis.values.length === 0 ? axis.emptyLabel : axis.multi ? axis.values.join(', ') : `*${axis.values[0]}*`}
|
|
446
|
+
</span>
|
|
447
|
+
</AxisButton>
|
|
448
|
+
))}
|
|
449
|
+
|
|
450
|
+
<Popover
|
|
451
|
+
open={minorOpen || minorDialog !== null}
|
|
452
|
+
onOpenChange={(next) => {
|
|
453
|
+
if (next) {
|
|
454
|
+
setMinorOpen(true);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
if (minorDialog) cancelDialog(minorDialog.id);
|
|
458
|
+
setMinorOpen(false);
|
|
459
|
+
}}
|
|
460
|
+
>
|
|
461
|
+
<PopoverTrigger asChild>
|
|
462
|
+
<Button
|
|
463
|
+
data-testid="axis-minor"
|
|
464
|
+
title="minor modes"
|
|
465
|
+
variant={activeMinors.length > 0 ? 'subtle' : 'outline'}
|
|
466
|
+
size="sm"
|
|
467
|
+
className={cn(
|
|
468
|
+
'h-[21px] rounded-[3px] px-2',
|
|
469
|
+
activeMinors.length > 0
|
|
470
|
+
? 'bg-doom-tint-magenta text-doom-magenta hover:bg-doom-tint-magenta hover:brightness-125'
|
|
471
|
+
: 'text-doom-faint',
|
|
472
|
+
)}
|
|
473
|
+
>
|
|
474
|
+
<span data-testid="minor-summary" className="text-[10px] font-bold">
|
|
475
|
+
{activeMinors[0]?.name ?? 'minor'}
|
|
476
|
+
</span>
|
|
477
|
+
{activeMinors.length > 1 ? <span className="text-[9px]">+{activeMinors.length - 1}</span> : null}
|
|
478
|
+
<ChevronDownIcon className="h-[10px] w-[10px]" />
|
|
479
|
+
</Button>
|
|
480
|
+
</PopoverTrigger>
|
|
481
|
+
{minorDialog ? (
|
|
482
|
+
<AxisMenu menu="minor" dialog={minorDialog} />
|
|
483
|
+
) : minorOpen ? (
|
|
484
|
+
<MinorModesPopup modes={modes} onClose={() => setMinorOpen(false)} />
|
|
485
|
+
) : null}
|
|
486
|
+
</Popover>
|
|
487
|
+
|
|
488
|
+
<PluginSurface slot={HOST_SLOTS.selectionBar} sessionId={activeId} />
|
|
489
|
+
|
|
490
|
+
<div className="min-w-0 flex-1" />
|
|
491
|
+
|
|
492
|
+
<Popover open={modelOpen} onOpenChange={setModelOpen}>
|
|
493
|
+
<PopoverTrigger asChild>
|
|
494
|
+
<Button data-testid="axis-model" title="model and thinking level" className={axisClass}>
|
|
495
|
+
<span data-testid="agent-model" className="text-[10px] text-doom-hi">
|
|
496
|
+
{agent?.model ?? '—'}
|
|
497
|
+
</span>
|
|
498
|
+
<span data-testid="agent-thinking" className="text-[10px] text-doom-yellow">
|
|
499
|
+
{agent?.thinkingLevel ?? ''}
|
|
500
|
+
</span>
|
|
501
|
+
<ChevronDownIcon className="h-[10px] w-[10px] text-doom-faint" />
|
|
502
|
+
</Button>
|
|
503
|
+
</PopoverTrigger>
|
|
504
|
+
{modelOpen ? (
|
|
505
|
+
<ModelPopup agent={agent} models={models} levels={thinkingLevels} onClose={() => setModelOpen(false)} />
|
|
506
|
+
) : null}
|
|
507
|
+
</Popover>
|
|
508
|
+
|
|
509
|
+
<span
|
|
510
|
+
data-testid="top-context"
|
|
511
|
+
title={
|
|
512
|
+
stats?.contextTokens != null && stats.contextWindow != null
|
|
513
|
+
? `${stats.contextTokens.toLocaleString()} of ${stats.contextWindow.toLocaleString()} tokens`
|
|
514
|
+
: 'context usage, once the session reports it'
|
|
515
|
+
}
|
|
516
|
+
className="text-[10px] text-doom-dim"
|
|
517
|
+
>
|
|
518
|
+
{stats?.contextPercent == null ? 'ctx —' : `ctx ${Math.round(stats.contextPercent)}%`}
|
|
519
|
+
</span>
|
|
520
|
+
<span
|
|
521
|
+
data-testid="top-cost"
|
|
522
|
+
title={stats ? `${stats.totalTokens.toLocaleString()} tokens this session` : ''}
|
|
523
|
+
className="text-[10px] text-doom-dim"
|
|
524
|
+
>
|
|
525
|
+
{stats ? `$${stats.cost.toFixed(2)}` : ''}
|
|
526
|
+
</span>
|
|
527
|
+
</footer>
|
|
528
|
+
);
|
|
529
|
+
}
|