ai-design-system 0.1.45 → 0.1.47
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/ai-elements/code-block.tsx +2 -2
- package/components/ai-elements/conversation.tsx +1 -1
- package/components/ai-elements/message.tsx +10 -2
- package/components/blocks/AIConversation/AIConversation.tsx +19 -5
- package/components/blocks/InboxPanel/InboxPanel.tsx +12 -1
- package/components/blocks/PromptInput/PromptInput.tsx +3 -1
- package/components/blocks/SectionLayout/SectionLayout.tsx +1 -1
- package/components/composites/ApprovalCard/ApprovalCard.tsx +70 -42
- package/components/composites/OrchestratorMessage/OrchestratorMessage.tsx +10 -3
- package/components/composites/SpecialistMessage/SpecialistMessage.tsx +16 -4
- package/components/features/AIDocEditor/AIDocEditor.tsx +38 -12
- package/components/features/RefinementPanel/RefinementPanel.mocks.ts +58 -2
- package/components/features/RefinementPanel/RefinementPanel.tsx +67 -25
- package/components/features/RefinementPanel/useRefinementPanel.d.ts +13 -8
- package/components/features/RefinementPanel/useRefinementPanel.mock.ts +6 -0
- package/dist/index.cjs +180 -62
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +45 -3
- package/dist/index.d.ts +9 -6
- package/dist/index.js +180 -62
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -110,12 +110,12 @@ export const CodeBlock = ({
|
|
|
110
110
|
>
|
|
111
111
|
<div className="relative">
|
|
112
112
|
<div
|
|
113
|
-
className="overflow-hidden dark:hidden [&>pre]:m-0 [&>pre]:bg-background! [&>pre]:p-4 [&>pre]:text-foreground! [&>pre]:text-sm [&_code]:font-mono [&_code]:text-sm"
|
|
113
|
+
className="overflow-x-auto overflow-y-hidden dark:hidden [&>pre]:m-0 [&>pre]:bg-background! [&>pre]:p-4 [&>pre]:text-foreground! [&>pre]:text-sm [&_code]:font-mono [&_code]:text-sm"
|
|
114
114
|
// biome-ignore lint/security/noDangerouslySetInnerHtml: "this is needed."
|
|
115
115
|
dangerouslySetInnerHTML={{ __html: html }}
|
|
116
116
|
/>
|
|
117
117
|
<div
|
|
118
|
-
className="hidden overflow-hidden dark:block [&>pre]:m-0 [&>pre]:bg-background! [&>pre]:p-4 [&>pre]:text-foreground! [&>pre]:text-sm [&_code]:font-mono [&_code]:text-sm"
|
|
118
|
+
className="hidden overflow-x-auto overflow-y-hidden dark:block [&>pre]:m-0 [&>pre]:bg-background! [&>pre]:p-4 [&>pre]:text-foreground! [&>pre]:text-sm [&_code]:font-mono [&_code]:text-sm"
|
|
119
119
|
// biome-ignore lint/security/noDangerouslySetInnerHtml: "this is needed."
|
|
120
120
|
dangerouslySetInnerHTML={{ __html: darkHtml }}
|
|
121
121
|
/>
|
|
@@ -11,7 +11,7 @@ export type ConversationProps = ComponentProps<typeof StickToBottom>;
|
|
|
11
11
|
|
|
12
12
|
export const Conversation = ({ className, ...props }: ConversationProps) => (
|
|
13
13
|
<StickToBottom
|
|
14
|
-
className={cn("relative flex-1 overflow-
|
|
14
|
+
className={cn("relative flex-1 overflow-hidden", className)}
|
|
15
15
|
initial="smooth"
|
|
16
16
|
resize="smooth"
|
|
17
17
|
role="log"
|
|
@@ -16,7 +16,7 @@ export const Message = ({ className, from, ...props }: MessageProps) => (
|
|
|
16
16
|
<div
|
|
17
17
|
className={cn(
|
|
18
18
|
"group flex w-full items-start gap-2 py-2",
|
|
19
|
-
from === "user" ? "is-user flex-row-reverse" : "is-assistant
|
|
19
|
+
from === "user" ? "is-user flex-row-reverse" : "is-assistant",
|
|
20
20
|
className
|
|
21
21
|
)}
|
|
22
22
|
{...props}
|
|
@@ -24,7 +24,7 @@ export const Message = ({ className, from, ...props }: MessageProps) => (
|
|
|
24
24
|
);
|
|
25
25
|
|
|
26
26
|
const messageContentVariants = cva(
|
|
27
|
-
"is-user:dark flex flex-col gap-2 overflow-hidden rounded-lg text-sm group-[.is-user]:ml-auto",
|
|
27
|
+
"is-user:dark flex flex-col gap-2 overflow-hidden rounded-lg text-sm group-[.is-user]:ml-auto min-w-0",
|
|
28
28
|
{
|
|
29
29
|
variants: {
|
|
30
30
|
variant: {
|
|
@@ -78,3 +78,11 @@ export const MessageAvatar = ({
|
|
|
78
78
|
<AvatarFallback>{name?.slice(0, 2) || "ME"}</AvatarFallback>
|
|
79
79
|
</Avatar>
|
|
80
80
|
);
|
|
81
|
+
|
|
82
|
+
export const MessageTypingIndicator = ({ className, ...props }: HTMLAttributes<HTMLDivElement>) => (
|
|
83
|
+
<div className={cn("flex items-center space-x-1 py-1.5 px-1 h-[24px]", className)} {...props}>
|
|
84
|
+
<div className="w-1.5 h-1.5 bg-current rounded-full animate-bounce [animation-delay:-0.3s]"></div>
|
|
85
|
+
<div className="w-1.5 h-1.5 bg-current rounded-full animate-bounce [animation-delay:-0.15s]"></div>
|
|
86
|
+
<div className="w-1.5 h-1.5 bg-current rounded-full animate-bounce"></div>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
@@ -11,6 +11,7 @@ import { UserMessage } from "@/components/composites/UserMessage"
|
|
|
11
11
|
import { SpecialistMessage } from "@/components/composites/SpecialistMessage"
|
|
12
12
|
import { OrchestratorMessage } from "@/components/composites/OrchestratorMessage"
|
|
13
13
|
import { ToolCallDisplay } from "@/components/composites/ToolCallDisplay"
|
|
14
|
+
import { ApprovalCard } from "@/components/composites/ApprovalCard"
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* AIConversation Section
|
|
@@ -31,6 +32,7 @@ interface AIMessage {
|
|
|
31
32
|
avatarName?: string;
|
|
32
33
|
toolCalls?: ToolCall[];
|
|
33
34
|
subAgents?: SubAgent[];
|
|
35
|
+
isLoading?: boolean;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
export interface AIConversationProps
|
|
@@ -90,9 +92,14 @@ export const AIConversation = React.memo<AIConversationProps>(
|
|
|
90
92
|
[emptyState?.description]
|
|
91
93
|
)
|
|
92
94
|
|
|
95
|
+
const toMessageString = (content: unknown): string =>
|
|
96
|
+
typeof content === 'string' ? content : ''
|
|
97
|
+
|
|
93
98
|
const renderedMessages = React.useMemo(
|
|
94
99
|
() =>
|
|
95
100
|
messages.map((message) => {
|
|
101
|
+
const contentStr = toMessageString(message.content)
|
|
102
|
+
|
|
96
103
|
// Render based on role field
|
|
97
104
|
if (message.role === "user") {
|
|
98
105
|
return (
|
|
@@ -100,7 +107,7 @@ export const AIConversation = React.memo<AIConversationProps>(
|
|
|
100
107
|
key={message.id}
|
|
101
108
|
message={{
|
|
102
109
|
id: message.id,
|
|
103
|
-
content:
|
|
110
|
+
content: contentStr,
|
|
104
111
|
avatarSrc: message.avatarSrc,
|
|
105
112
|
avatarName: message.avatarName,
|
|
106
113
|
}}
|
|
@@ -114,19 +121,26 @@ export const AIConversation = React.memo<AIConversationProps>(
|
|
|
114
121
|
const subAgents = message.subAgents || []
|
|
115
122
|
|
|
116
123
|
// Filter tool calls that aren't "task" type (those become sub-agents)
|
|
124
|
+
// Also completely hide "ask_user" and "ask_question" tools so they are ONLY rendered in the prompt input area
|
|
117
125
|
const directToolCalls =
|
|
118
126
|
message.toolCalls?.filter(
|
|
119
|
-
(tc) => tc.name !== "task"
|
|
127
|
+
(tc) => tc.name !== "task" && tc.name !== "ask_user"
|
|
120
128
|
) || []
|
|
121
129
|
|
|
130
|
+
const hasContent = contentStr.trim() !== ""
|
|
131
|
+
if (!hasContent && directToolCalls.length === 0 && subAgents.length === 0 && !message.isLoading) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
122
135
|
return (
|
|
123
136
|
<OrchestratorMessage
|
|
124
137
|
key={message.id}
|
|
125
138
|
message={{
|
|
126
139
|
id: message.id,
|
|
127
|
-
content:
|
|
140
|
+
content: contentStr,
|
|
128
141
|
avatarSrc: message.avatarSrc,
|
|
129
142
|
avatarName: message.avatarName,
|
|
143
|
+
isLoading: message.isLoading,
|
|
130
144
|
}}
|
|
131
145
|
showAvatar={showAvatars}
|
|
132
146
|
>
|
|
@@ -143,7 +157,7 @@ export const AIConversation = React.memo<AIConversationProps>(
|
|
|
143
157
|
id: subAgent.id,
|
|
144
158
|
name: subAgent.subAgentName,
|
|
145
159
|
description: undefined,
|
|
146
|
-
content:
|
|
160
|
+
content: typeof subAgent.output === 'string' ? subAgent.output : (subAgent.output ? JSON.stringify(subAgent.output) : ''),
|
|
147
161
|
status: subAgent.status,
|
|
148
162
|
toolCalls: [],
|
|
149
163
|
}}
|
|
@@ -161,7 +175,7 @@ export const AIConversation = React.memo<AIConversationProps>(
|
|
|
161
175
|
message={{
|
|
162
176
|
id: message.id,
|
|
163
177
|
name: message.avatarName || "Agent",
|
|
164
|
-
content:
|
|
178
|
+
content: contentStr,
|
|
165
179
|
toolCalls: message.toolCalls?.filter((tc) => tc.name !== "task"),
|
|
166
180
|
status: "completed",
|
|
167
181
|
}}
|
|
@@ -13,6 +13,8 @@ export interface InboxPanelProps {
|
|
|
13
13
|
isLoading?: boolean
|
|
14
14
|
emptyMessage?: string
|
|
15
15
|
className?: string
|
|
16
|
+
title?: string
|
|
17
|
+
headerAction?: React.ReactNode
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
export const InboxPanel = React.memo<InboxPanelProps>(
|
|
@@ -26,10 +28,19 @@ export const InboxPanel = React.memo<InboxPanelProps>(
|
|
|
26
28
|
isLoading = false,
|
|
27
29
|
emptyMessage,
|
|
28
30
|
className,
|
|
31
|
+
title,
|
|
32
|
+
headerAction,
|
|
29
33
|
}) => {
|
|
30
34
|
return (
|
|
31
35
|
<section className={`flex min-h-0 flex-1 flex-col overflow-hidden ${className ?? ""}`}>
|
|
32
|
-
|
|
36
|
+
{title || headerAction ? (
|
|
37
|
+
<div className="flex items-center justify-between px-4 pt-4 pb-2">
|
|
38
|
+
{title ? <h2 className="text-sm font-semibold text-muted-foreground">{title}</h2> : <div />}
|
|
39
|
+
{headerAction ? <div>{headerAction}</div> : null}
|
|
40
|
+
</div>
|
|
41
|
+
) : null}
|
|
42
|
+
|
|
43
|
+
<div className="px-4 pb-2">
|
|
33
44
|
<Input
|
|
34
45
|
aria-label="Search inbox items"
|
|
35
46
|
className="h-8"
|
|
@@ -29,6 +29,7 @@ export interface PromptInputBlockProps
|
|
|
29
29
|
event: FormEvent<HTMLFormElement>
|
|
30
30
|
) => void | Promise<void>;
|
|
31
31
|
dialog?: ReactNode;
|
|
32
|
+
loading?: boolean;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
export const PromptInput = React.memo<PromptInputBlockProps>(
|
|
@@ -39,6 +40,7 @@ export const PromptInput = React.memo<PromptInputBlockProps>(
|
|
|
39
40
|
onChange,
|
|
40
41
|
onSubmit,
|
|
41
42
|
dialog,
|
|
43
|
+
loading = false,
|
|
42
44
|
...props
|
|
43
45
|
}) => {
|
|
44
46
|
const handleSubmit = React.useCallback(
|
|
@@ -80,7 +82,7 @@ export const PromptInput = React.memo<PromptInputBlockProps>(
|
|
|
80
82
|
<PromptInputTools>
|
|
81
83
|
{}
|
|
82
84
|
</PromptInputTools>
|
|
83
|
-
<PromptInputSubmit disabled={disabled} />
|
|
85
|
+
<PromptInputSubmit disabled={disabled} status={loading ? "submitted" : undefined} />
|
|
84
86
|
</PromptInputFooter>
|
|
85
87
|
</AIPromptInput>
|
|
86
88
|
);
|
|
@@ -32,7 +32,7 @@ export const SectionLayout = React.memo<SectionLayoutProps>(
|
|
|
32
32
|
{section.header && (
|
|
33
33
|
<AppHeader {...section.header} />
|
|
34
34
|
)}
|
|
35
|
-
<div className="min-h-0 flex-1 overflow-
|
|
35
|
+
<div className="min-h-0 flex-1 overflow-hidden flex flex-col">
|
|
36
36
|
{section.content}
|
|
37
37
|
</div>
|
|
38
38
|
</div>
|
|
@@ -98,6 +98,10 @@ export const ApprovalCard = React.memo<ApprovalCardProps>(
|
|
|
98
98
|
const [rejectReason, setRejectReason] = useState("");
|
|
99
99
|
const [showRejectInput, setShowRejectInput] = useState(false);
|
|
100
100
|
|
|
101
|
+
const hideSkipButton = (actionRequest.args?.hide_skip_button as boolean) ?? false;
|
|
102
|
+
const submitButtonText = (actionRequest.args?.button_submit_text as string) || 'Continue';
|
|
103
|
+
const skipButtonText = (actionRequest.args?.button_skip_text as string) || 'Skip';
|
|
104
|
+
|
|
101
105
|
// Question states
|
|
102
106
|
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
|
|
103
107
|
const [selectedAnswers, setSelectedAnswers] = useState<Record<number, string | string[]>>({});
|
|
@@ -117,13 +121,15 @@ export const ApprovalCard = React.memo<ApprovalCardProps>(
|
|
|
117
121
|
const isQuestionAction =
|
|
118
122
|
actionRequest.name === "ask_question" ||
|
|
119
123
|
(actionRequest.args &&
|
|
120
|
-
(typeof actionRequest.args.question === "string" ||
|
|
121
|
-
Array.isArray(actionRequest.args.questions)));
|
|
124
|
+
((typeof actionRequest.args.question === "string") ||
|
|
125
|
+
(Array.isArray(actionRequest.args.questions) && actionRequest.args.questions.length > 0)));
|
|
122
126
|
|
|
123
127
|
// Parse questions
|
|
124
128
|
let questions: Question[] = [];
|
|
125
129
|
if (actionRequest.args && Array.isArray(actionRequest.args.questions)) {
|
|
126
130
|
questions = actionRequest.args.questions as Question[];
|
|
131
|
+
} else if (actionRequest.args && actionRequest.args.questions && typeof actionRequest.args.questions === "object") {
|
|
132
|
+
questions = Object.values(actionRequest.args.questions) as Question[];
|
|
127
133
|
} else if (actionRequest.args && typeof actionRequest.args.question === "string") {
|
|
128
134
|
questions = [
|
|
129
135
|
{
|
|
@@ -144,19 +150,37 @@ export const ApprovalCard = React.memo<ApprovalCardProps>(
|
|
|
144
150
|
// Render interactive question view
|
|
145
151
|
if (isQuestionAction && currentQuestion) {
|
|
146
152
|
const handleContinue = () => {
|
|
153
|
+
const qIdx = currentQuestionIndex;
|
|
154
|
+
const selIdx = selectedOptionIndices[qIdx];
|
|
155
|
+
const hasOtherSelected = selIdx === options.length;
|
|
156
|
+
const hasRegularSelection = selIdx !== null && selIdx !== undefined && !hasOtherSelected;
|
|
157
|
+
const hasMultiSelectItems = isMultiSelect && Array.isArray(selectedAnswers[qIdx]) && (selectedAnswers[qIdx] as string[]).length > 0;
|
|
158
|
+
|
|
159
|
+
const isApprovalType = actionRequest.args?.type === 'approval';
|
|
160
|
+
const isOtherEmpty = hasOtherSelected && !otherTexts[qIdx]?.trim();
|
|
161
|
+
const hasNoValidSelection = (!hasRegularSelection && !hasMultiSelectItems && !hasOtherSelected) || isOtherEmpty;
|
|
162
|
+
|
|
163
|
+
let answer: string | string[] = "Approved";
|
|
164
|
+
|
|
165
|
+
if (hasOtherSelected) {
|
|
166
|
+
answer = otherTexts[qIdx] || "Approved";
|
|
167
|
+
} else if (isMultiSelect) {
|
|
168
|
+
answer = (selectedAnswers[qIdx] as string[]).join(", ") || "Approved";
|
|
169
|
+
} else if (hasRegularSelection) {
|
|
170
|
+
answer = selectedAnswers[qIdx] as string || "Approved";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!isApprovalType && hasNoValidSelection) return;
|
|
174
|
+
|
|
147
175
|
if (currentQuestionIndex === questions.length - 1) {
|
|
148
176
|
const finalAnswers = questions.map((q, idx) => {
|
|
149
|
-
if (
|
|
150
|
-
return otherTexts[idx] || "";
|
|
151
|
-
}
|
|
177
|
+
if (idx === qIdx) return answer;
|
|
152
178
|
return selectedAnswers[idx] || "";
|
|
153
179
|
});
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
onEdit({ ...actionRequest.args, answers: finalAnswers });
|
|
157
|
-
}
|
|
158
|
-
onApprove();
|
|
180
|
+
if (onEdit) onEdit({ ...actionRequest.args, answers: finalAnswers });
|
|
181
|
+
else onApprove();
|
|
159
182
|
} else {
|
|
183
|
+
setSelectedAnswers((prev) => ({ ...prev, [qIdx]: answer }));
|
|
160
184
|
setCurrentQuestionIndex((prev) => prev + 1);
|
|
161
185
|
}
|
|
162
186
|
};
|
|
@@ -297,17 +321,19 @@ export const ApprovalCard = React.memo<ApprovalCardProps>(
|
|
|
297
321
|
</div>
|
|
298
322
|
|
|
299
323
|
<ConfirmationActions>
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
onReject
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
324
|
+
{!hideSkipButton && (
|
|
325
|
+
<ConfirmationAction
|
|
326
|
+
variant="outline"
|
|
327
|
+
type="button"
|
|
328
|
+
onClick={() => {
|
|
329
|
+
if (onReject) {
|
|
330
|
+
onReject("Skipped question");
|
|
331
|
+
}
|
|
332
|
+
}}
|
|
333
|
+
>
|
|
334
|
+
{skipButtonText}
|
|
335
|
+
</ConfirmationAction>
|
|
336
|
+
)}
|
|
311
337
|
|
|
312
338
|
<ConfirmationAction
|
|
313
339
|
type="button"
|
|
@@ -318,7 +344,7 @@ export const ApprovalCard = React.memo<ApprovalCardProps>(
|
|
|
318
344
|
{currentQuestionIndex === questions.length - 1 ? (
|
|
319
345
|
<>
|
|
320
346
|
<Check className="h-4 w-4 mr-2" />
|
|
321
|
-
|
|
347
|
+
{submitButtonText}
|
|
322
348
|
</>
|
|
323
349
|
) : (
|
|
324
350
|
<>
|
|
@@ -423,24 +449,26 @@ export const ApprovalCard = React.memo<ApprovalCardProps>(
|
|
|
423
449
|
)}
|
|
424
450
|
|
|
425
451
|
<div className="mt-3 space-y-3">
|
|
426
|
-
{Object.entries(actionRequest.args)
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
452
|
+
{Object.entries(actionRequest.args)
|
|
453
|
+
.filter(([_, value]) => typeof value === "string" || typeof value === "number" || typeof value === "boolean")
|
|
454
|
+
.map(([key, value]) => {
|
|
455
|
+
const strValue = formatValue(value);
|
|
456
|
+
const isMultiline = strValue.includes("\n");
|
|
457
|
+
return (
|
|
458
|
+
<div key={key} className="space-y-1">
|
|
459
|
+
<div className="text-[10px] font-semibold text-muted-foreground uppercase tracking-wider">
|
|
460
|
+
{key}
|
|
461
|
+
</div>
|
|
462
|
+
{isMultiline ? (
|
|
463
|
+
<pre className="text-xs text-card-foreground whitespace-pre-wrap break-all font-mono bg-muted/50 rounded-md px-2.5 py-1.5 border border-border">
|
|
464
|
+
{strValue}
|
|
465
|
+
</pre>
|
|
466
|
+
) : (
|
|
467
|
+
<div className="text-sm text-card-foreground break-all">{strValue}</div>
|
|
468
|
+
)}
|
|
433
469
|
</div>
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
{strValue}
|
|
437
|
-
</pre>
|
|
438
|
-
) : (
|
|
439
|
-
<div className="text-sm text-card-foreground break-all">{strValue}</div>
|
|
440
|
-
)}
|
|
441
|
-
</div>
|
|
442
|
-
);
|
|
443
|
-
})}
|
|
470
|
+
);
|
|
471
|
+
})}
|
|
444
472
|
</div>
|
|
445
473
|
</div>
|
|
446
474
|
|
|
@@ -489,7 +517,7 @@ export const ApprovalCard = React.memo<ApprovalCardProps>(
|
|
|
489
517
|
</>
|
|
490
518
|
) : (
|
|
491
519
|
<>
|
|
492
|
-
{canReject && (
|
|
520
|
+
{canReject && !hideSkipButton && (
|
|
493
521
|
<ConfirmationAction
|
|
494
522
|
type="button"
|
|
495
523
|
variant="outline"
|
|
@@ -497,7 +525,7 @@ export const ApprovalCard = React.memo<ApprovalCardProps>(
|
|
|
497
525
|
disabled={isProcessing}
|
|
498
526
|
>
|
|
499
527
|
<X className="h-4 w-4 mr-2" />
|
|
500
|
-
|
|
528
|
+
{skipButtonText}
|
|
501
529
|
</ConfirmationAction>
|
|
502
530
|
)}
|
|
503
531
|
{canEdit && (
|
|
@@ -518,7 +546,7 @@ export const ApprovalCard = React.memo<ApprovalCardProps>(
|
|
|
518
546
|
disabled={isProcessing}
|
|
519
547
|
>
|
|
520
548
|
<Check className="h-4 w-4 mr-2" />
|
|
521
|
-
|
|
549
|
+
{submitButtonText}
|
|
522
550
|
</ConfirmationAction>
|
|
523
551
|
</>
|
|
524
552
|
)}
|
|
@@ -3,7 +3,9 @@ import {
|
|
|
3
3
|
Message,
|
|
4
4
|
MessageContent,
|
|
5
5
|
MessageAvatar,
|
|
6
|
+
MessageTypingIndicator,
|
|
6
7
|
} from "@/components/ai-elements/message"
|
|
8
|
+
import { Response } from "@/components/ai-elements/response"
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* OrchestratorMessage Block
|
|
@@ -17,6 +19,7 @@ export interface OrchestratorMessageData {
|
|
|
17
19
|
content: string
|
|
18
20
|
avatarSrc?: string
|
|
19
21
|
avatarName?: string
|
|
22
|
+
isLoading?: boolean
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
export interface OrchestratorMessageProps {
|
|
@@ -53,11 +56,15 @@ export const OrchestratorMessage = React.memo<OrchestratorMessageProps>(
|
|
|
53
56
|
/>
|
|
54
57
|
)}
|
|
55
58
|
|
|
56
|
-
<div className="flex-1">
|
|
59
|
+
<div className="flex-1 min-w-0">
|
|
57
60
|
{/* Orchestrator's message content */}
|
|
58
|
-
{
|
|
61
|
+
{message.isLoading ? (
|
|
62
|
+
<MessageContent variant="contained" className="w-fit pr-6">
|
|
63
|
+
<MessageTypingIndicator />
|
|
64
|
+
</MessageContent>
|
|
65
|
+
) : hasContent && (
|
|
59
66
|
<MessageContent variant="contained">
|
|
60
|
-
{message.content}
|
|
67
|
+
<Response mode={message.isLoading ? "streaming" : "static"} isAnimating={!!message.isLoading}>{message.content}</Response>
|
|
61
68
|
</MessageContent>
|
|
62
69
|
)}
|
|
63
70
|
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "@/components/ai-elements/plan"
|
|
10
10
|
import { ToolCallDisplay, type ToolCall } from "@/components/composites/ToolCallDisplay"
|
|
11
11
|
import { cn } from "@/lib/utils"
|
|
12
|
+
import { Response } from "@/components/ai-elements/response"
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* SpecialistMessage Block
|
|
@@ -64,10 +65,17 @@ export const SpecialistMessage = React.memo<SpecialistMessageProps>(
|
|
|
64
65
|
[message.toolCalls]
|
|
65
66
|
)
|
|
66
67
|
|
|
68
|
+
const description = React.useMemo(() => {
|
|
69
|
+
if (message.description) return message.description;
|
|
70
|
+
if (message.status === "active") return "Thinking...";
|
|
71
|
+
return undefined;
|
|
72
|
+
}, [message.description, message.status]);
|
|
73
|
+
|
|
67
74
|
return (
|
|
68
75
|
<Plan
|
|
69
76
|
defaultOpen={collapsible ? defaultOpen : true}
|
|
70
77
|
className={cn(isNested && "ml-8")}
|
|
78
|
+
isStreaming={message.status === "active"}
|
|
71
79
|
>
|
|
72
80
|
<PlanHeader>
|
|
73
81
|
<div>
|
|
@@ -75,8 +83,8 @@ export const SpecialistMessage = React.memo<SpecialistMessageProps>(
|
|
|
75
83
|
{message.icon && message.icon}
|
|
76
84
|
<PlanTitle>{message.name}</PlanTitle>
|
|
77
85
|
</div>
|
|
78
|
-
{
|
|
79
|
-
<PlanDescription>{
|
|
86
|
+
{description && (
|
|
87
|
+
<PlanDescription>{description}</PlanDescription>
|
|
80
88
|
)}
|
|
81
89
|
</div>
|
|
82
90
|
{collapsible && <PlanTrigger />}
|
|
@@ -85,9 +93,13 @@ export const SpecialistMessage = React.memo<SpecialistMessageProps>(
|
|
|
85
93
|
<PlanContent>
|
|
86
94
|
{/* Message content */}
|
|
87
95
|
{hasContent && (
|
|
88
|
-
<
|
|
96
|
+
<Response
|
|
97
|
+
className="size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0"
|
|
98
|
+
mode={message.status === "active" ? "streaming" : "static"}
|
|
99
|
+
isAnimating={message.status === "active"}
|
|
100
|
+
>
|
|
89
101
|
{message.content}
|
|
90
|
-
</
|
|
102
|
+
</Response>
|
|
91
103
|
)}
|
|
92
104
|
|
|
93
105
|
{/* Tool calls */}
|
|
@@ -38,6 +38,7 @@ import { DocumentTabBar } from '@/components/composites/DocumentTabBar'
|
|
|
38
38
|
import { cn } from '@/lib/utils'
|
|
39
39
|
import type { JSONContent } from '@tiptap/core'
|
|
40
40
|
import type { Annotation, User } from '@/types/ai-editor/annotations'
|
|
41
|
+
import { Streamdown } from 'streamdown'
|
|
41
42
|
|
|
42
43
|
interface DocumentWithAnnotations {
|
|
43
44
|
file: { id: string; name: string; isDirty?: boolean; format?: 'json' | 'markdown' | string; lastModified?: number }
|
|
@@ -145,6 +146,7 @@ function isMultiTabMode(props: AIDocEditorProps): props is AIDocEditorMultiTabPr
|
|
|
145
146
|
* - Support for comments, suggestions, and block additions
|
|
146
147
|
* - Controlled component pattern (all state managed by parent)
|
|
147
148
|
* - Backward-compatible with single-document consumers
|
|
149
|
+
* - Markdown content rendered via Streamdown for proper formatting
|
|
148
150
|
*
|
|
149
151
|
* Note: The refinement panel (right sidebar with Accept All/Reject All) is a
|
|
150
152
|
* separate component in the parent application, not part of this feature.
|
|
@@ -156,7 +158,7 @@ export const AIDocEditor = React.memo<AIDocEditorProps>(
|
|
|
156
158
|
mode,
|
|
157
159
|
onAnnotationAdd,
|
|
158
160
|
onAnnotationUpdate,
|
|
159
|
-
onAnnotationDelete: _onAnnotationDelete,
|
|
161
|
+
onAnnotationDelete: _onAnnotationDelete,
|
|
160
162
|
className,
|
|
161
163
|
} = props
|
|
162
164
|
|
|
@@ -199,6 +201,16 @@ export const AIDocEditor = React.memo<AIDocEditorProps>(
|
|
|
199
201
|
* Single-document mode
|
|
200
202
|
*/
|
|
201
203
|
if (!isMultiTab) {
|
|
204
|
+
const isMarkdown = props.format === 'markdown'
|
|
205
|
+
if (isMarkdown) {
|
|
206
|
+
return (
|
|
207
|
+
<div className={cn('ai-doc-editor p-6', className)}>
|
|
208
|
+
<Streamdown mode="streaming">
|
|
209
|
+
{props.content as string}
|
|
210
|
+
</Streamdown>
|
|
211
|
+
</div>
|
|
212
|
+
)
|
|
213
|
+
}
|
|
202
214
|
return (
|
|
203
215
|
<DocumentEditorWithComments
|
|
204
216
|
content={props.content}
|
|
@@ -234,6 +246,8 @@ export const AIDocEditor = React.memo<AIDocEditorProps>(
|
|
|
234
246
|
)
|
|
235
247
|
}
|
|
236
248
|
|
|
249
|
+
const isMarkdown = currentDocument.file.format === 'markdown'
|
|
250
|
+
|
|
237
251
|
return (
|
|
238
252
|
<div className={cn('ai-doc-editor flex flex-col h-full', className)}>
|
|
239
253
|
<DocumentTabBar
|
|
@@ -243,17 +257,29 @@ export const AIDocEditor = React.memo<AIDocEditorProps>(
|
|
|
243
257
|
onTabClose={props.onTabClose}
|
|
244
258
|
/>
|
|
245
259
|
<div className="flex-1 overflow-auto">
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
260
|
+
{isMarkdown ? (
|
|
261
|
+
<div className="p-6">
|
|
262
|
+
<Streamdown
|
|
263
|
+
mode="streaming"
|
|
264
|
+
isAnimating
|
|
265
|
+
className="[&>*:first-child]:mt-0 [&>*:last-child]:mb-0"
|
|
266
|
+
>
|
|
267
|
+
{currentDocument.content as string}
|
|
268
|
+
</Streamdown>
|
|
269
|
+
</div>
|
|
270
|
+
) : (
|
|
271
|
+
<DocumentEditorWithComments
|
|
272
|
+
content={currentDocument.content}
|
|
273
|
+
format={currentDocument.file.format as 'json' | 'markdown' | undefined}
|
|
274
|
+
annotations={currentDocument.annotations}
|
|
275
|
+
currentUserId={currentUser.id}
|
|
276
|
+
currentUserName={currentUser.name}
|
|
277
|
+
readOnly={mode === 'readonly'}
|
|
278
|
+
onAnnotationAdd={handleAnnotationAdd}
|
|
279
|
+
onAnnotationUpdate={handleAnnotationUpdate}
|
|
280
|
+
className="p-6"
|
|
281
|
+
/>
|
|
282
|
+
)}
|
|
257
283
|
</div>
|
|
258
284
|
</div>
|
|
259
285
|
)
|
|
@@ -105,6 +105,62 @@ export const reviewStateMessages: RefinementMessage[] = [
|
|
|
105
105
|
},
|
|
106
106
|
],
|
|
107
107
|
},
|
|
108
|
+
{
|
|
109
|
+
id: '3',
|
|
110
|
+
type: 'human',
|
|
111
|
+
role: 'user',
|
|
112
|
+
content: 'Can we also make sure the colors meet the contrast requirements for dark mode?',
|
|
113
|
+
avatarSrc:
|
|
114
|
+
'https://images.unsplash.com/photo-1494790108755-2616b612b786?w=32&h=32&fit=crop&crop=face',
|
|
115
|
+
avatarName: 'User',
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: '4',
|
|
119
|
+
type: 'ai',
|
|
120
|
+
role: 'orchestrator',
|
|
121
|
+
content: 'Absolutely. I will have the accessibility specialist double-check the color contrast ratios in both light and dark mode.',
|
|
122
|
+
avatarSrc:
|
|
123
|
+
'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=32&h=32&fit=crop&crop=face',
|
|
124
|
+
avatarName: 'Coordinator',
|
|
125
|
+
subAgents: [
|
|
126
|
+
{
|
|
127
|
+
id: 'agent_3',
|
|
128
|
+
name: 'accessibility-specialist',
|
|
129
|
+
subAgentName: 'a11y-agent',
|
|
130
|
+
input: 'Verify color contrast ratios for the new Button styles in both light and dark mode themes according to WCAG 2.1 AA.',
|
|
131
|
+
output: 'Contrast ratios checked: Primary button passes at 4.5:1 on light mode, but fails on dark mode (3.2:1). Adjusting the primary-dark token to #66b2ff to meet the 4.5:1 requirement.',
|
|
132
|
+
status: 'completed',
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: '5',
|
|
138
|
+
type: 'human',
|
|
139
|
+
role: 'user',
|
|
140
|
+
content: 'Perfect! How about the hover state? Does it also pass?',
|
|
141
|
+
avatarSrc:
|
|
142
|
+
'https://images.unsplash.com/photo-1494790108755-2616b612b786?w=32&h=32&fit=crop&crop=face',
|
|
143
|
+
avatarName: 'User',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: '6',
|
|
147
|
+
type: 'ai',
|
|
148
|
+
role: 'orchestrator',
|
|
149
|
+
content: 'Good catch. Let me check the hover and active states for both themes.',
|
|
150
|
+
avatarSrc:
|
|
151
|
+
'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=32&h=32&fit=crop&crop=face',
|
|
152
|
+
avatarName: 'Coordinator',
|
|
153
|
+
subAgents: [
|
|
154
|
+
{
|
|
155
|
+
id: 'agent_4',
|
|
156
|
+
name: 'accessibility-specialist',
|
|
157
|
+
subAgentName: 'a11y-agent',
|
|
158
|
+
input: 'Verify color contrast for hover and active states.',
|
|
159
|
+
output: 'Hover states pass contrast checks. The active state requires a slight tweak for the outline variant. Applied fixes.',
|
|
160
|
+
status: 'completed',
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
},
|
|
108
164
|
]
|
|
109
165
|
|
|
110
166
|
/**
|
|
@@ -114,7 +170,7 @@ export const reviewStateMessages: RefinementMessage[] = [
|
|
|
114
170
|
* Sample single-question approval request (HITL interactive question)
|
|
115
171
|
*/
|
|
116
172
|
export const approvalQuestionRequest: ActionRequest = {
|
|
117
|
-
name: "
|
|
173
|
+
name: "ask_user",
|
|
118
174
|
description: "The agent needs your input to determine the workflow order",
|
|
119
175
|
args: {
|
|
120
176
|
question: "Where in the workflow timeline should the SEO review step run?",
|
|
@@ -130,7 +186,7 @@ export const approvalQuestionRequest: ActionRequest = {
|
|
|
130
186
|
* Sample multi-question approval request (HITL poll with multiple questions)
|
|
131
187
|
*/
|
|
132
188
|
export const approvalMultiQuestionRequest: ActionRequest = {
|
|
133
|
-
name: "
|
|
189
|
+
name: "ask_user",
|
|
134
190
|
description: "The agent needs your preferences to proceed",
|
|
135
191
|
args: {
|
|
136
192
|
questions: [
|