spiracha 2.0.0 → 2.2.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/AGENTS.md +27 -12
- package/README.md +42 -19
- package/apps/ui/AGENTS.md +18 -10
- package/apps/ui/README.md +36 -11
- package/apps/ui/src/components/analytics-breakdowns.tsx +106 -0
- package/apps/ui/src/components/antigravity-conversations-table.tsx +51 -11
- package/apps/ui/src/components/antigravity-workspaces-table.tsx +1 -1
- package/apps/ui/src/components/app-shell.tsx +46 -3
- package/apps/ui/src/components/breadcrumbs.tsx +21 -3
- package/apps/ui/src/components/claude-code-sessions-table.tsx +42 -6
- package/apps/ui/src/components/claude-code-workspaces-table.tsx +3 -1
- package/apps/ui/src/components/cursor-threads-table.tsx +10 -47
- package/apps/ui/src/components/data-table.tsx +23 -5
- package/apps/ui/src/components/delete-confirm-dialog.tsx +4 -3
- package/apps/ui/src/components/export-dialog.tsx +79 -74
- package/apps/ui/src/components/grok-sessions-table.tsx +146 -0
- package/apps/ui/src/components/grok-workspaces-table.tsx +53 -0
- package/apps/ui/src/components/kiro-sessions-table.tsx +40 -4
- package/apps/ui/src/components/opencode-sessions-table.tsx +40 -4
- package/apps/ui/src/components/page-header.tsx +15 -9
- package/apps/ui/src/components/projects-table.tsx +4 -1
- package/apps/ui/src/components/qoder-sessions-table.tsx +23 -2
- package/apps/ui/src/components/qoder-workspaces-table.tsx +5 -1
- package/apps/ui/src/components/route-error-panel.tsx +13 -0
- package/apps/ui/src/components/selection-actions-toolbar.tsx +80 -0
- package/apps/ui/src/components/thread-goals-panel.tsx +50 -0
- package/apps/ui/src/components/thread-tools-panel.tsx +142 -0
- package/apps/ui/src/components/threads-table.tsx +13 -47
- package/apps/ui/src/components/transcript-view.tsx +148 -52
- package/apps/ui/src/components/ui/select.tsx +1 -1
- package/apps/ui/src/lib/antigravity-conversation-state.ts +17 -4
- package/apps/ui/src/lib/antigravity-server.ts +209 -11
- package/apps/ui/src/lib/antigravity-transcript-events.ts +57 -73
- package/apps/ui/src/lib/claude-code-queries.ts +8 -0
- package/apps/ui/src/lib/claude-code-server.ts +185 -7
- package/apps/ui/src/lib/claude-code-transcript-events.ts +8 -1
- package/apps/ui/src/lib/codex-queries.ts +16 -0
- package/apps/ui/src/lib/codex-server.ts +134 -52
- package/apps/ui/src/lib/cursor-server.ts +68 -53
- package/apps/ui/src/lib/cursor-transcript-events.ts +11 -86
- package/apps/ui/src/lib/delete-batch.ts +52 -0
- package/apps/ui/src/lib/error-presentation.ts +43 -0
- package/apps/ui/src/lib/export-mutation.ts +20 -0
- package/apps/ui/src/lib/export-options.ts +15 -0
- package/apps/ui/src/lib/formatters.ts +3 -5
- package/apps/ui/src/lib/grok-queries.ts +22 -0
- package/apps/ui/src/lib/grok-server.ts +168 -0
- package/apps/ui/src/lib/grok-transcript-events.ts +149 -0
- package/apps/ui/src/lib/kiro-server.ts +97 -7
- package/apps/ui/src/lib/mutation-error.ts +15 -0
- package/apps/ui/src/lib/opencode-server.ts +96 -7
- package/apps/ui/src/lib/path-utils.ts +1 -1
- package/apps/ui/src/lib/qoder-queries.ts +15 -0
- package/apps/ui/src/lib/qoder-server.ts +67 -11
- package/apps/ui/src/lib/qoder-transcript-events.ts +1 -1
- package/apps/ui/src/lib/route-search.ts +114 -0
- package/apps/ui/src/lib/route-state-reset.tsx +10 -0
- package/apps/ui/src/lib/settings-server.ts +33 -0
- package/apps/ui/src/lib/settings-store.tsx +82 -38
- package/apps/ui/src/lib/settings.ts +65 -0
- package/apps/ui/src/lib/source-session-export-server.ts +86 -3
- package/apps/ui/src/lib/thread-metadata.ts +22 -0
- package/apps/ui/src/lib/thread-transcript-load.ts +15 -0
- package/apps/ui/src/lib/thread-transcript-stats.ts +10 -1
- package/apps/ui/src/lib/workspace-delete-navigation.ts +20 -0
- package/apps/ui/src/routeTree.gen.ts +341 -234
- package/apps/ui/src/routes/__root.tsx +12 -15
- package/apps/ui/src/routes/analytics.tsx +10 -61
- package/apps/ui/src/routes/antigravity-conversations.$conversationId.tsx +149 -29
- package/apps/ui/src/routes/antigravity.$workspaceKey.tsx +262 -42
- package/apps/ui/src/routes/antigravity.index.tsx +2 -2
- package/apps/ui/src/routes/api.v1.conversations.$source.$id.ts +4 -0
- package/apps/ui/src/routes/api.v1.conversations.delete.ts +12 -0
- package/apps/ui/src/routes/api.v1.conversations.export.ts +12 -0
- package/apps/ui/src/routes/claude-code-sessions.$sessionId.tsx +168 -28
- package/apps/ui/src/routes/claude-code.$workspaceKey.tsx +179 -45
- package/apps/ui/src/routes/claude-code.index.tsx +2 -2
- package/apps/ui/src/routes/codex.$project.tsx +33 -31
- package/apps/ui/src/routes/codex.index.tsx +5 -10
- package/apps/ui/src/routes/cursor-threads.$composerId.tsx +31 -18
- package/apps/ui/src/routes/cursor.$workspaceKey.tsx +44 -28
- package/apps/ui/src/routes/cursor.index.tsx +5 -2
- package/apps/ui/src/routes/grok-sessions.$sessionId.tsx +401 -0
- package/apps/ui/src/routes/grok.$workspaceKey.tsx +295 -0
- package/apps/ui/src/routes/grok.index.tsx +48 -0
- package/apps/ui/src/routes/index.tsx +2 -18
- package/apps/ui/src/routes/kiro-sessions.$sessionId.tsx +91 -27
- package/apps/ui/src/routes/kiro.$workspaceKey.tsx +213 -51
- package/apps/ui/src/routes/kiro.index.tsx +2 -2
- package/apps/ui/src/routes/opencode-sessions.$sessionId.tsx +108 -29
- package/apps/ui/src/routes/opencode.$workspaceKey.tsx +231 -55
- package/apps/ui/src/routes/opencode.index.tsx +6 -2
- package/apps/ui/src/routes/qoder-sessions.$sessionId.tsx +19 -15
- package/apps/ui/src/routes/qoder.$workspaceKey.tsx +89 -48
- package/apps/ui/src/routes/qoder.index.tsx +2 -2
- package/apps/ui/src/routes/threads.$threadId.tsx +620 -105
- package/apps/ui/vite.config.ts +22 -12
- package/bin/spiracha.ts +28 -1
- package/package.json +37 -27
- package/src/client.ts +183 -5
- package/src/lib/antigravity-db.ts +424 -94
- package/src/lib/antigravity-exporter-types.ts +3 -0
- package/src/lib/antigravity-keychain.ts +7 -8
- package/src/lib/antigravity-projects.ts +201 -0
- package/src/lib/antigravity-transcript-phase.ts +47 -0
- package/src/lib/claude-code-db.ts +534 -42
- package/src/lib/claude-code-exporter-types.ts +5 -0
- package/src/lib/claude-code-transcript-phase.ts +60 -1
- package/src/lib/claude-code-transcript.ts +8 -5
- package/src/lib/codex-analytics.ts +32 -5
- package/src/lib/codex-browser-db.ts +540 -137
- package/src/lib/codex-browser-export.ts +18 -27
- package/src/lib/codex-browser-types.ts +21 -1
- package/src/lib/codex-thread-cache.ts +106 -23
- package/src/lib/codex-thread-parser.ts +149 -55
- package/src/lib/codex-thread-recovery.ts +38 -11
- package/src/lib/codex-transcript-filter.ts +31 -0
- package/src/lib/codex-transcript-renderer.ts +87 -79
- package/src/lib/concurrency.ts +56 -3
- package/src/lib/conversation-api.ts +410 -27
- package/src/lib/conversation-data/adapter-helpers.ts +25 -3
- package/src/lib/conversation-data/antigravity-adapter.ts +70 -26
- package/src/lib/conversation-data/claude-code-adapter.ts +69 -12
- package/src/lib/conversation-data/codex-adapter.ts +108 -73
- package/src/lib/conversation-data/cursor-adapter.ts +77 -29
- package/src/lib/conversation-data/grok-adapter.ts +223 -0
- package/src/lib/conversation-data/index.ts +105 -11
- package/src/lib/conversation-data/kiro-adapter.ts +59 -12
- package/src/lib/conversation-data/message-selector.ts +2 -5
- package/src/lib/conversation-data/opencode-adapter.ts +67 -17
- package/src/lib/conversation-data/path-match.ts +1 -13
- package/src/lib/conversation-data/qoder-adapter.ts +67 -37
- package/src/lib/conversation-data/types.ts +43 -0
- package/src/lib/conversation-zip-export.ts +75 -0
- package/src/lib/cursor-db.ts +68 -28
- package/src/lib/cursor-id.ts +17 -0
- package/src/lib/cursor-recovery.ts +146 -35
- package/src/lib/cursor-transcript-phase.ts +40 -0
- package/src/lib/cursor-transcript.ts +21 -5
- package/src/lib/grok-db.ts +1068 -0
- package/src/lib/grok-exporter-types.ts +110 -0
- package/src/lib/grok-transcript-phase.ts +61 -0
- package/src/lib/grok-transcript.ts +167 -0
- package/src/lib/kiro-db.ts +187 -43
- package/src/lib/kiro-transcript.ts +0 -4
- package/src/lib/model-label.ts +11 -3
- package/src/lib/opencode-db.ts +599 -60
- package/src/lib/opencode-think-tags.ts +17 -3
- package/src/lib/opencode-transcript.ts +8 -5
- package/src/lib/portable-path.ts +9 -0
- package/src/lib/qoder-acp-client.ts +11 -2
- package/src/lib/qoder-db.ts +51 -17
- package/src/lib/qoder-transcript-phase.ts +64 -0
- package/src/lib/qoder-transcript.ts +30 -2
- package/src/lib/shared.ts +63 -15
- package/src/lib/sqlite-error.ts +31 -5
- package/src/lib/transcript-load-limiter.ts +87 -0
- package/src/lib/ui-cache.ts +185 -24
- package/src/lib/ui-export-archive.ts +58 -39
- package/src/lib/ui-export-files.ts +75 -15
- package/src/lib/ui-export-zip.ts +40 -0
- package/apps/ui/package.json +0 -65
|
@@ -3,6 +3,8 @@ import { createRootRouteWithContext, HeadContent, Outlet, Scripts } from '@tanst
|
|
|
3
3
|
import type { ReactNode } from 'react';
|
|
4
4
|
import { AppShell } from '#/components/app-shell';
|
|
5
5
|
import { TooltipProvider } from '#/components/ui/tooltip';
|
|
6
|
+
import { getErrorPresentation } from '#/lib/error-presentation';
|
|
7
|
+
import { getInitialSettingsFn } from '#/lib/settings-server';
|
|
6
8
|
import { SettingsProvider } from '#/lib/settings-store';
|
|
7
9
|
import appCss from '#/styles.css?url';
|
|
8
10
|
|
|
@@ -23,26 +25,18 @@ const themeInitScript = `
|
|
|
23
25
|
`;
|
|
24
26
|
|
|
25
27
|
function RootErrorComponent({ error }: { error: Error }) {
|
|
26
|
-
const
|
|
27
|
-
error.message.includes('unable to open database') ||
|
|
28
|
-
error.message.includes('database is locked') ||
|
|
29
|
-
error.message.includes('SQLITE_');
|
|
28
|
+
const presentation = getErrorPresentation(error, { fallbackTitle: 'Something went wrong' });
|
|
30
29
|
|
|
31
30
|
return (
|
|
32
31
|
<div className="flex min-h-screen items-center justify-center bg-[#101418] px-4 text-[#eef3f7]">
|
|
33
32
|
<div className="max-w-[30rem] text-center">
|
|
34
|
-
<h1 className="mb-3 font-semibold text-base">
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
{isSqliteError ? (
|
|
38
|
-
<p className="my-2 text-[#99a3af] text-[0.875rem] leading-6">
|
|
39
|
-
Spiracha could not open the Codex SQLite database. Codex may have an exclusive lock on the file,
|
|
40
|
-
or the database does not exist yet. Close Codex or wait a moment, then reload.
|
|
41
|
-
</p>
|
|
33
|
+
<h1 className="mb-3 font-semibold text-base">{presentation.title}</h1>
|
|
34
|
+
{presentation.isDatabaseError ? (
|
|
35
|
+
<p className="my-2 text-[#99a3af] text-[0.875rem] leading-6">{presentation.description}</p>
|
|
42
36
|
) : (
|
|
43
37
|
<p className="my-2 text-[#99a3af] text-[0.875rem] leading-6">
|
|
44
38
|
<code className="rounded border border-white/10 bg-[#12181e] px-1.5 py-1 text-[0.8em]">
|
|
45
|
-
{
|
|
39
|
+
{presentation.description}
|
|
46
40
|
</code>
|
|
47
41
|
</p>
|
|
48
42
|
)}
|
|
@@ -87,7 +81,7 @@ export const Route = createRootRouteWithContext<RouterContext>()({
|
|
|
87
81
|
},
|
|
88
82
|
{
|
|
89
83
|
content:
|
|
90
|
-
'Browse local Codex, Claude Code, Kiro, Qoder, Cursor, Antigravity, and OpenCode history through a compact workspace UI.',
|
|
84
|
+
'Browse local Codex, Claude Code, Grok, Kiro, Qoder, Cursor, Antigravity, and OpenCode history through a compact workspace UI.',
|
|
91
85
|
name: 'description',
|
|
92
86
|
},
|
|
93
87
|
{
|
|
@@ -95,12 +89,15 @@ export const Route = createRootRouteWithContext<RouterContext>()({
|
|
|
95
89
|
},
|
|
96
90
|
],
|
|
97
91
|
}),
|
|
92
|
+
loader: () => getInitialSettingsFn(),
|
|
98
93
|
});
|
|
99
94
|
|
|
100
95
|
function RootComponent() {
|
|
96
|
+
const initialSettings = Route.useLoaderData();
|
|
97
|
+
|
|
101
98
|
return (
|
|
102
99
|
<RootDocument>
|
|
103
|
-
<SettingsProvider>
|
|
100
|
+
<SettingsProvider initialSettings={initialSettings}>
|
|
104
101
|
<TooltipProvider>
|
|
105
102
|
<AppShell>
|
|
106
103
|
<Outlet />
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import type { ModelTokenSummary, ToolUsageSummary } from '@spiracha/lib/codex-browser-types';
|
|
2
1
|
import { useSuspenseQuery } from '@tanstack/react-query';
|
|
3
2
|
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
|
4
|
-
import { createColumnHelper } from '@tanstack/react-table';
|
|
5
3
|
import { startTransition } from 'react';
|
|
6
|
-
import {
|
|
4
|
+
import { AnalyticsBreakdowns } from '#/components/analytics-breakdowns';
|
|
7
5
|
import { MetricCard } from '#/components/metric-card';
|
|
8
6
|
import { PageHeader } from '#/components/page-header';
|
|
9
7
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '#/components/ui/select';
|
|
@@ -16,34 +14,6 @@ import {
|
|
|
16
14
|
withAnalyticsProjectSearch,
|
|
17
15
|
} from '#/lib/route-search';
|
|
18
16
|
|
|
19
|
-
const toolUsageColumnHelper = createColumnHelper<ToolUsageSummary>();
|
|
20
|
-
const toolUsageColumns = [
|
|
21
|
-
toolUsageColumnHelper.accessor('name', {
|
|
22
|
-
cell: (info) => <span className="font-mono text-sm">{info.getValue()}</span>,
|
|
23
|
-
header: 'Tool',
|
|
24
|
-
}),
|
|
25
|
-
toolUsageColumnHelper.accessor('count', {
|
|
26
|
-
cell: (info) => <span className="font-mono text-sm">{formatNumber(info.getValue())}</span>,
|
|
27
|
-
header: 'Calls',
|
|
28
|
-
}),
|
|
29
|
-
] as const;
|
|
30
|
-
|
|
31
|
-
const modelColumnHelper = createColumnHelper<ModelTokenSummary>();
|
|
32
|
-
const modelColumns = [
|
|
33
|
-
modelColumnHelper.accessor('model', {
|
|
34
|
-
cell: (info) => <span className="font-mono text-sm">{info.getValue()}</span>,
|
|
35
|
-
header: 'Model',
|
|
36
|
-
}),
|
|
37
|
-
modelColumnHelper.accessor('threadCount', {
|
|
38
|
-
cell: (info) => <span className="font-mono text-sm">{formatNumber(info.getValue())}</span>,
|
|
39
|
-
header: 'Threads',
|
|
40
|
-
}),
|
|
41
|
-
modelColumnHelper.accessor('totalTokens', {
|
|
42
|
-
cell: (info) => <span className="font-mono text-sm">{formatTokens(info.getValue())}</span>,
|
|
43
|
-
header: 'Tokens',
|
|
44
|
-
}),
|
|
45
|
-
] as const;
|
|
46
|
-
|
|
47
17
|
export const Route = createFileRoute('/analytics')({
|
|
48
18
|
component: AnalyticsPage,
|
|
49
19
|
loader: ({ context, deps }) => {
|
|
@@ -98,7 +68,7 @@ function AnalyticsPage() {
|
|
|
98
68
|
title="Analytics"
|
|
99
69
|
/>
|
|
100
70
|
|
|
101
|
-
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-
|
|
71
|
+
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
|
102
72
|
<MetricCard label="Threads" value={formatNumber(analytics.summary.totalThreads)} />
|
|
103
73
|
<MetricCard label="Projects" value={formatNumber(analytics.summary.totalProjects)} />
|
|
104
74
|
<MetricCard label="Tokens" value={formatTokens(analytics.summary.totalTokens)} />
|
|
@@ -106,38 +76,17 @@ function AnalyticsPage() {
|
|
|
106
76
|
label="Average per thread"
|
|
107
77
|
value={formatTokens(Math.round(analytics.summary.averageTokensPerThread))}
|
|
108
78
|
/>
|
|
79
|
+
<MetricCard label="Median per thread" value={formatTokens(analytics.summary.medianTokensPerThread)} />
|
|
109
80
|
<MetricCard label="Web search threads" value={formatNumber(analytics.summary.threadsWithWebSearch)} />
|
|
81
|
+
<MetricCard label="Archived threads" value={formatNumber(analytics.summary.archivedThreads)} />
|
|
110
82
|
</div>
|
|
111
83
|
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
</p>
|
|
119
|
-
</div>
|
|
120
|
-
<DataTable
|
|
121
|
-
columns={toolUsageColumns}
|
|
122
|
-
data={analytics.toolUsage}
|
|
123
|
-
emptyMessage="No tool calls recorded."
|
|
124
|
-
/>
|
|
125
|
-
</section>
|
|
126
|
-
|
|
127
|
-
<section className="space-y-4">
|
|
128
|
-
<div>
|
|
129
|
-
<h3 className="font-semibold text-sm">Model token breakdown</h3>
|
|
130
|
-
<p className="mt-1 text-[var(--muted-foreground)] text-sm">
|
|
131
|
-
Compare model usage and token concentration within the current project scope.
|
|
132
|
-
</p>
|
|
133
|
-
</div>
|
|
134
|
-
<DataTable
|
|
135
|
-
columns={modelColumns}
|
|
136
|
-
data={analytics.modelsByTokens}
|
|
137
|
-
emptyMessage="No model usage recorded."
|
|
138
|
-
/>
|
|
139
|
-
</section>
|
|
140
|
-
</div>
|
|
84
|
+
<AnalyticsBreakdowns
|
|
85
|
+
modelsByTokens={analytics.modelsByTokens}
|
|
86
|
+
reasoningEfforts={analytics.reasoningEfforts}
|
|
87
|
+
sources={analytics.sources}
|
|
88
|
+
toolUsage={analytics.toolUsage}
|
|
89
|
+
/>
|
|
141
90
|
</div>
|
|
142
91
|
);
|
|
143
92
|
}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import { useMutation, useSuspenseQuery } from '@tanstack/react-query';
|
|
2
|
-
import { createFileRoute, Link } from '@tanstack/react-router';
|
|
3
|
-
import { Download, ScrollText } from 'lucide-react';
|
|
1
|
+
import { useMutation, useQueryClient, useSuspenseQuery } from '@tanstack/react-query';
|
|
2
|
+
import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
|
|
3
|
+
import { Download, ScrollText, Trash2 } from 'lucide-react';
|
|
4
4
|
import { useMemo, useState } from 'react';
|
|
5
5
|
import { AntigravityKeychainPanel } from '#/components/antigravity-keychain-panel';
|
|
6
6
|
import { Breadcrumbs } from '#/components/breadcrumbs';
|
|
7
|
+
import { DeleteConfirmDialog } from '#/components/delete-confirm-dialog';
|
|
8
|
+
import { ExportDialog } from '#/components/export-dialog';
|
|
7
9
|
import { JsonPanel } from '#/components/json-panel';
|
|
8
10
|
import { LoadingPanel } from '#/components/loading-panel';
|
|
9
11
|
import { MetadataSection } from '#/components/metadata-section';
|
|
10
12
|
import { MetricCard } from '#/components/metric-card';
|
|
11
13
|
import { PageHeader } from '#/components/page-header';
|
|
12
|
-
import {
|
|
14
|
+
import { RouteErrorPanel } from '#/components/route-error-panel';
|
|
13
15
|
import { TextDocumentPanel } from '#/components/text-document-panel';
|
|
14
|
-
import { TranscriptView } from '#/components/transcript-view';
|
|
16
|
+
import { DEFAULT_SHOW_USER_MESSAGES, TranscriptView } from '#/components/transcript-view';
|
|
15
17
|
import { Button } from '#/components/ui/button';
|
|
16
18
|
import { Checkbox } from '#/components/ui/checkbox';
|
|
17
19
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '#/components/ui/tabs';
|
|
@@ -19,8 +21,10 @@ import { canExportAntigravityConversation } from '#/lib/antigravity-conversation
|
|
|
19
21
|
import {
|
|
20
22
|
antigravityConversationDetailQueryOptions,
|
|
21
23
|
antigravityDecryptionQueryOptions,
|
|
24
|
+
antigravityWorkspacesQueryOptions,
|
|
22
25
|
} from '#/lib/antigravity-queries';
|
|
23
26
|
import {
|
|
27
|
+
deleteAntigravityConversationFn,
|
|
24
28
|
exportAntigravityArtifactsFn,
|
|
25
29
|
exportAntigravityConversationFn,
|
|
26
30
|
type getAntigravityConversationDetailFn,
|
|
@@ -29,8 +33,11 @@ import {
|
|
|
29
33
|
antigravityMarkdownToThreadEvents,
|
|
30
34
|
getAntigravityThreadTranscriptStats,
|
|
31
35
|
} from '#/lib/antigravity-transcript-events';
|
|
32
|
-
import { downloadTextFile } from '#/lib/download';
|
|
33
|
-
import {
|
|
36
|
+
import { downloadTextFile, downloadUrlFile } from '#/lib/download';
|
|
37
|
+
import type { ExportDialogOptions } from '#/lib/export-options';
|
|
38
|
+
import { formatBytes, formatDateTime, formatList, formatNumber } from '#/lib/formatters';
|
|
39
|
+
import { RouteStateResetBoundary } from '#/lib/route-state-reset';
|
|
40
|
+
import { shouldNavigateToSourceIndexAfterDelete } from '#/lib/workspace-delete-navigation';
|
|
34
41
|
|
|
35
42
|
type AntigravityConversationDetail = Awaited<ReturnType<typeof getAntigravityConversationDetailFn>>;
|
|
36
43
|
|
|
@@ -56,10 +63,10 @@ const buildConversationMetadata = (detail: AntigravityConversationDetail) => {
|
|
|
56
63
|
value: (
|
|
57
64
|
<Link
|
|
58
65
|
className="text-[var(--accent)]"
|
|
59
|
-
params={{ workspaceKey: detail.
|
|
66
|
+
params={{ workspaceKey: detail.conversationGroup.key }}
|
|
60
67
|
to="/antigravity/$workspaceKey"
|
|
61
68
|
>
|
|
62
|
-
{detail.
|
|
69
|
+
{detail.conversationGroup.label}
|
|
63
70
|
</Link>
|
|
64
71
|
),
|
|
65
72
|
},
|
|
@@ -95,7 +102,7 @@ const buildTranscriptStatsItems = (
|
|
|
95
102
|
|
|
96
103
|
const stats = getAntigravityThreadTranscriptStats(events);
|
|
97
104
|
return [
|
|
98
|
-
{ label: 'Event kinds', value: [...new Set(events.map((event) => event.kind))]
|
|
105
|
+
{ label: 'Event kinds', value: formatList([...new Set(events.map((event) => event.kind))]) },
|
|
99
106
|
{ label: 'Messages', value: formatNumber(stats.messageCount) },
|
|
100
107
|
{ label: 'User messages', value: formatNumber(stats.userMessageCount) },
|
|
101
108
|
{ label: 'Assistant messages', value: formatNumber(stats.assistantMessageCount) },
|
|
@@ -106,8 +113,23 @@ const buildTranscriptStatsItems = (
|
|
|
106
113
|
];
|
|
107
114
|
};
|
|
108
115
|
|
|
116
|
+
const getConversationSizeLabel = (detail: AntigravityConversationDetail): string => {
|
|
117
|
+
if (detail.conversation.totalBytes > 0) {
|
|
118
|
+
return formatBytes(detail.conversation.totalBytes);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return detail.conversation.summaryPath ? 'Summary' : formatBytes(detail.conversation.totalBytes);
|
|
122
|
+
};
|
|
123
|
+
|
|
109
124
|
export const Route = createFileRoute('/antigravity-conversations/$conversationId')({
|
|
110
|
-
component:
|
|
125
|
+
component: () => {
|
|
126
|
+
const { conversationId } = Route.useParams();
|
|
127
|
+
return (
|
|
128
|
+
<RouteStateResetBoundary routeKey={conversationId}>
|
|
129
|
+
<AntigravityConversationDetailPage />
|
|
130
|
+
</RouteStateResetBoundary>
|
|
131
|
+
);
|
|
132
|
+
},
|
|
111
133
|
errorComponent: AntigravityConversationDetailErrorComponent,
|
|
112
134
|
loader: async ({ context, params }) => {
|
|
113
135
|
await Promise.all([
|
|
@@ -124,7 +146,7 @@ export const Route = createFileRoute('/antigravity-conversations/$conversationId
|
|
|
124
146
|
});
|
|
125
147
|
|
|
126
148
|
function AntigravityConversationDetailErrorComponent({ error }: { error: Error }) {
|
|
127
|
-
return <
|
|
149
|
+
return <RouteErrorPanel error={error} title="Failed to load Antigravity conversation" />;
|
|
128
150
|
}
|
|
129
151
|
|
|
130
152
|
function AntigravityConversationHeaderActions({
|
|
@@ -133,6 +155,7 @@ function AntigravityConversationHeaderActions({
|
|
|
133
155
|
exportArtifactsPending,
|
|
134
156
|
exportConversationPending,
|
|
135
157
|
showConversationExport,
|
|
158
|
+
onDeleteConversation,
|
|
136
159
|
onExportArtifacts,
|
|
137
160
|
onExportConversation,
|
|
138
161
|
}: {
|
|
@@ -141,6 +164,7 @@ function AntigravityConversationHeaderActions({
|
|
|
141
164
|
exportArtifactsPending: boolean;
|
|
142
165
|
exportConversationPending: boolean;
|
|
143
166
|
showConversationExport: boolean;
|
|
167
|
+
onDeleteConversation: () => void;
|
|
144
168
|
onExportArtifacts: () => void;
|
|
145
169
|
onExportConversation: () => void;
|
|
146
170
|
}) {
|
|
@@ -170,6 +194,15 @@ function AntigravityConversationHeaderActions({
|
|
|
170
194
|
Export artifacts
|
|
171
195
|
</Button>
|
|
172
196
|
) : null}
|
|
197
|
+
<Button
|
|
198
|
+
className="rounded-full border-[var(--destructive)]/20 text-[var(--destructive)]"
|
|
199
|
+
type="button"
|
|
200
|
+
variant="outline"
|
|
201
|
+
onClick={onDeleteConversation}
|
|
202
|
+
>
|
|
203
|
+
<Trash2 className="mr-2 size-4" />
|
|
204
|
+
Delete
|
|
205
|
+
</Button>
|
|
173
206
|
</div>
|
|
174
207
|
);
|
|
175
208
|
}
|
|
@@ -281,13 +314,17 @@ function AntigravityRawPanels({
|
|
|
281
314
|
}
|
|
282
315
|
|
|
283
316
|
function AntigravityConversationDetailPage() {
|
|
317
|
+
const navigate = useNavigate();
|
|
318
|
+
const queryClient = useQueryClient();
|
|
284
319
|
const decryptionState = useSuspenseQuery(antigravityDecryptionQueryOptions()).data;
|
|
285
320
|
const detail = useSuspenseQuery(antigravityConversationDetailQueryOptions(Route.useParams().conversationId)).data;
|
|
321
|
+
const [deleteOpen, setDeleteOpen] = useState(false);
|
|
322
|
+
const [exportOpen, setExportOpen] = useState(false);
|
|
286
323
|
const [showToolCalls, setShowToolCalls] = useState(false);
|
|
287
324
|
const [showCommentary, setShowCommentary] = useState(false);
|
|
288
325
|
const [showExtraEvents, setShowExtraEvents] = useState(false);
|
|
289
326
|
const [showRawJson, setShowRawJson] = useState(false);
|
|
290
|
-
const [showUserMessages, setShowUserMessages] = useState(
|
|
327
|
+
const [showUserMessages, setShowUserMessages] = useState(DEFAULT_SHOW_USER_MESSAGES);
|
|
291
328
|
const transcriptEvents = useMemo(
|
|
292
329
|
() => antigravityMarkdownToThreadEvents(detail.conversationMarkdown),
|
|
293
330
|
[detail.conversationMarkdown],
|
|
@@ -300,10 +337,26 @@ function AntigravityConversationDetailPage() {
|
|
|
300
337
|
const showConversationExport = canExportConversation || detail.transcriptLocked;
|
|
301
338
|
|
|
302
339
|
const exportConversationMutation = useMutation({
|
|
303
|
-
mutationFn: () =>
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
340
|
+
mutationFn: async (options: ExportDialogOptions) => {
|
|
341
|
+
const download = await exportAntigravityConversationFn({
|
|
342
|
+
data: {
|
|
343
|
+
conversationId: detail.conversation.conversationId,
|
|
344
|
+
includeCommentary: options.includeCommentary,
|
|
345
|
+
includeMetadata: options.includeMetadata,
|
|
346
|
+
includeTools: options.includeTools,
|
|
347
|
+
outputFormat: options.outputFormat,
|
|
348
|
+
zipArchive: options.zipArchive,
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
if (download.mode === 'download') {
|
|
352
|
+
downloadTextFile(download.fileName, download.content, download.mimeType);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
await downloadUrlFile(download.fileName, download.downloadUrl);
|
|
357
|
+
},
|
|
358
|
+
onSuccess: () => {
|
|
359
|
+
setExportOpen(false);
|
|
307
360
|
},
|
|
308
361
|
});
|
|
309
362
|
|
|
@@ -315,6 +368,37 @@ function AntigravityConversationDetailPage() {
|
|
|
315
368
|
},
|
|
316
369
|
});
|
|
317
370
|
|
|
371
|
+
const deleteConversationMutation = useMutation({
|
|
372
|
+
mutationFn: () =>
|
|
373
|
+
deleteAntigravityConversationFn({ data: { conversationId: detail.conversation.conversationId } }),
|
|
374
|
+
onSuccess: async () => {
|
|
375
|
+
await Promise.all([
|
|
376
|
+
queryClient.invalidateQueries({ queryKey: ['antigravity-workspaces'] }),
|
|
377
|
+
queryClient.invalidateQueries({
|
|
378
|
+
queryKey: ['antigravity-conversations', detail.conversationGroup.key],
|
|
379
|
+
}),
|
|
380
|
+
queryClient.invalidateQueries({
|
|
381
|
+
queryKey: ['antigravity-conversation', detail.conversation.conversationId],
|
|
382
|
+
}),
|
|
383
|
+
]);
|
|
384
|
+
const workspaces = await queryClient.fetchQuery(antigravityWorkspacesQueryOptions());
|
|
385
|
+
if (
|
|
386
|
+
shouldNavigateToSourceIndexAfterDelete(
|
|
387
|
+
workspaces,
|
|
388
|
+
detail.conversationGroup.key,
|
|
389
|
+
(workspace) => workspace.key,
|
|
390
|
+
)
|
|
391
|
+
) {
|
|
392
|
+
navigate({ to: '/antigravity' });
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
navigate({
|
|
396
|
+
params: { workspaceKey: detail.conversationGroup.key },
|
|
397
|
+
to: '/antigravity/$workspaceKey',
|
|
398
|
+
});
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
|
|
318
402
|
return (
|
|
319
403
|
<div className="space-y-6">
|
|
320
404
|
<PageHeader
|
|
@@ -325,8 +409,9 @@ function AntigravityConversationDetailPage() {
|
|
|
325
409
|
exportArtifactsPending={exportArtifactsMutation.isPending}
|
|
326
410
|
exportConversationPending={exportConversationMutation.isPending}
|
|
327
411
|
showConversationExport={showConversationExport}
|
|
412
|
+
onDeleteConversation={() => setDeleteOpen(true)}
|
|
328
413
|
onExportArtifacts={() => exportArtifactsMutation.mutate()}
|
|
329
|
-
onExportConversation={() =>
|
|
414
|
+
onExportConversation={() => setExportOpen(true)}
|
|
330
415
|
/>
|
|
331
416
|
}
|
|
332
417
|
breadcrumb={
|
|
@@ -334,8 +419,8 @@ function AntigravityConversationDetailPage() {
|
|
|
334
419
|
items={[
|
|
335
420
|
{ label: 'Antigravity', to: '/antigravity' },
|
|
336
421
|
{
|
|
337
|
-
label: detail.
|
|
338
|
-
params: { workspaceKey: detail.
|
|
422
|
+
label: detail.conversationGroup.label,
|
|
423
|
+
params: { workspaceKey: detail.conversationGroup.key },
|
|
339
424
|
to: '/antigravity/$workspaceKey',
|
|
340
425
|
},
|
|
341
426
|
{ label: detail.conversation.title },
|
|
@@ -352,7 +437,7 @@ function AntigravityConversationDetailPage() {
|
|
|
352
437
|
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
|
|
353
438
|
<MetricCard label="Transcript entries" value={formatNumber(detail.conversation.transcriptEntryCount)} />
|
|
354
439
|
<MetricCard label="Artifacts" value={formatNumber(detail.conversation.artifactCount)} />
|
|
355
|
-
<MetricCard label="Size" value={
|
|
440
|
+
<MetricCard label="Size" value={getConversationSizeLabel(detail)} />
|
|
356
441
|
<MetricCard
|
|
357
442
|
helper={detail.conversation.transcriptSource ?? 'summary'}
|
|
358
443
|
label="Indexed items"
|
|
@@ -418,14 +503,6 @@ function AntigravityConversationDetailPage() {
|
|
|
418
503
|
</TabsContent>
|
|
419
504
|
</Tabs>
|
|
420
505
|
|
|
421
|
-
{exportConversationMutation.isError ? (
|
|
422
|
-
<p className="text-[var(--destructive)] text-sm">
|
|
423
|
-
{exportConversationMutation.error instanceof Error
|
|
424
|
-
? exportConversationMutation.error.message
|
|
425
|
-
: 'Conversation export failed'}
|
|
426
|
-
</p>
|
|
427
|
-
) : null}
|
|
428
|
-
|
|
429
506
|
{exportArtifactsMutation.isError ? (
|
|
430
507
|
<p className="text-[var(--destructive)] text-sm">
|
|
431
508
|
{exportArtifactsMutation.error instanceof Error
|
|
@@ -433,6 +510,49 @@ function AntigravityConversationDetailPage() {
|
|
|
433
510
|
: 'Artifact export failed'}
|
|
434
511
|
</p>
|
|
435
512
|
) : null}
|
|
513
|
+
|
|
514
|
+
<ExportDialog
|
|
515
|
+
errorMessage={
|
|
516
|
+
exportConversationMutation.isError
|
|
517
|
+
? exportConversationMutation.error instanceof Error
|
|
518
|
+
? exportConversationMutation.error.message
|
|
519
|
+
: 'Conversation export failed'
|
|
520
|
+
: null
|
|
521
|
+
}
|
|
522
|
+
open={exportOpen}
|
|
523
|
+
pending={exportConversationMutation.isPending}
|
|
524
|
+
showCommentaryOption={detail.conversation.transcriptSource !== 'safe-storage'}
|
|
525
|
+
showToolsOption={detail.conversation.transcriptSource !== 'safe-storage'}
|
|
526
|
+
title={`Export ${detail.conversation.title}`}
|
|
527
|
+
onExport={(options) => exportConversationMutation.mutate(options)}
|
|
528
|
+
onOpenChange={(open) => {
|
|
529
|
+
setExportOpen(open);
|
|
530
|
+
if (!open) {
|
|
531
|
+
exportConversationMutation.reset();
|
|
532
|
+
}
|
|
533
|
+
}}
|
|
534
|
+
/>
|
|
535
|
+
|
|
536
|
+
<DeleteConfirmDialog
|
|
537
|
+
confirmLabel={deleteConversationMutation.isPending ? 'Deleting...' : 'Delete conversation'}
|
|
538
|
+
description="Permanently delete this Antigravity conversation from disk. This removes its summary entry, conversation file, transcript logs, and generated artifacts."
|
|
539
|
+
errorMessage={
|
|
540
|
+
deleteConversationMutation.isError
|
|
541
|
+
? deleteConversationMutation.error instanceof Error
|
|
542
|
+
? deleteConversationMutation.error.message
|
|
543
|
+
: 'Conversation delete failed'
|
|
544
|
+
: null
|
|
545
|
+
}
|
|
546
|
+
open={deleteOpen}
|
|
547
|
+
title="Delete this Antigravity conversation?"
|
|
548
|
+
onConfirm={() => deleteConversationMutation.mutate()}
|
|
549
|
+
onOpenChange={(open) => {
|
|
550
|
+
setDeleteOpen(open);
|
|
551
|
+
if (!open) {
|
|
552
|
+
deleteConversationMutation.reset();
|
|
553
|
+
}
|
|
554
|
+
}}
|
|
555
|
+
/>
|
|
436
556
|
</div>
|
|
437
557
|
);
|
|
438
558
|
}
|