@workflow/web-shared 4.1.0-beta.47 → 4.1.0-beta.49
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/README.md +26 -52
- package/dist/components/error-boundary.d.ts.map +1 -0
- package/dist/{error-boundary.js → components/error-boundary.js} +1 -1
- package/dist/components/error-boundary.js.map +1 -0
- package/dist/{event-list-view.d.ts → components/event-list-view.d.ts} +2 -3
- package/dist/components/event-list-view.d.ts.map +1 -0
- package/dist/{event-list-view.js → components/event-list-view.js} +9 -17
- package/dist/components/event-list-view.js.map +1 -0
- package/dist/{hook-actions.d.ts → components/hook-actions.d.ts} +2 -3
- package/dist/components/hook-actions.d.ts.map +1 -0
- package/dist/{hook-actions.js → components/hook-actions.js} +3 -4
- package/dist/components/hook-actions.js.map +1 -0
- package/dist/components/index.d.ts +10 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +8 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/run-trace-view.d.ts +22 -0
- package/dist/components/run-trace-view.d.ts.map +1 -0
- package/dist/components/run-trace-view.js +11 -0
- package/dist/components/run-trace-view.js.map +1 -0
- package/dist/components/sidebar/attribute-panel.d.ts.map +1 -0
- package/dist/{sidebar → components/sidebar}/attribute-panel.js +3 -3
- package/dist/components/sidebar/attribute-panel.js.map +1 -0
- package/dist/components/sidebar/conversation-view.d.ts.map +1 -0
- package/dist/components/sidebar/conversation-view.js.map +1 -0
- package/dist/components/sidebar/detail-card.d.ts.map +1 -0
- package/dist/components/sidebar/detail-card.js.map +1 -0
- package/dist/components/sidebar/entity-detail-panel.d.ts +32 -0
- package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -0
- package/dist/{sidebar → components/sidebar}/entity-detail-panel.js +61 -49
- package/dist/components/sidebar/entity-detail-panel.js.map +1 -0
- package/dist/components/sidebar/events-list.d.ts +8 -0
- package/dist/components/sidebar/events-list.d.ts.map +1 -0
- package/dist/components/sidebar/events-list.js +16 -0
- package/dist/components/sidebar/events-list.js.map +1 -0
- package/dist/components/sidebar/resolve-hook-modal.d.ts.map +1 -0
- package/dist/components/sidebar/resolve-hook-modal.js.map +1 -0
- package/dist/components/stream-viewer.d.ts +18 -0
- package/dist/components/stream-viewer.d.ts.map +1 -0
- package/dist/{stream-viewer.js → components/stream-viewer.js} +1 -59
- package/dist/components/stream-viewer.js.map +1 -0
- package/dist/components/trace-viewer/components/map.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/map.js.map +1 -0
- package/dist/components/trace-viewer/components/markers.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/markers.js.map +1 -0
- package/dist/components/trace-viewer/components/node.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/node.js.map +1 -0
- package/dist/components/trace-viewer/components/search-input.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/search-input.js.map +1 -0
- package/dist/components/trace-viewer/components/search.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/search.js.map +1 -0
- package/dist/components/trace-viewer/components/span-detail-panel.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/span-detail-panel.js.map +1 -0
- package/dist/components/trace-viewer/components/ui.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/ui.js.map +1 -0
- package/dist/components/trace-viewer/components/zoom-button.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/zoom-button.js.map +1 -0
- package/dist/components/trace-viewer/components/zoom-icons.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/zoom-icons.js.map +1 -0
- package/dist/components/trace-viewer/context.d.ts.map +1 -0
- package/dist/components/trace-viewer/context.js.map +1 -0
- package/dist/components/trace-viewer/index.d.ts.map +1 -0
- package/dist/components/trace-viewer/index.js.map +1 -0
- package/dist/components/trace-viewer/trace-viewer.d.ts.map +1 -0
- package/dist/components/trace-viewer/trace-viewer.js.map +1 -0
- package/dist/components/trace-viewer/types.d.ts.map +1 -0
- package/dist/components/trace-viewer/types.js.map +1 -0
- package/dist/components/trace-viewer/util/constants.d.ts.map +1 -0
- package/dist/components/trace-viewer/util/constants.js.map +1 -0
- package/dist/components/trace-viewer/util/scrollbar-width.d.ts.map +1 -0
- package/dist/components/trace-viewer/util/scrollbar-width.js.map +1 -0
- package/dist/{trace-viewer → components/trace-viewer}/util/timing.d.ts +1 -1
- package/dist/components/trace-viewer/util/timing.d.ts.map +1 -0
- package/dist/{trace-viewer → components/trace-viewer}/util/timing.js +1 -1
- package/dist/components/trace-viewer/util/timing.js.map +1 -0
- package/dist/components/trace-viewer/util/tree.d.ts.map +1 -0
- package/dist/components/trace-viewer/util/tree.js.map +1 -0
- package/dist/components/trace-viewer/util/use-immediate-style.d.ts.map +1 -0
- package/dist/components/trace-viewer/util/use-immediate-style.js.map +1 -0
- package/dist/components/trace-viewer/util/use-streaming-spans.d.ts.map +1 -0
- package/dist/components/trace-viewer/util/use-streaming-spans.js.map +1 -0
- package/dist/components/trace-viewer/util/use-trackpad-zoom.d.ts.map +1 -0
- package/dist/components/trace-viewer/util/use-trackpad-zoom.js.map +1 -0
- package/dist/components/trace-viewer/worker.d.ts.map +1 -0
- package/dist/components/trace-viewer/worker.js.map +1 -0
- package/dist/components/workflow-trace-view.d.ts +24 -0
- package/dist/components/workflow-trace-view.d.ts.map +1 -0
- package/dist/components/workflow-trace-view.js +152 -0
- package/dist/components/workflow-trace-view.js.map +1 -0
- package/dist/components/workflow-traces/event-colors.d.ts.map +1 -0
- package/dist/components/workflow-traces/event-colors.js.map +1 -0
- package/dist/components/workflow-traces/trace-colors.d.ts.map +1 -0
- package/dist/components/workflow-traces/trace-colors.js.map +1 -0
- package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -0
- package/dist/components/workflow-traces/trace-span-construction.js.map +1 -0
- package/dist/components/workflow-traces/trace-time-utils.d.ts.map +1 -0
- package/dist/components/workflow-traces/trace-time-utils.js.map +1 -0
- package/dist/index.d.ts +3 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -9
- package/dist/index.js.map +1 -1
- package/package.json +15 -11
- package/src/components/error-boundary.tsx +79 -0
- package/src/components/event-list-view.tsx +429 -0
- package/src/components/hook-actions.tsx +167 -0
- package/src/components/index.d.ts +1 -0
- package/src/components/index.ts +23 -0
- package/src/components/run-trace-view.tsx +75 -0
- package/src/components/sidebar/attribute-panel.tsx +938 -0
- package/src/components/sidebar/conversation-view.tsx +235 -0
- package/src/components/sidebar/detail-card.tsx +43 -0
- package/src/components/sidebar/entity-detail-panel.tsx +338 -0
- package/src/components/sidebar/events-list.tsx +119 -0
- package/src/components/sidebar/resolve-hook-modal.tsx +219 -0
- package/src/components/stream-viewer.tsx +143 -0
- package/src/components/trace-viewer/components/map.tsx +226 -0
- package/src/components/trace-viewer/components/markers.tsx +564 -0
- package/src/components/trace-viewer/components/node.tsx +259 -0
- package/src/components/trace-viewer/components/search-input.tsx +52 -0
- package/src/components/trace-viewer/components/search.tsx +47 -0
- package/src/components/trace-viewer/components/span-detail-panel.tsx +650 -0
- package/src/components/trace-viewer/components/ui.tsx +156 -0
- package/src/components/trace-viewer/components/zoom-button.tsx +61 -0
- package/src/components/trace-viewer/components/zoom-icons.tsx +65 -0
- package/src/components/trace-viewer/context.tsx +633 -0
- package/src/components/trace-viewer/index.tsx +4 -0
- package/src/components/trace-viewer/modules.d.ts +16 -0
- package/src/components/trace-viewer/trace-viewer.module.css +1292 -0
- package/src/components/trace-viewer/trace-viewer.tsx +448 -0
- package/src/components/trace-viewer/types.ts +234 -0
- package/src/components/trace-viewer/util/constants.ts +8 -0
- package/src/components/trace-viewer/util/scrollbar-width.ts +13 -0
- package/src/components/trace-viewer/util/timing.ts +33 -0
- package/src/components/trace-viewer/util/tree.ts +277 -0
- package/src/components/trace-viewer/util/use-immediate-style.ts +38 -0
- package/src/components/trace-viewer/util/use-streaming-spans.ts +415 -0
- package/src/components/trace-viewer/util/use-trackpad-zoom.tsx +51 -0
- package/src/components/trace-viewer/worker.ts +128 -0
- package/src/components/ui/alert.tsx +59 -0
- package/src/components/ui/card.tsx +88 -0
- package/src/components/ui/error-card.tsx +65 -0
- package/src/components/ui/skeleton.tsx +15 -0
- package/src/components/workflow-trace-view.tsx +306 -0
- package/src/components/workflow-traces/event-colors.ts +94 -0
- package/src/components/workflow-traces/trace-colors.ts +112 -0
- package/src/components/workflow-traces/trace-span-construction.ts +299 -0
- package/src/components/workflow-traces/trace-time-utils.ts +50 -0
- package/src/hooks/use-dark-mode.ts +34 -0
- package/src/index.d.ts +1 -0
- package/src/index.ts +29 -0
- package/src/lib/event-analysis.ts +231 -0
- package/src/lib/utils.ts +166 -0
- package/dist/api/workflow-api-client.d.ts +0 -543
- package/dist/api/workflow-api-client.d.ts.map +0 -1
- package/dist/api/workflow-api-client.js +0 -953
- package/dist/api/workflow-api-client.js.map +0 -1
- package/dist/api/workflow-server-actions.d.ts +0 -230
- package/dist/api/workflow-server-actions.d.ts.map +0 -1
- package/dist/api/workflow-server-actions.js +0 -861
- package/dist/api/workflow-server-actions.js.map +0 -1
- package/dist/error-boundary.d.ts.map +0 -1
- package/dist/error-boundary.js.map +0 -1
- package/dist/event-list-view.d.ts.map +0 -1
- package/dist/event-list-view.js.map +0 -1
- package/dist/hook-actions.d.ts.map +0 -1
- package/dist/hook-actions.js.map +0 -1
- package/dist/run-trace-view.d.ts +0 -8
- package/dist/run-trace-view.d.ts.map +0 -1
- package/dist/run-trace-view.js +0 -15
- package/dist/run-trace-view.js.map +0 -1
- package/dist/sidebar/attribute-panel.d.ts.map +0 -1
- package/dist/sidebar/attribute-panel.js.map +0 -1
- package/dist/sidebar/conversation-view.d.ts.map +0 -1
- package/dist/sidebar/conversation-view.js.map +0 -1
- package/dist/sidebar/detail-card.d.ts.map +0 -1
- package/dist/sidebar/detail-card.js.map +0 -1
- package/dist/sidebar/entity-detail-panel.d.ts +0 -12
- package/dist/sidebar/entity-detail-panel.d.ts.map +0 -1
- package/dist/sidebar/entity-detail-panel.js.map +0 -1
- package/dist/sidebar/events-list.d.ts +0 -9
- package/dist/sidebar/events-list.d.ts.map +0 -1
- package/dist/sidebar/events-list.js +0 -36
- package/dist/sidebar/events-list.js.map +0 -1
- package/dist/sidebar/resolve-hook-modal.d.ts.map +0 -1
- package/dist/sidebar/resolve-hook-modal.js.map +0 -1
- package/dist/stream-viewer.d.ts +0 -13
- package/dist/stream-viewer.d.ts.map +0 -1
- package/dist/stream-viewer.js.map +0 -1
- package/dist/trace-viewer/components/map.d.ts.map +0 -1
- package/dist/trace-viewer/components/map.js.map +0 -1
- package/dist/trace-viewer/components/markers.d.ts.map +0 -1
- package/dist/trace-viewer/components/markers.js.map +0 -1
- package/dist/trace-viewer/components/node.d.ts.map +0 -1
- package/dist/trace-viewer/components/node.js.map +0 -1
- package/dist/trace-viewer/components/search-input.d.ts.map +0 -1
- package/dist/trace-viewer/components/search-input.js.map +0 -1
- package/dist/trace-viewer/components/search.d.ts.map +0 -1
- package/dist/trace-viewer/components/search.js.map +0 -1
- package/dist/trace-viewer/components/span-detail-panel.d.ts.map +0 -1
- package/dist/trace-viewer/components/span-detail-panel.js.map +0 -1
- package/dist/trace-viewer/components/ui.d.ts.map +0 -1
- package/dist/trace-viewer/components/ui.js.map +0 -1
- package/dist/trace-viewer/components/zoom-button.d.ts.map +0 -1
- package/dist/trace-viewer/components/zoom-button.js.map +0 -1
- package/dist/trace-viewer/components/zoom-icons.d.ts.map +0 -1
- package/dist/trace-viewer/components/zoom-icons.js.map +0 -1
- package/dist/trace-viewer/context.d.ts.map +0 -1
- package/dist/trace-viewer/context.js.map +0 -1
- package/dist/trace-viewer/index.d.ts.map +0 -1
- package/dist/trace-viewer/index.js.map +0 -1
- package/dist/trace-viewer/trace-viewer.d.ts.map +0 -1
- package/dist/trace-viewer/trace-viewer.js.map +0 -1
- package/dist/trace-viewer/types.d.ts.map +0 -1
- package/dist/trace-viewer/types.js.map +0 -1
- package/dist/trace-viewer/util/constants.d.ts.map +0 -1
- package/dist/trace-viewer/util/constants.js.map +0 -1
- package/dist/trace-viewer/util/scrollbar-width.d.ts.map +0 -1
- package/dist/trace-viewer/util/scrollbar-width.js.map +0 -1
- package/dist/trace-viewer/util/timing.d.ts.map +0 -1
- package/dist/trace-viewer/util/timing.js.map +0 -1
- package/dist/trace-viewer/util/tree.d.ts.map +0 -1
- package/dist/trace-viewer/util/tree.js.map +0 -1
- package/dist/trace-viewer/util/use-immediate-style.d.ts.map +0 -1
- package/dist/trace-viewer/util/use-immediate-style.js.map +0 -1
- package/dist/trace-viewer/util/use-streaming-spans.d.ts.map +0 -1
- package/dist/trace-viewer/util/use-streaming-spans.js.map +0 -1
- package/dist/trace-viewer/util/use-trackpad-zoom.d.ts.map +0 -1
- package/dist/trace-viewer/util/use-trackpad-zoom.js.map +0 -1
- package/dist/trace-viewer/worker.d.ts.map +0 -1
- package/dist/trace-viewer/worker.js.map +0 -1
- package/dist/workflow-trace-view.d.ts +0 -14
- package/dist/workflow-trace-view.d.ts.map +0 -1
- package/dist/workflow-trace-view.js +0 -135
- package/dist/workflow-trace-view.js.map +0 -1
- package/dist/workflow-traces/event-colors.d.ts.map +0 -1
- package/dist/workflow-traces/event-colors.js.map +0 -1
- package/dist/workflow-traces/trace-colors.d.ts.map +0 -1
- package/dist/workflow-traces/trace-colors.js.map +0 -1
- package/dist/workflow-traces/trace-span-construction.d.ts.map +0 -1
- package/dist/workflow-traces/trace-span-construction.js.map +0 -1
- package/dist/workflow-traces/trace-time-utils.d.ts.map +0 -1
- package/dist/workflow-traces/trace-time-utils.js.map +0 -1
- package/server/README.md +0 -1
- package/server/package.json +0 -4
- /package/dist/{error-boundary.d.ts → components/error-boundary.d.ts} +0 -0
- /package/dist/{sidebar → components/sidebar}/attribute-panel.d.ts +0 -0
- /package/dist/{sidebar → components/sidebar}/conversation-view.d.ts +0 -0
- /package/dist/{sidebar → components/sidebar}/conversation-view.js +0 -0
- /package/dist/{sidebar → components/sidebar}/detail-card.d.ts +0 -0
- /package/dist/{sidebar → components/sidebar}/detail-card.js +0 -0
- /package/dist/{sidebar → components/sidebar}/resolve-hook-modal.d.ts +0 -0
- /package/dist/{sidebar → components/sidebar}/resolve-hook-modal.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/map.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/map.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/markers.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/markers.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/node.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/node.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/search-input.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/search-input.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/search.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/search.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/span-detail-panel.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/span-detail-panel.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/ui.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/ui.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/zoom-button.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/zoom-button.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/zoom-icons.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/components/zoom-icons.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/context.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/context.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/index.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/index.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/trace-viewer.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/trace-viewer.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/trace-viewer.module.css +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/types.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/types.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/constants.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/constants.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/scrollbar-width.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/scrollbar-width.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/tree.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/tree.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/use-immediate-style.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/use-immediate-style.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/use-streaming-spans.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/use-streaming-spans.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/use-trackpad-zoom.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/util/use-trackpad-zoom.js +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/worker.d.ts +0 -0
- /package/dist/{trace-viewer → components/trace-viewer}/worker.js +0 -0
- /package/dist/{workflow-traces → components/workflow-traces}/event-colors.d.ts +0 -0
- /package/dist/{workflow-traces → components/workflow-traces}/event-colors.js +0 -0
- /package/dist/{workflow-traces → components/workflow-traces}/trace-colors.d.ts +0 -0
- /package/dist/{workflow-traces → components/workflow-traces}/trace-colors.js +0 -0
- /package/dist/{workflow-traces → components/workflow-traces}/trace-span-construction.d.ts +0 -0
- /package/dist/{workflow-traces → components/workflow-traces}/trace-span-construction.js +0 -0
- /package/dist/{workflow-traces → components/workflow-traces}/trace-time-utils.d.ts +0 -0
- /package/dist/{workflow-traces → components/workflow-traces}/trace-time-utils.js +0 -0
|
@@ -0,0 +1,938 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { parseStepName, parseWorkflowName } from '@workflow/utils/parse-name';
|
|
4
|
+
import type { Event, Hook, Step, WorkflowRun } from '@workflow/world';
|
|
5
|
+
import type { ModelMessage } from 'ai';
|
|
6
|
+
import type { ReactNode } from 'react';
|
|
7
|
+
import { createContext, useContext, useMemo, useState } from 'react';
|
|
8
|
+
import { ErrorCard } from '../ui/error-card';
|
|
9
|
+
import { useDarkMode } from '../../hooks/use-dark-mode';
|
|
10
|
+
import { extractConversation, isDoStreamStep } from '../../lib/utils';
|
|
11
|
+
import { ConversationView } from './conversation-view';
|
|
12
|
+
import { DetailCard } from './detail-card';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Tab button for conversation/JSON toggle
|
|
16
|
+
*/
|
|
17
|
+
function TabButton({
|
|
18
|
+
active,
|
|
19
|
+
onClick,
|
|
20
|
+
children,
|
|
21
|
+
}: {
|
|
22
|
+
active: boolean;
|
|
23
|
+
onClick: () => void;
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
}) {
|
|
26
|
+
return (
|
|
27
|
+
<button
|
|
28
|
+
type="button"
|
|
29
|
+
onClick={onClick}
|
|
30
|
+
className="px-3 py-1.5 text-[11px] font-medium transition-colors -mb-px"
|
|
31
|
+
style={{
|
|
32
|
+
// Explicit styles to prevent app-level button overrides when web-shared
|
|
33
|
+
// is embedded in a self-hosted app.
|
|
34
|
+
backgroundColor: 'transparent',
|
|
35
|
+
borderTop: 'none',
|
|
36
|
+
borderLeft: 'none',
|
|
37
|
+
borderRight: 'none',
|
|
38
|
+
borderBottom: `2px solid ${active ? 'var(--ds-blue-600)' : 'transparent'}`,
|
|
39
|
+
borderRadius: 0,
|
|
40
|
+
outline: 'none',
|
|
41
|
+
boxShadow: 'none',
|
|
42
|
+
cursor: 'pointer',
|
|
43
|
+
color: active ? 'var(--ds-gray-1000)' : 'var(--ds-gray-600)',
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
{children}
|
|
47
|
+
</button>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Tabbed view for conversation and raw JSON
|
|
53
|
+
*/
|
|
54
|
+
function ConversationWithTabs({
|
|
55
|
+
conversation,
|
|
56
|
+
args,
|
|
57
|
+
}: {
|
|
58
|
+
conversation: ModelMessage[];
|
|
59
|
+
args: unknown[];
|
|
60
|
+
}) {
|
|
61
|
+
const [activeTab, setActiveTab] = useState<'conversation' | 'json'>(
|
|
62
|
+
'conversation'
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<DetailCard summary={`Input (${conversation.length} messages)`}>
|
|
67
|
+
<div
|
|
68
|
+
className="rounded-md border"
|
|
69
|
+
style={{
|
|
70
|
+
borderColor: 'var(--ds-gray-300)',
|
|
71
|
+
backgroundColor: 'transparent',
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
<div
|
|
75
|
+
className="flex gap-1 border-b"
|
|
76
|
+
style={{
|
|
77
|
+
borderColor: 'var(--ds-gray-300)',
|
|
78
|
+
backgroundColor: 'transparent',
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
<TabButton
|
|
82
|
+
active={activeTab === 'conversation'}
|
|
83
|
+
onClick={() => setActiveTab('conversation')}
|
|
84
|
+
>
|
|
85
|
+
Conversation
|
|
86
|
+
</TabButton>
|
|
87
|
+
<TabButton
|
|
88
|
+
active={activeTab === 'json'}
|
|
89
|
+
onClick={() => setActiveTab('json')}
|
|
90
|
+
>
|
|
91
|
+
Raw JSON
|
|
92
|
+
</TabButton>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
{activeTab === 'conversation' ? (
|
|
96
|
+
<ConversationView messages={conversation} />
|
|
97
|
+
) : (
|
|
98
|
+
<div className="p-3">
|
|
99
|
+
{Array.isArray(args)
|
|
100
|
+
? args.map((v, i) => (
|
|
101
|
+
<div className="mt-2 first:mt-0" key={i}>
|
|
102
|
+
{JsonBlock(v)}
|
|
103
|
+
</div>
|
|
104
|
+
))
|
|
105
|
+
: JsonBlock(args)}
|
|
106
|
+
</div>
|
|
107
|
+
)}
|
|
108
|
+
</div>
|
|
109
|
+
</DetailCard>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Context for stream click handler
|
|
115
|
+
*/
|
|
116
|
+
const StreamClickContext = createContext<
|
|
117
|
+
((streamId: string) => void) | undefined
|
|
118
|
+
>(undefined);
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Marker for stream reference objects that can be rendered as links
|
|
122
|
+
* This is duplicated from @workflow/core/observability to avoid pulling in
|
|
123
|
+
* Node.js dependencies into the client bundle.
|
|
124
|
+
*/
|
|
125
|
+
const STREAM_REF_TYPE = '__workflow_stream_ref__';
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* A stream reference object that contains the stream ID and can be
|
|
129
|
+
* detected in the UI to render as a clickable link
|
|
130
|
+
*/
|
|
131
|
+
interface StreamRef {
|
|
132
|
+
__type: typeof STREAM_REF_TYPE;
|
|
133
|
+
streamId: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Check if a value is a StreamRef object
|
|
138
|
+
*
|
|
139
|
+
*/
|
|
140
|
+
const isStreamRef = (value: unknown): value is StreamRef => {
|
|
141
|
+
// TODO: This is duplicated from @workflow/core/observability, but can't be pulled
|
|
142
|
+
// in client-side code because it's a Node.js dependency.
|
|
143
|
+
return (
|
|
144
|
+
value !== null &&
|
|
145
|
+
typeof value === 'object' &&
|
|
146
|
+
'__type' in value &&
|
|
147
|
+
value.__type === STREAM_REF_TYPE &&
|
|
148
|
+
'streamId' in value &&
|
|
149
|
+
typeof value.streamId === 'string'
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Marker for custom class instance references.
|
|
155
|
+
* This is duplicated from @workflow/core/observability to avoid pulling in
|
|
156
|
+
* Node.js dependencies into the client bundle.
|
|
157
|
+
*/
|
|
158
|
+
const CLASS_INSTANCE_REF_TYPE = '__workflow_class_instance_ref__';
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* A class instance reference object that contains the class name and serialized data.
|
|
162
|
+
* Used in o11y when a custom class instance is encountered but the class is not
|
|
163
|
+
* registered for deserialization.
|
|
164
|
+
*/
|
|
165
|
+
interface ClassInstanceRef {
|
|
166
|
+
__type: typeof CLASS_INSTANCE_REF_TYPE;
|
|
167
|
+
className: string;
|
|
168
|
+
classId: string;
|
|
169
|
+
data: unknown;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Check if a value is a ClassInstanceRef object
|
|
174
|
+
*/
|
|
175
|
+
const isClassInstanceRef = (value: unknown): value is ClassInstanceRef => {
|
|
176
|
+
return (
|
|
177
|
+
value !== null &&
|
|
178
|
+
typeof value === 'object' &&
|
|
179
|
+
'__type' in value &&
|
|
180
|
+
value.__type === CLASS_INSTANCE_REF_TYPE &&
|
|
181
|
+
'className' in value &&
|
|
182
|
+
typeof value.className === 'string'
|
|
183
|
+
);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
import ColorHash from 'color-hash';
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Color hash instance configured for nice saturation and lightness.
|
|
190
|
+
* Returns HSL values which we can transform for different use cases.
|
|
191
|
+
*/
|
|
192
|
+
const colorHash = new ColorHash({
|
|
193
|
+
saturation: [0.5, 0.6, 0.7],
|
|
194
|
+
lightness: [0.4, 0.5, 0.6],
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Convert HSL to CSS hsl() string
|
|
199
|
+
*/
|
|
200
|
+
const hslToString = (h: number, s: number, l: number): string => {
|
|
201
|
+
return `hsl(${h}, ${Math.round(s * 100)}%, ${Math.round(l * 100)}%)`;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get consistent colors for a class ID using color-hash and HSL transformations.
|
|
206
|
+
* Adjusts colors based on light/dark mode for optimal appearance.
|
|
207
|
+
*/
|
|
208
|
+
const getClassColors = (
|
|
209
|
+
classId: string,
|
|
210
|
+
isDark: boolean
|
|
211
|
+
): { header: string; body: string; text: string } => {
|
|
212
|
+
const [h, s, l] = colorHash.hsl(classId);
|
|
213
|
+
|
|
214
|
+
if (isDark) {
|
|
215
|
+
// Dark mode: vibrant header, dark body, light text
|
|
216
|
+
return {
|
|
217
|
+
header: hslToString(h, s, Math.min(l + 0.1, 0.6)), // Slightly brighter header
|
|
218
|
+
body: hslToString(h, s * 0.8, 0.15), // Very dark, slightly desaturated body
|
|
219
|
+
text: hslToString(h, s * 0.6, 0.8), // Light, slightly desaturated text
|
|
220
|
+
};
|
|
221
|
+
} else {
|
|
222
|
+
// Light mode: vibrant header, light body, dark text
|
|
223
|
+
return {
|
|
224
|
+
header: hslToString(h, s, l), // Use base color for header
|
|
225
|
+
body: hslToString(h, s * 0.4, 0.95), // Very light, desaturated body
|
|
226
|
+
text: hslToString(h, s * 0.8, 0.25), // Dark, saturated text
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Renders a ClassInstanceRef as a styled card showing the class name and serialized data.
|
|
233
|
+
* The header color is determined by hashing the classId for visual distinction.
|
|
234
|
+
* Reacts to theme changes for proper dark/light mode support.
|
|
235
|
+
*/
|
|
236
|
+
const ClassInstanceRefDisplay = ({
|
|
237
|
+
classInstanceRef,
|
|
238
|
+
}: {
|
|
239
|
+
classInstanceRef: ClassInstanceRef;
|
|
240
|
+
}) => {
|
|
241
|
+
const isDark = useDarkMode();
|
|
242
|
+
const colors = getClassColors(classInstanceRef.classId, isDark);
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<div
|
|
246
|
+
className="inline-flex flex-col rounded text-[11px] font-mono my-1"
|
|
247
|
+
style={{
|
|
248
|
+
backgroundColor: colors.body,
|
|
249
|
+
border: `1px solid ${colors.header}`,
|
|
250
|
+
}}
|
|
251
|
+
>
|
|
252
|
+
<div
|
|
253
|
+
className="flex items-center gap-1.5 px-2 py-1 rounded-t"
|
|
254
|
+
style={{
|
|
255
|
+
backgroundColor: colors.header,
|
|
256
|
+
color: '#FFFFFF',
|
|
257
|
+
}}
|
|
258
|
+
title={`Custom class: ${classInstanceRef.classId}`}
|
|
259
|
+
>
|
|
260
|
+
<svg
|
|
261
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
262
|
+
width="12"
|
|
263
|
+
height="12"
|
|
264
|
+
viewBox="0 0 24 24"
|
|
265
|
+
fill="none"
|
|
266
|
+
stroke="currentColor"
|
|
267
|
+
strokeWidth="2"
|
|
268
|
+
strokeLinecap="round"
|
|
269
|
+
strokeLinejoin="round"
|
|
270
|
+
>
|
|
271
|
+
<title>Class instance</title>
|
|
272
|
+
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
|
|
273
|
+
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
|
|
274
|
+
<line x1="12" y1="22.08" x2="12" y2="12" />
|
|
275
|
+
</svg>
|
|
276
|
+
<span className="font-semibold">{classInstanceRef.className}</span>
|
|
277
|
+
</div>
|
|
278
|
+
<pre
|
|
279
|
+
className="px-2 py-1.5 overflow-x-auto whitespace-pre-wrap"
|
|
280
|
+
style={{ color: colors.text }}
|
|
281
|
+
>
|
|
282
|
+
{JSON.stringify(classInstanceRef.data, null, 2)}
|
|
283
|
+
</pre>
|
|
284
|
+
</div>
|
|
285
|
+
);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Renders a StreamRef as a styled link/badge
|
|
290
|
+
*/
|
|
291
|
+
const StreamRefDisplay = ({ streamRef }: { streamRef: StreamRef }) => {
|
|
292
|
+
const onStreamClick = useContext(StreamClickContext);
|
|
293
|
+
|
|
294
|
+
const handleClick = () => {
|
|
295
|
+
if (onStreamClick) {
|
|
296
|
+
onStreamClick(streamRef.streamId);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
return (
|
|
301
|
+
<button
|
|
302
|
+
type="button"
|
|
303
|
+
onClick={handleClick}
|
|
304
|
+
className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-mono cursor-pointer hover:opacity-80 transition-opacity"
|
|
305
|
+
style={{
|
|
306
|
+
backgroundColor: 'var(--ds-blue-200)',
|
|
307
|
+
color: 'var(--ds-blue-900)',
|
|
308
|
+
border: '1px solid var(--ds-blue-400)',
|
|
309
|
+
}}
|
|
310
|
+
title={`Click to view stream: ${streamRef.streamId}`}
|
|
311
|
+
>
|
|
312
|
+
<svg
|
|
313
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
314
|
+
width="10"
|
|
315
|
+
height="10"
|
|
316
|
+
viewBox="0 0 24 24"
|
|
317
|
+
fill="none"
|
|
318
|
+
stroke="currentColor"
|
|
319
|
+
strokeWidth="2"
|
|
320
|
+
strokeLinecap="round"
|
|
321
|
+
strokeLinejoin="round"
|
|
322
|
+
>
|
|
323
|
+
<title>Stream icon</title>
|
|
324
|
+
<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" />
|
|
325
|
+
<circle cx="12" cy="12" r="3" />
|
|
326
|
+
</svg>
|
|
327
|
+
{streamRef.streamId.length > 40
|
|
328
|
+
? `${streamRef.streamId.slice(0, 20)}...${streamRef.streamId.slice(
|
|
329
|
+
-15
|
|
330
|
+
)}`
|
|
331
|
+
: streamRef.streamId}
|
|
332
|
+
</button>
|
|
333
|
+
);
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Recursively transforms a value for JSON display, replacing StreamRef and
|
|
338
|
+
* ClassInstanceRef objects with placeholder strings that can be identified
|
|
339
|
+
* and replaced with React elements.
|
|
340
|
+
*/
|
|
341
|
+
const transformValueForDisplay = (
|
|
342
|
+
value: unknown
|
|
343
|
+
): {
|
|
344
|
+
json: string;
|
|
345
|
+
streamRefs: Map<string, StreamRef>;
|
|
346
|
+
classInstanceRefs: Map<string, ClassInstanceRef>;
|
|
347
|
+
} => {
|
|
348
|
+
const streamRefs = new Map<string, StreamRef>();
|
|
349
|
+
const classInstanceRefs = new Map<string, ClassInstanceRef>();
|
|
350
|
+
let counter = 0;
|
|
351
|
+
|
|
352
|
+
const transform = (v: unknown): unknown => {
|
|
353
|
+
if (isStreamRef(v)) {
|
|
354
|
+
const placeholder = `__STREAM_REF_${counter++}__`;
|
|
355
|
+
streamRefs.set(placeholder, v);
|
|
356
|
+
return placeholder;
|
|
357
|
+
}
|
|
358
|
+
if (isClassInstanceRef(v)) {
|
|
359
|
+
const placeholder = `__CLASS_INSTANCE_REF_${counter++}__`;
|
|
360
|
+
classInstanceRefs.set(placeholder, v);
|
|
361
|
+
return placeholder;
|
|
362
|
+
}
|
|
363
|
+
if (Array.isArray(v)) {
|
|
364
|
+
return v.map(transform);
|
|
365
|
+
}
|
|
366
|
+
if (v !== null && typeof v === 'object') {
|
|
367
|
+
const result: Record<string, unknown> = {};
|
|
368
|
+
for (const [key, val] of Object.entries(v)) {
|
|
369
|
+
result[key] = transform(val);
|
|
370
|
+
}
|
|
371
|
+
return result;
|
|
372
|
+
}
|
|
373
|
+
return v;
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
const transformed = transform(value);
|
|
377
|
+
return {
|
|
378
|
+
json: JSON.stringify(transformed, null, 2),
|
|
379
|
+
streamRefs,
|
|
380
|
+
classInstanceRefs,
|
|
381
|
+
};
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const JsonBlock = (value: unknown) => {
|
|
385
|
+
const { json, streamRefs, classInstanceRefs } =
|
|
386
|
+
transformValueForDisplay(value);
|
|
387
|
+
|
|
388
|
+
// If no special refs, just render plain JSON
|
|
389
|
+
if (streamRefs.size === 0 && classInstanceRefs.size === 0) {
|
|
390
|
+
return (
|
|
391
|
+
<pre
|
|
392
|
+
className="text-[11px] overflow-x-auto rounded-md border p-3"
|
|
393
|
+
style={{
|
|
394
|
+
borderColor: 'var(--ds-gray-300)',
|
|
395
|
+
backgroundColor: 'var(--ds-gray-100)',
|
|
396
|
+
color: 'var(--ds-gray-1000)',
|
|
397
|
+
}}
|
|
398
|
+
>
|
|
399
|
+
<code>{json}</code>
|
|
400
|
+
</pre>
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Build a combined map of all placeholders to their React elements
|
|
405
|
+
const placeholderComponents = new Map<string, ReactNode>();
|
|
406
|
+
let keyIndex = 0;
|
|
407
|
+
|
|
408
|
+
for (const [placeholder, streamRef] of streamRefs) {
|
|
409
|
+
placeholderComponents.set(
|
|
410
|
+
placeholder,
|
|
411
|
+
<StreamRefDisplay key={keyIndex++} streamRef={streamRef} />
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
for (const [placeholder, classInstanceRef] of classInstanceRefs) {
|
|
416
|
+
placeholderComponents.set(
|
|
417
|
+
placeholder,
|
|
418
|
+
<ClassInstanceRefDisplay
|
|
419
|
+
key={keyIndex++}
|
|
420
|
+
classInstanceRef={classInstanceRef}
|
|
421
|
+
/>
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Split the JSON by all placeholders and render with React elements
|
|
426
|
+
const parts: ReactNode[] = [];
|
|
427
|
+
let remaining = json;
|
|
428
|
+
|
|
429
|
+
// Process placeholders in order of their appearance in the string
|
|
430
|
+
while (remaining.length > 0) {
|
|
431
|
+
let earliestIndex = -1;
|
|
432
|
+
let earliestPlaceholder = '';
|
|
433
|
+
let earliestComponent: ReactNode = null;
|
|
434
|
+
|
|
435
|
+
// Find the earliest placeholder in the remaining string
|
|
436
|
+
for (const [placeholder, component] of placeholderComponents) {
|
|
437
|
+
const index = remaining.indexOf(`"${placeholder}"`);
|
|
438
|
+
if (index !== -1 && (earliestIndex === -1 || index < earliestIndex)) {
|
|
439
|
+
earliestIndex = index;
|
|
440
|
+
earliestPlaceholder = placeholder;
|
|
441
|
+
earliestComponent = component;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (earliestIndex === -1) {
|
|
446
|
+
// No more placeholders found, add the rest
|
|
447
|
+
parts.push(remaining);
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Add text before the placeholder
|
|
452
|
+
if (earliestIndex > 0) {
|
|
453
|
+
parts.push(remaining.slice(0, earliestIndex));
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Add the component
|
|
457
|
+
parts.push(earliestComponent);
|
|
458
|
+
|
|
459
|
+
// Move past the placeholder
|
|
460
|
+
remaining = remaining.slice(earliestIndex + earliestPlaceholder.length + 2); // +2 for quotes
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return (
|
|
464
|
+
<pre
|
|
465
|
+
className="text-[11px] overflow-x-auto rounded-md border p-3"
|
|
466
|
+
style={{
|
|
467
|
+
borderColor: 'var(--ds-gray-300)',
|
|
468
|
+
backgroundColor: 'var(--ds-gray-100)',
|
|
469
|
+
color: 'var(--ds-gray-1000)',
|
|
470
|
+
}}
|
|
471
|
+
>
|
|
472
|
+
<code>{parts}</code>
|
|
473
|
+
</pre>
|
|
474
|
+
);
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
type AttributeKey =
|
|
478
|
+
| keyof Step
|
|
479
|
+
| keyof WorkflowRun
|
|
480
|
+
| keyof Hook
|
|
481
|
+
| keyof Event
|
|
482
|
+
| 'eventData'
|
|
483
|
+
| 'resumeAt'
|
|
484
|
+
| 'expiredAt'
|
|
485
|
+
| 'workflowCoreVersion';
|
|
486
|
+
|
|
487
|
+
const attributeOrder: AttributeKey[] = [
|
|
488
|
+
'workflowName',
|
|
489
|
+
'stepName',
|
|
490
|
+
'status',
|
|
491
|
+
'stepId',
|
|
492
|
+
'hookId',
|
|
493
|
+
'eventId',
|
|
494
|
+
'runId',
|
|
495
|
+
'attempt',
|
|
496
|
+
'token',
|
|
497
|
+
'correlationId',
|
|
498
|
+
'eventType',
|
|
499
|
+
'deploymentId',
|
|
500
|
+
'specVersion',
|
|
501
|
+
'workflowCoreVersion',
|
|
502
|
+
'ownerId',
|
|
503
|
+
'projectId',
|
|
504
|
+
'environment',
|
|
505
|
+
'executionContext',
|
|
506
|
+
'createdAt',
|
|
507
|
+
'startedAt',
|
|
508
|
+
'updatedAt',
|
|
509
|
+
'completedAt',
|
|
510
|
+
'expiredAt',
|
|
511
|
+
'retryAfter',
|
|
512
|
+
'error',
|
|
513
|
+
'metadata',
|
|
514
|
+
'eventData',
|
|
515
|
+
'input',
|
|
516
|
+
'output',
|
|
517
|
+
'resumeAt',
|
|
518
|
+
];
|
|
519
|
+
|
|
520
|
+
const sortByAttributeOrder = (a: string, b: string): number => {
|
|
521
|
+
const aIndex = attributeOrder.indexOf(a as AttributeKey) || 0;
|
|
522
|
+
const bIndex = attributeOrder.indexOf(b as AttributeKey) || 0;
|
|
523
|
+
return aIndex - bIndex;
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Display names for attributes that should render differently from their key.
|
|
528
|
+
*/
|
|
529
|
+
const attributeDisplayNames: Partial<Record<AttributeKey, string>> = {
|
|
530
|
+
workflowCoreVersion: '@workflow/core version',
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Get the display name for an attribute key.
|
|
535
|
+
*/
|
|
536
|
+
const getAttributeDisplayName = (attribute: string): string => {
|
|
537
|
+
return attributeDisplayNames[attribute as AttributeKey] ?? attribute;
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
export const localMillisecondTime = (value: unknown): string => {
|
|
541
|
+
let date: Date;
|
|
542
|
+
if (value instanceof Date) {
|
|
543
|
+
date = value;
|
|
544
|
+
} else if (typeof value === 'number') {
|
|
545
|
+
date = new Date(value);
|
|
546
|
+
} else if (typeof value === 'string') {
|
|
547
|
+
date = new Date(value);
|
|
548
|
+
} else {
|
|
549
|
+
date = new Date(String(value));
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// e.g. 12/17/2025, 9:08:55.182 AM
|
|
553
|
+
return date.toLocaleString(undefined, {
|
|
554
|
+
year: 'numeric',
|
|
555
|
+
month: 'numeric',
|
|
556
|
+
day: 'numeric',
|
|
557
|
+
hour: 'numeric',
|
|
558
|
+
minute: 'numeric',
|
|
559
|
+
second: 'numeric',
|
|
560
|
+
fractionalSecondDigits: 3,
|
|
561
|
+
});
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
interface DisplayContext {
|
|
565
|
+
stepName?: string;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const attributeToDisplayFn: Record<
|
|
569
|
+
AttributeKey,
|
|
570
|
+
(value: unknown, context?: DisplayContext) => null | string | ReactNode
|
|
571
|
+
> = {
|
|
572
|
+
// Names that need pretty-printing
|
|
573
|
+
workflowName: (value: unknown) =>
|
|
574
|
+
parseWorkflowName(String(value))?.shortName ?? '?',
|
|
575
|
+
stepName: (value: unknown) => parseStepName(String(value))?.shortName ?? '?',
|
|
576
|
+
// IDs
|
|
577
|
+
runId: (value: unknown) => String(value),
|
|
578
|
+
stepId: (value: unknown) => String(value),
|
|
579
|
+
hookId: (value: unknown) => String(value),
|
|
580
|
+
eventId: (value: unknown) => String(value),
|
|
581
|
+
// Run/step details
|
|
582
|
+
status: (value: unknown) => String(value),
|
|
583
|
+
attempt: (value: unknown) => String(value),
|
|
584
|
+
// Hook details
|
|
585
|
+
token: (value: unknown) => String(value),
|
|
586
|
+
// Event details
|
|
587
|
+
eventType: (value: unknown) => String(value),
|
|
588
|
+
correlationId: (value: unknown) => String(value),
|
|
589
|
+
// Project details
|
|
590
|
+
deploymentId: (value: unknown) => String(value),
|
|
591
|
+
specVersion: (value: unknown) => String(value),
|
|
592
|
+
workflowCoreVersion: (value: unknown) => String(value),
|
|
593
|
+
// Tenancy (we don't show these)
|
|
594
|
+
ownerId: (_value: unknown) => null,
|
|
595
|
+
projectId: (_value: unknown) => null,
|
|
596
|
+
environment: (_value: unknown) => null,
|
|
597
|
+
executionContext: (_value: unknown) => null,
|
|
598
|
+
// Dates
|
|
599
|
+
// TODO: relative time with tooltips for ISO times
|
|
600
|
+
createdAt: localMillisecondTime,
|
|
601
|
+
startedAt: localMillisecondTime,
|
|
602
|
+
updatedAt: localMillisecondTime,
|
|
603
|
+
completedAt: localMillisecondTime,
|
|
604
|
+
expiredAt: localMillisecondTime,
|
|
605
|
+
retryAfter: localMillisecondTime,
|
|
606
|
+
resumeAt: localMillisecondTime,
|
|
607
|
+
// Resolved attributes, won't actually use this function
|
|
608
|
+
metadata: JsonBlock,
|
|
609
|
+
input: (value: unknown, context?: DisplayContext) => {
|
|
610
|
+
// Check if input has args + closure vars structure
|
|
611
|
+
if (value && typeof value === 'object' && 'args' in value) {
|
|
612
|
+
const { args, closureVars } = value as {
|
|
613
|
+
args: unknown[];
|
|
614
|
+
closureVars?: Record<string, unknown>;
|
|
615
|
+
};
|
|
616
|
+
const argCount = Array.isArray(args) ? args.length : 0;
|
|
617
|
+
const hasClosureVars = closureVars && Object.keys(closureVars).length > 0;
|
|
618
|
+
|
|
619
|
+
// Check if this is a doStreamStep - show conversation view with tabs
|
|
620
|
+
if (context?.stepName && isDoStreamStep(context.stepName)) {
|
|
621
|
+
const conversation = extractConversation(args);
|
|
622
|
+
if (conversation && conversation.length > 0) {
|
|
623
|
+
return (
|
|
624
|
+
<>
|
|
625
|
+
<ConversationWithTabs conversation={conversation} args={args} />
|
|
626
|
+
{hasClosureVars && (
|
|
627
|
+
<DetailCard summary="Closure Variables">
|
|
628
|
+
{JsonBlock(closureVars)}
|
|
629
|
+
</DetailCard>
|
|
630
|
+
)}
|
|
631
|
+
</>
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return (
|
|
637
|
+
<>
|
|
638
|
+
<DetailCard summary={`Input (${argCount} arguments)`}>
|
|
639
|
+
{Array.isArray(args)
|
|
640
|
+
? args.map((v, i) => (
|
|
641
|
+
<div className="mt-2" key={i}>
|
|
642
|
+
{JsonBlock(v)}
|
|
643
|
+
</div>
|
|
644
|
+
))
|
|
645
|
+
: JsonBlock(args)}
|
|
646
|
+
</DetailCard>
|
|
647
|
+
{hasClosureVars && (
|
|
648
|
+
<DetailCard summary="Closure Variables">
|
|
649
|
+
{JsonBlock(closureVars)}
|
|
650
|
+
</DetailCard>
|
|
651
|
+
)}
|
|
652
|
+
</>
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Fallback: treat as plain array or object
|
|
657
|
+
const argCount = Array.isArray(value) ? value.length : 0;
|
|
658
|
+
return (
|
|
659
|
+
<DetailCard summary={`Input (${argCount} arguments)`}>
|
|
660
|
+
{Array.isArray(value)
|
|
661
|
+
? value.map((v, i) => (
|
|
662
|
+
<div className="mt-2" key={i}>
|
|
663
|
+
{JsonBlock(v)}
|
|
664
|
+
</div>
|
|
665
|
+
))
|
|
666
|
+
: JsonBlock(value)}
|
|
667
|
+
</DetailCard>
|
|
668
|
+
);
|
|
669
|
+
},
|
|
670
|
+
output: (value: unknown) => {
|
|
671
|
+
return <DetailCard summary="Output">{JsonBlock(value)}</DetailCard>;
|
|
672
|
+
},
|
|
673
|
+
error: (value: unknown) => {
|
|
674
|
+
// Handle structured error format
|
|
675
|
+
if (value && typeof value === 'object' && 'message' in value) {
|
|
676
|
+
const error = value as {
|
|
677
|
+
message: string;
|
|
678
|
+
stack?: string;
|
|
679
|
+
code?: string;
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
return (
|
|
683
|
+
<DetailCard summary="Error">
|
|
684
|
+
<div className="flex flex-col gap-2">
|
|
685
|
+
{/* Show code if it exists */}
|
|
686
|
+
{error.code && (
|
|
687
|
+
<div>
|
|
688
|
+
<span
|
|
689
|
+
className="text-[11px] font-medium"
|
|
690
|
+
style={{ color: 'var(--ds-gray-700)' }}
|
|
691
|
+
>
|
|
692
|
+
Error Code:{' '}
|
|
693
|
+
</span>
|
|
694
|
+
<code
|
|
695
|
+
className="text-[11px]"
|
|
696
|
+
style={{ color: 'var(--ds-gray-1000)' }}
|
|
697
|
+
>
|
|
698
|
+
{error.code}
|
|
699
|
+
</code>
|
|
700
|
+
</div>
|
|
701
|
+
)}
|
|
702
|
+
{/* Show stack if available, otherwise just the message */}
|
|
703
|
+
<pre
|
|
704
|
+
className="text-[11px] overflow-x-auto rounded-md border p-3"
|
|
705
|
+
style={{
|
|
706
|
+
borderColor: 'var(--ds-gray-300)',
|
|
707
|
+
backgroundColor: 'var(--ds-gray-100)',
|
|
708
|
+
color: 'var(--ds-gray-1000)',
|
|
709
|
+
whiteSpace: 'pre-wrap',
|
|
710
|
+
}}
|
|
711
|
+
>
|
|
712
|
+
<code>{error.stack || error.message}</code>
|
|
713
|
+
</pre>
|
|
714
|
+
</div>
|
|
715
|
+
</DetailCard>
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Fallback for plain string errors
|
|
720
|
+
return (
|
|
721
|
+
<DetailCard summary="Error">
|
|
722
|
+
<pre
|
|
723
|
+
className="text-[11px] overflow-x-auto rounded-md border p-3"
|
|
724
|
+
style={{
|
|
725
|
+
borderColor: 'var(--ds-gray-300)',
|
|
726
|
+
backgroundColor: 'var(--ds-gray-100)',
|
|
727
|
+
color: 'var(--ds-gray-1000)',
|
|
728
|
+
whiteSpace: 'pre-wrap',
|
|
729
|
+
}}
|
|
730
|
+
>
|
|
731
|
+
<code>{String(value)}</code>
|
|
732
|
+
</pre>
|
|
733
|
+
</DetailCard>
|
|
734
|
+
);
|
|
735
|
+
},
|
|
736
|
+
eventData: (value: unknown) => {
|
|
737
|
+
return <DetailCard summary="Event Data">{JsonBlock(value)}</DetailCard>;
|
|
738
|
+
},
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
const resolvableAttributes = [
|
|
742
|
+
'input',
|
|
743
|
+
'output',
|
|
744
|
+
'error',
|
|
745
|
+
'metadata',
|
|
746
|
+
'eventData',
|
|
747
|
+
];
|
|
748
|
+
|
|
749
|
+
const ExpiredDataMessage = () => (
|
|
750
|
+
<div
|
|
751
|
+
className="text-copy-12 rounded-md border p-4 my-2"
|
|
752
|
+
style={{
|
|
753
|
+
borderColor: 'var(--ds-gray-300)',
|
|
754
|
+
backgroundColor: 'var(--ds-gray-100)',
|
|
755
|
+
color: 'var(--ds-gray-700)',
|
|
756
|
+
}}
|
|
757
|
+
>
|
|
758
|
+
<span>The data for this run has expired and is no longer available.</span>
|
|
759
|
+
</div>
|
|
760
|
+
);
|
|
761
|
+
|
|
762
|
+
export const AttributeBlock = ({
|
|
763
|
+
attribute,
|
|
764
|
+
value,
|
|
765
|
+
isLoading,
|
|
766
|
+
inline = false,
|
|
767
|
+
context,
|
|
768
|
+
}: {
|
|
769
|
+
attribute: string;
|
|
770
|
+
value: unknown;
|
|
771
|
+
isLoading?: boolean;
|
|
772
|
+
inline?: boolean;
|
|
773
|
+
context?: DisplayContext;
|
|
774
|
+
}) => {
|
|
775
|
+
const displayFn =
|
|
776
|
+
attributeToDisplayFn[attribute as keyof typeof attributeToDisplayFn];
|
|
777
|
+
if (!displayFn) {
|
|
778
|
+
return null;
|
|
779
|
+
}
|
|
780
|
+
const displayValue = displayFn(value, context);
|
|
781
|
+
if (!displayValue) {
|
|
782
|
+
return null;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
if (inline) {
|
|
786
|
+
return (
|
|
787
|
+
<div className="flex items-center gap-1.5">
|
|
788
|
+
<span
|
|
789
|
+
className="text-[11px] font-medium"
|
|
790
|
+
style={{ color: 'var(--ds-gray-700)' }}
|
|
791
|
+
>
|
|
792
|
+
{attribute}
|
|
793
|
+
</span>
|
|
794
|
+
<span className="text-[11px]" style={{ color: 'var(--ds-gray-1000)' }}>
|
|
795
|
+
{displayValue}
|
|
796
|
+
</span>
|
|
797
|
+
</div>
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
return (
|
|
802
|
+
<div className="relative">
|
|
803
|
+
{typeof isLoading === 'boolean' && isLoading && (
|
|
804
|
+
<div className="absolute top-9 right-4">
|
|
805
|
+
<div
|
|
806
|
+
className="animate-spin rounded-full h-4 w-4 border-b-2"
|
|
807
|
+
style={{ borderColor: 'var(--ds-gray-900)' }}
|
|
808
|
+
/>
|
|
809
|
+
</div>
|
|
810
|
+
)}
|
|
811
|
+
<div key={attribute} className="flex flex-col gap-0 my-2">
|
|
812
|
+
<span
|
|
813
|
+
className="text-xs font-medium"
|
|
814
|
+
style={{ color: 'var(--ds-gray-700)' }}
|
|
815
|
+
>
|
|
816
|
+
{attribute}
|
|
817
|
+
</span>
|
|
818
|
+
<span className="text-xs" style={{ color: 'var(--ds-gray-1000)' }}>
|
|
819
|
+
{displayValue}
|
|
820
|
+
</span>
|
|
821
|
+
</div>
|
|
822
|
+
</div>
|
|
823
|
+
);
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
export const AttributePanel = ({
|
|
827
|
+
data,
|
|
828
|
+
isLoading,
|
|
829
|
+
error,
|
|
830
|
+
expiredAt,
|
|
831
|
+
onStreamClick,
|
|
832
|
+
}: {
|
|
833
|
+
data: Record<string, unknown>;
|
|
834
|
+
isLoading?: boolean;
|
|
835
|
+
error?: Error;
|
|
836
|
+
expiredAt?: string | Date;
|
|
837
|
+
/** Callback when a stream reference is clicked */
|
|
838
|
+
onStreamClick?: (streamId: string) => void;
|
|
839
|
+
}) => {
|
|
840
|
+
// Extract workflowCoreVersion from executionContext for display
|
|
841
|
+
const displayData = useMemo(() => {
|
|
842
|
+
const result = { ...data };
|
|
843
|
+
const execCtx = data.executionContext as
|
|
844
|
+
| Record<string, unknown>
|
|
845
|
+
| undefined;
|
|
846
|
+
if (execCtx?.workflowCoreVersion) {
|
|
847
|
+
result.workflowCoreVersion = execCtx.workflowCoreVersion;
|
|
848
|
+
}
|
|
849
|
+
return result;
|
|
850
|
+
}, [data]);
|
|
851
|
+
const hasExpired = expiredAt != null && new Date(expiredAt) < new Date();
|
|
852
|
+
const basicAttributes = Object.keys(displayData)
|
|
853
|
+
.filter((key) => !resolvableAttributes.includes(key))
|
|
854
|
+
.sort(sortByAttributeOrder);
|
|
855
|
+
const resolvedAttributes = Object.keys(displayData)
|
|
856
|
+
.filter((key) => resolvableAttributes.includes(key))
|
|
857
|
+
.sort(sortByAttributeOrder);
|
|
858
|
+
|
|
859
|
+
// Filter out attributes that return null
|
|
860
|
+
const visibleBasicAttributes = basicAttributes.filter((attribute) => {
|
|
861
|
+
const displayFn =
|
|
862
|
+
attributeToDisplayFn[attribute as keyof typeof attributeToDisplayFn];
|
|
863
|
+
if (!displayFn) return false;
|
|
864
|
+
const displayValue = displayFn(
|
|
865
|
+
displayData[attribute as keyof typeof displayData]
|
|
866
|
+
);
|
|
867
|
+
return displayValue !== null;
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
// Memoize context object to avoid object reconstruction on render
|
|
871
|
+
const displayContext = useMemo(
|
|
872
|
+
() => ({
|
|
873
|
+
stepName: displayData.stepName as string | undefined,
|
|
874
|
+
}),
|
|
875
|
+
[displayData.stepName]
|
|
876
|
+
);
|
|
877
|
+
|
|
878
|
+
return (
|
|
879
|
+
<StreamClickContext.Provider value={onStreamClick}>
|
|
880
|
+
<div>
|
|
881
|
+
{/* Basic attributes in a vertical layout with border */}
|
|
882
|
+
{visibleBasicAttributes.length > 0 && (
|
|
883
|
+
<div
|
|
884
|
+
className="flex flex-col divide-y rounded-lg border mb-3 overflow-hidden"
|
|
885
|
+
style={{
|
|
886
|
+
borderColor: 'var(--ds-gray-300)',
|
|
887
|
+
backgroundColor: 'var(--ds-gray-100)',
|
|
888
|
+
}}
|
|
889
|
+
>
|
|
890
|
+
{visibleBasicAttributes.map((attribute) => (
|
|
891
|
+
<div
|
|
892
|
+
key={attribute}
|
|
893
|
+
className="flex items-center justify-between px-3 py-1.5"
|
|
894
|
+
style={{
|
|
895
|
+
borderColor: 'var(--ds-gray-300)',
|
|
896
|
+
}}
|
|
897
|
+
>
|
|
898
|
+
<span
|
|
899
|
+
className="text-[11px] font-medium"
|
|
900
|
+
style={{ color: 'var(--ds-gray-700)' }}
|
|
901
|
+
>
|
|
902
|
+
{getAttributeDisplayName(attribute)}
|
|
903
|
+
</span>
|
|
904
|
+
<span
|
|
905
|
+
className="text-[11px] font-mono"
|
|
906
|
+
style={{ color: 'var(--ds-gray-1000)' }}
|
|
907
|
+
>
|
|
908
|
+
{attributeToDisplayFn[
|
|
909
|
+
attribute as keyof typeof attributeToDisplayFn
|
|
910
|
+
]?.(displayData[attribute as keyof typeof displayData])}
|
|
911
|
+
</span>
|
|
912
|
+
</div>
|
|
913
|
+
))}
|
|
914
|
+
</div>
|
|
915
|
+
)}
|
|
916
|
+
{error ? (
|
|
917
|
+
<ErrorCard
|
|
918
|
+
title="Failed to load resource details"
|
|
919
|
+
details={error.message}
|
|
920
|
+
className="my-4"
|
|
921
|
+
/>
|
|
922
|
+
) : hasExpired ? (
|
|
923
|
+
<ExpiredDataMessage />
|
|
924
|
+
) : (
|
|
925
|
+
resolvedAttributes.map((attribute) => (
|
|
926
|
+
<AttributeBlock
|
|
927
|
+
isLoading={isLoading}
|
|
928
|
+
key={attribute}
|
|
929
|
+
attribute={attribute}
|
|
930
|
+
value={displayData[attribute as keyof typeof displayData]}
|
|
931
|
+
context={displayContext}
|
|
932
|
+
/>
|
|
933
|
+
))
|
|
934
|
+
)}
|
|
935
|
+
</div>
|
|
936
|
+
</StreamClickContext.Provider>
|
|
937
|
+
);
|
|
938
|
+
};
|