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.
@@ -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-y-auto", className)}
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 bg-muted/50 -mx-4 px-4",
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: message.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: message.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: `**Task:** ${typeof subAgent.input === 'string' ? subAgent.input : JSON.stringify(subAgent.input)}${subAgent.output ? `\n\n**Output:** ${typeof subAgent.output === 'string' ? subAgent.output : JSON.stringify(subAgent.output)}` : ''}`,
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: message.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
- <div className="p-4">
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-auto [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden">
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 (selectedOptionIndices[idx] === q.options?.length) {
150
- return otherTexts[idx] || "";
151
- }
177
+ if (idx === qIdx) return answer;
152
178
  return selectedAnswers[idx] || "";
153
179
  });
154
-
155
- if (onEdit) {
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
- <ConfirmationAction
301
- variant="outline"
302
- type="button"
303
- onClick={() => {
304
- if (onReject) {
305
- onReject("Skipped question");
306
- }
307
- }}
308
- >
309
- Skip
310
- </ConfirmationAction>
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
- Continue
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).map(([key, value]) => {
427
- const strValue = formatValue(value);
428
- const isMultiline = strValue.includes("\n");
429
- return (
430
- <div key={key} className="space-y-1">
431
- <div className="text-[10px] font-semibold text-muted-foreground uppercase tracking-wider">
432
- {key}
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
- {isMultiline ? (
435
- <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">
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
- Reject
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
- Approve
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
- {hasContent && (
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
- {message.description && (
79
- <PlanDescription>{message.description}</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
- <div className="prose prose-sm dark:prose-invert max-w-none">
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
- </div>
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, // Callback for consumers; passed through on demand
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
- <DocumentEditorWithComments
247
- content={currentDocument.content}
248
- format={currentDocument.file.format as 'json' | 'markdown' | undefined}
249
- annotations={currentDocument.annotations}
250
- currentUserId={currentUser.id}
251
- currentUserName={currentUser.name}
252
- readOnly={mode === 'readonly'}
253
- onAnnotationAdd={handleAnnotationAdd}
254
- onAnnotationUpdate={handleAnnotationUpdate}
255
- className="p-6"
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: "ask_question",
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: "ask_question",
189
+ name: "ask_user",
134
190
  description: "The agent needs your preferences to proceed",
135
191
  args: {
136
192
  questions: [