@flamingo-stack/openframe-frontend-core 0.0.187 → 0.0.188
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-FMWHOUFE.js → chunk-6XYXWVYY.js} +2 -2
- package/dist/{chunk-ALW3D72O.cjs → chunk-HIMEIPED.cjs} +9 -9
- package/dist/{chunk-ALW3D72O.cjs.map → chunk-HIMEIPED.cjs.map} +1 -1
- package/dist/{chunk-RZ3HHPQH.js → chunk-J2C2TI5Z.js} +2174 -2119
- package/dist/chunk-J2C2TI5Z.js.map +1 -0
- package/dist/{chunk-TMD5LDX4.cjs → chunk-VJTFBYVG.cjs} +57 -2
- package/dist/{chunk-TMD5LDX4.cjs.map → chunk-VJTFBYVG.cjs.map} +1 -1
- package/dist/{chunk-EUTOT74J.cjs → chunk-W5AWCFKE.cjs} +762 -760
- package/dist/chunk-W5AWCFKE.cjs.map +1 -0
- package/dist/{chunk-M36UJN3T.js → chunk-YOMHP4V3.js} +4091 -4089
- package/dist/chunk-YOMHP4V3.js.map +1 -0
- package/dist/components/chat/approval-batch-message.d.ts.map +1 -1
- package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
- package/dist/components/chat/chat-message-list.d.ts.map +1 -1
- package/dist/components/chat/context-compaction-display.d.ts.map +1 -1
- package/dist/components/chat/tool-execution-display.d.ts.map +1 -1
- package/dist/components/chat/types/api.types.d.ts +2 -6
- package/dist/components/chat/types/api.types.d.ts.map +1 -1
- package/dist/components/chat/types/message.types.d.ts +20 -0
- package/dist/components/chat/types/message.types.d.ts.map +1 -1
- package/dist/components/chat/types/network.types.d.ts +2 -0
- package/dist/components/chat/types/network.types.d.ts.map +1 -1
- package/dist/components/chat/types/processing.types.d.ts +2 -6
- package/dist/components/chat/types/processing.types.d.ts.map +1 -1
- package/dist/components/chat/utils/chunk-parser.d.ts.map +1 -1
- package/dist/components/chat/utils/extract-incomplete-message-state.d.ts +2 -6
- package/dist/components/chat/utils/extract-incomplete-message-state.d.ts.map +1 -1
- package/dist/components/chat/utils/message-segment-accumulator.d.ts +2 -6
- package/dist/components/chat/utils/message-segment-accumulator.d.ts.map +1 -1
- package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
- package/dist/components/chat/utils/tool-call-helpers.d.ts +21 -2
- package/dist/components/chat/utils/tool-call-helpers.d.ts.map +1 -1
- package/dist/components/features/index.cjs +4 -4
- package/dist/components/features/index.js +3 -3
- package/dist/components/icons-v2-generated/index.cjs +4 -2
- package/dist/components/icons-v2-generated/index.cjs.map +1 -1
- package/dist/components/icons-v2-generated/index.d.ts +1 -0
- package/dist/components/icons-v2-generated/index.d.ts.map +1 -1
- package/dist/components/icons-v2-generated/index.js +3 -1
- package/dist/components/icons-v2-generated/loaders/dots-loader-icon.d.ts +8 -0
- package/dist/components/icons-v2-generated/loaders/dots-loader-icon.d.ts.map +1 -0
- package/dist/components/icons-v2-generated/loaders/index.d.ts +2 -0
- package/dist/components/icons-v2-generated/loaders/index.d.ts.map +1 -0
- package/dist/components/index.cjs +4 -6
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +3 -5
- package/dist/components/navigation/index.cjs +4 -4
- package/dist/components/navigation/index.js +3 -3
- package/dist/components/ui/index.cjs +4 -6
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.d.ts +0 -1
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +3 -5
- package/dist/hooks/index.cjs +3 -3
- package/dist/hooks/index.js +2 -2
- package/dist/index.cjs +4 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -5
- package/package.json +1 -1
- package/src/components/chat/approval-batch-message.tsx +4 -5
- package/src/components/chat/chat-message-enhanced.tsx +0 -37
- package/src/components/chat/chat-message-list.tsx +57 -0
- package/src/components/chat/context-compaction-display.tsx +2 -3
- package/src/components/chat/thinking-display.tsx +2 -2
- package/src/components/chat/tool-execution-display.tsx +14 -11
- package/src/components/chat/types/api.types.ts +2 -2
- package/src/components/chat/types/message.types.ts +21 -0
- package/src/components/chat/types/network.types.ts +2 -0
- package/src/components/chat/types/processing.types.ts +2 -6
- package/src/components/chat/utils/chunk-parser.ts +2 -0
- package/src/components/chat/utils/extract-incomplete-message-state.ts +4 -2
- package/src/components/chat/utils/message-segment-accumulator.ts +11 -2
- package/src/components/chat/utils/process-historical-messages.ts +2 -0
- package/src/components/chat/utils/tool-call-helpers.ts +97 -14
- package/src/components/icons-v2/loaders/dots-loader.svg +1 -0
- package/src/components/icons-v2-generated/index.ts +1 -0
- package/src/components/icons-v2-generated/loaders/dots-loader-icon.tsx +53 -0
- package/src/components/icons-v2-generated/loaders/index.ts +1 -0
- package/src/components/ui/index.ts +0 -2
- package/src/stories/DotsLoaderIcon.stories.tsx +103 -0
- package/dist/chunk-EUTOT74J.cjs.map +0 -1
- package/dist/chunk-M36UJN3T.js.map +0 -1
- package/dist/chunk-RZ3HHPQH.js.map +0 -1
- package/dist/components/ui/pulse-dots.d.ts +0 -7
- package/dist/components/ui/pulse-dots.d.ts.map +0 -1
- package/src/components/ui/pulse-dots.tsx +0 -56
- /package/dist/{chunk-FMWHOUFE.js.map → chunk-6XYXWVYY.js.map} +0 -0
|
@@ -3,14 +3,13 @@
|
|
|
3
3
|
import { forwardRef, useMemo, useState } from "react"
|
|
4
4
|
|
|
5
5
|
import { cn } from "../../utils/cn"
|
|
6
|
-
import { CheckCircleIcon, XmarkCircleIcon } from "../icons-v2-generated"
|
|
6
|
+
import { CheckCircleIcon, DotsLoaderIcon, XmarkCircleIcon } from "../icons-v2-generated"
|
|
7
7
|
import { ToolType } from "../platform"
|
|
8
8
|
import { ToolIcon } from "../tool-icon"
|
|
9
|
-
import { PulseDots } from "../ui/pulse-dots"
|
|
10
9
|
import { ExpandChevron } from "./expand-chevron"
|
|
11
10
|
import { useCollapsible } from "./hooks/use-collapsible"
|
|
12
11
|
import { ArgRow, ResultBlock } from "./tool-call-blocks"
|
|
13
|
-
import { COMMAND_BODY_ARG_KEYS } from "./utils/tool-call-helpers"
|
|
12
|
+
import { COMMAND_BODY_ARG_KEYS, getToolCallTitle } from "./utils/tool-call-helpers"
|
|
14
13
|
import type { ToolExecutionDisplayProps } from "./types"
|
|
15
14
|
|
|
16
15
|
const COMMAND_BODY_KEYS = new Set<string>(COMMAND_BODY_ARG_KEYS)
|
|
@@ -24,11 +23,15 @@ const ToolExecutionDisplay = forwardRef<HTMLDivElement, ToolExecutionDisplayProp
|
|
|
24
23
|
const isExecuted = message.type === "EXECUTED_TOOL"
|
|
25
24
|
const integratedToolType = (message.integratedToolType as ToolType) || ("OPENFRAME" as ToolType)
|
|
26
25
|
|
|
27
|
-
const previewText = useMemo(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
const previewText = useMemo(
|
|
27
|
+
() =>
|
|
28
|
+
getToolCallTitle({
|
|
29
|
+
args: message.parameters,
|
|
30
|
+
title: message.toolTitle,
|
|
31
|
+
name: message.toolFunction,
|
|
32
|
+
}),
|
|
33
|
+
[message.parameters, message.toolTitle, message.toolFunction],
|
|
34
|
+
)
|
|
32
35
|
|
|
33
36
|
const argEntries = useMemo<Array<[string, unknown]>>(() => {
|
|
34
37
|
if (!message.parameters || typeof message.parameters !== "object") return []
|
|
@@ -41,7 +44,7 @@ const ToolExecutionDisplay = forwardRef<HTMLDivElement, ToolExecutionDisplayProp
|
|
|
41
44
|
const hasBody = argEntries.length > 0 || hasResult || isExecuting
|
|
42
45
|
|
|
43
46
|
const renderStatusIcon = () => {
|
|
44
|
-
if (isExecuting) return <
|
|
47
|
+
if (isExecuting) return <DotsLoaderIcon size={16} className="text-ods-text-secondary" />
|
|
45
48
|
if (isExecuted && message.success === true) return <CheckCircleIcon className="w-4 h-4 text-ods-success" />
|
|
46
49
|
if (isExecuted && message.success === false) return <XmarkCircleIcon className="w-4 h-4 text-ods-error" />
|
|
47
50
|
return null
|
|
@@ -72,7 +75,7 @@ const ToolExecutionDisplay = forwardRef<HTMLDivElement, ToolExecutionDisplayProp
|
|
|
72
75
|
: "text-ods-text-secondary line-clamp-2 max-h-10 break-all",
|
|
73
76
|
)}
|
|
74
77
|
>
|
|
75
|
-
{
|
|
78
|
+
{previewText}
|
|
76
79
|
</div>
|
|
77
80
|
<div className="flex items-center justify-center shrink-0 w-5 h-5">{renderStatusIcon()}</div>
|
|
78
81
|
<div className="flex items-center justify-center shrink-0 w-5 h-5">
|
|
@@ -91,7 +94,7 @@ const ToolExecutionDisplay = forwardRef<HTMLDivElement, ToolExecutionDisplayProp
|
|
|
91
94
|
{isExecuting && (
|
|
92
95
|
<div className="flex flex-col gap-1 items-start w-full">
|
|
93
96
|
<span className="text-ods-text-secondary">Result:</span>
|
|
94
|
-
<
|
|
97
|
+
<DotsLoaderIcon size={16} className="text-ods-text-secondary" />
|
|
95
98
|
</div>
|
|
96
99
|
)}
|
|
97
100
|
</div>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type { ChunkData, NatsMessageType, FetchChunksFunction } from './network.types'
|
|
7
7
|
import type { ChatType, ChatApprovalStatus } from './chat.types'
|
|
8
|
-
import type { MessageSegment, PendingToolCallData, TokenUsageData } from './message.types'
|
|
8
|
+
import type { MessageSegment, PendingToolCallData, TokenUsageData, ExecutingToolState } from './message.types'
|
|
9
9
|
|
|
10
10
|
// ========== Hook Options ==========
|
|
11
11
|
|
|
@@ -150,7 +150,7 @@ export interface UseRealtimeChunkProcessorOptions {
|
|
|
150
150
|
/** Pending approvals that haven't been resolved */
|
|
151
151
|
pendingApprovals?: Map<string, { command: string; explanation?: string; approvalType: string }>
|
|
152
152
|
/** Executing tools waiting for completion */
|
|
153
|
-
executingTools?: Map<string,
|
|
153
|
+
executingTools?: Map<string, ExecutingToolState>
|
|
154
154
|
/** Escalated approvals */
|
|
155
155
|
escalatedApprovals?: Map<
|
|
156
156
|
string,
|
|
@@ -35,6 +35,8 @@ export interface ToolExecutionData {
|
|
|
35
35
|
type: 'EXECUTING_TOOL' | 'EXECUTED_TOOL'
|
|
36
36
|
integratedToolType: string
|
|
37
37
|
toolFunction: string
|
|
38
|
+
/** Backend-issued human-readable title (mirrors `PendingToolCallData.toolTitle`). */
|
|
39
|
+
toolTitle?: string
|
|
38
40
|
parameters?: Record<string, any>
|
|
39
41
|
result?: string
|
|
40
42
|
success?: boolean
|
|
@@ -46,6 +48,21 @@ export interface ToolExecutionData {
|
|
|
46
48
|
toolExecutionRequestId?: string
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Snapshot of an in-flight tool kept between the `EXECUTING_TOOL` and
|
|
53
|
+
* `EXECUTED_TOOL` events. The backend only sends `toolTitle` on
|
|
54
|
+
* `EXECUTING_TOOL`; carrying this state lets the accumulator restore it onto
|
|
55
|
+
* the merged `EXECUTED_TOOL` segment instead of falling back to the raw
|
|
56
|
+
* `toolFunction`.
|
|
57
|
+
*/
|
|
58
|
+
export interface ExecutingToolState {
|
|
59
|
+
integratedToolType: string
|
|
60
|
+
toolFunction: string
|
|
61
|
+
/** Mirrors {@link ToolExecutionData.toolTitle}; absent on `EXECUTED_TOOL`. */
|
|
62
|
+
toolTitle?: string
|
|
63
|
+
parameters?: Record<string, any>
|
|
64
|
+
}
|
|
65
|
+
|
|
49
66
|
// ========== Approval Request Types ==========
|
|
50
67
|
|
|
51
68
|
export interface ApprovalRequestData {
|
|
@@ -171,6 +188,8 @@ export interface ExecutingToolMessageData extends MessageDataBase {
|
|
|
171
188
|
type: 'EXECUTING_TOOL'
|
|
172
189
|
integratedToolType?: string
|
|
173
190
|
toolFunction?: string
|
|
191
|
+
/** Backend-issued human-readable title (wire field, mirrors `ChunkData.title`). */
|
|
192
|
+
title?: string
|
|
174
193
|
parameters?: Record<string, any>
|
|
175
194
|
toolExecutionRequestId?: string
|
|
176
195
|
}
|
|
@@ -179,6 +198,8 @@ export interface ExecutedToolMessageData extends MessageDataBase {
|
|
|
179
198
|
type: 'EXECUTED_TOOL'
|
|
180
199
|
integratedToolType?: string
|
|
181
200
|
toolFunction?: string
|
|
201
|
+
/** Backend-issued human-readable title (wire field, mirrors `ChunkData.title`). */
|
|
202
|
+
title?: string
|
|
182
203
|
parameters?: Record<string, any>
|
|
183
204
|
result?: string
|
|
184
205
|
success?: boolean
|
|
@@ -38,6 +38,8 @@ export interface ChunkData {
|
|
|
38
38
|
text?: string
|
|
39
39
|
integratedToolType?: string
|
|
40
40
|
toolFunction?: string
|
|
41
|
+
/** Execution chunks carry the human-readable title as `title`. */
|
|
42
|
+
title?: string
|
|
41
43
|
parameters?: Record<string, any>
|
|
42
44
|
result?: string
|
|
43
45
|
success?: boolean
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Contains types for message parsing, accumulation, and processing
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { MessageSegment, ProcessedMessage, ToolExecutionSegment, TokenUsageData, PendingToolCallData } from './message.types'
|
|
6
|
+
import type { MessageSegment, ProcessedMessage, ToolExecutionSegment, TokenUsageData, PendingToolCallData, ExecutingToolState } from './message.types'
|
|
7
7
|
import type { ChatApprovalStatus, AssistantType } from './chat.types'
|
|
8
8
|
import type { ChunkData, NatsMessageType } from './network.types'
|
|
9
9
|
|
|
@@ -39,11 +39,7 @@ export interface PendingApproval {
|
|
|
39
39
|
export interface AccumulatorState {
|
|
40
40
|
segments: MessageSegment[]
|
|
41
41
|
pendingApprovals: Map<string, PendingApproval>
|
|
42
|
-
executingTools: Map<string,
|
|
43
|
-
integratedToolType: string
|
|
44
|
-
toolFunction: string
|
|
45
|
-
parameters?: Record<string, any>
|
|
46
|
-
}>
|
|
42
|
+
executingTools: Map<string, ExecutingToolState>
|
|
47
43
|
escalatedApprovals?: Map<
|
|
48
44
|
string,
|
|
49
45
|
{ command: string; explanation?: string; approvalType: string; toolCalls?: PendingToolCallData[] }
|
|
@@ -74,6 +74,7 @@ export function parseChunkToAction(chunk: unknown): ParsedChunkAction | null {
|
|
|
74
74
|
type: 'EXECUTING_TOOL',
|
|
75
75
|
integratedToolType: data.integratedToolType || '',
|
|
76
76
|
toolFunction: data.toolFunction || '',
|
|
77
|
+
toolTitle: typeof data.title === 'string' ? data.title : undefined,
|
|
77
78
|
parameters: data.parameters,
|
|
78
79
|
toolExecutionRequestId: typeof data.toolExecutionRequestId === 'string' ? data.toolExecutionRequestId : undefined,
|
|
79
80
|
}
|
|
@@ -89,6 +90,7 @@ export function parseChunkToAction(chunk: unknown): ParsedChunkAction | null {
|
|
|
89
90
|
type: 'EXECUTED_TOOL',
|
|
90
91
|
integratedToolType: data.integratedToolType || '',
|
|
91
92
|
toolFunction: data.toolFunction || '',
|
|
93
|
+
toolTitle: typeof data.title === 'string' ? data.title : undefined,
|
|
92
94
|
parameters: data.parameters,
|
|
93
95
|
result: data.result,
|
|
94
96
|
success: data.success,
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
ProcessedMessage,
|
|
8
8
|
MessageSegment,
|
|
9
9
|
PendingApproval,
|
|
10
|
+
ExecutingToolState,
|
|
10
11
|
} from '../types'
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -18,7 +19,7 @@ export function extractIncompleteMessageState(
|
|
|
18
19
|
): {
|
|
19
20
|
existingSegments?: MessageSegment[]
|
|
20
21
|
pendingApprovals?: Map<string, PendingApproval>
|
|
21
|
-
executingTools?: Map<string,
|
|
22
|
+
executingTools?: Map<string, ExecutingToolState>
|
|
22
23
|
} | undefined {
|
|
23
24
|
if (!lastMessage || lastMessage.role !== 'assistant' || typeof lastMessage.content === 'string') {
|
|
24
25
|
return undefined
|
|
@@ -26,7 +27,7 @@ export function extractIncompleteMessageState(
|
|
|
26
27
|
|
|
27
28
|
const segments = lastMessage.content as MessageSegment[]
|
|
28
29
|
const pendingApprovals = new Map<string, PendingApproval>()
|
|
29
|
-
const executingTools = new Map<string,
|
|
30
|
+
const executingTools = new Map<string, ExecutingToolState>()
|
|
30
31
|
let hasIncompleteState = false
|
|
31
32
|
|
|
32
33
|
segments.forEach(segment => {
|
|
@@ -39,6 +40,7 @@ export function extractIncompleteMessageState(
|
|
|
39
40
|
executingTools.set(toolKey, {
|
|
40
41
|
integratedToolType: segment.data.integratedToolType,
|
|
41
42
|
toolFunction: segment.data.toolFunction,
|
|
43
|
+
toolTitle: segment.data.toolTitle,
|
|
42
44
|
parameters: segment.data.parameters,
|
|
43
45
|
})
|
|
44
46
|
hasIncompleteState = true
|
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
PendingToolCallData,
|
|
21
21
|
AccumulatorState,
|
|
22
22
|
ChatApprovalStatus,
|
|
23
|
+
ExecutingToolState,
|
|
23
24
|
} from '../types'
|
|
24
25
|
|
|
25
26
|
export interface AccumulatorCallbacks {
|
|
@@ -34,7 +35,7 @@ export interface AccumulatorCallbacks {
|
|
|
34
35
|
export class MessageSegmentAccumulator {
|
|
35
36
|
private segments: MessageSegment[] = []
|
|
36
37
|
private pendingApprovals: Map<string, PendingApproval> = new Map()
|
|
37
|
-
private executingTools: Map<string,
|
|
38
|
+
private executingTools: Map<string, ExecutingToolState> = new Map()
|
|
38
39
|
private callbacks: AccumulatorCallbacks = {}
|
|
39
40
|
|
|
40
41
|
constructor(callbacks?: AccumulatorCallbacks) {
|
|
@@ -57,7 +58,7 @@ export class MessageSegmentAccumulator {
|
|
|
57
58
|
initializeWithState(state: {
|
|
58
59
|
existingSegments?: MessageSegment[]
|
|
59
60
|
pendingApprovals?: Map<string, PendingApproval>
|
|
60
|
-
executingTools?: Map<string,
|
|
61
|
+
executingTools?: Map<string, ExecutingToolState>
|
|
61
62
|
}): void {
|
|
62
63
|
if (state.existingSegments) {
|
|
63
64
|
this.segments = [...state.existingSegments]
|
|
@@ -164,6 +165,7 @@ export class MessageSegmentAccumulator {
|
|
|
164
165
|
this.executingTools.set(toolKey, {
|
|
165
166
|
integratedToolType: toolData.integratedToolType,
|
|
166
167
|
toolFunction: toolData.toolFunction,
|
|
168
|
+
toolTitle: toolData.toolTitle,
|
|
167
169
|
parameters: toolData.parameters,
|
|
168
170
|
})
|
|
169
171
|
this.segments.push(segment)
|
|
@@ -179,10 +181,17 @@ export class MessageSegmentAccumulator {
|
|
|
179
181
|
)
|
|
180
182
|
|
|
181
183
|
const executingTool = this.executingTools.get(toolKey)
|
|
184
|
+
// The backend omits `toolTitle` on EXECUTED_TOOL; restore it from the
|
|
185
|
+
// paired EXECUTING segment (or its tracked state) so the completed
|
|
186
|
+
// segment keeps the human-readable title instead of falling back to the
|
|
187
|
+
// raw `toolFunction`.
|
|
188
|
+
const existingExecuting =
|
|
189
|
+
existingIndex !== -1 ? (this.segments[existingIndex] as ToolExecutionSegment) : undefined
|
|
182
190
|
const mergedSegment: ToolExecutionSegment = {
|
|
183
191
|
type: 'tool_execution',
|
|
184
192
|
data: {
|
|
185
193
|
...toolData,
|
|
194
|
+
toolTitle: toolData.toolTitle ?? existingExecuting?.data.toolTitle ?? executingTool?.toolTitle,
|
|
186
195
|
parameters: toolData.parameters || executingTool?.parameters,
|
|
187
196
|
}
|
|
188
197
|
}
|
|
@@ -231,6 +231,7 @@ function processMessageData(
|
|
|
231
231
|
type: 'EXECUTING_TOOL',
|
|
232
232
|
integratedToolType: data.integratedToolType || '',
|
|
233
233
|
toolFunction: data.toolFunction || '',
|
|
234
|
+
toolTitle: typeof data.title === 'string' ? data.title : undefined,
|
|
234
235
|
parameters: data.parameters,
|
|
235
236
|
toolExecutionRequestId: data.toolExecutionRequestId,
|
|
236
237
|
},
|
|
@@ -246,6 +247,7 @@ function processMessageData(
|
|
|
246
247
|
type: 'EXECUTED_TOOL',
|
|
247
248
|
integratedToolType: data.integratedToolType || '',
|
|
248
249
|
toolFunction: data.toolFunction || '',
|
|
250
|
+
toolTitle: typeof data.title === 'string' ? data.title : undefined,
|
|
249
251
|
parameters: data.parameters,
|
|
250
252
|
result: data.result,
|
|
251
253
|
success: data.success,
|
|
@@ -8,19 +8,38 @@ import type { PendingToolCallData } from '../types'
|
|
|
8
8
|
export const COMMAND_BODY_ARG_KEYS = ['command', 'query', 'script', 'scriptContent', 'code'] as const
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
* Order: command/query/script/scriptContent/code arg →
|
|
11
|
+
* Resolve the human-readable title/preview line for a tool call.
|
|
12
|
+
* Order: command/query/script/scriptContent/code arg → title → name → fallback.
|
|
13
|
+
*
|
|
14
|
+
* Shape-agnostic so both the approval card (`PendingToolCallData`:
|
|
15
|
+
* `toolCallArguments`/`toolName`) and the execution card (`ToolExecutionData`:
|
|
16
|
+
* `parameters`/`toolFunction`) can share one source of truth.
|
|
13
17
|
*/
|
|
14
|
-
export function
|
|
15
|
-
|
|
18
|
+
export function getToolCallTitle(opts: {
|
|
19
|
+
args?: Record<string, unknown> | null
|
|
20
|
+
title?: string | null
|
|
21
|
+
name?: string | null
|
|
22
|
+
}): string {
|
|
23
|
+
const { args, title, name } = opts
|
|
16
24
|
if (args && typeof args === 'object') {
|
|
17
|
-
const a = args as Record<string, unknown>
|
|
18
25
|
for (const key of COMMAND_BODY_ARG_KEYS) {
|
|
19
|
-
const candidate =
|
|
26
|
+
const candidate = args[key]
|
|
20
27
|
if (typeof candidate === 'string' && candidate.trim()) return candidate
|
|
21
28
|
}
|
|
22
29
|
}
|
|
23
|
-
return
|
|
30
|
+
return title || name || 'Tool call'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Extract a human-readable command string from a batch tool call.
|
|
35
|
+
* Thin adapter over {@link getToolCallTitle}.
|
|
36
|
+
*/
|
|
37
|
+
export function getCommandText(call: PendingToolCallData): string {
|
|
38
|
+
return getToolCallTitle({
|
|
39
|
+
args: call.toolCallArguments,
|
|
40
|
+
title: call.toolTitle,
|
|
41
|
+
name: call.toolName,
|
|
42
|
+
})
|
|
24
43
|
}
|
|
25
44
|
|
|
26
45
|
export type FormattedArgValue =
|
|
@@ -29,6 +48,74 @@ export type FormattedArgValue =
|
|
|
29
48
|
|
|
30
49
|
const INLINE_STRING_MAX = 80
|
|
31
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Pretty-print like `JSON.stringify(value, null, 2)`, but render multi-line
|
|
53
|
+
* string values across real lines instead of escaped `\n` sequences.
|
|
54
|
+
*
|
|
55
|
+
* Trade-off: the output is optimised for human reading, not for being
|
|
56
|
+
* re-parsed as JSON (multi-line string bodies lose their surrounding quotes
|
|
57
|
+
* and escaping). Single-line strings keep normal JSON quoting/escaping.
|
|
58
|
+
*
|
|
59
|
+
* `indent` is the nesting level of this value's content lines / closing
|
|
60
|
+
* bracket (matching the 2-space step of `JSON.stringify(_, null, 2)`).
|
|
61
|
+
*/
|
|
62
|
+
function expandJson(value: unknown, indent: number): string {
|
|
63
|
+
const pad = (n: number) => ' '.repeat(n)
|
|
64
|
+
|
|
65
|
+
if (value === null) return 'null'
|
|
66
|
+
const t = typeof value
|
|
67
|
+
if (t === 'number' || t === 'boolean' || t === 'bigint') return String(value)
|
|
68
|
+
|
|
69
|
+
if (t === 'string') {
|
|
70
|
+
const s = (value as string).replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
|
71
|
+
if (!s.includes('\n')) return JSON.stringify(s)
|
|
72
|
+
return s
|
|
73
|
+
.replace(/^\n+/, '')
|
|
74
|
+
.replace(/\n+$/, '')
|
|
75
|
+
.split('\n')
|
|
76
|
+
.map((line) => pad(indent) + line)
|
|
77
|
+
.join('\n')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (Array.isArray(value)) {
|
|
81
|
+
if (value.length === 0) return '[]'
|
|
82
|
+
const items = value.map((v) =>
|
|
83
|
+
typeof v === 'string' && /[\r\n]/.test(v)
|
|
84
|
+
? expandJson(v, indent + 1)
|
|
85
|
+
: pad(indent + 1) + expandJson(v, indent + 1),
|
|
86
|
+
)
|
|
87
|
+
return '[\n' + items.join(',\n') + '\n' + pad(indent) + ']'
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (t === 'object') {
|
|
91
|
+
const entries = Object.entries(value as Record<string, unknown>)
|
|
92
|
+
if (entries.length === 0) return '{}'
|
|
93
|
+
const items = entries.map(([k, v]) => {
|
|
94
|
+
const key = JSON.stringify(k)
|
|
95
|
+
if (typeof v === 'string' && /[\r\n]/.test(v)) {
|
|
96
|
+
return pad(indent + 1) + key + ':\n' + expandJson(v, indent + 2)
|
|
97
|
+
}
|
|
98
|
+
return pad(indent + 1) + key + ': ' + expandJson(v, indent + 1)
|
|
99
|
+
})
|
|
100
|
+
return '{\n' + items.join(',\n') + '\n' + pad(indent) + '}'
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return JSON.stringify(value)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Entry point for {@link expandJson}: readable pretty-print where multi-line
|
|
108
|
+
* string fields (script bodies, command output, …) are shown with real line
|
|
109
|
+
* breaks instead of literal `\n`.
|
|
110
|
+
*/
|
|
111
|
+
export function expandedJsonStringify(value: unknown): string {
|
|
112
|
+
try {
|
|
113
|
+
return expandJson(value, 0)
|
|
114
|
+
} catch {
|
|
115
|
+
return String(value)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
32
119
|
/**
|
|
33
120
|
* Decide how to render a single `toolCallArguments` value:
|
|
34
121
|
* - objects/arrays → pretty-printed JSON block
|
|
@@ -50,7 +137,7 @@ export function formatToolArgValue(value: unknown): FormattedArgValue {
|
|
|
50
137
|
(trimmed.startsWith('[') && trimmed.endsWith(']'))
|
|
51
138
|
) {
|
|
52
139
|
try {
|
|
53
|
-
return { kind: 'block', text:
|
|
140
|
+
return { kind: 'block', text: expandedJsonStringify(JSON.parse(trimmed)), language: 'json' }
|
|
54
141
|
} catch {
|
|
55
142
|
// not valid JSON — fall through
|
|
56
143
|
}
|
|
@@ -65,11 +152,7 @@ export function formatToolArgValue(value: unknown): FormattedArgValue {
|
|
|
65
152
|
}
|
|
66
153
|
|
|
67
154
|
function safeStringify(value: unknown): string {
|
|
68
|
-
|
|
69
|
-
return JSON.stringify(value, null, 2)
|
|
70
|
-
} catch {
|
|
71
|
-
return String(value)
|
|
72
|
-
}
|
|
155
|
+
return expandedJsonStringify(value)
|
|
73
156
|
}
|
|
74
157
|
|
|
75
158
|
/**
|
|
@@ -92,7 +175,7 @@ export function formatToolResult(value: string | undefined | null): FormattedArg
|
|
|
92
175
|
(trimmed.startsWith('[') && trimmed.endsWith(']'))
|
|
93
176
|
) {
|
|
94
177
|
try {
|
|
95
|
-
return { kind: 'block', text:
|
|
178
|
+
return { kind: 'block', text: expandedJsonStringify(JSON.parse(trimmed)), language: 'json' }
|
|
96
179
|
} catch {
|
|
97
180
|
// fall through
|
|
98
181
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="#888" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="4" cy="12" r="3"><animate attributeName="r" dur="0.75s" values="3;.2;3" repeatCount="indefinite" begin="0s"/></circle><circle cx="12" cy="12" r="3"><animate attributeName="r" dur="0.75s" values="3;.2;3" repeatCount="indefinite" begin="0.15s"/></circle><circle cx="20" cy="12" r="3"><animate attributeName="r" dur="0.75s" values="3;.2;3" repeatCount="indefinite" begin="0.3s"/></circle></svg>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { SVGProps } from "react";
|
|
2
|
+
export interface DotsLoaderIconProps
|
|
3
|
+
extends Omit<SVGProps<SVGSVGElement>, "width" | "height"> {
|
|
4
|
+
className?: string;
|
|
5
|
+
size?: number;
|
|
6
|
+
color?: string;
|
|
7
|
+
}
|
|
8
|
+
export function DotsLoaderIcon({
|
|
9
|
+
className = "",
|
|
10
|
+
size = 24,
|
|
11
|
+
color = "currentColor",
|
|
12
|
+
...props
|
|
13
|
+
}: DotsLoaderIconProps) {
|
|
14
|
+
return (
|
|
15
|
+
<svg
|
|
16
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
17
|
+
fill={color}
|
|
18
|
+
viewBox="0 0 24 24"
|
|
19
|
+
width={size}
|
|
20
|
+
height={size}
|
|
21
|
+
className={className}
|
|
22
|
+
{...props}
|
|
23
|
+
>
|
|
24
|
+
<circle cx={4} cy={12} r={3}>
|
|
25
|
+
<animate
|
|
26
|
+
attributeName="r"
|
|
27
|
+
dur="0.75s"
|
|
28
|
+
values="3;.2;3"
|
|
29
|
+
repeatCount="indefinite"
|
|
30
|
+
begin="0s"
|
|
31
|
+
/>
|
|
32
|
+
</circle>
|
|
33
|
+
<circle cx={12} cy={12} r={3}>
|
|
34
|
+
<animate
|
|
35
|
+
attributeName="r"
|
|
36
|
+
dur="0.75s"
|
|
37
|
+
values="3;.2;3"
|
|
38
|
+
repeatCount="indefinite"
|
|
39
|
+
begin="0.15s"
|
|
40
|
+
/>
|
|
41
|
+
</circle>
|
|
42
|
+
<circle cx={20} cy={12} r={3}>
|
|
43
|
+
<animate
|
|
44
|
+
attributeName="r"
|
|
45
|
+
dur="0.75s"
|
|
46
|
+
values="3;.2;3"
|
|
47
|
+
repeatCount="indefinite"
|
|
48
|
+
begin="0.3s"
|
|
49
|
+
/>
|
|
50
|
+
</circle>
|
|
51
|
+
</svg>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DotsLoaderIcon } from './dots-loader-icon';
|
|
@@ -44,8 +44,6 @@ export * from './menubar'
|
|
|
44
44
|
export * from './navigation-menu'
|
|
45
45
|
export * from './tab-content'
|
|
46
46
|
export * from './tab-navigation'
|
|
47
|
-
// Animation components
|
|
48
|
-
export * from './pulse-dots'
|
|
49
47
|
// Feedback components
|
|
50
48
|
export * from './alert'
|
|
51
49
|
export * from './badge'
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/nextjs-vite';
|
|
2
|
+
import { DotsLoaderIcon } from '../components/icons-v2-generated/loaders/dots-loader-icon';
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'UI/DotsLoaderIcon',
|
|
6
|
+
component: DotsLoaderIcon,
|
|
7
|
+
argTypes: {
|
|
8
|
+
size: {
|
|
9
|
+
control: { type: 'number', min: 12, max: 128, step: 4 },
|
|
10
|
+
},
|
|
11
|
+
color: {
|
|
12
|
+
control: 'text',
|
|
13
|
+
description: 'CSS color. Defaults to `currentColor` — pass an ODS token like `var(--ods-accent)`.',
|
|
14
|
+
},
|
|
15
|
+
className: { control: 'text' },
|
|
16
|
+
},
|
|
17
|
+
parameters: {
|
|
18
|
+
layout: 'centered',
|
|
19
|
+
},
|
|
20
|
+
} satisfies Meta<typeof DotsLoaderIcon>;
|
|
21
|
+
|
|
22
|
+
export default meta;
|
|
23
|
+
type Story = StoryObj<typeof meta>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Default — 24px, inherits text color via `currentColor`.
|
|
27
|
+
* The three dots animate in an infinite loop (SMIL chain).
|
|
28
|
+
*/
|
|
29
|
+
export const Default: Story = {
|
|
30
|
+
args: {
|
|
31
|
+
size: 24,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Large size for full-page / blocking loading states.
|
|
37
|
+
*/
|
|
38
|
+
export const Large: Story = {
|
|
39
|
+
args: {
|
|
40
|
+
size: 64,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Inherits the surrounding text color through `currentColor`.
|
|
46
|
+
*/
|
|
47
|
+
export const InheritsTextColor: Story = {
|
|
48
|
+
args: {
|
|
49
|
+
size: 40,
|
|
50
|
+
},
|
|
51
|
+
render: (args) => (
|
|
52
|
+
<div className="text-ods-accent">
|
|
53
|
+
<DotsLoaderIcon {...args} />
|
|
54
|
+
</div>
|
|
55
|
+
),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Explicit ODS color tokens — never hardcode hex.
|
|
60
|
+
*/
|
|
61
|
+
export const OdsColors: Story = {
|
|
62
|
+
args: {
|
|
63
|
+
size: 40,
|
|
64
|
+
},
|
|
65
|
+
render: (args) => (
|
|
66
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '2rem' }}>
|
|
67
|
+
<DotsLoaderIcon {...args} color="var(--ods-text-primary)" />
|
|
68
|
+
<DotsLoaderIcon {...args} color="var(--ods-text-secondary)" />
|
|
69
|
+
<DotsLoaderIcon {...args} color="var(--ods-accent)" />
|
|
70
|
+
<DotsLoaderIcon {...args} color="var(--ods-error)" />
|
|
71
|
+
</div>
|
|
72
|
+
),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Size scale.
|
|
77
|
+
*/
|
|
78
|
+
export const Sizes: Story = {
|
|
79
|
+
args: {},
|
|
80
|
+
render: () => (
|
|
81
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '2rem' }}>
|
|
82
|
+
<DotsLoaderIcon size={16} />
|
|
83
|
+
<DotsLoaderIcon size={24} />
|
|
84
|
+
<DotsLoaderIcon size={40} />
|
|
85
|
+
<DotsLoaderIcon size={64} />
|
|
86
|
+
<DotsLoaderIcon size={96} />
|
|
87
|
+
</div>
|
|
88
|
+
),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* In context — centered inside a card, as used for inline loading.
|
|
93
|
+
*/
|
|
94
|
+
export const InCard: Story = {
|
|
95
|
+
args: {
|
|
96
|
+
size: 32,
|
|
97
|
+
},
|
|
98
|
+
render: (args) => (
|
|
99
|
+
<div className="flex items-center justify-center bg-ods-card border border-ods-border rounded-lg w-64 h-40 text-ods-text-secondary">
|
|
100
|
+
<DotsLoaderIcon {...args} />
|
|
101
|
+
</div>
|
|
102
|
+
),
|
|
103
|
+
};
|