@workflow/web-shared 5.0.0-beta.2 → 5.0.0-beta.4
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/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +2 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/new-trace-viewer/components/copy-button.d.ts +9 -0
- package/dist/components/new-trace-viewer/components/copy-button.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/copy-button.js +27 -0
- package/dist/components/new-trace-viewer/components/copy-button.js.map +1 -0
- package/dist/components/new-trace-viewer/components/event-list.d.ts +8 -0
- package/dist/components/new-trace-viewer/components/event-list.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/event-list.js +38 -0
- package/dist/components/new-trace-viewer/components/event-list.js.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts +18 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js +54 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts +20 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js +81 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts +25 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js +101 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts +20 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js +114 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js.map +1 -0
- package/dist/components/new-trace-viewer/components/split-pane.d.ts +14 -0
- package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/split-pane.js +99 -0
- package/dist/components/new-trace-viewer/components/split-pane.js.map +1 -0
- package/dist/components/new-trace-viewer/components/timeline.d.ts +31 -0
- package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/timeline.js +138 -0
- package/dist/components/new-trace-viewer/components/timeline.js.map +1 -0
- package/dist/components/new-trace-viewer/context.d.ts +15 -0
- package/dist/components/new-trace-viewer/context.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/context.js +47 -0
- package/dist/components/new-trace-viewer/context.js.map +1 -0
- package/dist/components/new-trace-viewer/detail-panel.d.ts +10 -0
- package/dist/components/new-trace-viewer/detail-panel.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/detail-panel.js +12 -0
- package/dist/components/new-trace-viewer/detail-panel.js.map +1 -0
- package/dist/components/new-trace-viewer/icons.d.ts +6 -0
- package/dist/components/new-trace-viewer/icons.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/icons.js +15 -0
- package/dist/components/new-trace-viewer/icons.js.map +1 -0
- package/dist/components/new-trace-viewer/trace-viewer.d.ts +8 -0
- package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/trace-viewer.js +349 -0
- package/dist/components/new-trace-viewer/trace-viewer.js.map +1 -0
- package/dist/components/new-trace-viewer/types.d.ts +1 -0
- package/dist/components/new-trace-viewer/types.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/types.js +2 -0
- package/dist/components/new-trace-viewer/types.js.map +1 -0
- package/dist/components/new-trace-viewer/util/constants.d.ts +1 -0
- package/dist/components/new-trace-viewer/util/constants.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/util/constants.js +2 -0
- package/dist/components/new-trace-viewer/util/constants.js.map +1 -0
- package/dist/components/new-trace-viewer/utils.d.ts +42 -0
- package/dist/components/new-trace-viewer/utils.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/utils.js +305 -0
- package/dist/components/new-trace-viewer/utils.js.map +1 -0
- package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
- package/dist/components/sidebar/attribute-panel.js +42 -23
- package/dist/components/sidebar/attribute-panel.js.map +1 -1
- package/dist/components/sidebar/copyable-data-block.js +1 -1
- package/dist/components/sidebar/copyable-data-block.js.map +1 -1
- package/dist/components/sidebar/detail-card.d.ts.map +1 -1
- package/dist/components/sidebar/detail-card.js +1 -1
- package/dist/components/sidebar/detail-card.js.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.d.ts +1 -3
- package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.js +3 -25
- package/dist/components/sidebar/entity-detail-panel.js.map +1 -1
- package/dist/components/sidebar/events-list.d.ts.map +1 -1
- package/dist/components/sidebar/events-list.js +2 -2
- package/dist/components/sidebar/events-list.js.map +1 -1
- package/dist/components/sidebar/sidebar-data-context.d.ts +29 -0
- package/dist/components/sidebar/sidebar-data-context.d.ts.map +1 -0
- package/dist/components/sidebar/sidebar-data-context.js +19 -0
- package/dist/components/sidebar/sidebar-data-context.js.map +1 -0
- package/dist/components/stream-viewer.d.ts +1 -1
- package/dist/components/stream-viewer.d.ts.map +1 -1
- package/dist/components/stream-viewer.js +2 -26
- package/dist/components/stream-viewer.js.map +1 -1
- package/dist/components/trace-viewer/util/timing.d.ts +1 -1
- package/dist/components/trace-viewer/util/timing.d.ts.map +1 -1
- package/dist/components/trace-viewer/util/timing.js +5 -8
- package/dist/components/trace-viewer/util/timing.js.map +1 -1
- package/dist/components/trace-viewer-new.d.ts +10 -0
- package/dist/components/trace-viewer-new.d.ts.map +1 -0
- package/dist/components/trace-viewer-new.js +23 -0
- package/dist/components/trace-viewer-new.js.map +1 -0
- package/dist/components/ui/data-inspector.d.ts +21 -0
- package/dist/components/ui/data-inspector.d.ts.map +1 -1
- package/dist/components/ui/data-inspector.js +99 -4
- package/dist/components/ui/data-inspector.js.map +1 -1
- package/dist/components/ui/inspector-theme.d.ts +9 -10
- package/dist/components/ui/inspector-theme.d.ts.map +1 -1
- package/dist/components/ui/inspector-theme.js +38 -71
- package/dist/components/ui/inspector-theme.js.map +1 -1
- package/dist/components/workflow-trace-view.d.ts +1 -3
- package/dist/components/workflow-trace-view.d.ts.map +1 -1
- package/dist/components/workflow-trace-view.js +4 -4
- package/dist/components/workflow-trace-view.js.map +1 -1
- package/dist/components/workflow-traces/trace-span-construction.d.ts +2 -2
- package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
- package/dist/components/workflow-traces/trace-span-construction.js +16 -3
- package/dist/components/workflow-traces/trace-span-construction.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/stream-display.d.ts +18 -0
- package/dist/lib/stream-display.d.ts.map +1 -0
- package/dist/lib/stream-display.js +30 -0
- package/dist/lib/stream-display.js.map +1 -0
- package/dist/lib/trace-builder.d.ts.map +1 -1
- package/dist/lib/trace-builder.js +2 -1
- package/dist/lib/trace-builder.js.map +1 -1
- package/dist/lib/utils.d.ts +9 -9
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +26 -31
- package/dist/lib/utils.js.map +1 -1
- package/dist/styles.css +470 -1
- package/package.json +3 -2
- package/src/components/index.ts +5 -0
- package/src/components/new-trace-viewer/components/copy-button.tsx +68 -0
- package/src/components/new-trace-viewer/components/event-list.tsx +116 -0
- package/src/components/new-trace-viewer/components/middle-truncate/copy-selection.ts +114 -0
- package/src/components/new-trace-viewer/components/middle-truncate/middle-truncate.tsx +157 -0
- package/src/components/new-trace-viewer/components/middle-truncate/truncate.ts +132 -0
- package/src/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.ts +167 -0
- package/src/components/new-trace-viewer/components/split-pane.tsx +185 -0
- package/src/components/new-trace-viewer/components/timeline.tsx +382 -0
- package/src/components/new-trace-viewer/context.tsx +92 -0
- package/src/components/new-trace-viewer/detail-panel.tsx +58 -0
- package/src/components/new-trace-viewer/icons.tsx +79 -0
- package/src/components/new-trace-viewer/trace-viewer.tsx +645 -0
- package/src/components/new-trace-viewer/types.ts +0 -0
- package/src/components/new-trace-viewer/util/constants.ts +0 -0
- package/src/components/new-trace-viewer/utils.ts +450 -0
- package/src/components/sidebar/attribute-panel.tsx +43 -32
- package/src/components/sidebar/copyable-data-block.tsx +1 -1
- package/src/components/sidebar/detail-card.tsx +1 -19
- package/src/components/sidebar/entity-detail-panel.tsx +0 -71
- package/src/components/sidebar/events-list.tsx +9 -6
- package/src/components/sidebar/sidebar-data-context.tsx +62 -0
- package/src/components/stream-viewer.tsx +18 -40
- package/src/components/trace-viewer/util/timing.ts +5 -9
- package/src/components/trace-viewer-new.tsx +49 -0
- package/src/components/ui/data-inspector.tsx +235 -4
- package/src/components/ui/inspector-theme.ts +39 -81
- package/src/components/workflow-trace-view.tsx +2 -6
- package/src/components/workflow-traces/trace-span-construction.ts +22 -4
- package/src/index.ts +1 -0
- package/src/lib/stream-display.ts +48 -0
- package/src/lib/trace-builder.ts +2 -1
- package/src/lib/utils.ts +27 -32
- package/src/styles.css +470 -1
|
@@ -4,10 +4,8 @@ import type { Event, Hook, Step, WorkflowRun } from '@workflow/world';
|
|
|
4
4
|
import clsx from 'clsx';
|
|
5
5
|
import { Send, Zap } from 'lucide-react';
|
|
6
6
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
|
-
import { isEncryptedMarker } from '../../lib/hydration';
|
|
8
7
|
import { useToast } from '../../lib/toast';
|
|
9
8
|
import { DecryptClickContext } from '../ui/data-inspector';
|
|
10
|
-
import { DecryptButton } from '../ui/decrypt-button';
|
|
11
9
|
import { AttributePanel } from './attribute-panel';
|
|
12
10
|
import { EventsList } from './events-list';
|
|
13
11
|
import { ResolveHookModal } from './resolve-hook-modal';
|
|
@@ -69,7 +67,6 @@ export function EntityDetailPanel({
|
|
|
69
67
|
onDecrypt,
|
|
70
68
|
isDecrypting = false,
|
|
71
69
|
selectedSpan,
|
|
72
|
-
hasEncryptedData = false,
|
|
73
70
|
}: {
|
|
74
71
|
run: WorkflowRun;
|
|
75
72
|
/** Callback when a stream reference is clicked */
|
|
@@ -108,8 +105,6 @@ export function EntityDetailPanel({
|
|
|
108
105
|
isDecrypting?: boolean;
|
|
109
106
|
/** Info about the currently selected span from the trace viewer */
|
|
110
107
|
selectedSpan: SelectedSpanInfo | null;
|
|
111
|
-
/** Run-level hint: the run contains encrypted data (from probe). */
|
|
112
|
-
hasEncryptedData?: boolean;
|
|
113
108
|
}): React.JSX.Element | null {
|
|
114
109
|
const toast = useToast();
|
|
115
110
|
const [stoppingSleep, setStoppingSleep] = useState(false);
|
|
@@ -216,17 +211,6 @@ export function EntityDetailPanel({
|
|
|
216
211
|
const error = spanDetailError ?? undefined;
|
|
217
212
|
const loading = spanDetailLoading ?? false;
|
|
218
213
|
|
|
219
|
-
const hasEncryptedFields = useMemo(() => {
|
|
220
|
-
if (!spanDetailData) return false;
|
|
221
|
-
const d = spanDetailData as Record<string, unknown>;
|
|
222
|
-
return (
|
|
223
|
-
isEncryptedMarker(d.input) ||
|
|
224
|
-
isEncryptedMarker(d.output) ||
|
|
225
|
-
isEncryptedMarker(d.error) ||
|
|
226
|
-
isEncryptedMarker(d.metadata)
|
|
227
|
-
);
|
|
228
|
-
}, [spanDetailData]);
|
|
229
|
-
|
|
230
214
|
// Get the hook token for resolving (prefer fetched data, then hooks array fallback)
|
|
231
215
|
const hookToken = useMemo(() => {
|
|
232
216
|
if (resource !== 'hook' || !resourceId) return undefined;
|
|
@@ -354,61 +338,12 @@ export function EntityDetailPanel({
|
|
|
354
338
|
return null;
|
|
355
339
|
}
|
|
356
340
|
|
|
357
|
-
const resourceLabel = resource.charAt(0).toUpperCase() + resource.slice(1);
|
|
358
341
|
const hasPendingActions =
|
|
359
342
|
(resource === 'sleep' && canWakeUp) ||
|
|
360
343
|
(resource === 'hook' && canResolveHook);
|
|
361
|
-
const runStateLabel = run.completedAt ? 'Completed' : 'Live';
|
|
362
344
|
|
|
363
345
|
return (
|
|
364
346
|
<div className="flex h-full flex-col">
|
|
365
|
-
<div
|
|
366
|
-
className="border-b px-3 py-3"
|
|
367
|
-
style={{ borderColor: 'var(--ds-gray-200)' }}
|
|
368
|
-
>
|
|
369
|
-
<div className="flex items-start justify-between gap-2">
|
|
370
|
-
<div className="min-w-0">
|
|
371
|
-
<div className="flex items-center gap-2">
|
|
372
|
-
<span
|
|
373
|
-
className="inline-flex items-center rounded-full border px-2 py-0.5 text-[13px] font-medium"
|
|
374
|
-
style={{
|
|
375
|
-
borderColor: 'var(--ds-gray-300)',
|
|
376
|
-
color: 'var(--ds-gray-900)',
|
|
377
|
-
backgroundColor: 'var(--ds-background-100)',
|
|
378
|
-
}}
|
|
379
|
-
>
|
|
380
|
-
{resourceLabel}
|
|
381
|
-
</span>
|
|
382
|
-
<span
|
|
383
|
-
className="text-[13px]"
|
|
384
|
-
style={{
|
|
385
|
-
color: run.completedAt
|
|
386
|
-
? 'var(--ds-gray-700)'
|
|
387
|
-
: 'var(--ds-green-800)',
|
|
388
|
-
}}
|
|
389
|
-
>
|
|
390
|
-
{runStateLabel}
|
|
391
|
-
</span>
|
|
392
|
-
</div>
|
|
393
|
-
<p
|
|
394
|
-
className="mt-1 truncate font-mono text-[13px]"
|
|
395
|
-
style={{ color: 'var(--ds-gray-700)' }}
|
|
396
|
-
title={resourceId}
|
|
397
|
-
>
|
|
398
|
-
{resourceId}
|
|
399
|
-
</p>
|
|
400
|
-
</div>
|
|
401
|
-
{(hasEncryptedFields || hasEncryptedData || encryptionKey) &&
|
|
402
|
-
onDecrypt && (
|
|
403
|
-
<DecryptButton
|
|
404
|
-
decrypted={!!encryptionKey}
|
|
405
|
-
loading={isDecrypting}
|
|
406
|
-
onClick={onDecrypt}
|
|
407
|
-
/>
|
|
408
|
-
)}
|
|
409
|
-
</div>
|
|
410
|
-
</div>
|
|
411
|
-
|
|
412
347
|
<DecryptClickContext.Provider
|
|
413
348
|
value={onDecrypt ? { onDecrypt, isDecrypting } : undefined}
|
|
414
349
|
>
|
|
@@ -479,12 +414,6 @@ export function EntityDetailPanel({
|
|
|
479
414
|
|
|
480
415
|
<div className="space-y-4">
|
|
481
416
|
<section>
|
|
482
|
-
<h3
|
|
483
|
-
className="mb-2 text-[13px] font-medium uppercase tracking-wide"
|
|
484
|
-
style={{ color: 'var(--ds-gray-700)' }}
|
|
485
|
-
>
|
|
486
|
-
Details
|
|
487
|
-
</h3>
|
|
488
417
|
<AttributePanel
|
|
489
418
|
data={displayData}
|
|
490
419
|
moduleSpecifier={moduleSpecifier}
|
|
@@ -142,8 +142,11 @@ function EventItem({
|
|
|
142
142
|
className="flex min-h-[32px] items-center justify-between gap-4 px-2.5 py-1.5"
|
|
143
143
|
style={{ borderColor: 'var(--ds-gray-300)' }}
|
|
144
144
|
>
|
|
145
|
-
<span
|
|
146
|
-
|
|
145
|
+
<span
|
|
146
|
+
className="text-[13px] font-medium"
|
|
147
|
+
style={{ color: 'var(--ds-gray-700)' }}
|
|
148
|
+
>
|
|
149
|
+
Event ID
|
|
147
150
|
</span>
|
|
148
151
|
<span
|
|
149
152
|
className="max-w-[70%] truncate text-right text-[13px] font-mono"
|
|
@@ -159,10 +162,10 @@ function EventItem({
|
|
|
159
162
|
style={{ borderColor: 'var(--ds-gray-300)' }}
|
|
160
163
|
>
|
|
161
164
|
<span
|
|
162
|
-
className="text-[
|
|
165
|
+
className="text-[13px] font-medium"
|
|
163
166
|
style={{ color: 'var(--ds-gray-700)' }}
|
|
164
167
|
>
|
|
165
|
-
|
|
168
|
+
Correlation ID
|
|
166
169
|
</span>
|
|
167
170
|
<span
|
|
168
171
|
className="max-w-[70%] truncate text-right text-[13px] font-mono"
|
|
@@ -311,10 +314,10 @@ export function EventsList({
|
|
|
311
314
|
return (
|
|
312
315
|
<div className="mt-2" style={{ color: 'var(--ds-gray-1000)' }}>
|
|
313
316
|
<h3
|
|
314
|
-
className="text-
|
|
317
|
+
className="text-label-14 font-medium mt-4 mb-2"
|
|
315
318
|
style={{ color: 'var(--ds-gray-1000)' }}
|
|
316
319
|
>
|
|
317
|
-
Events
|
|
320
|
+
Events
|
|
318
321
|
</h3>
|
|
319
322
|
{isLoading ? (
|
|
320
323
|
<div className="flex flex-col gap-4">
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, type ReactNode } from 'react';
|
|
4
|
+
import type { Event, Hook, Step, WorkflowRun } from '@workflow/world';
|
|
5
|
+
import type { SpanSelectionInfo } from './entity-detail-panel';
|
|
6
|
+
|
|
7
|
+
export interface SidebarDataContextValue {
|
|
8
|
+
run: WorkflowRun;
|
|
9
|
+
events: Event[];
|
|
10
|
+
spanDetailData: WorkflowRun | Step | Hook | Event | null;
|
|
11
|
+
spanDetailError?: Error | null;
|
|
12
|
+
spanDetailLoading?: boolean;
|
|
13
|
+
onSpanSelect: (info: SpanSelectionInfo) => void;
|
|
14
|
+
onStreamClick?: (streamId: string) => void;
|
|
15
|
+
onRunClick?: (runId: string) => void;
|
|
16
|
+
onWakeUpSleep?: (
|
|
17
|
+
runId: string,
|
|
18
|
+
correlationId: string
|
|
19
|
+
) => Promise<{ stoppedCount: number }>;
|
|
20
|
+
onLoadEventData?: (
|
|
21
|
+
correlationId: string,
|
|
22
|
+
eventId: string
|
|
23
|
+
) => Promise<unknown | null>;
|
|
24
|
+
onResolveHook?: (
|
|
25
|
+
hookToken: string,
|
|
26
|
+
payload: unknown,
|
|
27
|
+
hook?: Hook
|
|
28
|
+
) => Promise<void>;
|
|
29
|
+
encryptionKey?: Uint8Array;
|
|
30
|
+
onDecrypt?: () => void;
|
|
31
|
+
isDecrypting?: boolean;
|
|
32
|
+
hasEncryptedData?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const SidebarDataContext = createContext<SidebarDataContextValue | null>(null);
|
|
36
|
+
SidebarDataContext.displayName = 'SidebarDataContext';
|
|
37
|
+
|
|
38
|
+
export function SidebarDataProvider({
|
|
39
|
+
value,
|
|
40
|
+
children,
|
|
41
|
+
}: {
|
|
42
|
+
value: SidebarDataContextValue;
|
|
43
|
+
children: ReactNode;
|
|
44
|
+
}) {
|
|
45
|
+
return (
|
|
46
|
+
<SidebarDataContext.Provider value={value}>
|
|
47
|
+
{children}
|
|
48
|
+
</SidebarDataContext.Provider>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function useSidebarData(): SidebarDataContextValue {
|
|
53
|
+
const ctx = useContext(SidebarDataContext);
|
|
54
|
+
if (!ctx) {
|
|
55
|
+
throw new Error('useSidebarData must be used within a SidebarDataProvider');
|
|
56
|
+
}
|
|
57
|
+
return ctx;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function useSidebarDataOptional(): SidebarDataContextValue | null {
|
|
61
|
+
return useContext(SidebarDataContext);
|
|
62
|
+
}
|
|
@@ -5,37 +5,13 @@ import { Virtuoso, type VirtuosoHandle } from 'react-virtuoso';
|
|
|
5
5
|
import { DataInspector } from './ui/data-inspector';
|
|
6
6
|
import { Skeleton } from './ui/skeleton';
|
|
7
7
|
|
|
8
|
-
// ──────────────────────────────────────────────────────────────────────────
|
|
9
|
-
// Helpers
|
|
10
|
-
// ──────────────────────────────────────────────────────────────────────────
|
|
11
|
-
|
|
12
|
-
function deserializeChunkText(text: string): string {
|
|
13
|
-
try {
|
|
14
|
-
const parsed = JSON.parse(text);
|
|
15
|
-
if (typeof parsed === 'string') {
|
|
16
|
-
return parsed;
|
|
17
|
-
}
|
|
18
|
-
return JSON.stringify(parsed, null, 2);
|
|
19
|
-
} catch {
|
|
20
|
-
return text;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function parseChunkData(text: string): unknown {
|
|
25
|
-
try {
|
|
26
|
-
return JSON.parse(text);
|
|
27
|
-
} catch {
|
|
28
|
-
return text;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
8
|
// ──────────────────────────────────────────────────────────────────────────
|
|
33
9
|
// Types
|
|
34
10
|
// ──────────────────────────────────────────────────────────────────────────
|
|
35
11
|
|
|
36
12
|
export interface StreamChunk {
|
|
37
13
|
id: number;
|
|
38
|
-
|
|
14
|
+
value: unknown;
|
|
39
15
|
}
|
|
40
16
|
|
|
41
17
|
type Chunk = StreamChunk;
|
|
@@ -62,8 +38,6 @@ const ChunkRow = React.memo(function ChunkRow({
|
|
|
62
38
|
chunk: Chunk;
|
|
63
39
|
index: number;
|
|
64
40
|
}) {
|
|
65
|
-
const parsed = parseChunkData(chunk.text);
|
|
66
|
-
|
|
67
41
|
return (
|
|
68
42
|
<div
|
|
69
43
|
className="text-[11px] rounded-md border p-3"
|
|
@@ -72,22 +46,26 @@ const ChunkRow = React.memo(function ChunkRow({
|
|
|
72
46
|
backgroundColor: 'var(--ds-gray-100)',
|
|
73
47
|
}}
|
|
74
48
|
>
|
|
75
|
-
<
|
|
76
|
-
className="select-none mr-2"
|
|
77
|
-
style={{ color: 'var(--ds-gray-500)' }}
|
|
78
|
-
>
|
|
79
|
-
[{index}]
|
|
80
|
-
</span>
|
|
81
|
-
{typeof parsed === 'string' ? (
|
|
49
|
+
<div className="flex items-start gap-2">
|
|
82
50
|
<span
|
|
83
|
-
className="
|
|
84
|
-
style={{ color: 'var(--ds-gray-
|
|
51
|
+
className="select-none pt-px"
|
|
52
|
+
style={{ color: 'var(--ds-gray-500)' }}
|
|
85
53
|
>
|
|
86
|
-
{
|
|
54
|
+
[{index}]
|
|
87
55
|
</span>
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
56
|
+
<div className="min-w-0 flex-1">
|
|
57
|
+
{typeof chunk.value === 'string' ? (
|
|
58
|
+
<span
|
|
59
|
+
className="whitespace-pre-wrap break-words"
|
|
60
|
+
style={{ color: 'var(--ds-gray-1000)' }}
|
|
61
|
+
>
|
|
62
|
+
{chunk.value}
|
|
63
|
+
</span>
|
|
64
|
+
) : (
|
|
65
|
+
<DataInspector data={chunk.value} expandLevel={1} />
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
91
69
|
</div>
|
|
92
70
|
);
|
|
93
71
|
});
|
|
@@ -14,22 +14,18 @@ export function getMsInHighRes(ms: number): [number, number] {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Formats a duration for timeline display (compact
|
|
17
|
+
* Formats a duration for timeline display (compact format).
|
|
18
18
|
* @deprecated Use formatDuration(ms, true) instead.
|
|
19
19
|
*/
|
|
20
20
|
export const formatDurationForTimeline = (ms: number): string =>
|
|
21
21
|
formatDuration(ms, true);
|
|
22
22
|
|
|
23
|
-
const timeSelectionFormatter = new Intl.NumberFormat(undefined, {
|
|
24
|
-
minimumFractionDigits: 2,
|
|
25
|
-
maximumFractionDigits: 2,
|
|
26
|
-
});
|
|
27
|
-
|
|
28
23
|
export function formatTimeSelection(ms: number): string {
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
const roundedMs = Math.round(ms);
|
|
25
|
+
if (roundedMs < 1000) {
|
|
26
|
+
return `${roundedMs}ms`;
|
|
31
27
|
}
|
|
32
|
-
return `${
|
|
28
|
+
return `${Math.round(ms / 1000)}s`;
|
|
33
29
|
}
|
|
34
30
|
|
|
35
31
|
/**
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import type { WorkflowRun } from '@workflow/core/runtime';
|
|
3
|
+
import type { Event } from '@workflow/world';
|
|
4
|
+
import { buildTrace, type TraceWithMeta } from '../lib/trace-builder';
|
|
5
|
+
import { NewTraceViewer as NewTraceViewerComponent } from './new-trace-viewer/trace-viewer';
|
|
6
|
+
import {
|
|
7
|
+
SidebarDataProvider,
|
|
8
|
+
type SidebarDataContextValue,
|
|
9
|
+
} from './sidebar/sidebar-data-context';
|
|
10
|
+
import type { Trace } from './trace-viewer/types';
|
|
11
|
+
|
|
12
|
+
const NewTraceViewer = ({
|
|
13
|
+
run,
|
|
14
|
+
events,
|
|
15
|
+
sidebarData,
|
|
16
|
+
}: {
|
|
17
|
+
run: WorkflowRun;
|
|
18
|
+
events: Event[];
|
|
19
|
+
sidebarData: SidebarDataContextValue;
|
|
20
|
+
}) => {
|
|
21
|
+
// Build trace only when actual data changes — no timer-driven rebuilds.
|
|
22
|
+
// Active span widths are animated imperatively by useLiveTick at 60fps.
|
|
23
|
+
const traceWithMeta: TraceWithMeta | undefined = useMemo(() => {
|
|
24
|
+
if (!run?.runId) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
return buildTrace(run, events, new Date());
|
|
28
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- `new Date()` is intentionally not a dep; useLiveTick handles live growth
|
|
29
|
+
}, [run, events]);
|
|
30
|
+
const trace = traceWithMeta;
|
|
31
|
+
|
|
32
|
+
if (!trace) {
|
|
33
|
+
return (
|
|
34
|
+
<div className="relative w-full h-full flex items-center justify-center">
|
|
35
|
+
<div className="text-gray-500 text-sm">Loading trace…</div>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<SidebarDataProvider value={sidebarData}>
|
|
42
|
+
<div className="relative w-full h-full flex">
|
|
43
|
+
<NewTraceViewerComponent trace={trace as Trace} />
|
|
44
|
+
</div>
|
|
45
|
+
</SidebarDataProvider>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export { NewTraceViewer };
|
|
@@ -26,6 +26,11 @@ import {
|
|
|
26
26
|
} from 'react-inspector';
|
|
27
27
|
import { useDarkMode } from '../../hooks/use-dark-mode';
|
|
28
28
|
import { ENCRYPTED_DISPLAY_NAME } from '../../lib/hydration';
|
|
29
|
+
import {
|
|
30
|
+
type DecodedStreamChunkSource,
|
|
31
|
+
type FormattedStreamChunkDisplay,
|
|
32
|
+
formatArrayBufferViewForDisplay,
|
|
33
|
+
} from '../../lib/stream-display';
|
|
29
34
|
import {
|
|
30
35
|
type InspectorThemeExtended,
|
|
31
36
|
inspectorThemeDark,
|
|
@@ -43,6 +48,7 @@ import { Spinner } from './spinner';
|
|
|
43
48
|
const STREAM_REF_TYPE = '__workflow_stream_ref__';
|
|
44
49
|
const CLASS_INSTANCE_REF_TYPE = '__workflow_class_instance_ref__';
|
|
45
50
|
const RUN_REF_TYPE = '__workflow_run_ref__';
|
|
51
|
+
const BYTES_DISPLAY_TYPE = '__workflow_bytes_display__';
|
|
46
52
|
|
|
47
53
|
interface StreamRef {
|
|
48
54
|
__type: typeof STREAM_REF_TYPE;
|
|
@@ -54,6 +60,32 @@ interface RunRef {
|
|
|
54
60
|
runId: string;
|
|
55
61
|
}
|
|
56
62
|
|
|
63
|
+
interface BytesDisplay {
|
|
64
|
+
__type: typeof BYTES_DISPLAY_TYPE;
|
|
65
|
+
text: string;
|
|
66
|
+
decodedFrom?: DecodedStreamChunkSource;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function deserializeChunkText(text: string): string {
|
|
70
|
+
try {
|
|
71
|
+
const parsed = JSON.parse(text);
|
|
72
|
+
if (typeof parsed === 'string') {
|
|
73
|
+
return parsed;
|
|
74
|
+
}
|
|
75
|
+
return JSON.stringify(parsed, null, 2);
|
|
76
|
+
} catch {
|
|
77
|
+
return text;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseChunkData(text: string): unknown {
|
|
82
|
+
try {
|
|
83
|
+
return JSON.parse(text);
|
|
84
|
+
} catch {
|
|
85
|
+
return text;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
57
89
|
function isStreamRef(value: unknown): value is StreamRef {
|
|
58
90
|
if (value === null || typeof value !== 'object') return false;
|
|
59
91
|
// Check both enumerable and non-enumerable __type (opaque refs use non-enumerable)
|
|
@@ -67,6 +99,12 @@ function isRunRef(value: unknown): value is RunRef {
|
|
|
67
99
|
return desc?.value === RUN_REF_TYPE;
|
|
68
100
|
}
|
|
69
101
|
|
|
102
|
+
export function isBytesDisplay(value: unknown): value is BytesDisplay {
|
|
103
|
+
if (value === null || typeof value !== 'object') return false;
|
|
104
|
+
const desc = Object.getOwnPropertyDescriptor(value, '__type');
|
|
105
|
+
return desc?.value === BYTES_DISPLAY_TYPE;
|
|
106
|
+
}
|
|
107
|
+
|
|
70
108
|
function isClassInstanceRef(value: unknown): value is {
|
|
71
109
|
__type: string;
|
|
72
110
|
className: string;
|
|
@@ -219,6 +257,147 @@ const ExtendedThemeContext = createContext<InspectorThemeExtended>(
|
|
|
219
257
|
inspectorThemeExtendedLight
|
|
220
258
|
);
|
|
221
259
|
|
|
260
|
+
function DecodedBytesChunk({
|
|
261
|
+
decodedText,
|
|
262
|
+
source,
|
|
263
|
+
}: {
|
|
264
|
+
decodedText: string;
|
|
265
|
+
source: DecodedStreamChunkSource;
|
|
266
|
+
}) {
|
|
267
|
+
const [selectedView, setSelectedView] = useState<'decoded' | 'bytes'>(
|
|
268
|
+
'decoded'
|
|
269
|
+
);
|
|
270
|
+
const parsed = parseChunkData(decodedText);
|
|
271
|
+
|
|
272
|
+
return (
|
|
273
|
+
<div className="min-w-0">
|
|
274
|
+
{selectedView === 'decoded' ? (
|
|
275
|
+
<div className="min-w-0">
|
|
276
|
+
{typeof parsed === 'string' ? (
|
|
277
|
+
<span
|
|
278
|
+
className="whitespace-pre-wrap break-words"
|
|
279
|
+
style={{ color: 'var(--ds-gray-1000)' }}
|
|
280
|
+
>
|
|
281
|
+
{deserializeChunkText(parsed)}
|
|
282
|
+
</span>
|
|
283
|
+
) : (
|
|
284
|
+
<DataInspector data={parsed} expandLevel={1} />
|
|
285
|
+
)}
|
|
286
|
+
</div>
|
|
287
|
+
) : (
|
|
288
|
+
<DecodedBytesInspector decodedText={decodedText} source={source} />
|
|
289
|
+
)}
|
|
290
|
+
<div className="mt-2 flex">
|
|
291
|
+
<div
|
|
292
|
+
className="inline-flex overflow-hidden rounded border"
|
|
293
|
+
style={{ borderColor: 'var(--ds-gray-400)' }}
|
|
294
|
+
title={`${source.type} decoded as ${source.encoding.toUpperCase()} text. Switch to Bytes to inspect the summarized raw value.`}
|
|
295
|
+
>
|
|
296
|
+
<button
|
|
297
|
+
type="button"
|
|
298
|
+
className="h-5 px-1.5 text-[10px] font-medium"
|
|
299
|
+
style={{
|
|
300
|
+
backgroundColor:
|
|
301
|
+
selectedView === 'decoded'
|
|
302
|
+
? 'var(--ds-gray-200)'
|
|
303
|
+
: 'var(--ds-gray-100)',
|
|
304
|
+
color: 'var(--ds-gray-900)',
|
|
305
|
+
}}
|
|
306
|
+
onClick={() => setSelectedView('decoded')}
|
|
307
|
+
aria-pressed={selectedView === 'decoded'}
|
|
308
|
+
aria-label="Show decoded text"
|
|
309
|
+
>
|
|
310
|
+
Decoded
|
|
311
|
+
</button>
|
|
312
|
+
<button
|
|
313
|
+
type="button"
|
|
314
|
+
className="h-5 border-l px-1.5 text-[10px] font-medium"
|
|
315
|
+
style={{
|
|
316
|
+
borderColor: 'var(--ds-gray-400)',
|
|
317
|
+
backgroundColor:
|
|
318
|
+
selectedView === 'bytes'
|
|
319
|
+
? 'var(--ds-gray-200)'
|
|
320
|
+
: 'var(--ds-gray-100)',
|
|
321
|
+
color: 'var(--ds-gray-900)',
|
|
322
|
+
}}
|
|
323
|
+
onClick={() => setSelectedView('bytes')}
|
|
324
|
+
aria-pressed={selectedView === 'bytes'}
|
|
325
|
+
aria-label="Show raw bytes summary"
|
|
326
|
+
>
|
|
327
|
+
Bytes
|
|
328
|
+
</button>
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function DecodedBytesInspector({
|
|
336
|
+
decodedText,
|
|
337
|
+
source,
|
|
338
|
+
}: {
|
|
339
|
+
decodedText: string;
|
|
340
|
+
source: DecodedStreamChunkSource;
|
|
341
|
+
}) {
|
|
342
|
+
const [expanded, setExpanded] = useState(false);
|
|
343
|
+
|
|
344
|
+
return (
|
|
345
|
+
<div className="font-mono">
|
|
346
|
+
<button
|
|
347
|
+
type="button"
|
|
348
|
+
className="flex max-w-full items-start gap-1 text-left"
|
|
349
|
+
style={{ color: 'var(--ds-gray-1000)' }}
|
|
350
|
+
onClick={() => setExpanded((value) => !value)}
|
|
351
|
+
title={`${source.type} decoded as ${source.encoding.toUpperCase()} text`}
|
|
352
|
+
>
|
|
353
|
+
<span className="select-none" style={{ color: 'var(--ds-gray-700)' }}>
|
|
354
|
+
{expanded ? '▼' : '▶'}
|
|
355
|
+
</span>
|
|
356
|
+
<span className="min-w-0 break-words">{source.rawSummary}</span>
|
|
357
|
+
</button>
|
|
358
|
+
{expanded && (
|
|
359
|
+
<div className="mt-1 pl-5">
|
|
360
|
+
<span style={{ color: 'var(--ds-gray-700)' }}>decoded: </span>
|
|
361
|
+
<span
|
|
362
|
+
className="whitespace-pre-wrap break-words"
|
|
363
|
+
style={{ color: 'var(--ds-green-900)' }}
|
|
364
|
+
>
|
|
365
|
+
{JSON.stringify(decodedText)}
|
|
366
|
+
</span>
|
|
367
|
+
</div>
|
|
368
|
+
)}
|
|
369
|
+
</div>
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function BytesDisplayLabel({
|
|
374
|
+
name,
|
|
375
|
+
display,
|
|
376
|
+
}: {
|
|
377
|
+
name?: string;
|
|
378
|
+
display: BytesDisplay;
|
|
379
|
+
}) {
|
|
380
|
+
return (
|
|
381
|
+
<div className="inline-block min-w-0 align-top">
|
|
382
|
+
{name != null && <ObjectName name={name} />}
|
|
383
|
+
{name != null && <span>: </span>}
|
|
384
|
+
{display.decodedFrom ? (
|
|
385
|
+
<DecodedBytesChunk
|
|
386
|
+
decodedText={display.text}
|
|
387
|
+
source={display.decodedFrom}
|
|
388
|
+
/>
|
|
389
|
+
) : (
|
|
390
|
+
<span
|
|
391
|
+
className="whitespace-pre-wrap break-words"
|
|
392
|
+
style={{ color: 'var(--ds-gray-1000)' }}
|
|
393
|
+
>
|
|
394
|
+
{display.text}
|
|
395
|
+
</span>
|
|
396
|
+
)}
|
|
397
|
+
</div>
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
222
401
|
// ---------------------------------------------------------------------------
|
|
223
402
|
// Custom nodeRenderer
|
|
224
403
|
// ---------------------------------------------------------------------------
|
|
@@ -248,6 +427,10 @@ function NodeRenderer({
|
|
|
248
427
|
}) {
|
|
249
428
|
const extendedTheme = useContext(ExtendedThemeContext);
|
|
250
429
|
|
|
430
|
+
if (isBytesDisplay(data)) {
|
|
431
|
+
return <BytesDisplayLabel name={name} display={data} />;
|
|
432
|
+
}
|
|
433
|
+
|
|
251
434
|
// Encrypted marker → flat label with Lock icon, clickable when onDecrypt is available
|
|
252
435
|
if (
|
|
253
436
|
data !== null &&
|
|
@@ -352,18 +535,53 @@ function makeOpaqueRef(ref: Record<string, unknown>): unknown {
|
|
|
352
535
|
return opaque;
|
|
353
536
|
}
|
|
354
537
|
|
|
538
|
+
function makeBytesDisplay(display: FormattedStreamChunkDisplay): unknown {
|
|
539
|
+
const opaque = Object.create(null);
|
|
540
|
+
Object.defineProperty(opaque, '__type', {
|
|
541
|
+
value: BYTES_DISPLAY_TYPE,
|
|
542
|
+
enumerable: false,
|
|
543
|
+
});
|
|
544
|
+
Object.defineProperty(opaque, 'text', {
|
|
545
|
+
value: display.text,
|
|
546
|
+
enumerable: false,
|
|
547
|
+
});
|
|
548
|
+
Object.defineProperty(opaque, 'decodedFrom', {
|
|
549
|
+
value: display.decodedFrom,
|
|
550
|
+
enumerable: false,
|
|
551
|
+
});
|
|
552
|
+
return opaque;
|
|
553
|
+
}
|
|
554
|
+
|
|
355
555
|
/**
|
|
356
|
-
* Recursively walk data and replace RunRef/StreamRef objects with
|
|
556
|
+
* Recursively walk data and replace RunRef/StreamRef/typed array objects with
|
|
357
557
|
* non-expandable versions so ObjectInspector doesn't show their internals.
|
|
358
558
|
* Only recurses into plain objects and arrays to avoid stripping class
|
|
359
|
-
* instances (Date, Error,
|
|
360
|
-
*
|
|
559
|
+
* instances (Date, Error, URL, Headers, etc.) that have their own rendering in
|
|
560
|
+
* NodeRenderer. Map and Set containers are preserved while their contents are
|
|
561
|
+
* prepared for display.
|
|
562
|
+
*
|
|
563
|
+
* Exported for testing the typed-array detection path used by hydrated
|
|
564
|
+
* AI agent stream chunks (e.g. `{ delta: new Uint8Array(...) }`).
|
|
361
565
|
*/
|
|
362
|
-
function collapseRefs(data: unknown): unknown {
|
|
566
|
+
export function collapseRefs(data: unknown): unknown {
|
|
363
567
|
if (data === null || typeof data !== 'object') return data;
|
|
568
|
+
if (ArrayBuffer.isView(data) && !(data instanceof DataView)) {
|
|
569
|
+
return makeBytesDisplay(formatArrayBufferViewForDisplay(data));
|
|
570
|
+
}
|
|
364
571
|
if (isRunRef(data) || isStreamRef(data))
|
|
365
572
|
return makeOpaqueRef(data as unknown as Record<string, unknown>);
|
|
366
573
|
if (Array.isArray(data)) return data.map(collapseRefs);
|
|
574
|
+
if (data instanceof Map) {
|
|
575
|
+
return new Map(
|
|
576
|
+
Array.from(data.entries(), ([key, value]) => [
|
|
577
|
+
collapseRefs(key),
|
|
578
|
+
collapseRefs(value),
|
|
579
|
+
])
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
if (data instanceof Set) {
|
|
583
|
+
return new Set(Array.from(data.values(), collapseRefs));
|
|
584
|
+
}
|
|
367
585
|
// Only recurse into plain objects — leave class instances untouched
|
|
368
586
|
const proto = Object.getPrototypeOf(data);
|
|
369
587
|
if (proto !== Object.prototype && proto !== null) return data;
|
|
@@ -467,9 +685,22 @@ function isObjectLike(value: unknown): value is Record<string, unknown> {
|
|
|
467
685
|
return typeof value === 'object' && value !== null;
|
|
468
686
|
}
|
|
469
687
|
|
|
688
|
+
function isSameBytesDisplay(a: BytesDisplay, b: BytesDisplay): boolean {
|
|
689
|
+
return (
|
|
690
|
+
a.text === b.text &&
|
|
691
|
+
a.decodedFrom?.type === b.decodedFrom?.type &&
|
|
692
|
+
a.decodedFrom?.encoding === b.decodedFrom?.encoding &&
|
|
693
|
+
a.decodedFrom?.rawSummary === b.decodedFrom?.rawSummary
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
|
|
470
697
|
function isDeepEqual(a: unknown, b: unknown, seen = new WeakMap()): boolean {
|
|
471
698
|
if (Object.is(a, b)) return true;
|
|
472
699
|
|
|
700
|
+
if (isBytesDisplay(a) || isBytesDisplay(b)) {
|
|
701
|
+
return isBytesDisplay(a) && isBytesDisplay(b) && isSameBytesDisplay(a, b);
|
|
702
|
+
}
|
|
703
|
+
|
|
473
704
|
if (a instanceof Date && b instanceof Date) {
|
|
474
705
|
return a.getTime() === b.getTime();
|
|
475
706
|
}
|