@tonyclaw/agent-inspector 3.0.28 → 3.0.30
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/.output/cli.js +791 -191
- package/.output/nitro.json +1 -1
- package/.output/public/assets/CompareDrawer-B6cXJohL.js +1 -0
- package/.output/public/assets/InspectorPet-mAzeGmEE.js +4108 -0
- package/.output/public/assets/ProxyViewerContainer-CkFWv_Nm.js +126 -0
- package/.output/public/assets/{ReplayDialog-CZMApaF4.js → ReplayDialog-DodiTL6E.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-DNWccR7O.js → RequestAnatomy-B3IauToI.js} +1 -1
- package/.output/public/assets/ResponseView-Hn-5ordK.js +3 -0
- package/.output/public/assets/StreamingChunkSequence-vLDOVQM_.js +1 -0
- package/.output/public/assets/_sessionId-BFH4TOaI.js +1 -0
- package/.output/public/assets/{_sessionId-DRfvuE2k.js → _sessionId-Cj_HIFZx.js} +1 -1
- package/.output/public/assets/{index-D4HkS7Ct.js → index-Bmj2lcWE.js} +1 -1
- package/.output/public/assets/index-CgJj-HBC.css +1 -0
- package/.output/public/assets/index-Ch7uqnCT.js +1 -0
- package/.output/public/assets/index-DkDdKvLl.js +76 -0
- package/.output/public/assets/{index-B3aAuqWq.js → index-ZlhqCrSg.js} +6 -6
- package/.output/public/assets/{json-viewer-wuJSKaed.js → json-viewer-BdrnVQu-.js} +1 -1
- package/.output/public/assets/jszip.min-CS_nt_o1.js +2 -0
- package/.output/public/assets/qwen-mMn3f5ul.webp +0 -0
- package/.output/server/_libs/jszip.mjs +35 -9
- package/.output/server/_libs/lucide-react.mjs +86 -86
- package/.output/server/_libs/radix-ui__react-dialog.mjs +3 -3
- package/.output/server/_libs/tanstack__react-router.mjs +1 -1
- package/.output/server/{_sessionId-BJTNiP_O.mjs → _sessionId-C5S3pGZd.mjs} +15 -15
- package/.output/server/_sessionId-g6D69lKq.mjs +81 -0
- package/.output/server/_ssr/{CompareDrawer-BMRH3N97.mjs → CompareDrawer-CqUmGHal.mjs} +138 -103
- package/.output/server/_ssr/InspectorPet-DI0UJd01.mjs +925 -0
- package/.output/server/_ssr/{ProxyViewerContainer-AFwcHAIR.mjs → ProxyViewerContainer-CvsUmbJ3.mjs} +1216 -1659
- package/.output/server/_ssr/{ReplayDialog-CbWvRHiN.mjs → ReplayDialog-DS48dpFG.mjs} +18 -18
- package/.output/server/_ssr/{RequestAnatomy-C4kcPyqN.mjs → RequestAnatomy-DxYVQU1C.mjs} +58 -58
- package/.output/server/_ssr/{ResponseView-D8VYt-gZ.mjs → ResponseView-BLXW73eq.mjs} +60 -60
- package/.output/server/_ssr/{StreamingChunkSequence-BUUo6vU0.mjs → StreamingChunkSequence-DD0iFuy0.mjs} +16 -16
- package/.output/server/_ssr/{index-DTwMEEXO.mjs → index-B10LIaK0.mjs} +15 -15
- package/.output/server/_ssr/index-RHVJnU2p.mjs +81 -0
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-BqF1pSQD.mjs → json-viewer-LDu2XBaK.mjs} +58 -58
- package/.output/server/_ssr/{router-C8BOns7y.mjs → router-GKvrwifJ.mjs} +9005 -6488
- package/.output/server/_tanstack-start-manifest_v-Df2n4mbT.mjs +4 -0
- package/.output/server/index.mjs +105 -77
- package/.output/workers/logFinalizer.worker.js +16908 -0
- package/.output/workers/sessionWorkerEntry.js +16904 -0
- package/README.md +21 -5
- package/package.json +6 -2
- package/src/assets/logos/qwen.webp +0 -0
- package/src/cli.ts +85 -10
- package/src/components/OnboardingBanner.tsx +41 -21
- package/src/components/ProxyViewer.tsx +198 -269
- package/src/components/ProxyViewerContainer.tsx +128 -17
- package/src/components/clients/ClientLogo.tsx +9 -6
- package/src/components/ecosystem/AgentLabDialog.tsx +166 -19
- package/src/components/errors/SafeErrorBoundary.tsx +201 -0
- package/src/components/pi-agent/PiAgentPanel.tsx +4 -1
- package/src/components/providers/ImportWizardDialog.tsx +18 -42
- package/src/components/providers/ProviderLogo.tsx +1 -1
- package/src/components/providers/ProvidersPanel.tsx +8 -5
- package/src/components/providers/SettingsDialog.tsx +34 -11
- package/src/components/proxy-viewer/ApplicationBar.tsx +63 -0
- package/src/components/proxy-viewer/CompareDrawer.tsx +92 -49
- package/src/components/proxy-viewer/ConversationGroupList.tsx +49 -34
- package/src/components/proxy-viewer/ConversationHeader.tsx +22 -18
- package/src/components/proxy-viewer/LiveConnectionStatus.tsx +186 -0
- package/src/components/proxy-viewer/LogEntry.tsx +283 -275
- package/src/components/proxy-viewer/RawExportWarning.tsx +77 -0
- package/src/components/proxy-viewer/SessionLoadProgressBar.tsx +61 -0
- package/src/components/proxy-viewer/ThreadConnector.tsx +16 -1
- package/src/components/proxy-viewer/TurnGroup.tsx +13 -2
- package/src/components/proxy-viewer/accessibility.ts +8 -0
- package/src/components/proxy-viewer/lazy.ts +4 -0
- package/src/components/proxy-viewer/liveConnectionState.ts +158 -0
- package/src/components/proxy-viewer/proxyViewerLogic.ts +10 -0
- package/src/components/proxy-viewer/viewerState.ts +2 -6
- package/src/components/ui/select.tsx +1 -1
- package/src/components/ui/tabs.tsx +3 -3
- package/src/components/ui/transient-toast.tsx +1 -1
- package/src/lib/apiClient.ts +17 -2
- package/src/lib/ecosystemContract.ts +34 -0
- package/src/lib/export-logs.ts +1 -1
- package/src/lib/providerContract.ts +70 -4
- package/src/lib/providerImportContract.ts +27 -0
- package/src/lib/providerModelMetadata.ts +16 -7
- package/src/lib/resourceLimits.ts +152 -0
- package/src/lib/safeDiagnostic.ts +38 -0
- package/src/lib/useProviders.ts +4 -4
- package/src/lib/utils.ts +5 -1
- package/src/mcp/server.ts +39 -6
- package/src/mcp/toolHandlers.ts +131 -4
- package/src/proxy/chunkStorage.ts +20 -6
- package/src/proxy/config.ts +20 -6
- package/src/proxy/dataDir.ts +3 -0
- package/src/proxy/ecosystemExecutionConfirmation.ts +114 -0
- package/src/proxy/ecosystemExecutionRoute.ts +116 -0
- package/src/proxy/ecosystemTasks.ts +48 -0
- package/src/proxy/evidenceExporter.ts +23 -9
- package/src/proxy/groupEvidenceExporter.ts +26 -5
- package/src/proxy/groupStore.ts +13 -3
- package/src/proxy/handler.ts +103 -20
- package/src/proxy/identityProxy.ts +333 -14
- package/src/proxy/logFinalizer.ts +73 -6
- package/src/proxy/logImportUpload.ts +128 -0
- package/src/proxy/logImporter.ts +321 -70
- package/src/proxy/logIndex.ts +16 -6
- package/src/proxy/logger.ts +294 -41
- package/src/proxy/privateDataPath.ts +183 -0
- package/src/proxy/providerScanStore.ts +87 -0
- package/src/proxy/providerSecretStore.ts +58 -19
- package/src/proxy/providers.ts +175 -58
- package/src/proxy/rawStreamCapture.ts +66 -5
- package/src/proxy/runStore.ts +13 -3
- package/src/proxy/runtimeAdmission.ts +52 -0
- package/src/proxy/runtimeHealth.ts +206 -0
- package/src/proxy/runtimeShutdown.ts +75 -0
- package/src/proxy/sessionArchive.ts +15 -2
- package/src/proxy/sessionProcess.ts +19 -0
- package/src/proxy/sessionRuntime.ts +7 -0
- package/src/proxy/shutdownCoordinator.ts +90 -0
- package/src/proxy/sqliteLogIndex.ts +18 -2
- package/src/proxy/store.ts +18 -2
- package/src/routes/__root.tsx +18 -1
- package/src/routes/api/alerts.summary.ts +12 -8
- package/src/routes/api/alerts.ts +27 -9
- package/src/routes/api/ecosystem.packages.$packageId.help.ts +3 -8
- package/src/routes/api/ecosystem.packages.$packageId.install.ts +3 -8
- package/src/routes/api/ecosystem.packages.$packageId.runner-presets.ts +3 -11
- package/src/routes/api/ecosystem.packages.$packageId.upgrade.ts +3 -8
- package/src/routes/api/ecosystem.recipes.$recipeId.run.ts +3 -8
- package/src/routes/api/health.ts +9 -2
- package/src/routes/api/knowledge.project-context.ts +11 -0
- package/src/routes/api/knowledge.search.ts +33 -2
- package/src/routes/api/logs.$id.body.ts +16 -2
- package/src/routes/api/logs.import.ts +7 -18
- package/src/routes/api/logs.stream.ts +147 -69
- package/src/routes/api/logs.ts +55 -14
- package/src/routes/api/providers.$providerId.model-metadata.ts +22 -4
- package/src/routes/api/providers.$providerId.ts +37 -6
- package/src/routes/api/providers.export.ts +34 -16
- package/src/routes/api/providers.import.ts +42 -8
- package/src/routes/api/providers.scan.ts +13 -8
- package/src/routes/api/providers.ts +36 -5
- package/src/routes/api/runs.ts +12 -12
- package/src/routes/api/sessions.ts +15 -8
- package/src/routes/index.tsx +6 -0
- package/src/routes/livez.ts +13 -0
- package/src/routes/readyz.ts +18 -0
- package/src/routes/session/$sessionId.tsx +6 -0
- package/styles/globals.css +19 -3
- package/.output/public/assets/CompareDrawer-TghZr5t1.js +0 -1
- package/.output/public/assets/ProxyViewerContainer-fShBAZjj.js +0 -4234
- package/.output/public/assets/ResponseView-d25MdFhY.js +0 -3
- package/.output/public/assets/StreamingChunkSequence-DIFZEnWe.js +0 -1
- package/.output/public/assets/index-BsxAGqMA.css +0 -1
- package/.output/public/assets/index-DzARL2Fi.js +0 -76
- package/.output/public/assets/qwen-CONDcHqt.png +0 -0
- package/.output/server/_tanstack-start-manifest_v-BBf2nRhb.mjs +0 -4
- package/src/assets/logos/mcp.png +0 -0
- package/src/assets/logos/qwen.png +0 -0
- package/src/components/ui/mcp-logo.tsx +0 -20
|
@@ -22,7 +22,7 @@ import { ProviderLogoStack } from "./ProviderLogoStack";
|
|
|
22
22
|
import { resolveLogFormat } from "./log-formats";
|
|
23
23
|
|
|
24
24
|
const HEADER_ACTION_BUTTON_CLASS =
|
|
25
|
-
"
|
|
25
|
+
"inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md bg-[var(--inspector-surface-low)] text-muted-foreground transition-colors hover:bg-[var(--inspector-surface-medium)] hover:text-foreground";
|
|
26
26
|
|
|
27
27
|
export function isSyntheticSessionId(conversationId: string): boolean {
|
|
28
28
|
return (
|
|
@@ -34,7 +34,7 @@ export function isSyntheticSessionId(conversationId: string): boolean {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
export function compactSessionLabel(conversationId: string): string {
|
|
37
|
-
if (isSyntheticSessionId(conversationId)) return "
|
|
37
|
+
if (isSyntheticSessionId(conversationId)) return "No session ID";
|
|
38
38
|
return conversationId.length > 24
|
|
39
39
|
? `${conversationId.slice(0, 12)}...${conversationId.slice(-12)}`
|
|
40
40
|
: conversationId;
|
|
@@ -54,7 +54,7 @@ function HeaderMetric({
|
|
|
54
54
|
return (
|
|
55
55
|
<span
|
|
56
56
|
className={cn(
|
|
57
|
-
"
|
|
57
|
+
"inline-flex h-7 min-w-0 items-center gap-1.5 rounded-md bg-[var(--inspector-surface-low)] px-2 text-xs text-muted-foreground",
|
|
58
58
|
className,
|
|
59
59
|
)}
|
|
60
60
|
title={title}
|
|
@@ -68,7 +68,7 @@ function HeaderMetric({
|
|
|
68
68
|
function PendingTurnBadge(): JSX.Element {
|
|
69
69
|
return (
|
|
70
70
|
<span
|
|
71
|
-
className="inline-flex h-6 shrink-0 items-center gap-1.5 rounded-md bg-
|
|
71
|
+
className="inline-flex h-6 shrink-0 items-center gap-1.5 rounded-md bg-[var(--inspector-surface-medium)] px-1.5 font-mono text-[10px] uppercase text-[var(--inspector-status-watch)] ring-1 ring-border/50"
|
|
72
72
|
title="This session has a turn that has not finished yet"
|
|
73
73
|
>
|
|
74
74
|
<Loader2 className="size-3 animate-spin" />
|
|
@@ -174,11 +174,11 @@ export function ConversationHeader({
|
|
|
174
174
|
data-nav-id={`conv-${conversationId}`}
|
|
175
175
|
data-nav-action={expanded ? "collapse" : "expand"}
|
|
176
176
|
className={cn(
|
|
177
|
-
"
|
|
178
|
-
"hover:bg-
|
|
177
|
+
"relative flex cursor-pointer items-start gap-2.5 overflow-hidden rounded-md bg-[var(--inspector-surface-low)] px-3 py-2.5 transition-colors",
|
|
178
|
+
"hover:bg-[var(--inspector-surface-medium)]",
|
|
179
179
|
"select-none",
|
|
180
180
|
"sticky top-0 z-10 mb-2",
|
|
181
|
-
"focus-visible:ring-1 focus-visible:ring-
|
|
181
|
+
"focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none",
|
|
182
182
|
)}
|
|
183
183
|
onClick={onToggle}
|
|
184
184
|
onKeyDown={(e) => {
|
|
@@ -198,20 +198,20 @@ export function ConversationHeader({
|
|
|
198
198
|
|
|
199
199
|
<span className="min-w-0 flex-1">
|
|
200
200
|
<span className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
|
|
201
|
-
<span className="shrink-0 rounded bg-
|
|
201
|
+
<span className="shrink-0 rounded bg-[var(--inspector-surface-medium)] px-1.5 py-0.5 font-mono text-[9px] uppercase text-muted-foreground">
|
|
202
202
|
Session
|
|
203
203
|
</span>
|
|
204
204
|
{isFallbackConversation && showProcessMetadata ? (
|
|
205
205
|
clientPid !== null && clientPid !== undefined ? (
|
|
206
206
|
<span
|
|
207
|
-
className="min-w-0 max-w-full truncate font-mono text-xs font-semibold text-
|
|
207
|
+
className="min-w-0 max-w-full truncate font-mono text-xs font-semibold text-foreground"
|
|
208
208
|
title={`PID ${clientPid}`}
|
|
209
209
|
>
|
|
210
210
|
PID {clientPid}
|
|
211
211
|
</span>
|
|
212
212
|
) : (
|
|
213
213
|
<span
|
|
214
|
-
className="min-w-0 max-w-full truncate font-mono text-xs font-semibold text-
|
|
214
|
+
className="min-w-0 max-w-full truncate font-mono text-xs font-semibold text-foreground"
|
|
215
215
|
title={conversationId}
|
|
216
216
|
>
|
|
217
217
|
{conversationId}
|
|
@@ -219,7 +219,7 @@ export function ConversationHeader({
|
|
|
219
219
|
)
|
|
220
220
|
) : (
|
|
221
221
|
<span
|
|
222
|
-
className="min-w-0 max-w-full truncate font-mono text-xs font-semibold text-
|
|
222
|
+
className="min-w-0 max-w-full truncate font-mono text-xs font-semibold text-foreground"
|
|
223
223
|
title={conversationId}
|
|
224
224
|
>
|
|
225
225
|
{sessionLabel}
|
|
@@ -231,7 +231,7 @@ export function ConversationHeader({
|
|
|
231
231
|
clientPid !== null &&
|
|
232
232
|
clientPid !== undefined && (
|
|
233
233
|
<span
|
|
234
|
-
className="
|
|
234
|
+
className="shrink-0 rounded bg-[var(--inspector-surface-medium)] px-1.5 py-0.5 font-mono text-xs tabular-nums text-muted-foreground"
|
|
235
235
|
title={`Client process ID ${clientPid}`}
|
|
236
236
|
>
|
|
237
237
|
PID {clientPid}
|
|
@@ -253,7 +253,7 @@ export function ConversationHeader({
|
|
|
253
253
|
|
|
254
254
|
{showClientIdentity && clientDisplayLabel !== null && clientDisplayLabel !== "" && (
|
|
255
255
|
<span
|
|
256
|
-
className="
|
|
256
|
+
className="inline-flex min-h-6 shrink-0 flex-wrap items-center gap-1 rounded-md bg-[var(--inspector-surface-medium)] px-1.5 py-0.5 text-xs text-muted-foreground"
|
|
257
257
|
title={clientTooltip ?? clientDisplayLabel}
|
|
258
258
|
>
|
|
259
259
|
<ClientLogo client={clientApp} className="size-4 shrink-0" decorative={true} />
|
|
@@ -272,9 +272,13 @@ export function ConversationHeader({
|
|
|
272
272
|
{callsLabel}
|
|
273
273
|
</HeaderMetric>
|
|
274
274
|
<HeaderMetric icon={<Zap className="size-3 shrink-0" />} className="max-w-full">
|
|
275
|
-
<span className="text-
|
|
275
|
+
<span className="text-[var(--inspector-status-info)]">
|
|
276
|
+
{formatTokens(totalInputTokens)}
|
|
277
|
+
</span>
|
|
276
278
|
{" / "}
|
|
277
|
-
<span className="text-
|
|
279
|
+
<span className="text-[var(--inspector-status-watch)]">
|
|
280
|
+
{formatTokens(totalOutputTokens)}
|
|
281
|
+
</span>
|
|
278
282
|
</HeaderMetric>
|
|
279
283
|
</span>
|
|
280
284
|
</span>
|
|
@@ -311,7 +315,7 @@ export function ConversationHeader({
|
|
|
311
315
|
|
|
312
316
|
{/* Expand chevron - shows spinner when collapsed and group has pending logs */}
|
|
313
317
|
<span
|
|
314
|
-
className="
|
|
318
|
+
className="inline-flex size-8 shrink-0 items-center justify-center rounded-md bg-[var(--inspector-surface-low)] text-muted-foreground transition-colors hover:bg-[var(--inspector-surface-medium)] hover:text-foreground"
|
|
315
319
|
aria-hidden="true"
|
|
316
320
|
>
|
|
317
321
|
{expanded ? (
|
|
@@ -326,10 +330,10 @@ export function ConversationHeader({
|
|
|
326
330
|
|
|
327
331
|
{isLoading && (
|
|
328
332
|
<span
|
|
329
|
-
className="pointer-events-none absolute inset-x-3 bottom-0 h-px overflow-hidden rounded-full bg-
|
|
333
|
+
className="pointer-events-none absolute inset-x-3 bottom-0 h-px overflow-hidden rounded-full bg-[var(--inspector-surface-medium)]"
|
|
330
334
|
aria-hidden="true"
|
|
331
335
|
>
|
|
332
|
-
<span className="absolute inset-y-0 left-0 w-1/3 animate-pulse rounded-full bg-
|
|
336
|
+
<span className="absolute inset-y-0 left-0 w-1/3 animate-pulse rounded-full bg-[var(--inspector-status-watch)] opacity-70" />
|
|
333
337
|
</span>
|
|
334
338
|
)}
|
|
335
339
|
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { type JSX } from "react";
|
|
2
|
+
import { CircleStop, RefreshCw } from "lucide-react";
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
import type { LiveConnectionPhase, LiveConnectionState } from "./liveConnectionState";
|
|
5
|
+
|
|
6
|
+
type ConnectionPresentation = {
|
|
7
|
+
label: string;
|
|
8
|
+
description: string;
|
|
9
|
+
toneClass: string;
|
|
10
|
+
dotClass: string;
|
|
11
|
+
canRetry: boolean;
|
|
12
|
+
canStop: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function formatLastSuccess(lastSuccessAt: number | null): string | null {
|
|
16
|
+
if (lastSuccessAt === null) return null;
|
|
17
|
+
return new Date(lastSuccessAt).toLocaleTimeString([], {
|
|
18
|
+
hour: "2-digit",
|
|
19
|
+
minute: "2-digit",
|
|
20
|
+
second: "2-digit",
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function buildDescription(state: LiveConnectionState, message: string, hasLogs: boolean): string {
|
|
25
|
+
const lastSuccess = formatLastSuccess(state.lastSuccessAt);
|
|
26
|
+
const retry = state.retryCount > 0 ? ` Retry ${state.retryCount.toString()}.` : "";
|
|
27
|
+
const retained = hasLogs ? " Captured logs remain visible." : "";
|
|
28
|
+
const lastUpdate = lastSuccess === null ? "" : ` Last update ${lastSuccess}.`;
|
|
29
|
+
return `${message}${lastUpdate}${retry}${retained}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function presentationForStatus(
|
|
33
|
+
status: LiveConnectionPhase,
|
|
34
|
+
state: LiveConnectionState,
|
|
35
|
+
hasLogs: boolean,
|
|
36
|
+
): ConnectionPresentation {
|
|
37
|
+
switch (status) {
|
|
38
|
+
case "connecting":
|
|
39
|
+
return {
|
|
40
|
+
label: "Connecting",
|
|
41
|
+
description: buildDescription(state, "Waiting for the first live update.", hasLogs),
|
|
42
|
+
toneClass: "border-cyan-400/20 bg-cyan-500/8 text-cyan-100",
|
|
43
|
+
dotClass: "bg-cyan-300 animate-pulse motion-reduce:animate-none",
|
|
44
|
+
canRetry: false,
|
|
45
|
+
canStop: false,
|
|
46
|
+
};
|
|
47
|
+
case "live":
|
|
48
|
+
return {
|
|
49
|
+
label: "Live",
|
|
50
|
+
description: buildDescription(state, "Live updates are current.", hasLogs),
|
|
51
|
+
toneClass: "border-emerald-400/20 bg-emerald-500/8 text-emerald-100",
|
|
52
|
+
dotClass: "bg-emerald-300",
|
|
53
|
+
canRetry: false,
|
|
54
|
+
canStop: false,
|
|
55
|
+
};
|
|
56
|
+
case "reconnecting":
|
|
57
|
+
return {
|
|
58
|
+
label: "Reconnecting",
|
|
59
|
+
description: buildDescription(state, "Live updates were interrupted.", hasLogs),
|
|
60
|
+
toneClass: "border-amber-400/20 bg-amber-500/8 text-amber-100",
|
|
61
|
+
dotClass: "bg-amber-300 animate-pulse motion-reduce:animate-none",
|
|
62
|
+
canRetry: true,
|
|
63
|
+
canStop: true,
|
|
64
|
+
};
|
|
65
|
+
case "stale":
|
|
66
|
+
return {
|
|
67
|
+
label: "Stale data",
|
|
68
|
+
description: buildDescription(state, "Live updates have not resumed.", hasLogs),
|
|
69
|
+
toneClass: "border-orange-400/25 bg-orange-500/10 text-orange-100",
|
|
70
|
+
dotClass: "bg-orange-300",
|
|
71
|
+
canRetry: true,
|
|
72
|
+
canStop: true,
|
|
73
|
+
};
|
|
74
|
+
case "offline":
|
|
75
|
+
return {
|
|
76
|
+
label: "Offline",
|
|
77
|
+
description: buildDescription(
|
|
78
|
+
state,
|
|
79
|
+
state.errorMessage ?? "Live updates are paused.",
|
|
80
|
+
hasLogs,
|
|
81
|
+
),
|
|
82
|
+
toneClass: "border-slate-400/20 bg-slate-500/10 text-slate-200",
|
|
83
|
+
dotClass: "bg-slate-400",
|
|
84
|
+
canRetry: true,
|
|
85
|
+
canStop: false,
|
|
86
|
+
};
|
|
87
|
+
case "error":
|
|
88
|
+
return {
|
|
89
|
+
label: "Connection error",
|
|
90
|
+
description: buildDescription(
|
|
91
|
+
state,
|
|
92
|
+
state.errorMessage ?? "The live stream reported a recoverable error.",
|
|
93
|
+
hasLogs,
|
|
94
|
+
),
|
|
95
|
+
toneClass: "border-red-400/25 bg-red-500/10 text-red-100",
|
|
96
|
+
dotClass: "bg-red-300",
|
|
97
|
+
canRetry: true,
|
|
98
|
+
canStop: true,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function LiveConnectionBadge({ state }: { state: LiveConnectionState }): JSX.Element {
|
|
104
|
+
const presentation = presentationForStatus(state.status, state, false);
|
|
105
|
+
const lastSuccess = formatLastSuccess(state.lastSuccessAt);
|
|
106
|
+
const retryLabel = state.retryCount > 0 ? `Retry ${state.retryCount.toString()}` : null;
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<span
|
|
110
|
+
role="status"
|
|
111
|
+
aria-label={presentation.description}
|
|
112
|
+
data-connection-state={state.status}
|
|
113
|
+
className={cn(
|
|
114
|
+
"inline-flex h-7 max-w-full items-center gap-1.5 rounded-full border px-2.5 text-xs font-medium",
|
|
115
|
+
presentation.toneClass,
|
|
116
|
+
)}
|
|
117
|
+
>
|
|
118
|
+
<span className={cn("size-1.5 shrink-0 rounded-full", presentation.dotClass)} />
|
|
119
|
+
<span className="shrink-0">{presentation.label}</span>
|
|
120
|
+
{retryLabel !== null && (
|
|
121
|
+
<span className="hidden truncate text-current/70 sm:inline">{retryLabel}</span>
|
|
122
|
+
)}
|
|
123
|
+
{lastSuccess !== null && (
|
|
124
|
+
<span className="hidden truncate font-mono text-[10px] text-current/65 md:inline">
|
|
125
|
+
Last {lastSuccess}
|
|
126
|
+
</span>
|
|
127
|
+
)}
|
|
128
|
+
</span>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function LiveConnectionStatus({
|
|
133
|
+
state,
|
|
134
|
+
hasLogs,
|
|
135
|
+
onRetry,
|
|
136
|
+
onStop,
|
|
137
|
+
}: {
|
|
138
|
+
state: LiveConnectionState;
|
|
139
|
+
hasLogs: boolean;
|
|
140
|
+
onRetry: () => void;
|
|
141
|
+
onStop: () => void;
|
|
142
|
+
}): JSX.Element {
|
|
143
|
+
const presentation = presentationForStatus(state.status, state, hasLogs);
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<div
|
|
147
|
+
role="status"
|
|
148
|
+
aria-live="polite"
|
|
149
|
+
data-connection-state={state.status}
|
|
150
|
+
className={cn(
|
|
151
|
+
"mt-2 flex flex-wrap items-center gap-x-3 gap-y-2 rounded-md border px-3 py-2 text-xs",
|
|
152
|
+
presentation.toneClass,
|
|
153
|
+
)}
|
|
154
|
+
>
|
|
155
|
+
<div className="flex min-w-0 flex-1 items-center gap-2">
|
|
156
|
+
<span className={cn("size-2 shrink-0 rounded-full", presentation.dotClass)} />
|
|
157
|
+
<span className="shrink-0 font-semibold">{presentation.label}</span>
|
|
158
|
+
<span className="min-w-0 text-current/75">{presentation.description}</span>
|
|
159
|
+
</div>
|
|
160
|
+
{(presentation.canRetry || presentation.canStop) && (
|
|
161
|
+
<div className="flex shrink-0 items-center gap-1.5">
|
|
162
|
+
{presentation.canRetry && (
|
|
163
|
+
<button
|
|
164
|
+
type="button"
|
|
165
|
+
onClick={onRetry}
|
|
166
|
+
className="inline-flex h-7 items-center gap-1 rounded-md bg-black/15 px-2 font-medium transition-colors hover:bg-black/25 focus-visible:ring-1 focus-visible:ring-current focus-visible:outline-none"
|
|
167
|
+
>
|
|
168
|
+
<RefreshCw className="size-3" />
|
|
169
|
+
Retry now
|
|
170
|
+
</button>
|
|
171
|
+
)}
|
|
172
|
+
{presentation.canStop && (
|
|
173
|
+
<button
|
|
174
|
+
type="button"
|
|
175
|
+
onClick={onStop}
|
|
176
|
+
className="inline-flex h-7 items-center gap-1 rounded-md px-2 text-current/70 transition-colors hover:bg-black/15 hover:text-current focus-visible:ring-1 focus-visible:ring-current focus-visible:outline-none"
|
|
177
|
+
>
|
|
178
|
+
<CircleStop className="size-3" />
|
|
179
|
+
Stop retrying
|
|
180
|
+
</button>
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
)}
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
}
|