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
|
@@ -1,21 +1,43 @@
|
|
|
1
1
|
import type { AntigravityConversation, AntigravityWorkspaceGroup } from '@spiracha/lib/antigravity-exporter-types';
|
|
2
|
-
import { useMutation, useSuspenseQuery } from '@tanstack/react-query';
|
|
3
|
-
import { createFileRoute } from '@tanstack/react-router';
|
|
4
|
-
import {
|
|
2
|
+
import { useMutation, useQueryClient, useSuspenseQuery } from '@tanstack/react-query';
|
|
3
|
+
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
|
4
|
+
import { Trash2 } from 'lucide-react';
|
|
5
|
+
import { useDeferredValue, useMemo, useState } from 'react';
|
|
5
6
|
import { AntigravityConversationsTable } from '#/components/antigravity-conversations-table';
|
|
6
7
|
import { AntigravityKeychainPanel } from '#/components/antigravity-keychain-panel';
|
|
8
|
+
import { DeleteConfirmDialog } from '#/components/delete-confirm-dialog';
|
|
9
|
+
import { ExportDialog } from '#/components/export-dialog';
|
|
7
10
|
import { ListSearchInput } from '#/components/list-search-input';
|
|
8
11
|
import { LoadingPanel } from '#/components/loading-panel';
|
|
9
12
|
import { PageHeader } from '#/components/page-header';
|
|
10
|
-
import {
|
|
13
|
+
import { RouteErrorPanel } from '#/components/route-error-panel';
|
|
14
|
+
import { Button } from '#/components/ui/button';
|
|
11
15
|
import {
|
|
12
16
|
antigravityConversationsQueryOptions,
|
|
13
17
|
antigravityDecryptionQueryOptions,
|
|
14
18
|
antigravityWorkspacesQueryOptions,
|
|
15
19
|
} from '#/lib/antigravity-queries';
|
|
16
|
-
import {
|
|
17
|
-
|
|
20
|
+
import {
|
|
21
|
+
deleteAntigravityConversationFn,
|
|
22
|
+
deleteAntigravityConversationsFn,
|
|
23
|
+
exportAntigravityArtifactsFn,
|
|
24
|
+
exportAntigravityConversationsFn,
|
|
25
|
+
} from '#/lib/antigravity-server';
|
|
26
|
+
import { downloadTextFile, downloadUrlFile } from '#/lib/download';
|
|
27
|
+
import { createExportSelectionMutationInput, type ExportSelectionMutationInput } from '#/lib/export-mutation';
|
|
18
28
|
import { matchesTextQuery } from '#/lib/text-filter';
|
|
29
|
+
import { isWorkspaceEmptiedByDelete } from '#/lib/workspace-delete-navigation';
|
|
30
|
+
|
|
31
|
+
type PendingConversationDelete = {
|
|
32
|
+
conversations: AntigravityConversation[];
|
|
33
|
+
scope: 'all' | 'selected';
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type PendingConversationExport = {
|
|
37
|
+
conversationIds: string[];
|
|
38
|
+
label: string;
|
|
39
|
+
supportsTranscriptFilters: boolean;
|
|
40
|
+
};
|
|
19
41
|
|
|
20
42
|
const findWorkspaceOrThrow = (workspaces: AntigravityWorkspaceGroup[], workspaceKey: string) => {
|
|
21
43
|
const workspace = workspaces.find((candidate) => candidate.key === workspaceKey);
|
|
@@ -26,6 +48,63 @@ const findWorkspaceOrThrow = (workspaces: AntigravityWorkspaceGroup[], workspace
|
|
|
26
48
|
return workspace;
|
|
27
49
|
};
|
|
28
50
|
|
|
51
|
+
const buildConversationExport = (selectedConversations: AntigravityConversation[]): PendingConversationExport => ({
|
|
52
|
+
conversationIds: selectedConversations.map((conversation) => conversation.conversationId),
|
|
53
|
+
label:
|
|
54
|
+
selectedConversations.length === 1
|
|
55
|
+
? selectedConversations[0]!.title
|
|
56
|
+
: `${selectedConversations.length} selected conversations`,
|
|
57
|
+
supportsTranscriptFilters: selectedConversations.every(
|
|
58
|
+
(conversation) => conversation.transcriptSource !== 'safe-storage',
|
|
59
|
+
),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const getDeleteConfirmLabel = (pendingDelete: PendingConversationDelete | null, isPending: boolean) => {
|
|
63
|
+
if (isPending) {
|
|
64
|
+
return 'Deleting...';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (pendingDelete?.scope === 'all') {
|
|
68
|
+
return 'Delete all';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return pendingDelete && pendingDelete.conversations.length > 1 ? 'Delete conversations' : 'Delete conversation';
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const getDeleteDescription = (pendingDelete: PendingConversationDelete | null) => {
|
|
75
|
+
if (!pendingDelete) {
|
|
76
|
+
return 'Permanently delete the selected Antigravity conversations from disk.';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (pendingDelete.scope === 'all') {
|
|
80
|
+
return `Permanently delete all ${pendingDelete.conversations.length} Antigravity conversations in this workspace from disk. This removes their summaries, conversation files, transcript logs, and generated artifacts.`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (pendingDelete.conversations.length === 1) {
|
|
84
|
+
return `Permanently delete "${pendingDelete.conversations[0]!.title}" from Antigravity history. This removes the summary entry, conversation file, transcript logs, and generated artifacts that belong to this conversation.`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return `Permanently delete ${pendingDelete.conversations.length} selected Antigravity conversations from disk. This removes their summaries, conversation files, transcript logs, and generated artifacts.`;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const getDeleteTitle = (pendingDelete: PendingConversationDelete | null) => {
|
|
91
|
+
if (pendingDelete?.scope === 'all') {
|
|
92
|
+
return `Delete all ${pendingDelete.conversations.length} Antigravity conversations?`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return pendingDelete && pendingDelete.conversations.length > 1
|
|
96
|
+
? `Delete ${pendingDelete.conversations.length} Antigravity conversations?`
|
|
97
|
+
: 'Delete this Antigravity conversation?';
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const AntigravityWorkspaceErrors = ({ artifactError }: { artifactError: Error | null }) => {
|
|
101
|
+
if (!artifactError) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return <p className="text-[var(--destructive)] text-sm">{artifactError.message}</p>;
|
|
106
|
+
};
|
|
107
|
+
|
|
29
108
|
export const Route = createFileRoute('/antigravity/$workspaceKey')({
|
|
30
109
|
component: AntigravityWorkspacePage,
|
|
31
110
|
errorComponent: AntigravityWorkspaceErrorComponent,
|
|
@@ -46,52 +125,142 @@ export const Route = createFileRoute('/antigravity/$workspaceKey')({
|
|
|
46
125
|
});
|
|
47
126
|
|
|
48
127
|
function AntigravityWorkspaceErrorComponent({ error }: { error: Error }) {
|
|
49
|
-
return <
|
|
128
|
+
return <RouteErrorPanel error={error} title="Failed to load Antigravity workspace" />;
|
|
50
129
|
}
|
|
51
130
|
|
|
52
131
|
function AntigravityWorkspacePage() {
|
|
132
|
+
const navigate = useNavigate({ from: Route.fullPath });
|
|
53
133
|
const params = Route.useParams();
|
|
134
|
+
const queryClient = useQueryClient();
|
|
54
135
|
const workspaces = useSuspenseQuery(antigravityWorkspacesQueryOptions()).data;
|
|
55
136
|
const workspace = findWorkspaceOrThrow(workspaces, params.workspaceKey);
|
|
56
137
|
const conversations = useSuspenseQuery(antigravityConversationsQueryOptions(workspace.key)).data;
|
|
57
138
|
const decryptionState = useSuspenseQuery(antigravityDecryptionQueryOptions()).data ?? null;
|
|
58
139
|
const [searchInput, setSearchInput] = useState('');
|
|
140
|
+
const [pendingDelete, setPendingDelete] = useState<PendingConversationDelete | null>(null);
|
|
141
|
+
const [pendingExport, setPendingExport] = useState<PendingConversationExport | null>(null);
|
|
59
142
|
const deferredSearch = useDeferredValue(searchInput);
|
|
60
143
|
|
|
61
|
-
const
|
|
144
|
+
const exportArtifactsMutation = useMutation({
|
|
62
145
|
mutationFn: (conversation: AntigravityConversation) =>
|
|
63
|
-
|
|
146
|
+
exportAntigravityArtifactsFn({ data: { conversationId: conversation.conversationId } }),
|
|
64
147
|
onSuccess: (result) => {
|
|
65
148
|
downloadTextFile(result.filename, result.content, 'text/markdown; charset=utf-8');
|
|
66
149
|
},
|
|
67
150
|
});
|
|
68
151
|
|
|
69
|
-
const
|
|
70
|
-
mutationFn: (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
152
|
+
const exportConversationsMutation = useMutation({
|
|
153
|
+
mutationFn: async ({ ids, options }: ExportSelectionMutationInput) => {
|
|
154
|
+
const download = await exportAntigravityConversationsFn({
|
|
155
|
+
data: {
|
|
156
|
+
conversationIds: [...ids],
|
|
157
|
+
includeCommentary: options.includeCommentary,
|
|
158
|
+
includeMetadata: options.includeMetadata,
|
|
159
|
+
includeTools: options.includeTools,
|
|
160
|
+
outputFormat: options.outputFormat,
|
|
161
|
+
zipArchive: options.zipArchive,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (download.mode === 'download') {
|
|
166
|
+
downloadTextFile(download.fileName, download.content, download.mimeType);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
await downloadUrlFile(download.fileName, download.downloadUrl);
|
|
171
|
+
},
|
|
172
|
+
onSuccess: () => {
|
|
173
|
+
setPendingExport(null);
|
|
74
174
|
},
|
|
75
175
|
});
|
|
76
176
|
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
177
|
+
const deleteMutation = useMutation({
|
|
178
|
+
mutationFn: async (conversationIds: string[]) =>
|
|
179
|
+
conversationIds.length === 1
|
|
180
|
+
? deleteAntigravityConversationFn({ data: { conversationId: conversationIds[0]! } })
|
|
181
|
+
: deleteAntigravityConversationsFn({ data: { conversationIds } }),
|
|
182
|
+
onSettled: async (_result, _error, conversationIds) => {
|
|
183
|
+
await Promise.all([
|
|
184
|
+
queryClient.invalidateQueries({ queryKey: ['antigravity-workspaces'] }),
|
|
185
|
+
queryClient.invalidateQueries({ queryKey: ['antigravity-conversations', workspace.key] }),
|
|
186
|
+
...conversationIds.map((conversationId) =>
|
|
187
|
+
queryClient.invalidateQueries({
|
|
188
|
+
queryKey: ['antigravity-conversation', conversationId],
|
|
189
|
+
}),
|
|
190
|
+
),
|
|
191
|
+
]);
|
|
192
|
+
},
|
|
193
|
+
onSuccess: async (result) => {
|
|
194
|
+
const conversationIds = result.deletedConversationIds;
|
|
195
|
+
const workspaceEmptied = isWorkspaceEmptiedByDelete(
|
|
196
|
+
conversations,
|
|
197
|
+
conversationIds,
|
|
198
|
+
(conversation) => conversation.conversationId,
|
|
199
|
+
);
|
|
200
|
+
setPendingDelete(null);
|
|
201
|
+
if (workspaceEmptied) {
|
|
202
|
+
await navigate({ to: '/antigravity' });
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const visibleConversations = useMemo(
|
|
208
|
+
() =>
|
|
209
|
+
conversations.filter((conversation) =>
|
|
210
|
+
matchesTextQuery(deferredSearch, [
|
|
211
|
+
conversation.title,
|
|
212
|
+
conversation.conversationId,
|
|
213
|
+
conversation.transcriptSource,
|
|
214
|
+
conversation.workspaceLabel,
|
|
215
|
+
]),
|
|
216
|
+
),
|
|
217
|
+
[conversations, deferredSearch],
|
|
84
218
|
);
|
|
219
|
+
const visibleConversationsById = useMemo(
|
|
220
|
+
() => new Map(visibleConversations.map((conversation) => [conversation.conversationId, conversation])),
|
|
221
|
+
[visibleConversations],
|
|
222
|
+
);
|
|
223
|
+
const lookupSelectedConversations = (conversationIds: string[]) =>
|
|
224
|
+
conversationIds
|
|
225
|
+
.map((conversationId) => visibleConversationsById.get(conversationId) ?? null)
|
|
226
|
+
.filter((conversation): conversation is AntigravityConversation => conversation !== null);
|
|
227
|
+
const openExportForConversations = (selectedConversations: AntigravityConversation[]) => {
|
|
228
|
+
if (selectedConversations.length === 0) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
setPendingExport(buildConversationExport(selectedConversations));
|
|
233
|
+
};
|
|
234
|
+
const openDeleteForConversations = (
|
|
235
|
+
selectedConversations: AntigravityConversation[],
|
|
236
|
+
scope: PendingConversationDelete['scope'],
|
|
237
|
+
) => {
|
|
238
|
+
if (selectedConversations.length > 0) {
|
|
239
|
+
setPendingDelete({ conversations: selectedConversations, scope });
|
|
240
|
+
}
|
|
241
|
+
};
|
|
85
242
|
|
|
86
243
|
return (
|
|
87
244
|
<div className="space-y-6">
|
|
88
245
|
<PageHeader
|
|
89
246
|
actions={
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
247
|
+
<div className="flex flex-col gap-2 sm:flex-row">
|
|
248
|
+
<Button
|
|
249
|
+
className="rounded-full"
|
|
250
|
+
disabled={deleteMutation.isPending || conversations.length === 0}
|
|
251
|
+
type="button"
|
|
252
|
+
variant="destructive"
|
|
253
|
+
onClick={() => openDeleteForConversations(conversations, 'all')}
|
|
254
|
+
>
|
|
255
|
+
<Trash2 className="size-4" />
|
|
256
|
+
Delete all
|
|
257
|
+
</Button>
|
|
258
|
+
<ListSearchInput
|
|
259
|
+
placeholder="Search title, id, or transcript source"
|
|
260
|
+
value={searchInput}
|
|
261
|
+
onValueChange={setSearchInput}
|
|
262
|
+
/>
|
|
263
|
+
</div>
|
|
95
264
|
}
|
|
96
265
|
eyebrow="Antigravity workspace"
|
|
97
266
|
subtitle="Inspect conversation coverage across Antigravity transcripts, raw payloads, and generated artifacts."
|
|
@@ -103,25 +272,76 @@ function AntigravityWorkspacePage() {
|
|
|
103
272
|
<AntigravityConversationsTable
|
|
104
273
|
conversations={visibleConversations}
|
|
105
274
|
decryptionState={decryptionState}
|
|
275
|
+
onDeleteConversation={(conversation) => openDeleteForConversations([conversation], 'selected')}
|
|
276
|
+
onDeleteConversations={(conversationIds) =>
|
|
277
|
+
openDeleteForConversations(lookupSelectedConversations(conversationIds), 'selected')
|
|
278
|
+
}
|
|
106
279
|
onExportArtifacts={(conversation) => exportArtifactsMutation.mutate(conversation)}
|
|
107
|
-
onExportConversation={(conversation) =>
|
|
280
|
+
onExportConversation={(conversation) => openExportForConversations([conversation])}
|
|
281
|
+
onExportConversations={(conversationIds) =>
|
|
282
|
+
openExportForConversations(lookupSelectedConversations(conversationIds))
|
|
283
|
+
}
|
|
108
284
|
/>
|
|
109
285
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
286
|
+
<AntigravityWorkspaceErrors
|
|
287
|
+
artifactError={exportArtifactsMutation.isError ? exportArtifactsMutation.error : null}
|
|
288
|
+
/>
|
|
289
|
+
|
|
290
|
+
<ExportDialog
|
|
291
|
+
errorMessage={
|
|
292
|
+
exportConversationsMutation.isError
|
|
293
|
+
? exportConversationsMutation.error instanceof Error
|
|
294
|
+
? exportConversationsMutation.error.message
|
|
295
|
+
: 'Conversation export failed'
|
|
296
|
+
: null
|
|
297
|
+
}
|
|
298
|
+
forceZipArchive={pendingExport ? pendingExport.conversationIds.length > 1 : false}
|
|
299
|
+
open={pendingExport !== null}
|
|
300
|
+
pending={exportConversationsMutation.isPending}
|
|
301
|
+
showCommentaryOption={pendingExport?.supportsTranscriptFilters ?? true}
|
|
302
|
+
showToolsOption={pendingExport?.supportsTranscriptFilters ?? true}
|
|
303
|
+
title={pendingExport ? `Export ${pendingExport.label}` : 'Export conversation'}
|
|
304
|
+
onExport={(options) => {
|
|
305
|
+
if (pendingExport) {
|
|
306
|
+
exportConversationsMutation.mutate(
|
|
307
|
+
createExportSelectionMutationInput(pendingExport.conversationIds, options),
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}}
|
|
311
|
+
onOpenChange={(open) => {
|
|
312
|
+
if (!open) {
|
|
313
|
+
setPendingExport(null);
|
|
314
|
+
exportConversationsMutation.reset();
|
|
315
|
+
}
|
|
316
|
+
}}
|
|
317
|
+
/>
|
|
318
|
+
|
|
319
|
+
<DeleteConfirmDialog
|
|
320
|
+
confirmLabel={getDeleteConfirmLabel(pendingDelete, deleteMutation.isPending)}
|
|
321
|
+
description={getDeleteDescription(pendingDelete)}
|
|
322
|
+
errorMessage={
|
|
323
|
+
deleteMutation.isError
|
|
324
|
+
? deleteMutation.error instanceof Error
|
|
325
|
+
? deleteMutation.error.message
|
|
326
|
+
: 'Conversation delete failed'
|
|
327
|
+
: null
|
|
328
|
+
}
|
|
329
|
+
open={pendingDelete !== null}
|
|
330
|
+
title={getDeleteTitle(pendingDelete)}
|
|
331
|
+
onConfirm={() => {
|
|
332
|
+
if (pendingDelete) {
|
|
333
|
+
deleteMutation.mutate(
|
|
334
|
+
pendingDelete.conversations.map((conversation) => conversation.conversationId),
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
}}
|
|
338
|
+
onOpenChange={(open) => {
|
|
339
|
+
if (!open) {
|
|
340
|
+
setPendingDelete(null);
|
|
341
|
+
deleteMutation.reset();
|
|
342
|
+
}
|
|
343
|
+
}}
|
|
344
|
+
/>
|
|
125
345
|
</div>
|
|
126
346
|
);
|
|
127
347
|
}
|
|
@@ -5,7 +5,7 @@ import { AntigravityKeychainPanel } from '#/components/antigravity-keychain-pane
|
|
|
5
5
|
import { AntigravityWorkspacesTable } from '#/components/antigravity-workspaces-table';
|
|
6
6
|
import { ListSearchInput } from '#/components/list-search-input';
|
|
7
7
|
import { PageHeader } from '#/components/page-header';
|
|
8
|
-
import {
|
|
8
|
+
import { RouteErrorPanel } from '#/components/route-error-panel';
|
|
9
9
|
import { antigravityWorkspacesQueryOptions } from '#/lib/antigravity-queries';
|
|
10
10
|
import { matchesTextQuery } from '#/lib/text-filter';
|
|
11
11
|
|
|
@@ -16,7 +16,7 @@ export const Route = createFileRoute('/antigravity/')({
|
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
function AntigravityErrorComponent({ error }: { error: Error }) {
|
|
19
|
-
return <
|
|
19
|
+
return <RouteErrorPanel error={error} title="Failed to load Antigravity workspaces" />;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function AntigravityPage() {
|
|
@@ -3,6 +3,10 @@ import { createFileRoute } from '@tanstack/react-router';
|
|
|
3
3
|
export const Route = createFileRoute('/api/v1/conversations/$source/$id')({
|
|
4
4
|
server: {
|
|
5
5
|
handlers: {
|
|
6
|
+
DELETE: async ({ request }) => {
|
|
7
|
+
const { handleConversationApiRequest } = await import('@spiracha/lib/conversation-api');
|
|
8
|
+
return handleConversationApiRequest(request);
|
|
9
|
+
},
|
|
6
10
|
GET: async ({ request }) => {
|
|
7
11
|
const { handleConversationApiRequest } = await import('@spiracha/lib/conversation-api');
|
|
8
12
|
return handleConversationApiRequest(request);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
2
|
+
|
|
3
|
+
export const Route = createFileRoute('/api/v1/conversations/delete')({
|
|
4
|
+
server: {
|
|
5
|
+
handlers: {
|
|
6
|
+
POST: async ({ request }) => {
|
|
7
|
+
const { handleConversationApiRequest } = await import('@spiracha/lib/conversation-api');
|
|
8
|
+
return handleConversationApiRequest(request);
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
2
|
+
|
|
3
|
+
export const Route = createFileRoute('/api/v1/conversations/export')({
|
|
4
|
+
server: {
|
|
5
|
+
handlers: {
|
|
6
|
+
POST: async ({ request }) => {
|
|
7
|
+
const { handleConversationApiRequest } = await import('@spiracha/lib/conversation-api');
|
|
8
|
+
return handleConversationApiRequest(request);
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
});
|