ai-design-system 0.1.64 → 0.1.65
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/components/blocks/AIConversation/AIConversation.tsx +139 -76
- package/components/composites/FileTreeExplorer/FileTreeExplorer.tsx +1 -1
- package/components/features/TextEditor/TextEditor.tsx +36 -10
- package/dist/index.cjs +129 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +129 -65
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -12,7 +12,6 @@ import { SpecialistMessage } from "@/components/composites/SpecialistMessage"
|
|
|
12
12
|
import { OrchestratorMessage } from "@/components/composites/OrchestratorMessage"
|
|
13
13
|
import { ToolCallDisplay } from "@/components/composites/ToolCallDisplay"
|
|
14
14
|
import { ReasoningDisplay } from "@/components/composites/ReasoningDisplay"
|
|
15
|
-
import { ApprovalCard } from "@/components/composites/ApprovalCard"
|
|
16
15
|
|
|
17
16
|
/**
|
|
18
17
|
* AIConversation Section
|
|
@@ -24,6 +23,11 @@ import { ApprovalCard } from "@/components/composites/ApprovalCard"
|
|
|
24
23
|
* Based on reference implementation from deep-agents-ui ChatInterface.
|
|
25
24
|
*/
|
|
26
25
|
|
|
26
|
+
type AIMessageBlock =
|
|
27
|
+
| { type: 'text'; id: string; text: string }
|
|
28
|
+
| { type: 'toolCall'; id: string; toolCall: ToolCall }
|
|
29
|
+
| { type: 'subAgent'; id: string; subAgent: SubAgent }
|
|
30
|
+
|
|
27
31
|
interface AIMessage {
|
|
28
32
|
id: string;
|
|
29
33
|
type: 'human' | 'ai';
|
|
@@ -34,6 +38,7 @@ interface AIMessage {
|
|
|
34
38
|
toolCalls?: ToolCall[];
|
|
35
39
|
subAgents?: SubAgent[];
|
|
36
40
|
isLoading?: boolean;
|
|
41
|
+
blocks?: AIMessageBlock[];
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
export interface AIConversationProps
|
|
@@ -112,71 +117,95 @@ export const AIConversation = React.memo<AIConversationProps>(
|
|
|
112
117
|
(!msg.toolCalls || msg.toolCalls.length === 0) &&
|
|
113
118
|
(!msg.subAgents || msg.subAgents.length === 0);
|
|
114
119
|
|
|
115
|
-
if (
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
currentGroup.content
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
if (currentGroup) {
|
|
121
|
+
if (msg.content) {
|
|
122
|
+
currentGroup.content = currentGroup.content
|
|
123
|
+
? `${currentGroup.content}\n\n${msg.content}`
|
|
124
|
+
: msg.content;
|
|
125
|
+
}
|
|
126
|
+
if (msg.toolCalls) {
|
|
127
|
+
const existingToolCalls = currentGroup.toolCalls || [];
|
|
128
|
+
const updatedToolCalls = [...existingToolCalls];
|
|
129
|
+
for (const newCall of msg.toolCalls) {
|
|
130
|
+
const existingIndex = updatedToolCalls.findIndex(tc => tc.id === newCall.id);
|
|
131
|
+
if (existingIndex >= 0) {
|
|
132
|
+
updatedToolCalls[existingIndex] = newCall;
|
|
133
|
+
} else {
|
|
134
|
+
updatedToolCalls.push(newCall);
|
|
135
|
+
}
|
|
121
136
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
137
|
+
currentGroup.toolCalls = updatedToolCalls;
|
|
138
|
+
}
|
|
139
|
+
if (msg.subAgents) {
|
|
140
|
+
const existingSubAgents = currentGroup.subAgents || [];
|
|
141
|
+
const updatedSubAgents = [...existingSubAgents];
|
|
142
|
+
for (const newAgent of msg.subAgents) {
|
|
143
|
+
const existingIndex = updatedSubAgents.findIndex(a => a.id === newAgent.id);
|
|
144
|
+
if (existingIndex >= 0) {
|
|
145
|
+
updatedSubAgents[existingIndex] = newAgent;
|
|
146
|
+
} else {
|
|
147
|
+
if (
|
|
148
|
+
updatedSubAgents.length > 0 &&
|
|
149
|
+
updatedSubAgents[updatedSubAgents.length - 1].subAgentName === newAgent.subAgentName
|
|
150
|
+
) {
|
|
151
|
+
const prev = updatedSubAgents[updatedSubAgents.length - 1];
|
|
152
|
+
let mergedOutput = prev.output;
|
|
153
|
+
if (newAgent.output) {
|
|
154
|
+
const prevStr = typeof prev.output === 'string' ? prev.output : (prev.output ? JSON.stringify(prev.output) : '');
|
|
155
|
+
const newStr = typeof newAgent.output === 'string' ? newAgent.output : JSON.stringify(newAgent.output);
|
|
156
|
+
mergedOutput = prevStr ? `${prevStr}\n\n${newStr}` : newStr;
|
|
157
|
+
}
|
|
158
|
+
updatedSubAgents[updatedSubAgents.length - 1] = {
|
|
159
|
+
...newAgent,
|
|
160
|
+
id: prev.id,
|
|
161
|
+
input: prev.input,
|
|
162
|
+
output: mergedOutput
|
|
163
|
+
};
|
|
129
164
|
} else {
|
|
130
|
-
|
|
165
|
+
updatedSubAgents.push(newAgent);
|
|
131
166
|
}
|
|
132
167
|
}
|
|
133
|
-
currentGroup.toolCalls = updatedToolCalls;
|
|
134
168
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
169
|
+
currentGroup.subAgents = updatedSubAgents;
|
|
170
|
+
}
|
|
171
|
+
if (msg.blocks) {
|
|
172
|
+
const existingBlocks = currentGroup.blocks || [];
|
|
173
|
+
const updatedBlocks = [...existingBlocks];
|
|
174
|
+
for (const newBlock of msg.blocks) {
|
|
175
|
+
const lastBlock = updatedBlocks[updatedBlocks.length - 1];
|
|
176
|
+
if (
|
|
177
|
+
updatedBlocks.length > 0 &&
|
|
178
|
+
lastBlock.type === 'text' &&
|
|
179
|
+
newBlock.type === 'text'
|
|
180
|
+
) {
|
|
181
|
+
updatedBlocks[updatedBlocks.length - 1] = {
|
|
182
|
+
...lastBlock,
|
|
183
|
+
text: (lastBlock.text || '') + '\n\n' + (newBlock.text || ''),
|
|
184
|
+
};
|
|
185
|
+
} else {
|
|
186
|
+
const existingIndex = updatedBlocks.findIndex(b => b.id === newBlock.id && b.type === newBlock.type && b.type !== 'text');
|
|
140
187
|
if (existingIndex >= 0) {
|
|
141
|
-
|
|
188
|
+
updatedBlocks[existingIndex] = newBlock;
|
|
142
189
|
} else {
|
|
143
|
-
|
|
144
|
-
if (
|
|
145
|
-
updatedSubAgents.length > 0 &&
|
|
146
|
-
updatedSubAgents[updatedSubAgents.length - 1].subAgentName === newAgent.subAgentName
|
|
147
|
-
) {
|
|
148
|
-
const prev = updatedSubAgents[updatedSubAgents.length - 1];
|
|
149
|
-
let mergedOutput = prev.output;
|
|
150
|
-
if (newAgent.output) {
|
|
151
|
-
const prevStr = typeof prev.output === 'string' ? prev.output : (prev.output ? JSON.stringify(prev.output) : '');
|
|
152
|
-
const newStr = typeof newAgent.output === 'string' ? newAgent.output : JSON.stringify(newAgent.output);
|
|
153
|
-
mergedOutput = prevStr ? `${prevStr}\n\n${newStr}` : newStr;
|
|
154
|
-
}
|
|
155
|
-
updatedSubAgents[updatedSubAgents.length - 1] = {
|
|
156
|
-
...newAgent,
|
|
157
|
-
id: prev.id, // keep the original id so React keys don't jump
|
|
158
|
-
input: prev.input, // keep original input or combine? Just keep original
|
|
159
|
-
output: mergedOutput
|
|
160
|
-
};
|
|
161
|
-
} else {
|
|
162
|
-
updatedSubAgents.push(newAgent);
|
|
163
|
-
}
|
|
190
|
+
updatedBlocks.push(newBlock);
|
|
164
191
|
}
|
|
165
192
|
}
|
|
166
|
-
currentGroup.subAgents = updatedSubAgents;
|
|
167
193
|
}
|
|
168
|
-
currentGroup.
|
|
169
|
-
} else {
|
|
170
|
-
currentGroup = {
|
|
171
|
-
...msg,
|
|
172
|
-
toolCalls: msg.toolCalls ? [...msg.toolCalls] : undefined,
|
|
173
|
-
subAgents: msg.subAgents ? [...msg.subAgents] : undefined,
|
|
174
|
-
};
|
|
175
|
-
result.push(currentGroup);
|
|
194
|
+
currentGroup.blocks = updatedBlocks;
|
|
176
195
|
}
|
|
196
|
+
currentGroup.isLoading = msg.isLoading;
|
|
177
197
|
} else {
|
|
198
|
+
currentGroup = {
|
|
199
|
+
...msg,
|
|
200
|
+
toolCalls: msg.toolCalls ? [...msg.toolCalls] : undefined,
|
|
201
|
+
subAgents: msg.subAgents ? [...msg.subAgents] : undefined,
|
|
202
|
+
blocks: msg.blocks ? [...msg.blocks] : undefined,
|
|
203
|
+
};
|
|
204
|
+
result.push(currentGroup);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (isFinalResponse) {
|
|
178
208
|
currentGroup = null;
|
|
179
|
-
result.push(msg);
|
|
180
209
|
}
|
|
181
210
|
} else {
|
|
182
211
|
currentGroup = null;
|
|
@@ -223,11 +252,11 @@ export const AIConversation = React.memo<AIConversationProps>(
|
|
|
223
252
|
const directToolCalls = allToolCalls.filter((tc) => tc.visibility !== "reasoning")
|
|
224
253
|
|
|
225
254
|
const hasReasoning = reasoningCalls.length > 0 || subAgents.length > 0 || directToolCalls.length > 0 || (message.isLoading && contentStr.trim() !== "");
|
|
226
|
-
const reasoningText = hasReasoning ? contentStr : undefined
|
|
255
|
+
const reasoningText = (hasReasoning && (!message.blocks || message.blocks.length === 0)) ? contentStr : undefined
|
|
227
256
|
const displayContentStr = hasReasoning ? "" : contentStr
|
|
228
257
|
const hasDisplayContent = displayContentStr.trim() !== ""
|
|
229
258
|
|
|
230
|
-
if (!hasDisplayContent && directToolCalls.length === 0 && reasoningCalls.length === 0 && subAgents.length === 0 && !message.isLoading) {
|
|
259
|
+
if (!hasDisplayContent && directToolCalls.length === 0 && reasoningCalls.length === 0 && subAgents.length === 0 && !message.isLoading && (!message.blocks || message.blocks.length === 0)) {
|
|
231
260
|
return null;
|
|
232
261
|
}
|
|
233
262
|
|
|
@@ -249,32 +278,66 @@ export const AIConversation = React.memo<AIConversationProps>(
|
|
|
249
278
|
{hasReasoning && (
|
|
250
279
|
<ReasoningDisplay
|
|
251
280
|
content={reasoningText}
|
|
252
|
-
items={reasoningCalls}
|
|
281
|
+
items={message.blocks && message.blocks.length > 0 ? [] : reasoningCalls}
|
|
253
282
|
isStreaming={isStreaming}
|
|
254
283
|
onToolAction={onToolAction}
|
|
255
284
|
defaultOpen={isStreaming || index === groupedMessages.length - 1}
|
|
256
285
|
>
|
|
257
|
-
{
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
286
|
+
{message.blocks && message.blocks.length > 0 ? (
|
|
287
|
+
message.blocks.map((block, i) => {
|
|
288
|
+
if (block.type === 'text') {
|
|
289
|
+
return block.text && block.text.trim() ? (
|
|
290
|
+
<div key={`text-${i}`} className="mb-4 text-muted-foreground whitespace-pre-wrap">
|
|
291
|
+
{block.text}
|
|
292
|
+
</div>
|
|
293
|
+
) : null;
|
|
294
|
+
} else if (block.type === 'toolCall' && block.toolCall && block.toolCall.name !== 'ask_user' && block.toolCall.name !== 'ask_question') {
|
|
295
|
+
return <ToolCallDisplay key={block.id} toolCall={block.toolCall} onToolAction={onToolAction} />;
|
|
296
|
+
} else if (block.type === 'subAgent' && block.subAgent) {
|
|
297
|
+
const subAgent = block.subAgent;
|
|
298
|
+
return (
|
|
299
|
+
<SpecialistMessage
|
|
300
|
+
key={subAgent.id}
|
|
301
|
+
message={{
|
|
302
|
+
id: subAgent.id,
|
|
303
|
+
name: subAgent.subAgentName,
|
|
304
|
+
description: undefined,
|
|
305
|
+
input: subAgent.input,
|
|
306
|
+
content: typeof subAgent.output === 'string' ? subAgent.output : (subAgent.output ? JSON.stringify(subAgent.output) : ''),
|
|
307
|
+
status: subAgent.status,
|
|
308
|
+
toolCalls: [],
|
|
309
|
+
}}
|
|
310
|
+
isNested={true}
|
|
311
|
+
/>
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
return null;
|
|
315
|
+
})
|
|
316
|
+
) : (
|
|
317
|
+
<>
|
|
318
|
+
{/* Render direct tool calls fallback */}
|
|
319
|
+
{directToolCalls.map((tc) => (
|
|
320
|
+
<ToolCallDisplay key={tc.id} toolCall={tc} onToolAction={onToolAction} />
|
|
321
|
+
))}
|
|
261
322
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
323
|
+
{/* Render specialist sub-agents fallback */}
|
|
324
|
+
{subAgents.map((subAgent) => (
|
|
325
|
+
<SpecialistMessage
|
|
326
|
+
key={subAgent.id}
|
|
327
|
+
message={{
|
|
328
|
+
id: subAgent.id,
|
|
329
|
+
name: subAgent.subAgentName,
|
|
330
|
+
description: undefined,
|
|
331
|
+
input: subAgent.input,
|
|
332
|
+
content: typeof subAgent.output === 'string' ? subAgent.output : (subAgent.output ? JSON.stringify(subAgent.output) : ''),
|
|
333
|
+
status: subAgent.status,
|
|
334
|
+
toolCalls: [],
|
|
335
|
+
}}
|
|
336
|
+
isNested={true}
|
|
337
|
+
/>
|
|
338
|
+
))}
|
|
339
|
+
</>
|
|
340
|
+
)}
|
|
278
341
|
</ReasoningDisplay>
|
|
279
342
|
)}
|
|
280
343
|
</OrchestratorMessage>
|
|
@@ -179,7 +179,7 @@ export const FileTreeExplorer = React.memo<FileTreeExplorerProps>(
|
|
|
179
179
|
) : null}
|
|
180
180
|
<a ref={downloadRef} style={{ display: 'none' }} />
|
|
181
181
|
</div>
|
|
182
|
-
<div className="p-2">
|
|
182
|
+
<div className="overflow-y-auto p-2">
|
|
183
183
|
<FileTree
|
|
184
184
|
className="border-none bg-transparent"
|
|
185
185
|
expanded={activeExpanded}
|
|
@@ -176,7 +176,6 @@ export const TextEditor = React.memo<TextEditorProps>(
|
|
|
176
176
|
className,
|
|
177
177
|
onAnnotationAdd,
|
|
178
178
|
onAnnotationUpdate,
|
|
179
|
-
onAnnotationClick,
|
|
180
179
|
} = props
|
|
181
180
|
|
|
182
181
|
const isMultiTab = isMultiTabMode(props)
|
|
@@ -212,18 +211,18 @@ export const TextEditor = React.memo<TextEditorProps>(
|
|
|
212
211
|
},
|
|
213
212
|
[onAnnotationUpdate]
|
|
214
213
|
)
|
|
215
|
-
const
|
|
216
|
-
|
|
214
|
+
const multiTabDocs = isMultiTab ? (props as TextEditorMultiTabProps).documents : undefined
|
|
215
|
+
const multiTabFileTree = isMultiTab ? (props as TextEditorMultiTabProps).fileTree : undefined
|
|
217
216
|
|
|
218
|
-
|
|
219
|
-
if (
|
|
220
|
-
return
|
|
217
|
+
const fileTreeNodes = useMemo(() => {
|
|
218
|
+
if (multiTabFileTree) {
|
|
219
|
+
return multiTabFileTree
|
|
221
220
|
}
|
|
222
221
|
|
|
223
|
-
if (!
|
|
222
|
+
if (!multiTabDocs) return []
|
|
224
223
|
const rootNodes: FileTreeNode[] = []
|
|
225
224
|
|
|
226
|
-
|
|
225
|
+
multiTabDocs.forEach((doc) => {
|
|
227
226
|
const parts = doc.file.id.split('/')
|
|
228
227
|
if (parts.length === 1) {
|
|
229
228
|
rootNodes.push({
|
|
@@ -257,7 +256,33 @@ export const TextEditor = React.memo<TextEditorProps>(
|
|
|
257
256
|
})
|
|
258
257
|
})
|
|
259
258
|
return rootNodes
|
|
260
|
-
}, [
|
|
259
|
+
}, [multiTabDocs, multiTabFileTree])
|
|
260
|
+
|
|
261
|
+
const handleTabSelect = isMultiTab ? (props as TextEditorMultiTabProps).onTabSelect : undefined
|
|
262
|
+
|
|
263
|
+
const LinkComponent = useCallback((linkProps: React.AnchorHTMLAttributes<HTMLAnchorElement>) => {
|
|
264
|
+
const { href, ...rest } = linkProps
|
|
265
|
+
const isExternal = href?.startsWith('http://') || href?.startsWith('https://') || href?.startsWith('mailto:')
|
|
266
|
+
const isAnchor = href?.startsWith('#')
|
|
267
|
+
|
|
268
|
+
const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
269
|
+
if (!isExternal && !isAnchor && href) {
|
|
270
|
+
e.preventDefault()
|
|
271
|
+
let targetId = href
|
|
272
|
+
if (targetId.startsWith('./')) {
|
|
273
|
+
targetId = targetId.slice(2)
|
|
274
|
+
}
|
|
275
|
+
if (handleTabSelect) {
|
|
276
|
+
handleTabSelect(targetId)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (isExternal) {
|
|
282
|
+
return <a href={href} target="_blank" rel="noopener noreferrer" {...rest} />
|
|
283
|
+
}
|
|
284
|
+
return <a href={href} onClick={handleClick} className="text-primary hover:underline cursor-pointer" {...rest} />
|
|
285
|
+
}, [handleTabSelect])
|
|
261
286
|
|
|
262
287
|
/**
|
|
263
288
|
* Single-document mode
|
|
@@ -274,7 +299,7 @@ export const TextEditor = React.memo<TextEditorProps>(
|
|
|
274
299
|
: contentStr
|
|
275
300
|
return (
|
|
276
301
|
<div className={cn('text-editor p-6 flex flex-col h-screen w-full flex-1', className)}>
|
|
277
|
-
<StreamingMarkdown mode="streaming">
|
|
302
|
+
<StreamingMarkdown mode="streaming" components={{ a: LinkComponent }}>
|
|
278
303
|
{content}
|
|
279
304
|
</StreamingMarkdown>
|
|
280
305
|
</div>
|
|
@@ -346,6 +371,7 @@ export const TextEditor = React.memo<TextEditorProps>(
|
|
|
346
371
|
mode="streaming"
|
|
347
372
|
isAnimating
|
|
348
373
|
className="[&>*:first-child]:mt-0 [&>*:last-child]:mb-0"
|
|
374
|
+
components={{ a: LinkComponent }}
|
|
349
375
|
>
|
|
350
376
|
{isCodeMulti
|
|
351
377
|
? `\`\`\`${format}\n${format === 'json' ? formatJson(currentDocument.content as string) : currentDocument.content}\n\`\`\``
|