@optilogic/chat 1.0.0-beta.8 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +136 -0
- package/dist/index.cjs +989 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +361 -2
- package/dist/index.d.ts +361 -2
- package/dist/index.js +964 -46
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/agent-response/AgentResponse.tsx +86 -14
- package/src/components/agent-response/components/HITLSection.tsx +95 -0
- package/src/components/agent-response/components/MetadataRow.tsx +15 -4
- package/src/components/agent-response/components/TruncatedMessage.tsx +52 -0
- package/src/components/agent-response/components/index.ts +6 -0
- package/src/components/agent-response/hooks/useAgentResponseAccumulator.ts +65 -8
- package/src/components/agent-response/index.ts +21 -0
- package/src/components/agent-response/types.ts +61 -1
- package/src/components/agent-timeline/AgentTimeline.tsx +256 -0
- package/src/components/agent-timeline/TimelineAgentBlock.tsx +84 -0
- package/src/components/agent-timeline/TimelineItem.tsx +97 -0
- package/src/components/agent-timeline/index.ts +14 -0
- package/src/components/agent-timeline/types.ts +49 -0
- package/src/components/agent-timeline/utils.ts +189 -0
- package/src/components/hitl-interactions/HITLInteractionRecord.tsx +139 -0
- package/src/components/hitl-interactions/HITLQuestionPanel.tsx +270 -0
- package/src/components/hitl-interactions/index.ts +18 -0
- package/src/components/inline-actions/ActionMarkdownRenderer.tsx +60 -0
- package/src/components/inline-actions/index.ts +18 -0
- package/src/components/inline-actions/parseResponseSegments.ts +66 -0
- package/src/components/inline-actions/prompts.ts +41 -0
- package/src/components/inline-actions/types.ts +57 -0
- package/src/components/user-prompt-input/UserPromptInput.tsx +13 -8
- package/src/components/user-prompt-input/types.ts +4 -0
- package/src/index.ts +42 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optilogic/chat",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Chat UI components for Optilogic - AgentResponse and related components for LLM interactions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"README.md"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@optilogic/core": "1.0.0
|
|
28
|
-
"@optilogic/editor": "1.0.0
|
|
27
|
+
"@optilogic/core": "1.0.0",
|
|
28
|
+
"@optilogic/editor": "1.0.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -6,11 +6,14 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import * as React from "react";
|
|
9
|
-
import { useState, useMemo, useCallback } from "react";
|
|
9
|
+
import { useState, useRef, useMemo, useCallback } from "react";
|
|
10
10
|
import { cn } from "@optilogic/core";
|
|
11
|
-
import { MetadataRow, ThinkingSection, ActionBar } from "./components";
|
|
11
|
+
import { MetadataRow, ThinkingSection, ActionBar, HITLSection } from "./components";
|
|
12
12
|
import { useThinkingTimer } from "./hooks";
|
|
13
13
|
import type { AgentResponseState, FeedbackValue } from "./types";
|
|
14
|
+
import type { HITLInteraction } from "../hitl-interactions";
|
|
15
|
+
import { AgentTimeline, createTimelineUIState } from "../agent-timeline";
|
|
16
|
+
import type { TimelineUIState } from "../agent-timeline";
|
|
14
17
|
|
|
15
18
|
export interface AgentResponseProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
16
19
|
/** The response state to render */
|
|
@@ -37,6 +40,35 @@ export interface AgentResponseProps extends React.HTMLAttributes<HTMLDivElement>
|
|
|
37
40
|
/** Action bar visibility mode */
|
|
38
41
|
actionsVisible?: boolean | "hover";
|
|
39
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Optional HITL (Human-in-the-Loop) interactions to display as a
|
|
45
|
+
* collapsible section within the response. When provided, a "Clarifying
|
|
46
|
+
* Questions" section appears between the thinking/metadata area and
|
|
47
|
+
* the response content.
|
|
48
|
+
*/
|
|
49
|
+
hitlInteractions?: HITLInteraction[];
|
|
50
|
+
|
|
51
|
+
/** Whether the HITL section starts expanded (default: false) */
|
|
52
|
+
defaultHITLExpanded?: boolean;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Optional content to display in the MetadataRow's middle area,
|
|
56
|
+
* between the thinking toggle (left) and activity indicators (right).
|
|
57
|
+
* Typically used for ephemeral status messages during processing.
|
|
58
|
+
* The parent is responsible for setting and clearing this content.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* <AgentResponse
|
|
62
|
+
* state={state}
|
|
63
|
+
* statusContent={
|
|
64
|
+
* state.status !== 'complete'
|
|
65
|
+
* ? <TruncatedMessage message="Analyzing data..." />
|
|
66
|
+
* : undefined
|
|
67
|
+
* }
|
|
68
|
+
* />
|
|
69
|
+
*/
|
|
70
|
+
statusContent?: React.ReactNode;
|
|
71
|
+
|
|
40
72
|
/**
|
|
41
73
|
* Custom markdown renderer for the response content.
|
|
42
74
|
* If not provided, the response will be rendered as plain text.
|
|
@@ -60,6 +92,12 @@ export interface AgentResponseProps extends React.HTMLAttributes<HTMLDivElement>
|
|
|
60
92
|
* />
|
|
61
93
|
*/
|
|
62
94
|
renderThinkingMarkdown?: (content: string) => React.ReactNode;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Maximum height of the AgentTimeline scrollable container.
|
|
98
|
+
* Defaults to "300px". Set to "none" to disable the constraint.
|
|
99
|
+
*/
|
|
100
|
+
timelineMaxHeight?: string;
|
|
63
101
|
}
|
|
64
102
|
|
|
65
103
|
/**
|
|
@@ -107,13 +145,20 @@ const AgentResponse = React.forwardRef<HTMLDivElement, AgentResponseProps>(
|
|
|
107
145
|
thinkingExpanded: controlledThinkingExpanded,
|
|
108
146
|
onThinkingExpandedChange,
|
|
109
147
|
actionsVisible = "hover",
|
|
148
|
+
hitlInteractions,
|
|
149
|
+
defaultHITLExpanded = false,
|
|
150
|
+
statusContent,
|
|
110
151
|
renderMarkdown,
|
|
111
152
|
renderThinkingMarkdown,
|
|
153
|
+
timelineMaxHeight,
|
|
112
154
|
className,
|
|
113
155
|
...props
|
|
114
156
|
},
|
|
115
157
|
ref
|
|
116
158
|
) => {
|
|
159
|
+
// Ref-backed timeline UI state (survives remounts during streaming)
|
|
160
|
+
const timelineUIStateRef = useRef<TimelineUIState>(createTimelineUIState());
|
|
161
|
+
|
|
117
162
|
// Uncontrolled thinking expanded state
|
|
118
163
|
const [uncontrolledExpanded, setUncontrolledExpanded] = useState(defaultThinkingExpanded);
|
|
119
164
|
|
|
@@ -149,9 +194,13 @@ const AgentResponse = React.forwardRef<HTMLDivElement, AgentResponseProps>(
|
|
|
149
194
|
return (state.responseCompleteTime - state.firstMessageTime) / 1000;
|
|
150
195
|
}, [state.firstMessageTime, state.responseCompleteTime]);
|
|
151
196
|
|
|
152
|
-
// Check if we have any thinking content (plain text or
|
|
197
|
+
// Check if we have any thinking content (plain text, structured, or timeline)
|
|
198
|
+
const hasTimelineEntries = !!(state.timelineEntries && state.timelineEntries.length > 0);
|
|
153
199
|
const hasThinkingContent =
|
|
154
|
-
!!state.thinking || (state.thinkingSteps && state.thinkingSteps.length > 0) || false;
|
|
200
|
+
!!state.thinking || (state.thinkingSteps && state.thinkingSteps.length > 0) || hasTimelineEntries || false;
|
|
201
|
+
|
|
202
|
+
const hasHITLInteractions =
|
|
203
|
+
hitlInteractions && hitlInteractions.length > 0;
|
|
155
204
|
|
|
156
205
|
// Derived state: has any content been received?
|
|
157
206
|
const hasAnyContent =
|
|
@@ -160,6 +209,7 @@ const AgentResponse = React.forwardRef<HTMLDivElement, AgentResponseProps>(
|
|
|
160
209
|
state.knowledge.length > 0 ||
|
|
161
210
|
state.memory.length > 0 ||
|
|
162
211
|
state.statusUpdates.length > 0 ||
|
|
212
|
+
hasHITLInteractions ||
|
|
163
213
|
state.response;
|
|
164
214
|
|
|
165
215
|
// Derived state: should show metadata row?
|
|
@@ -203,23 +253,45 @@ const AgentResponse = React.forwardRef<HTMLDivElement, AgentResponseProps>(
|
|
|
203
253
|
knowledge={state.knowledge}
|
|
204
254
|
memory={state.memory}
|
|
205
255
|
statusUpdates={state.statusUpdates}
|
|
256
|
+
statusContent={statusContent}
|
|
206
257
|
status={state.status}
|
|
207
258
|
elapsedTime={elapsedTime}
|
|
208
259
|
/>
|
|
209
260
|
|
|
210
|
-
{/* Thinking Content -
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
261
|
+
{/* Thinking Content - AgentTimeline when timeline entries exist, ThinkingSection otherwise */}
|
|
262
|
+
{hasTimelineEntries ? (
|
|
263
|
+
thinkingExpanded && (
|
|
264
|
+
<div className="pb-3 border-t border-border">
|
|
265
|
+
<AgentTimeline
|
|
266
|
+
entries={state.timelineEntries!}
|
|
267
|
+
renderMarkdown={renderThinkingMarkdown}
|
|
268
|
+
uiState={timelineUIStateRef.current}
|
|
269
|
+
maxHeight={timelineMaxHeight}
|
|
270
|
+
/>
|
|
271
|
+
</div>
|
|
272
|
+
)
|
|
273
|
+
) : (
|
|
274
|
+
<ThinkingSection
|
|
275
|
+
content={
|
|
276
|
+
state.thinkingSteps && state.thinkingSteps.length > 0
|
|
277
|
+
? state.thinkingSteps
|
|
278
|
+
: state.thinking
|
|
279
|
+
}
|
|
280
|
+
isExpanded={thinkingExpanded}
|
|
281
|
+
renderMarkdown={renderThinkingMarkdown}
|
|
282
|
+
/>
|
|
283
|
+
)}
|
|
220
284
|
</>
|
|
221
285
|
)}
|
|
222
286
|
|
|
287
|
+
{/* HITL Interactions - collapsible section */}
|
|
288
|
+
{hasHITLInteractions && (
|
|
289
|
+
<HITLSection
|
|
290
|
+
interactions={hitlInteractions}
|
|
291
|
+
defaultExpanded={defaultHITLExpanded}
|
|
292
|
+
/>
|
|
293
|
+
)}
|
|
294
|
+
|
|
223
295
|
{/* Response Section */}
|
|
224
296
|
{state.response && (
|
|
225
297
|
<div
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HITL Section Component
|
|
3
|
+
*
|
|
4
|
+
* Collapsible section for displaying completed HITL (Human-in-the-Loop)
|
|
5
|
+
* interactions within an AgentResponse. Follows the same pattern as
|
|
6
|
+
* ThinkingSection for consistent UX.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as React from "react";
|
|
10
|
+
import { useState, useCallback } from "react";
|
|
11
|
+
import { ChevronDown, ChevronRight, MessageCircleQuestion } from "lucide-react";
|
|
12
|
+
import { cn } from "@optilogic/core";
|
|
13
|
+
import { HITLInteractionRecord } from "../../hitl-interactions";
|
|
14
|
+
import type { HITLInteraction } from "../../hitl-interactions";
|
|
15
|
+
|
|
16
|
+
export interface HITLSectionProps
|
|
17
|
+
extends React.HTMLAttributes<HTMLDivElement> {
|
|
18
|
+
/** The HITL interactions to display */
|
|
19
|
+
interactions: HITLInteraction[];
|
|
20
|
+
/** Whether the section starts expanded (uncontrolled) */
|
|
21
|
+
defaultExpanded?: boolean;
|
|
22
|
+
/** Whether the section is expanded (controlled) */
|
|
23
|
+
isExpanded?: boolean;
|
|
24
|
+
/** Callback when expansion state changes (controlled) */
|
|
25
|
+
onExpandedChange?: (expanded: boolean) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const HITLSection = React.forwardRef<HTMLDivElement, HITLSectionProps>(
|
|
29
|
+
(
|
|
30
|
+
{
|
|
31
|
+
interactions,
|
|
32
|
+
defaultExpanded = false,
|
|
33
|
+
isExpanded: controlledExpanded,
|
|
34
|
+
onExpandedChange,
|
|
35
|
+
className,
|
|
36
|
+
...props
|
|
37
|
+
},
|
|
38
|
+
ref
|
|
39
|
+
) => {
|
|
40
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] =
|
|
41
|
+
useState(defaultExpanded);
|
|
42
|
+
|
|
43
|
+
const isControlled = controlledExpanded !== undefined;
|
|
44
|
+
const isExpanded = isControlled ? controlledExpanded : uncontrolledExpanded;
|
|
45
|
+
|
|
46
|
+
const toggleExpanded = useCallback(() => {
|
|
47
|
+
const newValue = !isExpanded;
|
|
48
|
+
if (isControlled) {
|
|
49
|
+
onExpandedChange?.(newValue);
|
|
50
|
+
} else {
|
|
51
|
+
setUncontrolledExpanded(newValue);
|
|
52
|
+
}
|
|
53
|
+
}, [isExpanded, isControlled, onExpandedChange]);
|
|
54
|
+
|
|
55
|
+
if (!interactions || interactions.length === 0) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
ref={ref}
|
|
62
|
+
className={cn("border-t border-border", className)}
|
|
63
|
+
{...props}
|
|
64
|
+
>
|
|
65
|
+
{/* Collapsible header */}
|
|
66
|
+
<button
|
|
67
|
+
onClick={toggleExpanded}
|
|
68
|
+
className="w-full flex items-center gap-2 py-2 px-3 hover:bg-muted/50 transition-colors text-left"
|
|
69
|
+
>
|
|
70
|
+
{isExpanded ? (
|
|
71
|
+
<ChevronDown className="w-3.5 h-3.5 text-muted-foreground flex-shrink-0" />
|
|
72
|
+
) : (
|
|
73
|
+
<ChevronRight className="w-3.5 h-3.5 text-muted-foreground flex-shrink-0" />
|
|
74
|
+
)}
|
|
75
|
+
<MessageCircleQuestion className="w-3.5 h-3.5 text-muted-foreground flex-shrink-0" />
|
|
76
|
+
<span className="text-xs font-medium text-foreground/80">
|
|
77
|
+
Clarifying Questions ({interactions.length})
|
|
78
|
+
</span>
|
|
79
|
+
</button>
|
|
80
|
+
|
|
81
|
+
{/* Expanded content */}
|
|
82
|
+
{isExpanded && (
|
|
83
|
+
<div className="px-3 pb-3 space-y-2">
|
|
84
|
+
{interactions.map((interaction, i) => (
|
|
85
|
+
<HITLInteractionRecord key={i} interaction={interaction} />
|
|
86
|
+
))}
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
HITLSection.displayName = "HITLSection";
|
|
94
|
+
|
|
95
|
+
export { HITLSection };
|
|
@@ -26,6 +26,8 @@ export interface MetadataRowProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
26
26
|
memory: MemoryItem[];
|
|
27
27
|
/** Status updates to display */
|
|
28
28
|
statusUpdates?: StatusItem[];
|
|
29
|
+
/** Optional content to display in the middle area between left content and activity indicators */
|
|
30
|
+
statusContent?: React.ReactNode;
|
|
29
31
|
/** Current response status */
|
|
30
32
|
status: AgentResponseStatus;
|
|
31
33
|
/** Elapsed time in seconds */
|
|
@@ -60,6 +62,7 @@ const MetadataRow = React.forwardRef<HTMLDivElement, MetadataRowProps>(
|
|
|
60
62
|
knowledge,
|
|
61
63
|
memory,
|
|
62
64
|
statusUpdates = [],
|
|
65
|
+
statusContent,
|
|
63
66
|
status,
|
|
64
67
|
elapsedTime,
|
|
65
68
|
className,
|
|
@@ -108,8 +111,8 @@ const MetadataRow = React.forwardRef<HTMLDivElement, MetadataRowProps>(
|
|
|
108
111
|
|
|
109
112
|
const leftContent = renderLeftContent();
|
|
110
113
|
|
|
111
|
-
// If nothing to show (no thinking, not processing, no activity), hide the row
|
|
112
|
-
if (!leftContent && !hasActivity) {
|
|
114
|
+
// If nothing to show (no thinking, not processing, no activity, no status content), hide the row
|
|
115
|
+
if (!leftContent && !hasActivity && !statusContent) {
|
|
113
116
|
return null;
|
|
114
117
|
}
|
|
115
118
|
|
|
@@ -125,15 +128,23 @@ const MetadataRow = React.forwardRef<HTMLDivElement, MetadataRowProps>(
|
|
|
125
128
|
{hasThinking ? (
|
|
126
129
|
<button
|
|
127
130
|
onClick={onToggle}
|
|
128
|
-
className="flex items-center gap-1.5 hover:bg-muted/50 -ml-1.5 pl-1.5 pr-2 py-0.5 rounded transition-colors"
|
|
131
|
+
className="flex items-center gap-1.5 hover:bg-muted/50 -ml-1.5 pl-1.5 pr-2 py-0.5 rounded transition-colors shrink-0"
|
|
129
132
|
>
|
|
130
133
|
{leftContent}
|
|
131
134
|
</button>
|
|
132
135
|
) : (
|
|
133
|
-
<div className="flex items-center gap-1.5">
|
|
136
|
+
<div className="flex items-center gap-1.5 shrink-0">
|
|
134
137
|
{leftContent}
|
|
135
138
|
</div>
|
|
136
139
|
)}
|
|
140
|
+
|
|
141
|
+
{/* Middle content - status content slot */}
|
|
142
|
+
{statusContent && (
|
|
143
|
+
<div className="flex-1 min-w-0 mx-2">
|
|
144
|
+
{statusContent}
|
|
145
|
+
</div>
|
|
146
|
+
)}
|
|
147
|
+
|
|
137
148
|
<ActivityIndicators
|
|
138
149
|
toolCalls={toolCalls}
|
|
139
150
|
knowledge={knowledge}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Truncated Message Component
|
|
3
|
+
*
|
|
4
|
+
* Renders a single-line text message with CSS-based truncation (text-overflow: ellipsis).
|
|
5
|
+
* Designed as a standalone utility that can be used anywhere, including as
|
|
6
|
+
* the statusContent slot in MetadataRow.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as React from "react";
|
|
10
|
+
import { cn } from "@optilogic/core";
|
|
11
|
+
|
|
12
|
+
export interface TruncatedMessageProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
13
|
+
/** The message string to display (truncated with ellipsis if it overflows) */
|
|
14
|
+
message: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* TruncatedMessage Component
|
|
19
|
+
*
|
|
20
|
+
* Displays a single-line text message that truncates with an ellipsis when
|
|
21
|
+
* it overflows its container. Uses CSS text-overflow for zero-JS truncation.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* <TruncatedMessage message="Searching the knowledge base for relevant documents..." />
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Inside MetadataRow's statusContent slot
|
|
28
|
+
* <AgentResponse
|
|
29
|
+
* state={state}
|
|
30
|
+
* statusContent={<TruncatedMessage message="Running analysis..." />}
|
|
31
|
+
* />
|
|
32
|
+
*/
|
|
33
|
+
const TruncatedMessage = React.forwardRef<HTMLDivElement, TruncatedMessageProps>(
|
|
34
|
+
({ message, className, ...props }, ref) => {
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
ref={ref}
|
|
38
|
+
className={cn(
|
|
39
|
+
"text-xs text-muted-foreground truncate min-w-0",
|
|
40
|
+
className
|
|
41
|
+
)}
|
|
42
|
+
title={message}
|
|
43
|
+
{...props}
|
|
44
|
+
>
|
|
45
|
+
{message}
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
TruncatedMessage.displayName = "TruncatedMessage";
|
|
51
|
+
|
|
52
|
+
export { TruncatedMessage };
|
|
@@ -13,3 +13,9 @@ export type { ThinkingSectionProps } from "./ThinkingSection";
|
|
|
13
13
|
|
|
14
14
|
export { ActionBar } from "./ActionBar";
|
|
15
15
|
export type { ActionBarProps } from "./ActionBar";
|
|
16
|
+
|
|
17
|
+
export { HITLSection } from "./HITLSection";
|
|
18
|
+
export type { HITLSectionProps } from "./HITLSection";
|
|
19
|
+
|
|
20
|
+
export { TruncatedMessage } from "./TruncatedMessage";
|
|
21
|
+
export type { TruncatedMessageProps } from "./TruncatedMessage";
|
|
@@ -15,7 +15,9 @@ import {
|
|
|
15
15
|
type MemoryItem,
|
|
16
16
|
type StatusItem,
|
|
17
17
|
type ThinkingStep,
|
|
18
|
+
type PotentialResponse,
|
|
18
19
|
} from "../types";
|
|
20
|
+
import { buildTimelineEntries } from "../../agent-timeline/utils";
|
|
19
21
|
|
|
20
22
|
export interface UseAgentResponseAccumulatorOptions {
|
|
21
23
|
/** WebSocket topic to filter messages (optional, for convenience) */
|
|
@@ -94,33 +96,50 @@ export function useAgentResponseAccumulator(
|
|
|
94
96
|
id: payload.thinkingStep.id || `step-${Date.now()}`,
|
|
95
97
|
label: payload.thinkingStep.label,
|
|
96
98
|
content: payload.thinkingStep.content,
|
|
97
|
-
depth: payload.thinkingStep.depth ?? 0,
|
|
99
|
+
depth: payload.thinkingStep.depth ?? payload.depth ?? 0,
|
|
98
100
|
isCollapsed: payload.thinkingStep.isCollapsed,
|
|
101
|
+
timestamp: Date.now(),
|
|
102
|
+
agentName: payload.agentName,
|
|
103
|
+
parentAgent: payload.parentAgent,
|
|
99
104
|
};
|
|
100
105
|
const thinkingStartTime = prev.thinkingStartTime ?? Date.now();
|
|
101
|
-
|
|
106
|
+
const next = {
|
|
102
107
|
...prev,
|
|
103
108
|
status: newStatus,
|
|
104
109
|
thinkingSteps: [...(prev.thinkingSteps || []), newStep],
|
|
105
110
|
thinkingStartTime,
|
|
106
111
|
firstMessageTime,
|
|
107
112
|
};
|
|
113
|
+
return { ...next, timelineEntries: buildTimelineEntries(next) };
|
|
108
114
|
}
|
|
109
115
|
|
|
110
|
-
// Plain text thinking
|
|
116
|
+
// Plain text thinking — concatenate for backward compat AND
|
|
117
|
+
// push a ThinkingStep so the timeline gets individual entries.
|
|
111
118
|
const newThinking = payload.message || payload.content || "";
|
|
112
119
|
// Add line break between thinking messages
|
|
113
120
|
const separator = prev.thinking && newThinking ? "\n\n" : "";
|
|
114
121
|
// Set thinkingStartTime on first thinking message
|
|
115
122
|
const thinkingStartTime =
|
|
116
123
|
prev.thinkingStartTime ?? (newThinking ? Date.now() : null);
|
|
117
|
-
|
|
124
|
+
const prevSteps = prev.thinkingSteps || [];
|
|
125
|
+
const plainStep: ThinkingStep = {
|
|
126
|
+
id: `step-${prevSteps.length}`,
|
|
127
|
+
label: newThinking,
|
|
128
|
+
content: newThinking,
|
|
129
|
+
depth: payload.depth ?? 0,
|
|
130
|
+
timestamp: Date.now(),
|
|
131
|
+
agentName: payload.agentName,
|
|
132
|
+
parentAgent: payload.parentAgent,
|
|
133
|
+
};
|
|
134
|
+
const next = {
|
|
118
135
|
...prev,
|
|
119
136
|
status: newStatus,
|
|
120
137
|
thinking: prev.thinking + separator + newThinking,
|
|
138
|
+
thinkingSteps: [...prevSteps, plainStep],
|
|
121
139
|
thinkingStartTime,
|
|
122
140
|
firstMessageTime,
|
|
123
141
|
};
|
|
142
|
+
return { ...next, timelineEntries: buildTimelineEntries(next) };
|
|
124
143
|
}
|
|
125
144
|
|
|
126
145
|
case "tool_call": {
|
|
@@ -132,13 +151,17 @@ export function useAgentResponseAccumulator(
|
|
|
132
151
|
name: toolName,
|
|
133
152
|
arguments: payload.tool?.arguments,
|
|
134
153
|
timestamp: Date.now(),
|
|
154
|
+
agentName: payload.agentName,
|
|
155
|
+
parentAgent: payload.parentAgent,
|
|
156
|
+
depth: payload.depth,
|
|
135
157
|
};
|
|
136
|
-
|
|
158
|
+
const next = {
|
|
137
159
|
...prev,
|
|
138
160
|
status: newStatus,
|
|
139
161
|
toolCalls: [...prev.toolCalls, newToolCall],
|
|
140
162
|
firstMessageTime,
|
|
141
163
|
};
|
|
164
|
+
return { ...next, timelineEntries: buildTimelineEntries(next) };
|
|
142
165
|
}
|
|
143
166
|
return { ...prev, status: newStatus, firstMessageTime };
|
|
144
167
|
}
|
|
@@ -152,13 +175,17 @@ export function useAgentResponseAccumulator(
|
|
|
152
175
|
source: payload.knowledge?.source || "unknown",
|
|
153
176
|
content: knowledgeContent,
|
|
154
177
|
timestamp: Date.now(),
|
|
178
|
+
agentName: payload.agentName,
|
|
179
|
+
parentAgent: payload.parentAgent,
|
|
180
|
+
depth: payload.depth,
|
|
155
181
|
};
|
|
156
|
-
|
|
182
|
+
const next = {
|
|
157
183
|
...prev,
|
|
158
184
|
status: newStatus,
|
|
159
185
|
knowledge: [...prev.knowledge, newKnowledge],
|
|
160
186
|
firstMessageTime,
|
|
161
187
|
};
|
|
188
|
+
return { ...next, timelineEntries: buildTimelineEntries(next) };
|
|
162
189
|
}
|
|
163
190
|
return { ...prev, status: newStatus, firstMessageTime };
|
|
164
191
|
}
|
|
@@ -172,13 +199,17 @@ export function useAgentResponseAccumulator(
|
|
|
172
199
|
type: payload.memory?.type || "unknown",
|
|
173
200
|
content: memoryContent,
|
|
174
201
|
timestamp: Date.now(),
|
|
202
|
+
agentName: payload.agentName,
|
|
203
|
+
parentAgent: payload.parentAgent,
|
|
204
|
+
depth: payload.depth,
|
|
175
205
|
};
|
|
176
|
-
|
|
206
|
+
const next = {
|
|
177
207
|
...prev,
|
|
178
208
|
status: newStatus,
|
|
179
209
|
memory: [...prev.memory, newMemory],
|
|
180
210
|
firstMessageTime,
|
|
181
211
|
};
|
|
212
|
+
return { ...next, timelineEntries: buildTimelineEntries(next) };
|
|
182
213
|
}
|
|
183
214
|
return { ...prev, status: newStatus, firstMessageTime };
|
|
184
215
|
}
|
|
@@ -200,13 +231,39 @@ export function useAgentResponseAccumulator(
|
|
|
200
231
|
message: statusMessage,
|
|
201
232
|
agent: payload.statusUpdate?.agent,
|
|
202
233
|
timestamp: Date.now(),
|
|
234
|
+
agentName: payload.agentName,
|
|
235
|
+
parentAgent: payload.parentAgent,
|
|
236
|
+
depth: payload.depth,
|
|
203
237
|
};
|
|
204
|
-
|
|
238
|
+
const next = {
|
|
205
239
|
...prev,
|
|
206
240
|
status: newStatus,
|
|
207
241
|
statusUpdates: [...prev.statusUpdates, newStatusItem],
|
|
208
242
|
firstMessageTime,
|
|
209
243
|
};
|
|
244
|
+
return { ...next, timelineEntries: buildTimelineEntries(next) };
|
|
245
|
+
}
|
|
246
|
+
return { ...prev, status: newStatus, firstMessageTime };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
case "potential_response": {
|
|
250
|
+
const respContent = payload.message || payload.content || "";
|
|
251
|
+
if (respContent) {
|
|
252
|
+
const newResp: PotentialResponse = {
|
|
253
|
+
id: `resp-${Date.now()}`,
|
|
254
|
+
content: respContent,
|
|
255
|
+
timestamp: Date.now(),
|
|
256
|
+
agentName: payload.agentName,
|
|
257
|
+
parentAgent: payload.parentAgent,
|
|
258
|
+
depth: payload.depth,
|
|
259
|
+
};
|
|
260
|
+
const next = {
|
|
261
|
+
...prev,
|
|
262
|
+
status: newStatus,
|
|
263
|
+
potentialResponses: [...(prev.potentialResponses || []), newResp],
|
|
264
|
+
firstMessageTime,
|
|
265
|
+
};
|
|
266
|
+
return { ...next, timelineEntries: buildTimelineEntries(next) };
|
|
210
267
|
}
|
|
211
268
|
return { ...prev, status: newStatus, firstMessageTime };
|
|
212
269
|
}
|
|
@@ -15,10 +15,14 @@ export {
|
|
|
15
15
|
MetadataRow,
|
|
16
16
|
ThinkingSection,
|
|
17
17
|
ActionBar,
|
|
18
|
+
HITLSection,
|
|
19
|
+
TruncatedMessage,
|
|
18
20
|
type ActivityIndicatorsProps,
|
|
19
21
|
type MetadataRowProps,
|
|
20
22
|
type ThinkingSectionProps,
|
|
21
23
|
type ActionBarProps,
|
|
24
|
+
type HITLSectionProps,
|
|
25
|
+
type TruncatedMessageProps,
|
|
22
26
|
} from "./components";
|
|
23
27
|
|
|
24
28
|
// Hooks
|
|
@@ -41,6 +45,7 @@ export type {
|
|
|
41
45
|
StatusItem,
|
|
42
46
|
ThinkingStep,
|
|
43
47
|
ThinkingContent,
|
|
48
|
+
PotentialResponse,
|
|
44
49
|
AgentMessage,
|
|
45
50
|
GenericWebSocketMessage,
|
|
46
51
|
} from "./types";
|
|
@@ -49,3 +54,19 @@ export { initialAgentResponseState } from "./types";
|
|
|
49
54
|
|
|
50
55
|
// Utilities
|
|
51
56
|
export { formatTime, formatTotalTime } from "./utils";
|
|
57
|
+
|
|
58
|
+
// Agent Timeline (replaces ThinkingSection for rich execution visibility)
|
|
59
|
+
export {
|
|
60
|
+
AgentTimeline,
|
|
61
|
+
createTimelineUIState,
|
|
62
|
+
buildTimelineEntries,
|
|
63
|
+
groupIntoAgentRuns,
|
|
64
|
+
deduplicateEntries,
|
|
65
|
+
} from "../agent-timeline";
|
|
66
|
+
export type {
|
|
67
|
+
TimelineUIState,
|
|
68
|
+
TimelineEntry,
|
|
69
|
+
TimelineEntryType,
|
|
70
|
+
AgentRun,
|
|
71
|
+
DisplayEntry,
|
|
72
|
+
} from "../agent-timeline";
|