@optilogic/chat 1.0.0-beta.1 → 1.0.0-beta.11
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 +235 -0
- package/dist/index.cjs +1292 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +524 -6
- package/dist/index.d.ts +524 -6
- package/dist/index.js +1267 -33
- package/dist/index.js.map +1 -1
- package/package.json +15 -9
- package/src/components/agent-response/AgentResponse.tsx +99 -10
- package/src/components/agent-response/components/ActivityIndicators.tsx +36 -4
- package/src/components/agent-response/components/HITLSection.tsx +95 -0
- package/src/components/agent-response/components/MetadataRow.tsx +21 -6
- package/src/components/agent-response/components/ThinkingSection.tsx +102 -10
- 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 +79 -4
- package/src/components/agent-response/index.ts +23 -0
- package/src/components/agent-response/types.ts +96 -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 +167 -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/UserPrompt.tsx +60 -0
- package/src/components/user-prompt/index.ts +1 -0
- package/src/components/user-prompt-input/UserPromptInput.tsx +326 -0
- package/src/components/user-prompt-input/index.ts +2 -0
- package/src/components/user-prompt-input/types.ts +52 -0
- package/src/index.ts +54 -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.
|
|
@@ -48,6 +80,18 @@ export interface AgentResponseProps extends React.HTMLAttributes<HTMLDivElement>
|
|
|
48
80
|
* />
|
|
49
81
|
*/
|
|
50
82
|
renderMarkdown?: (content: string) => React.ReactNode;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Custom markdown renderer for the thinking content.
|
|
86
|
+
* If not provided, the thinking will be rendered as plain preformatted text.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* <AgentResponse
|
|
90
|
+
* state={state}
|
|
91
|
+
* renderThinkingMarkdown={(content) => <MyMarkdownRenderer content={content} />}
|
|
92
|
+
* />
|
|
93
|
+
*/
|
|
94
|
+
renderThinkingMarkdown?: (content: string) => React.ReactNode;
|
|
51
95
|
}
|
|
52
96
|
|
|
53
97
|
/**
|
|
@@ -95,12 +139,19 @@ const AgentResponse = React.forwardRef<HTMLDivElement, AgentResponseProps>(
|
|
|
95
139
|
thinkingExpanded: controlledThinkingExpanded,
|
|
96
140
|
onThinkingExpandedChange,
|
|
97
141
|
actionsVisible = "hover",
|
|
142
|
+
hitlInteractions,
|
|
143
|
+
defaultHITLExpanded = false,
|
|
144
|
+
statusContent,
|
|
98
145
|
renderMarkdown,
|
|
146
|
+
renderThinkingMarkdown,
|
|
99
147
|
className,
|
|
100
148
|
...props
|
|
101
149
|
},
|
|
102
150
|
ref
|
|
103
151
|
) => {
|
|
152
|
+
// Ref-backed timeline UI state (survives remounts during streaming)
|
|
153
|
+
const timelineUIStateRef = useRef<TimelineUIState>(createTimelineUIState());
|
|
154
|
+
|
|
104
155
|
// Uncontrolled thinking expanded state
|
|
105
156
|
const [uncontrolledExpanded, setUncontrolledExpanded] = useState(defaultThinkingExpanded);
|
|
106
157
|
|
|
@@ -136,20 +187,31 @@ const AgentResponse = React.forwardRef<HTMLDivElement, AgentResponseProps>(
|
|
|
136
187
|
return (state.responseCompleteTime - state.firstMessageTime) / 1000;
|
|
137
188
|
}, [state.firstMessageTime, state.responseCompleteTime]);
|
|
138
189
|
|
|
190
|
+
// Check if we have any thinking content (plain text, structured, or timeline)
|
|
191
|
+
const hasTimelineEntries = !!(state.timelineEntries && state.timelineEntries.length > 0);
|
|
192
|
+
const hasThinkingContent =
|
|
193
|
+
!!state.thinking || (state.thinkingSteps && state.thinkingSteps.length > 0) || hasTimelineEntries || false;
|
|
194
|
+
|
|
195
|
+
const hasHITLInteractions =
|
|
196
|
+
hitlInteractions && hitlInteractions.length > 0;
|
|
197
|
+
|
|
139
198
|
// Derived state: has any content been received?
|
|
140
199
|
const hasAnyContent =
|
|
141
|
-
|
|
200
|
+
hasThinkingContent ||
|
|
142
201
|
state.toolCalls.length > 0 ||
|
|
143
202
|
state.knowledge.length > 0 ||
|
|
144
203
|
state.memory.length > 0 ||
|
|
204
|
+
state.statusUpdates.length > 0 ||
|
|
205
|
+
hasHITLInteractions ||
|
|
145
206
|
state.response;
|
|
146
207
|
|
|
147
208
|
// Derived state: should show metadata row?
|
|
148
209
|
const showMetadataRow =
|
|
149
|
-
|
|
210
|
+
hasThinkingContent ||
|
|
150
211
|
state.toolCalls.length > 0 ||
|
|
151
212
|
state.knowledge.length > 0 ||
|
|
152
213
|
state.memory.length > 0 ||
|
|
214
|
+
state.statusUpdates.length > 0 ||
|
|
153
215
|
state.status === "processing";
|
|
154
216
|
|
|
155
217
|
// Determine action bar visibility
|
|
@@ -177,24 +239,51 @@ const AgentResponse = React.forwardRef<HTMLDivElement, AgentResponseProps>(
|
|
|
177
239
|
{showMetadataRow && (
|
|
178
240
|
<>
|
|
179
241
|
<MetadataRow
|
|
180
|
-
hasThinking={
|
|
242
|
+
hasThinking={hasThinkingContent}
|
|
181
243
|
isExpanded={thinkingExpanded}
|
|
182
244
|
onToggle={toggleThinking}
|
|
183
245
|
toolCalls={state.toolCalls}
|
|
184
246
|
knowledge={state.knowledge}
|
|
185
247
|
memory={state.memory}
|
|
248
|
+
statusUpdates={state.statusUpdates}
|
|
249
|
+
statusContent={statusContent}
|
|
186
250
|
status={state.status}
|
|
187
251
|
elapsedTime={elapsedTime}
|
|
188
252
|
/>
|
|
189
253
|
|
|
190
|
-
{/* Thinking Content -
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
254
|
+
{/* Thinking Content - AgentTimeline when timeline entries exist, ThinkingSection otherwise */}
|
|
255
|
+
{hasTimelineEntries ? (
|
|
256
|
+
thinkingExpanded && (
|
|
257
|
+
<div className="px-3 pb-3 border-t border-border mt-2">
|
|
258
|
+
<AgentTimeline
|
|
259
|
+
entries={state.timelineEntries!}
|
|
260
|
+
renderMarkdown={renderThinkingMarkdown}
|
|
261
|
+
uiState={timelineUIStateRef.current}
|
|
262
|
+
/>
|
|
263
|
+
</div>
|
|
264
|
+
)
|
|
265
|
+
) : (
|
|
266
|
+
<ThinkingSection
|
|
267
|
+
content={
|
|
268
|
+
state.thinkingSteps && state.thinkingSteps.length > 0
|
|
269
|
+
? state.thinkingSteps
|
|
270
|
+
: state.thinking
|
|
271
|
+
}
|
|
272
|
+
isExpanded={thinkingExpanded}
|
|
273
|
+
renderMarkdown={renderThinkingMarkdown}
|
|
274
|
+
/>
|
|
275
|
+
)}
|
|
195
276
|
</>
|
|
196
277
|
)}
|
|
197
278
|
|
|
279
|
+
{/* HITL Interactions - collapsible section */}
|
|
280
|
+
{hasHITLInteractions && (
|
|
281
|
+
<HITLSection
|
|
282
|
+
interactions={hitlInteractions}
|
|
283
|
+
defaultExpanded={defaultHITLExpanded}
|
|
284
|
+
/>
|
|
285
|
+
)}
|
|
286
|
+
|
|
198
287
|
{/* Response Section */}
|
|
199
288
|
{state.response && (
|
|
200
289
|
<div
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import * as React from "react";
|
|
8
|
-
import { Wrench, Book, HardDrive } from "lucide-react";
|
|
8
|
+
import { Wrench, Book, HardDrive, Activity } from "lucide-react";
|
|
9
9
|
import { cn, Popover, PopoverTrigger, PopoverContent } from "@optilogic/core";
|
|
10
|
-
import type { ToolCall, KnowledgeItem, MemoryItem } from "../types";
|
|
10
|
+
import type { ToolCall, KnowledgeItem, MemoryItem, StatusItem } from "../types";
|
|
11
11
|
|
|
12
12
|
export interface ActivityIndicatorsProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
13
13
|
/** Tool calls to display */
|
|
@@ -16,6 +16,8 @@ export interface ActivityIndicatorsProps extends React.HTMLAttributes<HTMLDivEle
|
|
|
16
16
|
knowledge: KnowledgeItem[];
|
|
17
17
|
/** Memory items to display */
|
|
18
18
|
memory: MemoryItem[];
|
|
19
|
+
/** Status updates to display */
|
|
20
|
+
statusUpdates?: StatusItem[];
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
/**
|
|
@@ -32,14 +34,44 @@ export interface ActivityIndicatorsProps extends React.HTMLAttributes<HTMLDivEle
|
|
|
32
34
|
* />
|
|
33
35
|
*/
|
|
34
36
|
const ActivityIndicators = React.forwardRef<HTMLDivElement, ActivityIndicatorsProps>(
|
|
35
|
-
({ toolCalls, knowledge, memory, className, ...props }, ref) => {
|
|
37
|
+
({ toolCalls, knowledge, memory, statusUpdates = [], className, ...props }, ref) => {
|
|
36
38
|
const hasAnyActivity =
|
|
37
|
-
toolCalls.length > 0 || knowledge.length > 0 || memory.length > 0;
|
|
39
|
+
toolCalls.length > 0 || knowledge.length > 0 || memory.length > 0 || statusUpdates.length > 0;
|
|
38
40
|
|
|
39
41
|
if (!hasAnyActivity) return null;
|
|
40
42
|
|
|
41
43
|
return (
|
|
42
44
|
<div ref={ref} className={cn("flex items-center gap-2", className)} {...props}>
|
|
45
|
+
{/* Status Updates */}
|
|
46
|
+
{statusUpdates.length > 0 && (
|
|
47
|
+
<Popover>
|
|
48
|
+
<PopoverTrigger asChild>
|
|
49
|
+
<button
|
|
50
|
+
className="flex items-center gap-1 text-muted-foreground hover:text-foreground transition-colors"
|
|
51
|
+
onClick={(e) => e.stopPropagation()}
|
|
52
|
+
>
|
|
53
|
+
<Activity className="w-3.5 h-3.5" />
|
|
54
|
+
<span className="text-xs">{statusUpdates.length}</span>
|
|
55
|
+
</button>
|
|
56
|
+
</PopoverTrigger>
|
|
57
|
+
<PopoverContent className="w-80">
|
|
58
|
+
<div className="space-y-2">
|
|
59
|
+
<h4 className="font-medium text-sm">Status Updates</h4>
|
|
60
|
+
<div className="space-y-2 max-h-60 overflow-auto">
|
|
61
|
+
{statusUpdates.map((item) => (
|
|
62
|
+
<div key={item.id} className="p-2 bg-muted rounded text-xs">
|
|
63
|
+
{item.agent && (
|
|
64
|
+
<div className="font-medium text-muted-foreground">{item.agent}</div>
|
|
65
|
+
)}
|
|
66
|
+
<div className={item.agent ? "mt-1" : ""}>{item.message}</div>
|
|
67
|
+
</div>
|
|
68
|
+
))}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</PopoverContent>
|
|
72
|
+
</Popover>
|
|
73
|
+
)}
|
|
74
|
+
|
|
43
75
|
{/* Tool Calls */}
|
|
44
76
|
{toolCalls.length > 0 && (
|
|
45
77
|
<Popover>
|
|
@@ -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 };
|
|
@@ -9,7 +9,7 @@ import { ChevronDown, ChevronUp } from "lucide-react";
|
|
|
9
9
|
import { cn, LoadingSpinner } from "@optilogic/core";
|
|
10
10
|
import { ActivityIndicators } from "./ActivityIndicators";
|
|
11
11
|
import { formatTime } from "../utils";
|
|
12
|
-
import type { AgentResponseStatus, ToolCall, KnowledgeItem, MemoryItem } from "../types";
|
|
12
|
+
import type { AgentResponseStatus, ToolCall, KnowledgeItem, MemoryItem, StatusItem } from "../types";
|
|
13
13
|
|
|
14
14
|
export interface MetadataRowProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
15
15
|
/** Whether there is thinking content */
|
|
@@ -24,6 +24,10 @@ export interface MetadataRowProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
24
24
|
knowledge: KnowledgeItem[];
|
|
25
25
|
/** Memory items to display */
|
|
26
26
|
memory: MemoryItem[];
|
|
27
|
+
/** Status updates to display */
|
|
28
|
+
statusUpdates?: StatusItem[];
|
|
29
|
+
/** Optional content to display in the middle area between left content and activity indicators */
|
|
30
|
+
statusContent?: React.ReactNode;
|
|
27
31
|
/** Current response status */
|
|
28
32
|
status: AgentResponseStatus;
|
|
29
33
|
/** Elapsed time in seconds */
|
|
@@ -57,6 +61,8 @@ const MetadataRow = React.forwardRef<HTMLDivElement, MetadataRowProps>(
|
|
|
57
61
|
toolCalls,
|
|
58
62
|
knowledge,
|
|
59
63
|
memory,
|
|
64
|
+
statusUpdates = [],
|
|
65
|
+
statusContent,
|
|
60
66
|
status,
|
|
61
67
|
elapsedTime,
|
|
62
68
|
className,
|
|
@@ -67,7 +73,7 @@ const MetadataRow = React.forwardRef<HTMLDivElement, MetadataRowProps>(
|
|
|
67
73
|
const isProcessing = status === "processing";
|
|
68
74
|
const isComplete = status === "complete";
|
|
69
75
|
const hasActivity =
|
|
70
|
-
toolCalls.length > 0 || knowledge.length > 0 || memory.length > 0;
|
|
76
|
+
toolCalls.length > 0 || knowledge.length > 0 || memory.length > 0 || statusUpdates.length > 0;
|
|
71
77
|
|
|
72
78
|
// Determine what to show on the left side
|
|
73
79
|
const renderLeftContent = () => {
|
|
@@ -105,8 +111,8 @@ const MetadataRow = React.forwardRef<HTMLDivElement, MetadataRowProps>(
|
|
|
105
111
|
|
|
106
112
|
const leftContent = renderLeftContent();
|
|
107
113
|
|
|
108
|
-
// If nothing to show (no thinking, not processing, no activity), hide the row
|
|
109
|
-
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) {
|
|
110
116
|
return null;
|
|
111
117
|
}
|
|
112
118
|
|
|
@@ -122,19 +128,28 @@ const MetadataRow = React.forwardRef<HTMLDivElement, MetadataRowProps>(
|
|
|
122
128
|
{hasThinking ? (
|
|
123
129
|
<button
|
|
124
130
|
onClick={onToggle}
|
|
125
|
-
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"
|
|
126
132
|
>
|
|
127
133
|
{leftContent}
|
|
128
134
|
</button>
|
|
129
135
|
) : (
|
|
130
|
-
<div className="flex items-center gap-1.5">
|
|
136
|
+
<div className="flex items-center gap-1.5 shrink-0">
|
|
131
137
|
{leftContent}
|
|
132
138
|
</div>
|
|
133
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
|
+
|
|
134
148
|
<ActivityIndicators
|
|
135
149
|
toolCalls={toolCalls}
|
|
136
150
|
knowledge={knowledge}
|
|
137
151
|
memory={memory}
|
|
152
|
+
statusUpdates={statusUpdates}
|
|
138
153
|
/>
|
|
139
154
|
</div>
|
|
140
155
|
);
|
|
@@ -1,43 +1,135 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Thinking Section Component
|
|
3
3
|
*
|
|
4
|
-
* Collapsible section for displaying agent thinking/reasoning content
|
|
4
|
+
* Collapsible section for displaying agent thinking/reasoning content.
|
|
5
|
+
* Supports both plain text and structured collapsible sub-sections.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import * as React from "react";
|
|
9
|
+
import { useState, useCallback } from "react";
|
|
10
|
+
import { ChevronDown, ChevronRight } from "lucide-react";
|
|
8
11
|
import { cn } from "@optilogic/core";
|
|
12
|
+
import type { ThinkingStep } from "../types";
|
|
9
13
|
|
|
10
|
-
export interface ThinkingSectionProps
|
|
11
|
-
|
|
12
|
-
content
|
|
14
|
+
export interface ThinkingSectionProps
|
|
15
|
+
extends Omit<React.HTMLAttributes<HTMLDivElement>, "content"> {
|
|
16
|
+
/** The thinking content to display (string or structured steps) */
|
|
17
|
+
content: string | ThinkingStep[];
|
|
13
18
|
/** Whether the section is expanded */
|
|
14
19
|
isExpanded: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Custom markdown renderer for the thinking content.
|
|
22
|
+
* If not provided, the content will be rendered as plain preformatted text.
|
|
23
|
+
*/
|
|
24
|
+
renderMarkdown?: (content: string) => React.ReactNode;
|
|
15
25
|
}
|
|
16
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Internal component for rendering a collapsible thinking step
|
|
29
|
+
*/
|
|
30
|
+
interface ThinkingStepItemProps {
|
|
31
|
+
step: ThinkingStep;
|
|
32
|
+
renderMarkdown?: (content: string) => React.ReactNode;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const ThinkingStepItem: React.FC<ThinkingStepItemProps> = ({ step, renderMarkdown }) => {
|
|
36
|
+
const [isCollapsed, setIsCollapsed] = useState(step.isCollapsed ?? false);
|
|
37
|
+
|
|
38
|
+
const toggleCollapse = useCallback(() => {
|
|
39
|
+
setIsCollapsed((prev) => !prev);
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
const indentPadding = step.depth * 16;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="border-b border-border/50 last:border-b-0">
|
|
46
|
+
<button
|
|
47
|
+
onClick={toggleCollapse}
|
|
48
|
+
className="w-full flex items-center gap-1.5 py-1.5 px-2 hover:bg-muted/50 transition-colors text-left"
|
|
49
|
+
style={{ paddingLeft: `${indentPadding + 8}px` }}
|
|
50
|
+
>
|
|
51
|
+
{isCollapsed ? (
|
|
52
|
+
<ChevronRight className="w-3 h-3 text-muted-foreground flex-shrink-0" />
|
|
53
|
+
) : (
|
|
54
|
+
<ChevronDown className="w-3 h-3 text-muted-foreground flex-shrink-0" />
|
|
55
|
+
)}
|
|
56
|
+
<span className="text-xs font-medium text-foreground/80">{step.label}</span>
|
|
57
|
+
</button>
|
|
58
|
+
|
|
59
|
+
{!isCollapsed && (
|
|
60
|
+
<div
|
|
61
|
+
className="pb-2 px-2"
|
|
62
|
+
style={{ paddingLeft: `${indentPadding + 28}px` }}
|
|
63
|
+
>
|
|
64
|
+
{renderMarkdown ? (
|
|
65
|
+
<div className="text-xs text-muted-foreground">
|
|
66
|
+
{renderMarkdown(step.content)}
|
|
67
|
+
</div>
|
|
68
|
+
) : (
|
|
69
|
+
<pre className="text-xs text-muted-foreground whitespace-pre-wrap font-mono">
|
|
70
|
+
{step.content}
|
|
71
|
+
</pre>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
17
79
|
/**
|
|
18
80
|
* ThinkingSection Component
|
|
19
81
|
*
|
|
20
82
|
* Displays the agent's thinking/reasoning content in a collapsible panel.
|
|
83
|
+
* Supports both plain text content and structured collapsible sub-sections.
|
|
21
84
|
*
|
|
22
85
|
* @example
|
|
86
|
+
* // Plain text content
|
|
23
87
|
* <ThinkingSection content={state.thinking} isExpanded={isExpanded} />
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* // Structured content with sub-sections
|
|
91
|
+
* <ThinkingSection
|
|
92
|
+
* content={[
|
|
93
|
+
* { id: "1", label: "Analysis", content: "...", depth: 0 },
|
|
94
|
+
* { id: "2", label: "Sub-analysis", content: "...", depth: 1 },
|
|
95
|
+
* ]}
|
|
96
|
+
* isExpanded={isExpanded}
|
|
97
|
+
* />
|
|
24
98
|
*/
|
|
25
99
|
const ThinkingSection = React.forwardRef<HTMLDivElement, ThinkingSectionProps>(
|
|
26
|
-
({ content, isExpanded, className, ...props }, ref) => {
|
|
27
|
-
if (!isExpanded || !content) {
|
|
100
|
+
({ content, isExpanded, renderMarkdown, className, ...props }, ref) => {
|
|
101
|
+
if (!isExpanded || !content || (Array.isArray(content) && content.length === 0)) {
|
|
28
102
|
return null;
|
|
29
103
|
}
|
|
30
104
|
|
|
105
|
+
const isStructured = Array.isArray(content);
|
|
106
|
+
|
|
31
107
|
return (
|
|
32
108
|
<div
|
|
33
109
|
ref={ref}
|
|
34
110
|
className={cn("px-3 pb-3 border-t border-border", className)}
|
|
35
111
|
{...props}
|
|
36
112
|
>
|
|
37
|
-
<div className="mt-2 max-h-[200px] overflow-y-auto">
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
113
|
+
<div className="mt-2 max-h-[200px] overflow-y-auto scrollbar-thin">
|
|
114
|
+
{isStructured ? (
|
|
115
|
+
<div className="space-y-0">
|
|
116
|
+
{content.map((step) => (
|
|
117
|
+
<ThinkingStepItem
|
|
118
|
+
key={step.id}
|
|
119
|
+
step={step}
|
|
120
|
+
renderMarkdown={renderMarkdown}
|
|
121
|
+
/>
|
|
122
|
+
))}
|
|
123
|
+
</div>
|
|
124
|
+
) : renderMarkdown ? (
|
|
125
|
+
<div className="text-xs text-muted-foreground">
|
|
126
|
+
{renderMarkdown(content)}
|
|
127
|
+
</div>
|
|
128
|
+
) : (
|
|
129
|
+
<pre className="text-xs text-muted-foreground whitespace-pre-wrap font-mono">
|
|
130
|
+
{content}
|
|
131
|
+
</pre>
|
|
132
|
+
)}
|
|
41
133
|
</div>
|
|
42
134
|
</div>
|
|
43
135
|
);
|
|
@@ -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";
|