spiracha 2.0.0 → 2.1.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.
Files changed (104) hide show
  1. package/README.md +6 -1
  2. package/apps/ui/README.md +20 -2
  3. package/apps/ui/package.json +2 -2
  4. package/apps/ui/src/components/antigravity-conversations-table.tsx +50 -11
  5. package/apps/ui/src/components/antigravity-workspaces-table.tsx +1 -1
  6. package/apps/ui/src/components/app-shell.tsx +1 -0
  7. package/apps/ui/src/components/breadcrumbs.tsx +21 -3
  8. package/apps/ui/src/components/claude-code-sessions-table.tsx +42 -6
  9. package/apps/ui/src/components/claude-code-workspaces-table.tsx +3 -1
  10. package/apps/ui/src/components/cursor-threads-table.tsx +10 -47
  11. package/apps/ui/src/components/data-table.tsx +7 -5
  12. package/apps/ui/src/components/export-dialog.tsx +2 -12
  13. package/apps/ui/src/components/grok-sessions-table.tsx +146 -0
  14. package/apps/ui/src/components/grok-workspaces-table.tsx +53 -0
  15. package/apps/ui/src/components/kiro-sessions-table.tsx +40 -4
  16. package/apps/ui/src/components/opencode-sessions-table.tsx +40 -4
  17. package/apps/ui/src/components/page-header.tsx +15 -9
  18. package/apps/ui/src/components/qoder-sessions-table.tsx +23 -2
  19. package/apps/ui/src/components/qoder-workspaces-table.tsx +5 -1
  20. package/apps/ui/src/components/selection-actions-toolbar.tsx +80 -0
  21. package/apps/ui/src/components/threads-table.tsx +9 -46
  22. package/apps/ui/src/components/transcript-view.tsx +148 -52
  23. package/apps/ui/src/components/ui/select.tsx +1 -1
  24. package/apps/ui/src/lib/antigravity-conversation-state.ts +17 -4
  25. package/apps/ui/src/lib/antigravity-server.ts +152 -7
  26. package/apps/ui/src/lib/antigravity-transcript-events.ts +11 -3
  27. package/apps/ui/src/lib/claude-code-queries.ts +8 -0
  28. package/apps/ui/src/lib/claude-code-server.ts +175 -7
  29. package/apps/ui/src/lib/claude-code-transcript-events.ts +8 -1
  30. package/apps/ui/src/lib/codex-queries.ts +19 -3
  31. package/apps/ui/src/lib/codex-server.ts +135 -53
  32. package/apps/ui/src/lib/cursor-server.ts +48 -5
  33. package/apps/ui/src/lib/formatters.ts +3 -5
  34. package/apps/ui/src/lib/grok-queries.ts +22 -0
  35. package/apps/ui/src/lib/grok-server.ts +169 -0
  36. package/apps/ui/src/lib/grok-transcript-events.ts +149 -0
  37. package/apps/ui/src/lib/kiro-server.ts +85 -7
  38. package/apps/ui/src/lib/opencode-server.ts +85 -7
  39. package/apps/ui/src/lib/qoder-server.ts +67 -11
  40. package/apps/ui/src/lib/route-search.ts +114 -0
  41. package/apps/ui/src/lib/source-session-export-server.ts +86 -3
  42. package/apps/ui/src/lib/thread-transcript-load.ts +15 -0
  43. package/apps/ui/src/lib/workspace-delete-navigation.ts +12 -0
  44. package/apps/ui/src/routeTree.gen.ts +107 -0
  45. package/apps/ui/src/routes/antigravity-conversations.$conversationId.tsx +76 -10
  46. package/apps/ui/src/routes/antigravity.$workspaceKey.tsx +269 -31
  47. package/apps/ui/src/routes/api.v1.conversations.$source.$id.ts +4 -0
  48. package/apps/ui/src/routes/api.v1.conversations.delete.ts +12 -0
  49. package/apps/ui/src/routes/api.v1.conversations.export.ts +12 -0
  50. package/apps/ui/src/routes/claude-code-sessions.$sessionId.tsx +143 -17
  51. package/apps/ui/src/routes/claude-code.$workspaceKey.tsx +165 -26
  52. package/apps/ui/src/routes/cursor-threads.$composerId.tsx +2 -2
  53. package/apps/ui/src/routes/cursor.$workspaceKey.tsx +11 -1
  54. package/apps/ui/src/routes/grok-sessions.$sessionId.tsx +388 -0
  55. package/apps/ui/src/routes/grok.$workspaceKey.tsx +301 -0
  56. package/apps/ui/src/routes/grok.index.tsx +48 -0
  57. package/apps/ui/src/routes/kiro-sessions.$sessionId.tsx +66 -15
  58. package/apps/ui/src/routes/kiro.$workspaceKey.tsx +199 -32
  59. package/apps/ui/src/routes/opencode-sessions.$sessionId.tsx +105 -19
  60. package/apps/ui/src/routes/opencode.$workspaceKey.tsx +236 -44
  61. package/apps/ui/src/routes/opencode.index.tsx +16 -3
  62. package/apps/ui/src/routes/qoder-sessions.$sessionId.tsx +7 -4
  63. package/apps/ui/src/routes/qoder.$workspaceKey.tsx +75 -28
  64. package/apps/ui/src/routes/threads.$threadId.tsx +548 -64
  65. package/package.json +19 -18
  66. package/src/client.ts +134 -1
  67. package/src/lib/antigravity-db.ts +208 -32
  68. package/src/lib/antigravity-exporter-types.ts +3 -0
  69. package/src/lib/antigravity-projects.ts +201 -0
  70. package/src/lib/claude-code-db.ts +498 -32
  71. package/src/lib/claude-code-exporter-types.ts +5 -0
  72. package/src/lib/claude-code-transcript-phase.ts +60 -1
  73. package/src/lib/claude-code-transcript.ts +8 -5
  74. package/src/lib/codex-browser-export.ts +23 -27
  75. package/src/lib/codex-thread-cache.ts +41 -13
  76. package/src/lib/codex-thread-parser.ts +86 -35
  77. package/src/lib/codex-transcript-filter.ts +31 -0
  78. package/src/lib/codex-transcript-renderer.ts +39 -19
  79. package/src/lib/concurrency.ts +41 -0
  80. package/src/lib/conversation-api.ts +399 -19
  81. package/src/lib/conversation-data/antigravity-adapter.ts +21 -2
  82. package/src/lib/conversation-data/claude-code-adapter.ts +37 -6
  83. package/src/lib/conversation-data/codex-adapter.ts +28 -4
  84. package/src/lib/conversation-data/cursor-adapter.ts +35 -1
  85. package/src/lib/conversation-data/grok-adapter.ts +210 -0
  86. package/src/lib/conversation-data/index.ts +76 -1
  87. package/src/lib/conversation-data/kiro-adapter.ts +41 -7
  88. package/src/lib/conversation-data/opencode-adapter.ts +24 -2
  89. package/src/lib/conversation-data/qoder-adapter.ts +44 -19
  90. package/src/lib/conversation-data/types.ts +43 -0
  91. package/src/lib/conversation-zip-export.ts +57 -0
  92. package/src/lib/cursor-db.ts +34 -5
  93. package/src/lib/grok-db.ts +1026 -0
  94. package/src/lib/grok-exporter-types.ts +110 -0
  95. package/src/lib/grok-transcript-phase.ts +52 -0
  96. package/src/lib/grok-transcript.ts +154 -0
  97. package/src/lib/kiro-db.ts +52 -1
  98. package/src/lib/model-label.ts +11 -3
  99. package/src/lib/opencode-db.ts +598 -55
  100. package/src/lib/opencode-transcript.ts +8 -5
  101. package/src/lib/shared.ts +12 -0
  102. package/src/lib/transcript-load-limiter.ts +82 -0
  103. package/src/lib/ui-cache.ts +43 -17
  104. package/src/lib/ui-export-archive.ts +60 -0
@@ -0,0 +1,388 @@
1
+ import type { ThreadEvent, ThreadTranscriptStats } from '@spiracha/lib/codex-browser-types';
2
+ import type { GrokSessionTranscript } from '@spiracha/lib/grok-exporter-types';
3
+ import { useMutation, useQueryClient, useSuspenseQuery } from '@tanstack/react-query';
4
+ import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
5
+ import { Download, Trash2 } from 'lucide-react';
6
+ import { useMemo, useState } from 'react';
7
+ import { Breadcrumbs } from '#/components/breadcrumbs';
8
+ import { DeleteConfirmDialog } from '#/components/delete-confirm-dialog';
9
+ import { ExportDialog } from '#/components/export-dialog';
10
+ import { JsonPanel } from '#/components/json-panel';
11
+ import { LoadingPanel } from '#/components/loading-panel';
12
+ import { MetadataSection } from '#/components/metadata-section';
13
+ import { MetricCard } from '#/components/metric-card';
14
+ import { PageHeader } from '#/components/page-header';
15
+ import { ReloadErrorPanel } from '#/components/reload-error-panel';
16
+ import { DEFAULT_SHOW_USER_MESSAGES, TranscriptView } from '#/components/transcript-view';
17
+ import { Button } from '#/components/ui/button';
18
+ import { Checkbox } from '#/components/ui/checkbox';
19
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '#/components/ui/tabs';
20
+ import { downloadTextFile, downloadUrlFile } from '#/lib/download';
21
+ import { formatDateTime, formatList, formatNumber } from '#/lib/formatters';
22
+ import { grokSessionDetailQueryOptions } from '#/lib/grok-queries';
23
+ import { deleteGrokSessionFn, exportGrokSessionFn } from '#/lib/grok-server';
24
+ import { getGrokThreadTranscriptStats, grokTranscriptToThreadEvents } from '#/lib/grok-transcript-events';
25
+
26
+ type ExportDialogOptions = {
27
+ includeCommentary: boolean;
28
+ includeMetadata: boolean;
29
+ includeTools: boolean;
30
+ outputFormat: 'md' | 'txt';
31
+ zipArchive: boolean;
32
+ };
33
+
34
+ type TranscriptControlsProps = {
35
+ rawJsonDisabled?: boolean;
36
+ showCommentary: boolean;
37
+ showExtraEvents: boolean;
38
+ showRawJson: boolean;
39
+ showToolCalls: boolean;
40
+ showUserMessages: boolean;
41
+ onShowCommentaryChange: (checked: boolean) => void;
42
+ onShowExtraEventsChange: (checked: boolean) => void;
43
+ onShowRawJsonChange: (checked: boolean) => void;
44
+ onShowToolCallsChange: (checked: boolean) => void;
45
+ onShowUserMessagesChange: (checked: boolean) => void;
46
+ };
47
+
48
+ const GrokSessionDetailErrorComponent = ({ error }: { error: Error }) => {
49
+ return <ReloadErrorPanel description={error.message} title="Failed to load Grok session" />;
50
+ };
51
+
52
+ const buildSessionMetadata = (detail: GrokSessionTranscript) => [
53
+ { label: 'Session ID', value: <span data-mono="true">{detail.session.sessionId}</span> },
54
+ {
55
+ label: 'Workspace',
56
+ value: (
57
+ <Link
58
+ className="text-[var(--accent)]"
59
+ params={{ workspaceKey: detail.session.workspaceKey }}
60
+ to="/grok/$workspaceKey"
61
+ >
62
+ {detail.session.workspaceLabel}
63
+ </Link>
64
+ ),
65
+ },
66
+ { label: 'Worktree', value: detail.session.worktree },
67
+ { label: 'Session directory', value: detail.session.sessionDir },
68
+ { label: 'Agent', value: detail.session.agentName ?? 'unknown' },
69
+ { label: 'Model', value: detail.session.modelLabel ?? detail.session.currentModelId ?? 'unknown' },
70
+ { label: 'Git branch', value: detail.session.gitBranch ?? 'unknown' },
71
+ { label: 'Head commit', value: detail.session.headCommit ?? 'unknown' },
72
+ { label: 'Created', value: <span suppressHydrationWarning>{formatDateTime(detail.session.createdAtMs)}</span> },
73
+ { label: 'Updated', value: <span suppressHydrationWarning>{formatDateTime(detail.session.lastActiveAtMs)}</span> },
74
+ ];
75
+
76
+ const buildTranscriptStatsItems = (
77
+ detail: GrokSessionTranscript,
78
+ events: ThreadEvent[],
79
+ stats: ThreadTranscriptStats,
80
+ ) => [
81
+ { label: 'Event kinds', value: formatList([...new Set(events.map((event) => event.kind))]) },
82
+ { label: 'Messages', value: formatNumber(stats.messageCount) },
83
+ { label: 'User messages', value: formatNumber(stats.userMessageCount) },
84
+ { label: 'Assistant messages', value: formatNumber(stats.assistantMessageCount) },
85
+ { label: 'Reasoning events', value: formatNumber(events.filter((event) => event.kind === 'reasoning').length) },
86
+ { label: 'Final answers', value: formatNumber(stats.finalAnswerCount) },
87
+ { label: 'Tool calls', value: formatNumber(stats.toolCallCount) },
88
+ { label: 'Tool outputs', value: formatNumber(stats.toolOutputCount) },
89
+ { label: 'Renderable parts', value: formatNumber(detail.renderablePartCount) },
90
+ ];
91
+
92
+ const GrokTranscriptControls = ({
93
+ rawJsonDisabled = false,
94
+ showCommentary,
95
+ showExtraEvents,
96
+ showRawJson,
97
+ showToolCalls,
98
+ showUserMessages,
99
+ onShowCommentaryChange,
100
+ onShowExtraEventsChange,
101
+ onShowRawJsonChange,
102
+ onShowToolCallsChange,
103
+ onShowUserMessagesChange,
104
+ }: TranscriptControlsProps) => {
105
+ return (
106
+ <div className="flex flex-wrap gap-4 rounded-xl border border-[var(--border)] bg-[var(--panel)] px-4 py-3 shadow-[var(--panel-shadow)]">
107
+ <div className="flex items-center gap-2 text-sm">
108
+ <Checkbox
109
+ checked={showToolCalls}
110
+ id="grok-transcript-show-tool-calls"
111
+ onCheckedChange={(checked) => onShowToolCallsChange(checked === true)}
112
+ />
113
+ <label htmlFor="grok-transcript-show-tool-calls">Show tool calls</label>
114
+ </div>
115
+ <div className="flex items-center gap-2 text-sm">
116
+ <Checkbox
117
+ checked={showCommentary}
118
+ id="grok-transcript-show-commentary"
119
+ onCheckedChange={(checked) => onShowCommentaryChange(checked === true)}
120
+ />
121
+ <label htmlFor="grok-transcript-show-commentary">Show commentary</label>
122
+ </div>
123
+ <div className="flex items-center gap-2 text-sm">
124
+ <Checkbox
125
+ checked={showExtraEvents}
126
+ id="grok-transcript-show-extra-events"
127
+ onCheckedChange={(checked) => onShowExtraEventsChange(checked === true)}
128
+ />
129
+ <label htmlFor="grok-transcript-show-extra-events">Show extra events</label>
130
+ </div>
131
+ <div className="flex items-center gap-2 text-sm">
132
+ <Checkbox
133
+ checked={showRawJson}
134
+ disabled={rawJsonDisabled}
135
+ id="grok-transcript-show-raw-json"
136
+ onCheckedChange={(checked) => onShowRawJsonChange(checked === true)}
137
+ />
138
+ <label htmlFor="grok-transcript-show-raw-json">Raw JSON</label>
139
+ </div>
140
+ <div className="flex items-center gap-2 text-sm">
141
+ <Checkbox
142
+ checked={showUserMessages}
143
+ id="grok-transcript-show-user-messages"
144
+ onCheckedChange={(checked) => onShowUserMessagesChange(checked === true)}
145
+ />
146
+ <label htmlFor="grok-transcript-show-user-messages">User</label>
147
+ </div>
148
+ </div>
149
+ );
150
+ };
151
+
152
+ const GrokRawPanels = ({ detail, events }: { detail: GrokSessionTranscript; events: ThreadEvent[] }) => {
153
+ return (
154
+ <div className="space-y-4">
155
+ <JsonPanel title="Session summary" value={detail.session} />
156
+ <JsonPanel title="Grok transcript entries" value={detail.entries} />
157
+ <JsonPanel title="Transcript events" value={events} />
158
+ </div>
159
+ );
160
+ };
161
+
162
+ const GrokSessionDetailPage = () => {
163
+ const navigate = useNavigate();
164
+ const queryClient = useQueryClient();
165
+ const detail = useSuspenseQuery(grokSessionDetailQueryOptions(Route.useParams().sessionId)).data;
166
+ const [deleteOpen, setDeleteOpen] = useState(false);
167
+ const [pendingExport, setPendingExport] = useState(false);
168
+ const [showToolCalls, setShowToolCalls] = useState(false);
169
+ const [showCommentary, setShowCommentary] = useState(false);
170
+ const [showExtraEvents, setShowExtraEvents] = useState(false);
171
+ const [showRawJson, setShowRawJson] = useState(false);
172
+ const [showUserMessages, setShowUserMessages] = useState(DEFAULT_SHOW_USER_MESSAGES);
173
+ const transcriptEvents = useMemo(() => grokTranscriptToThreadEvents(detail), [detail]);
174
+ const transcriptStats = useMemo(() => getGrokThreadTranscriptStats(transcriptEvents), [transcriptEvents]);
175
+
176
+ const exportSessionMutation = useMutation({
177
+ mutationFn: async (options: ExportDialogOptions) => {
178
+ const download = await exportGrokSessionFn({
179
+ data: {
180
+ includeCommentary: options.includeCommentary,
181
+ includeMetadata: options.includeMetadata,
182
+ includeTools: options.includeTools,
183
+ outputFormat: options.outputFormat,
184
+ sessionId: detail.session.sessionId,
185
+ zipArchive: options.zipArchive,
186
+ },
187
+ });
188
+ if (download.mode === 'download') {
189
+ downloadTextFile(download.fileName, download.content, download.mimeType);
190
+ return;
191
+ }
192
+
193
+ await downloadUrlFile(download.fileName, download.downloadUrl);
194
+ },
195
+ onSuccess: () => {
196
+ setPendingExport(false);
197
+ },
198
+ });
199
+
200
+ const deleteSessionMutation = useMutation({
201
+ mutationFn: () => deleteGrokSessionFn({ data: { sessionId: detail.session.sessionId } }),
202
+ onSuccess: async () => {
203
+ await Promise.all([
204
+ queryClient.invalidateQueries({ queryKey: ['grok-workspaces'] }),
205
+ queryClient.invalidateQueries({ queryKey: ['grok-sessions', detail.session.workspaceKey] }),
206
+ queryClient.invalidateQueries({ queryKey: ['grok-session', detail.session.sessionId] }),
207
+ ]);
208
+ navigate({
209
+ params: { workspaceKey: detail.session.workspaceKey },
210
+ to: '/grok/$workspaceKey',
211
+ });
212
+ },
213
+ });
214
+
215
+ return (
216
+ <div className="space-y-6">
217
+ <PageHeader
218
+ actions={
219
+ <>
220
+ <Button
221
+ className="rounded-full"
222
+ type="button"
223
+ variant="outline"
224
+ onClick={() => setPendingExport(true)}
225
+ >
226
+ <Download className="mr-2 size-4" />
227
+ Export
228
+ </Button>
229
+ <Button
230
+ className="rounded-full border-[var(--destructive)]/20 text-[var(--destructive)]"
231
+ type="button"
232
+ variant="outline"
233
+ onClick={() => setDeleteOpen(true)}
234
+ >
235
+ <Trash2 className="mr-2 size-4" />
236
+ Delete
237
+ </Button>
238
+ </>
239
+ }
240
+ breadcrumb={
241
+ <Breadcrumbs
242
+ items={[
243
+ { label: 'Grok', to: '/grok' },
244
+ {
245
+ label: detail.session.workspaceLabel,
246
+ params: { workspaceKey: detail.session.workspaceKey },
247
+ to: '/grok/$workspaceKey',
248
+ },
249
+ { label: detail.session.title },
250
+ ]}
251
+ />
252
+ }
253
+ eyebrow="Grok session"
254
+ subtitle="Session detail for the selected local Grok CLI conversation."
255
+ title={detail.session.title}
256
+ />
257
+
258
+ <div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
259
+ <MetricCard label="Messages" value={formatNumber(detail.session.messageCount)} />
260
+ <MetricCard label="Tool calls" value={formatNumber(detail.session.toolCallCount)} />
261
+ <MetricCard label="Reasoning" value={formatNumber(detail.session.reasoningCount)} />
262
+ <MetricCard label="Renderable parts" value={formatNumber(detail.renderablePartCount)} />
263
+ </div>
264
+
265
+ <Tabs className="space-y-4" defaultValue="transcript">
266
+ <TabsList className="grid w-fit min-w-[24rem] grid-cols-3 rounded-full border border-[var(--border)] bg-[var(--panel)] p-1">
267
+ <TabsTrigger className="rounded-full px-5 text-sm" value="transcript">
268
+ Transcript
269
+ </TabsTrigger>
270
+ <TabsTrigger className="rounded-full px-5 text-sm" value="metadata">
271
+ Metadata
272
+ </TabsTrigger>
273
+ <TabsTrigger className="rounded-full px-5 text-sm" value="raw">
274
+ Raw
275
+ </TabsTrigger>
276
+ </TabsList>
277
+
278
+ <TabsContent className="space-y-3" value="transcript">
279
+ <GrokTranscriptControls
280
+ rawJsonDisabled={Boolean(detail.rawPayloadsOmitted) || transcriptEvents.length === 0}
281
+ showCommentary={showCommentary}
282
+ showExtraEvents={showExtraEvents}
283
+ showRawJson={showRawJson}
284
+ showToolCalls={showToolCalls}
285
+ showUserMessages={showUserMessages}
286
+ onShowCommentaryChange={setShowCommentary}
287
+ onShowExtraEventsChange={setShowExtraEvents}
288
+ onShowRawJsonChange={setShowRawJson}
289
+ onShowToolCallsChange={setShowToolCalls}
290
+ onShowUserMessagesChange={setShowUserMessages}
291
+ />
292
+ {transcriptEvents.length > 0 ? (
293
+ <TranscriptView
294
+ assistantModel={detail.session.modelLabel ?? detail.session.currentModelId}
295
+ events={transcriptEvents}
296
+ projectPath={detail.session.worktree}
297
+ showCommentary={showCommentary}
298
+ showExtraEvents={showExtraEvents}
299
+ showRawJson={showRawJson && !detail.rawPayloadsOmitted}
300
+ showToolCalls={showToolCalls}
301
+ showUserMessages={showUserMessages}
302
+ />
303
+ ) : (
304
+ <section className="rounded-[1.6rem] border border-[var(--border)] bg-[var(--panel)] p-5 shadow-[var(--panel-shadow)]">
305
+ <h3 className="font-semibold text-[var(--muted-foreground)] text-sm uppercase tracking-[0.18em]">
306
+ Transcript
307
+ </h3>
308
+ <p className="mt-4 text-[var(--muted-foreground)] text-sm">
309
+ No renderable Grok transcript content was found for this session.
310
+ </p>
311
+ </section>
312
+ )}
313
+ </TabsContent>
314
+
315
+ <TabsContent value="metadata">
316
+ <div className="grid gap-4 xl:grid-cols-[0.95fr_1.05fr]">
317
+ <MetadataSection items={buildSessionMetadata(detail)} title="Session metadata" />
318
+ <MetadataSection
319
+ items={buildTranscriptStatsItems(detail, transcriptEvents, transcriptStats)}
320
+ title="Transcript stats"
321
+ />
322
+ </div>
323
+ </TabsContent>
324
+
325
+ <TabsContent value="raw">
326
+ {detail.rawPayloadsOmitted ? (
327
+ <section className="mb-4 rounded-xl border border-[var(--border)] bg-[var(--panel)] p-4 text-[var(--muted-foreground)] text-sm">
328
+ Raw Grok payloads are omitted from the default session view to keep large transcripts
329
+ reloadable.
330
+ </section>
331
+ ) : null}
332
+ <GrokRawPanels detail={detail} events={transcriptEvents} />
333
+ </TabsContent>
334
+ </Tabs>
335
+
336
+ <ExportDialog
337
+ errorMessage={
338
+ exportSessionMutation.isError
339
+ ? exportSessionMutation.error instanceof Error
340
+ ? exportSessionMutation.error.message
341
+ : 'Export failed'
342
+ : null
343
+ }
344
+ open={pendingExport}
345
+ pending={exportSessionMutation.isPending}
346
+ title={`Export ${detail.session.title}`}
347
+ onExport={(options) => exportSessionMutation.mutate(options)}
348
+ onOpenChange={(open) => {
349
+ setPendingExport(open);
350
+ if (!open) {
351
+ exportSessionMutation.reset();
352
+ }
353
+ }}
354
+ />
355
+
356
+ <DeleteConfirmDialog
357
+ confirmLabel={deleteSessionMutation.isPending ? 'Deleting...' : 'Delete session'}
358
+ description="Permanently delete this Grok session from local history. This removes the session directory and transcript files."
359
+ errorMessage={
360
+ deleteSessionMutation.isError
361
+ ? deleteSessionMutation.error instanceof Error
362
+ ? deleteSessionMutation.error.message
363
+ : 'Session delete failed'
364
+ : null
365
+ }
366
+ open={deleteOpen}
367
+ title="Delete this Grok session?"
368
+ onConfirm={() => deleteSessionMutation.mutate()}
369
+ onOpenChange={(open) => {
370
+ setDeleteOpen(open);
371
+ if (!open) {
372
+ deleteSessionMutation.reset();
373
+ }
374
+ }}
375
+ />
376
+ </div>
377
+ );
378
+ };
379
+
380
+ export const Route = createFileRoute('/grok-sessions/$sessionId')({
381
+ component: GrokSessionDetailPage,
382
+ errorComponent: GrokSessionDetailErrorComponent,
383
+ loader: ({ context, params }) =>
384
+ context.queryClient.ensureQueryData(grokSessionDetailQueryOptions(params.sessionId)),
385
+ pendingComponent: () => (
386
+ <LoadingPanel description="Loading the Grok transcript and session metadata." title="Loading session" />
387
+ ),
388
+ });