@workflow/web-shared 4.1.0-beta.52 → 4.1.0-beta.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/dist/components/error-boundary.d.ts +15 -20
  2. package/dist/components/error-boundary.d.ts.map +1 -1
  3. package/dist/components/error-boundary.js +17 -31
  4. package/dist/components/error-boundary.js.map +1 -1
  5. package/dist/components/event-list-view.d.ts +7 -6
  6. package/dist/components/event-list-view.d.ts.map +1 -1
  7. package/dist/components/event-list-view.js +492 -109
  8. package/dist/components/event-list-view.js.map +1 -1
  9. package/dist/components/index.d.ts +1 -0
  10. package/dist/components/index.d.ts.map +1 -1
  11. package/dist/components/index.js +1 -0
  12. package/dist/components/index.js.map +1 -1
  13. package/dist/components/run-trace-view.d.ts +2 -1
  14. package/dist/components/run-trace-view.d.ts.map +1 -1
  15. package/dist/components/run-trace-view.js +2 -2
  16. package/dist/components/run-trace-view.js.map +1 -1
  17. package/dist/components/sidebar/attribute-panel.d.ts +2 -1
  18. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  19. package/dist/components/sidebar/attribute-panel.js +53 -142
  20. package/dist/components/sidebar/attribute-panel.js.map +1 -1
  21. package/dist/components/sidebar/conversation-view.d.ts.map +1 -1
  22. package/dist/components/sidebar/conversation-view.js +3 -17
  23. package/dist/components/sidebar/conversation-view.js.map +1 -1
  24. package/dist/components/sidebar/entity-detail-panel.d.ts +3 -1
  25. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
  26. package/dist/components/sidebar/entity-detail-panel.js +63 -10
  27. package/dist/components/sidebar/entity-detail-panel.js.map +1 -1
  28. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  29. package/dist/components/sidebar/events-list.js +4 -8
  30. package/dist/components/sidebar/events-list.js.map +1 -1
  31. package/dist/components/sidebar/resolve-hook-modal.d.ts +3 -0
  32. package/dist/components/sidebar/resolve-hook-modal.d.ts.map +1 -1
  33. package/dist/components/sidebar/resolve-hook-modal.js +152 -3
  34. package/dist/components/sidebar/resolve-hook-modal.js.map +1 -1
  35. package/dist/components/stream-viewer.d.ts +7 -5
  36. package/dist/components/stream-viewer.d.ts.map +1 -1
  37. package/dist/components/stream-viewer.js +54 -22
  38. package/dist/components/stream-viewer.js.map +1 -1
  39. package/dist/components/trace-viewer/components/markers.d.ts +2 -1
  40. package/dist/components/trace-viewer/components/markers.d.ts.map +1 -1
  41. package/dist/components/trace-viewer/components/markers.js +59 -20
  42. package/dist/components/trace-viewer/components/markers.js.map +1 -1
  43. package/dist/components/trace-viewer/components/node.d.ts +5 -1
  44. package/dist/components/trace-viewer/components/node.d.ts.map +1 -1
  45. package/dist/components/trace-viewer/components/node.js +250 -68
  46. package/dist/components/trace-viewer/components/node.js.map +1 -1
  47. package/dist/components/trace-viewer/components/span-content.d.ts +19 -0
  48. package/dist/components/trace-viewer/components/span-content.d.ts.map +1 -0
  49. package/dist/components/trace-viewer/components/span-content.js +137 -0
  50. package/dist/components/trace-viewer/components/span-content.js.map +1 -0
  51. package/dist/components/trace-viewer/components/span-detail-panel.d.ts.map +1 -1
  52. package/dist/components/trace-viewer/components/span-detail-panel.js +3 -2
  53. package/dist/components/trace-viewer/components/span-detail-panel.js.map +1 -1
  54. package/dist/components/trace-viewer/components/span-segments.d.ts +50 -0
  55. package/dist/components/trace-viewer/components/span-segments.d.ts.map +1 -0
  56. package/dist/components/trace-viewer/components/span-segments.js +392 -0
  57. package/dist/components/trace-viewer/components/span-segments.js.map +1 -0
  58. package/dist/components/trace-viewer/components/span-strategies.d.ts +46 -0
  59. package/dist/components/trace-viewer/components/span-strategies.d.ts.map +1 -0
  60. package/dist/components/trace-viewer/components/span-strategies.js +108 -0
  61. package/dist/components/trace-viewer/components/span-strategies.js.map +1 -0
  62. package/dist/components/trace-viewer/context.d.ts +7 -6
  63. package/dist/components/trace-viewer/context.d.ts.map +1 -1
  64. package/dist/components/trace-viewer/context.js +47 -18
  65. package/dist/components/trace-viewer/context.js.map +1 -1
  66. package/dist/components/trace-viewer/trace-viewer.d.ts +5 -1
  67. package/dist/components/trace-viewer/trace-viewer.d.ts.map +1 -1
  68. package/dist/components/trace-viewer/trace-viewer.js +87 -11
  69. package/dist/components/trace-viewer/trace-viewer.js.map +1 -1
  70. package/dist/components/trace-viewer/trace-viewer.module.css +179 -6
  71. package/dist/components/trace-viewer/util/timing.d.ts +5 -0
  72. package/dist/components/trace-viewer/util/timing.d.ts.map +1 -1
  73. package/dist/components/trace-viewer/util/timing.js +12 -0
  74. package/dist/components/trace-viewer/util/timing.js.map +1 -1
  75. package/dist/components/trace-viewer/util/use-streaming-spans.d.ts +1 -1
  76. package/dist/components/trace-viewer/util/use-streaming-spans.d.ts.map +1 -1
  77. package/dist/components/trace-viewer/util/use-streaming-spans.js +29 -17
  78. package/dist/components/trace-viewer/util/use-streaming-spans.js.map +1 -1
  79. package/dist/components/trace-viewer/worker.js +3 -1
  80. package/dist/components/trace-viewer/worker.js.map +1 -1
  81. package/dist/components/ui/alert.js +3 -3
  82. package/dist/components/ui/alert.js.map +1 -1
  83. package/dist/components/ui/card.d.ts.map +1 -1
  84. package/dist/components/ui/card.js +2 -2
  85. package/dist/components/ui/card.js.map +1 -1
  86. package/dist/components/ui/data-inspector.d.ts +17 -0
  87. package/dist/components/ui/data-inspector.d.ts.map +1 -0
  88. package/dist/components/ui/data-inspector.js +184 -0
  89. package/dist/components/ui/data-inspector.js.map +1 -0
  90. package/dist/components/ui/error-card.d.ts.map +1 -1
  91. package/dist/components/ui/error-card.js +4 -1
  92. package/dist/components/ui/error-card.js.map +1 -1
  93. package/dist/components/ui/inspector-theme.d.ts +39 -24
  94. package/dist/components/ui/inspector-theme.d.ts.map +1 -1
  95. package/dist/components/ui/inspector-theme.js +90 -38
  96. package/dist/components/ui/inspector-theme.js.map +1 -1
  97. package/dist/components/ui/skeleton.d.ts +1 -1
  98. package/dist/components/ui/skeleton.d.ts.map +1 -1
  99. package/dist/components/ui/skeleton.js +2 -2
  100. package/dist/components/ui/skeleton.js.map +1 -1
  101. package/dist/components/workflow-trace-view.d.ts +3 -1
  102. package/dist/components/workflow-trace-view.d.ts.map +1 -1
  103. package/dist/components/workflow-trace-view.js +435 -21
  104. package/dist/components/workflow-trace-view.js.map +1 -1
  105. package/dist/components/workflow-traces/trace-span-construction.d.ts +1 -1
  106. package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
  107. package/dist/components/workflow-traces/trace-span-construction.js +2 -2
  108. package/dist/components/workflow-traces/trace-span-construction.js.map +1 -1
  109. package/dist/lib/hydration.d.ts.map +1 -1
  110. package/dist/lib/hydration.js +17 -3
  111. package/dist/lib/hydration.js.map +1 -1
  112. package/dist/styles.css +186 -0
  113. package/package.json +8 -7
  114. package/src/components/error-boundary.tsx +29 -40
  115. package/src/components/event-list-view.tsx +1000 -287
  116. package/src/components/index.ts +1 -0
  117. package/src/components/run-trace-view.tsx +3 -0
  118. package/src/components/sidebar/attribute-panel.tsx +58 -258
  119. package/src/components/sidebar/conversation-view.tsx +30 -27
  120. package/src/components/sidebar/entity-detail-panel.tsx +86 -20
  121. package/src/components/sidebar/events-list.tsx +4 -11
  122. package/src/components/sidebar/resolve-hook-modal.tsx +206 -47
  123. package/src/components/stream-viewer.tsx +138 -61
  124. package/src/components/trace-viewer/components/markers.tsx +69 -21
  125. package/src/components/trace-viewer/components/node.tsx +346 -100
  126. package/src/components/trace-viewer/components/span-content.tsx +247 -0
  127. package/src/components/trace-viewer/components/span-detail-panel.tsx +7 -2
  128. package/src/components/trace-viewer/components/span-segments.ts +516 -0
  129. package/src/components/trace-viewer/components/span-strategies.ts +205 -0
  130. package/src/components/trace-viewer/context.tsx +92 -40
  131. package/src/components/trace-viewer/trace-viewer.module.css +179 -6
  132. package/src/components/trace-viewer/trace-viewer.tsx +115 -11
  133. package/src/components/trace-viewer/util/timing.ts +13 -0
  134. package/src/components/trace-viewer/util/use-streaming-spans.ts +28 -17
  135. package/src/components/trace-viewer/worker.ts +4 -1
  136. package/src/components/ui/alert.tsx +3 -3
  137. package/src/components/ui/card.tsx +3 -5
  138. package/src/components/ui/data-inspector.tsx +318 -0
  139. package/src/components/ui/error-card.tsx +17 -6
  140. package/src/components/ui/inspector-theme.ts +127 -39
  141. package/src/components/ui/skeleton.tsx +3 -1
  142. package/src/components/workflow-trace-view.tsx +625 -26
  143. package/src/components/workflow-traces/trace-span-construction.ts +3 -2
  144. package/src/lib/hydration.ts +17 -8
  145. package/src/styles.css +186 -0
@@ -21,4 +21,5 @@ export type {
21
21
  } from './sidebar/entity-detail-panel';
22
22
  export { type StreamChunk, StreamViewer } from './stream-viewer';
23
23
  export type { Span, SpanEvent } from './trace-viewer/types';
24
+ export { DataInspector, type DataInspectorProps } from './ui/data-inspector';
24
25
  export { WorkflowTraceViewer } from './workflow-trace-view';
@@ -24,6 +24,7 @@ interface RunTraceViewProps {
24
24
  payload: unknown,
25
25
  hook?: Hook
26
26
  ) => Promise<void>;
27
+ onCancelRun?: (runId: string) => Promise<void>;
27
28
  onStreamClick?: (streamId: string) => void;
28
29
  onSpanSelect?: (info: SpanSelectionInfo) => void;
29
30
  }
@@ -40,6 +41,7 @@ export function RunTraceView({
40
41
  spanDetailError,
41
42
  onWakeUpSleep,
42
43
  onResolveHook,
44
+ onCancelRun,
43
45
  onStreamClick,
44
46
  onSpanSelect,
45
47
  }: RunTraceViewProps) {
@@ -67,6 +69,7 @@ export function RunTraceView({
67
69
  spanDetailError={spanDetailError}
68
70
  onWakeUpSleep={onWakeUpSleep}
69
71
  onResolveHook={onResolveHook}
72
+ onCancelRun={onCancelRun}
70
73
  onStreamClick={onStreamClick}
71
74
  onSpanSelect={onSpanSelect}
72
75
  />
@@ -4,10 +4,9 @@ import { parseStepName, parseWorkflowName } from '@workflow/utils/parse-name';
4
4
  import type { Event, Hook, Step, WorkflowRun } from '@workflow/world';
5
5
  import type { ModelMessage } from 'ai';
6
6
  import type { ReactNode } from 'react';
7
- import { createContext, useContext, useMemo, useState } from 'react';
8
- import { ObjectInspector } from 'react-inspector';
9
- import { useDarkMode } from '../../hooks/use-dark-mode';
7
+ import { useMemo, useState } from 'react';
10
8
  import { extractConversation, isDoStreamStep } from '../../lib/utils';
9
+ import { DataInspector, StreamClickContext } from '../ui/data-inspector';
11
10
  import { ErrorCard } from '../ui/error-card';
12
11
  import { ConversationView } from './conversation-view';
13
12
  import { DetailCard } from './detail-card';
@@ -112,265 +111,16 @@ function ConversationWithTabs({
112
111
  }
113
112
 
114
113
  /**
115
- * Context for stream click handler
114
+ * Render a value with the shared DataInspector (ObjectInspector with
115
+ * custom theming, nodeRenderer for StreamRef/ClassInstanceRef, etc.)
116
116
  */
117
- const StreamClickContext = createContext<
118
- ((streamId: string) => void) | undefined
119
- >(undefined);
120
-
121
- /**
122
- * Marker for stream reference objects that can be rendered as links
123
- * This is duplicated from @workflow/core/observability to avoid pulling in
124
- * Node.js dependencies into the client bundle.
125
- */
126
- const STREAM_REF_TYPE = '__workflow_stream_ref__';
127
-
128
- /**
129
- * A stream reference object that contains the stream ID and can be
130
- * detected in the UI to render as a clickable link
131
- */
132
- interface StreamRef {
133
- __type: typeof STREAM_REF_TYPE;
134
- streamId: string;
135
- }
136
-
137
- /**
138
- * Check if a value is a StreamRef object
139
- *
140
- */
141
- const isStreamRef = (value: unknown): value is StreamRef => {
142
- // TODO: This is duplicated from @workflow/core/observability, but can't be pulled
143
- // in client-side code because it's a Node.js dependency.
144
- return (
145
- value !== null &&
146
- typeof value === 'object' &&
147
- '__type' in value &&
148
- value.__type === STREAM_REF_TYPE &&
149
- 'streamId' in value &&
150
- typeof value.streamId === 'string'
151
- );
152
- };
153
-
154
- /**
155
- * Marker for custom class instance references.
156
- * This is duplicated from @workflow/core/observability to avoid pulling in
157
- * Node.js dependencies into the client bundle.
158
- */
159
- const CLASS_INSTANCE_REF_TYPE = '__workflow_class_instance_ref__';
160
-
161
- /**
162
- * A class instance reference object that contains the class name and serialized data.
163
- * Used in o11y when a custom class instance is encountered but the class is not
164
- * registered for deserialization.
165
- */
166
- interface ClassInstanceRef {
167
- __type: typeof CLASS_INSTANCE_REF_TYPE;
168
- className: string;
169
- classId: string;
170
- data: unknown;
171
- }
172
-
173
- /**
174
- * Check if a value is a ClassInstanceRef object
175
- */
176
- const isClassInstanceRef = (value: unknown): value is ClassInstanceRef => {
177
- return (
178
- value !== null &&
179
- typeof value === 'object' &&
180
- '__type' in value &&
181
- value.__type === CLASS_INSTANCE_REF_TYPE &&
182
- 'className' in value &&
183
- typeof value.className === 'string'
184
- );
185
- };
186
-
187
- import ColorHash from 'color-hash';
188
-
189
- /**
190
- * Color hash instance configured for nice saturation and lightness.
191
- * Returns HSL values which we can transform for different use cases.
192
- */
193
- const colorHash = new ColorHash({
194
- saturation: [0.5, 0.6, 0.7],
195
- lightness: [0.4, 0.5, 0.6],
196
- });
197
-
198
- /**
199
- * Convert HSL to CSS hsl() string
200
- */
201
- const hslToString = (h: number, s: number, l: number): string => {
202
- return `hsl(${h}, ${Math.round(s * 100)}%, ${Math.round(l * 100)}%)`;
203
- };
204
-
205
- /**
206
- * Get consistent colors for a class ID using color-hash and HSL transformations.
207
- * Adjusts colors based on light/dark mode for optimal appearance.
208
- */
209
- const getClassColors = (
210
- classId: string,
211
- isDark: boolean
212
- ): { header: string; body: string; text: string } => {
213
- const [h, s, l] = colorHash.hsl(classId);
214
-
215
- if (isDark) {
216
- // Dark mode: vibrant header, dark body, light text
217
- return {
218
- header: hslToString(h, s, Math.min(l + 0.1, 0.6)), // Slightly brighter header
219
- body: hslToString(h, s * 0.8, 0.15), // Very dark, slightly desaturated body
220
- text: hslToString(h, s * 0.6, 0.8), // Light, slightly desaturated text
221
- };
222
- } else {
223
- // Light mode: vibrant header, light body, dark text
224
- return {
225
- header: hslToString(h, s, l), // Use base color for header
226
- body: hslToString(h, s * 0.4, 0.95), // Very light, desaturated body
227
- text: hslToString(h, s * 0.8, 0.25), // Dark, saturated text
228
- };
229
- }
230
- };
231
-
232
- /**
233
- * Renders a ClassInstanceRef as a styled card showing the class name and serialized data.
234
- * The header color is determined by hashing the classId for visual distinction.
235
- * Reacts to theme changes for proper dark/light mode support.
236
- */
237
- const ClassInstanceRefDisplay = ({
238
- classInstanceRef,
239
- }: {
240
- classInstanceRef: ClassInstanceRef;
241
- }) => {
242
- const isDark = useDarkMode();
243
- const colors = getClassColors(classInstanceRef.classId, isDark);
244
-
245
- return (
246
- <div
247
- className="inline-flex flex-col rounded text-[11px] font-mono my-1"
248
- style={{
249
- backgroundColor: colors.body,
250
- border: `1px solid ${colors.header}`,
251
- }}
252
- >
253
- <div
254
- className="flex items-center gap-1.5 px-2 py-1 rounded-t"
255
- style={{
256
- backgroundColor: colors.header,
257
- color: '#FFFFFF',
258
- }}
259
- title={`Custom class: ${classInstanceRef.classId}`}
260
- >
261
- <svg
262
- xmlns="http://www.w3.org/2000/svg"
263
- width="12"
264
- height="12"
265
- viewBox="0 0 24 24"
266
- fill="none"
267
- stroke="currentColor"
268
- strokeWidth="2"
269
- strokeLinecap="round"
270
- strokeLinejoin="round"
271
- >
272
- <title>Class instance</title>
273
- <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" />
274
- <polyline points="3.27 6.96 12 12.01 20.73 6.96" />
275
- <line x1="12" y1="22.08" x2="12" y2="12" />
276
- </svg>
277
- <span className="font-semibold">{classInstanceRef.className}</span>
278
- </div>
279
- <pre
280
- className="px-2 py-1.5 overflow-x-auto whitespace-pre-wrap"
281
- style={{ color: colors.text }}
282
- >
283
- {JSON.stringify(classInstanceRef.data, null, 2)}
284
- </pre>
285
- </div>
286
- );
287
- };
288
-
289
- /**
290
- * Renders a StreamRef as a styled link/badge
291
- */
292
- const StreamRefDisplay = ({ streamRef }: { streamRef: StreamRef }) => {
293
- const onStreamClick = useContext(StreamClickContext);
294
-
295
- const handleClick = () => {
296
- if (onStreamClick) {
297
- onStreamClick(streamRef.streamId);
298
- }
299
- };
300
-
301
- return (
302
- <button
303
- type="button"
304
- onClick={handleClick}
305
- 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"
306
- style={{
307
- backgroundColor: 'var(--ds-blue-200)',
308
- color: 'var(--ds-blue-900)',
309
- border: '1px solid var(--ds-blue-400)',
310
- }}
311
- title={`Click to view stream: ${streamRef.streamId}`}
312
- >
313
- <svg
314
- xmlns="http://www.w3.org/2000/svg"
315
- width="10"
316
- height="10"
317
- viewBox="0 0 24 24"
318
- fill="none"
319
- stroke="currentColor"
320
- strokeWidth="2"
321
- strokeLinecap="round"
322
- strokeLinejoin="round"
323
- >
324
- <title>Stream icon</title>
325
- <path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" />
326
- <circle cx="12" cy="12" r="3" />
327
- </svg>
328
- {streamRef.streamId.length > 40
329
- ? `${streamRef.streamId.slice(0, 20)}...${streamRef.streamId.slice(
330
- -15
331
- )}`
332
- : streamRef.streamId}
333
- </button>
334
- );
335
- };
336
-
337
- /**
338
- * Renders a value using react-inspector's ObjectInspector for proper
339
- * display of Map, Set, URLSearchParams, Date, Error, RegExp, typed
340
- * arrays, and other non-plain-object types.
341
- *
342
- * StreamRef and ClassInstanceRef objects are rendered inline as
343
- * custom components (clickable stream links and class cards).
344
- */
345
- const JsonBlock = (value: unknown) => {
346
- return <DataInspector data={value} />;
347
- };
348
-
349
- import { inspectorThemeDark, inspectorThemeLight } from '../ui/inspector-theme';
350
-
351
- function DataInspector({ data }: { data: unknown }) {
352
- const isDark = useDarkMode();
353
-
354
- // Render top-level StreamRef/ClassInstanceRef as full custom components
355
- if (isStreamRef(data)) {
356
- return <StreamRefDisplay streamRef={data} />;
357
- }
358
- if (isClassInstanceRef(data)) {
359
- return <ClassInstanceRefDisplay classInstanceRef={data} />;
360
- }
361
-
117
+ function JsonBlock(value: unknown) {
362
118
  return (
363
119
  <div
364
120
  className="overflow-x-auto rounded-md border p-3"
365
121
  style={{ borderColor: 'var(--ds-gray-300)' }}
366
122
  >
367
- <ObjectInspector
368
- data={data}
369
- // @ts-expect-error react-inspector accepts theme objects at runtime despite
370
- // types declaring string only — see https://github.com/storybookjs/react-inspector/blob/main/README.md#theme
371
- theme={isDark ? inspectorThemeDark : inspectorThemeLight}
372
- expandLevel={2}
373
- />
123
+ <DataInspector data={value} />
374
124
  </div>
375
125
  );
376
126
  }
@@ -380,6 +130,7 @@ type AttributeKey =
380
130
  | keyof WorkflowRun
381
131
  | keyof Hook
382
132
  | keyof Event
133
+ | 'moduleSpecifier'
383
134
  | 'eventData'
384
135
  | 'resumeAt'
385
136
  | 'expiredAt'
@@ -387,6 +138,7 @@ type AttributeKey =
387
138
 
388
139
  const attributeOrder: AttributeKey[] = [
389
140
  'workflowName',
141
+ 'moduleSpecifier',
390
142
  'stepName',
391
143
  'status',
392
144
  'stepId',
@@ -438,6 +190,19 @@ const getAttributeDisplayName = (attribute: string): string => {
438
190
  return attributeDisplayNames[attribute as AttributeKey] ?? attribute;
439
191
  };
440
192
 
193
+ const getModuleSpecifierFromName = (value: unknown): string => {
194
+ const raw = String(value);
195
+ const parsedStep = parseStepName(raw);
196
+ if (parsedStep) {
197
+ return parsedStep.moduleSpecifier;
198
+ }
199
+ const parsedWorkflow = parseWorkflowName(raw);
200
+ if (parsedWorkflow) {
201
+ return parsedWorkflow.moduleSpecifier;
202
+ }
203
+ return raw;
204
+ };
205
+
441
206
  export const localMillisecondTime = (value: unknown): string => {
442
207
  let date: Date;
443
208
  if (value instanceof Date) {
@@ -473,6 +238,7 @@ const attributeToDisplayFn: Record<
473
238
  // Names that need pretty-printing
474
239
  workflowName: (value: unknown) =>
475
240
  parseWorkflowName(String(value))?.shortName ?? '?',
241
+ moduleSpecifier: (value: unknown) => getModuleSpecifierFromName(value),
476
242
  stepName: (value: unknown) => parseStepName(String(value))?.shortName ?? '?',
477
243
  // IDs
478
244
  runId: (value: unknown) => String(value),
@@ -726,12 +492,14 @@ export const AttributeBlock = ({
726
492
 
727
493
  export const AttributePanel = ({
728
494
  data,
495
+ moduleSpecifier,
729
496
  isLoading,
730
497
  error,
731
498
  expiredAt,
732
499
  onStreamClick,
733
500
  }: {
734
501
  data: Record<string, unknown>;
502
+ moduleSpecifier?: string;
735
503
  isLoading?: boolean;
736
504
  error?: Error;
737
505
  expiredAt?: string | Date;
@@ -747,8 +515,15 @@ export const AttributePanel = ({
747
515
  if (execCtx?.workflowCoreVersion) {
748
516
  result.workflowCoreVersion = execCtx.workflowCoreVersion;
749
517
  }
518
+ if (moduleSpecifier) {
519
+ result.moduleSpecifier = moduleSpecifier;
520
+ } else if (typeof data.stepName === 'string') {
521
+ result.moduleSpecifier = data.stepName;
522
+ } else if (typeof data.workflowName === 'string') {
523
+ result.moduleSpecifier = data.workflowName;
524
+ }
750
525
  return result;
751
- }, [data]);
526
+ }, [data, moduleSpecifier]);
752
527
  const hasExpired = expiredAt != null && new Date(expiredAt) < new Date();
753
528
  const basicAttributes = Object.keys(displayData)
754
529
  .filter((key) => !resolvableAttributes.includes(key))
@@ -768,6 +543,31 @@ export const AttributePanel = ({
768
543
  return displayValue !== null;
769
544
  });
770
545
 
546
+ // Keep `moduleSpecifier` immediately after `workflowName` or `stepName`.
547
+ const orderedBasicAttributes = useMemo(() => {
548
+ const attributes = [...visibleBasicAttributes];
549
+ const moduleSpecifierIndex = attributes.indexOf('moduleSpecifier');
550
+ if (moduleSpecifierIndex === -1) {
551
+ return attributes;
552
+ }
553
+
554
+ attributes.splice(moduleSpecifierIndex, 1);
555
+ const workflowNameIndex = attributes.indexOf('workflowName');
556
+ if (workflowNameIndex !== -1) {
557
+ attributes.splice(workflowNameIndex + 1, 0, 'moduleSpecifier');
558
+ return attributes;
559
+ }
560
+
561
+ const stepNameIndex = attributes.indexOf('stepName');
562
+ if (stepNameIndex !== -1) {
563
+ attributes.splice(stepNameIndex + 1, 0, 'moduleSpecifier');
564
+ return attributes;
565
+ }
566
+
567
+ attributes.unshift('moduleSpecifier');
568
+ return attributes;
569
+ }, [visibleBasicAttributes]);
570
+
771
571
  // Memoize context object to avoid object reconstruction on render
772
572
  const displayContext = useMemo(
773
573
  () => ({
@@ -788,7 +588,7 @@ export const AttributePanel = ({
788
588
  backgroundColor: 'var(--ds-gray-100)',
789
589
  }}
790
590
  >
791
- {visibleBasicAttributes.map((attribute) => (
591
+ {orderedBasicAttributes.map((attribute) => (
792
592
  <div
793
593
  key={attribute}
794
594
  className="flex items-center justify-between px-3 py-1.5"
@@ -1,5 +1,6 @@
1
1
  import type { ModelMessage } from 'ai';
2
2
  import { Streamdown } from 'streamdown';
3
+ import { DataInspector } from '../ui/data-inspector';
3
4
 
4
5
  interface ConversationViewProps {
5
6
  messages: ModelMessage[];
@@ -108,24 +109,27 @@ function ContentPart({ part, role }: { part: ParsedPart; role: string }) {
108
109
  <span style={{ color: 'var(--ds-purple-900)' }}>{part.toolName}</span>
109
110
  </div>
110
111
  {part.input != null && (
111
- <pre
112
- className="mt-1.5 text-[10px] overflow-x-auto p-1.5 rounded"
113
- style={{
114
- backgroundColor: 'var(--ds-gray-100)',
115
- color: 'var(--ds-gray-800)',
116
- }}
112
+ <div
113
+ className="mt-1.5 overflow-x-auto p-1.5 rounded"
114
+ style={{ backgroundColor: 'var(--ds-gray-100)' }}
117
115
  >
118
- {typeof part.input === 'string'
119
- ? part.input
120
- : JSON.stringify(part.input, null, 2)}
121
- </pre>
116
+ {typeof part.input === 'string' ? (
117
+ <pre
118
+ className="text-[10px]"
119
+ style={{ color: 'var(--ds-gray-800)' }}
120
+ >
121
+ {part.input}
122
+ </pre>
123
+ ) : (
124
+ <DataInspector data={part.input} />
125
+ )}
126
+ </div>
122
127
  )}
123
128
  </div>
124
129
  );
125
130
  }
126
131
 
127
132
  if (part.type === 'tool-result') {
128
- const outputText = formatOutput(part.output);
129
133
  return (
130
134
  <div
131
135
  className="rounded border px-2 py-1.5"
@@ -140,16 +144,22 @@ function ContentPart({ part, role }: { part: ParsedPart; role: string }) {
140
144
  {part.toolName} result
141
145
  </span>
142
146
  </div>
143
- {outputText && (
144
- <pre
145
- className="mt-1.5 text-[10px] overflow-x-auto max-h-[80px] p-1.5 rounded"
146
- style={{
147
- backgroundColor: 'var(--ds-gray-100)',
148
- color: 'var(--ds-gray-800)',
149
- }}
147
+ {part.output != null && (
148
+ <div
149
+ className="mt-1.5 overflow-x-auto max-h-[200px] overflow-y-auto p-1.5 rounded"
150
+ style={{ backgroundColor: 'var(--ds-gray-100)' }}
150
151
  >
151
- {outputText}
152
- </pre>
152
+ {typeof part.output === 'string' ? (
153
+ <pre
154
+ className="text-[10px]"
155
+ style={{ color: 'var(--ds-gray-800)' }}
156
+ >
157
+ {part.output}
158
+ </pre>
159
+ ) : (
160
+ <DataInspector data={part.output} expandLevel={1} />
161
+ )}
162
+ </div>
153
163
  )}
154
164
  </div>
155
165
  );
@@ -226,10 +236,3 @@ function parseContent(content: unknown): ParsedPart[] {
226
236
 
227
237
  return [];
228
238
  }
229
-
230
- function formatOutput(output: unknown): string | null {
231
- if (output == null) return null;
232
- const text =
233
- typeof output === 'string' ? output : JSON.stringify(output, null, 2);
234
- return text.length > 500 ? `${text.slice(0, 500)}...` : text;
235
- }