ai-design-system 0.1.46 → 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.
@@ -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"
@@ -92,9 +92,14 @@ export const AIConversation = React.memo<AIConversationProps>(
92
92
  [emptyState?.description]
93
93
  )
94
94
 
95
+ const toMessageString = (content: unknown): string =>
96
+ typeof content === 'string' ? content : ''
97
+
95
98
  const renderedMessages = React.useMemo(
96
99
  () =>
97
100
  messages.map((message) => {
101
+ const contentStr = toMessageString(message.content)
102
+
98
103
  // Render based on role field
99
104
  if (message.role === "user") {
100
105
  return (
@@ -102,7 +107,7 @@ export const AIConversation = React.memo<AIConversationProps>(
102
107
  key={message.id}
103
108
  message={{
104
109
  id: message.id,
105
- content: message.content,
110
+ content: contentStr,
106
111
  avatarSrc: message.avatarSrc,
107
112
  avatarName: message.avatarName,
108
113
  }}
@@ -122,7 +127,7 @@ export const AIConversation = React.memo<AIConversationProps>(
122
127
  (tc) => tc.name !== "task" && tc.name !== "ask_user"
123
128
  ) || []
124
129
 
125
- const hasContent = message.content && message.content.trim() !== ""
130
+ const hasContent = contentStr.trim() !== ""
126
131
  if (!hasContent && directToolCalls.length === 0 && subAgents.length === 0 && !message.isLoading) {
127
132
  return null;
128
133
  }
@@ -132,7 +137,7 @@ export const AIConversation = React.memo<AIConversationProps>(
132
137
  key={message.id}
133
138
  message={{
134
139
  id: message.id,
135
- content: message.content,
140
+ content: contentStr,
136
141
  avatarSrc: message.avatarSrc,
137
142
  avatarName: message.avatarName,
138
143
  isLoading: message.isLoading,
@@ -170,7 +175,7 @@ export const AIConversation = React.memo<AIConversationProps>(
170
175
  message={{
171
176
  id: message.id,
172
177
  name: message.avatarName || "Agent",
173
- content: message.content,
178
+ content: contentStr,
174
179
  toolCalls: message.toolCalls?.filter((tc) => tc.name !== "task"),
175
180
  status: "completed",
176
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
  );
@@ -28,11 +28,11 @@ export const SectionLayout = React.memo<SectionLayoutProps>(
28
28
  ...section,
29
29
  resizable,
30
30
  content: (
31
- <div className="h-full min-h-0 flex flex-col overflow-auto">
31
+ <div className="h-full min-h-0 flex flex-col overflow-hidden">
32
32
  {section.header && (
33
33
  <AppHeader {...section.header} />
34
34
  )}
35
- <div className="min-h-0 flex-1 overflow-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
  )}
@@ -5,6 +5,7 @@ import {
5
5
  MessageAvatar,
6
6
  MessageTypingIndicator,
7
7
  } from "@/components/ai-elements/message"
8
+ import { Response } from "@/components/ai-elements/response"
8
9
 
9
10
  /**
10
11
  * OrchestratorMessage Block
@@ -63,7 +64,7 @@ export const OrchestratorMessage = React.memo<OrchestratorMessageProps>(
63
64
  </MessageContent>
64
65
  ) : hasContent && (
65
66
  <MessageContent variant="contained">
66
- {message.content}
67
+ <Response mode={message.isLoading ? "streaming" : "static"} isAnimating={!!message.isLoading}>{message.content}</Response>
67
68
  </MessageContent>
68
69
  )}
69
70
 
@@ -9,7 +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 { Streamdown } from "streamdown"
12
+ import { Response } from "@/components/ai-elements/response"
13
13
 
14
14
  /**
15
15
  * SpecialistMessage Block
@@ -65,10 +65,17 @@ export const SpecialistMessage = React.memo<SpecialistMessageProps>(
65
65
  [message.toolCalls]
66
66
  )
67
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
+
68
74
  return (
69
75
  <Plan
70
76
  defaultOpen={collapsible ? defaultOpen : true}
71
77
  className={cn(isNested && "ml-8")}
78
+ isStreaming={message.status === "active"}
72
79
  >
73
80
  <PlanHeader>
74
81
  <div>
@@ -76,8 +83,8 @@ export const SpecialistMessage = React.memo<SpecialistMessageProps>(
76
83
  {message.icon && message.icon}
77
84
  <PlanTitle>{message.name}</PlanTitle>
78
85
  </div>
79
- {message.description && (
80
- <PlanDescription>{message.description}</PlanDescription>
86
+ {description && (
87
+ <PlanDescription>{description}</PlanDescription>
81
88
  )}
82
89
  </div>
83
90
  {collapsible && <PlanTrigger />}
@@ -86,12 +93,13 @@ export const SpecialistMessage = React.memo<SpecialistMessageProps>(
86
93
  <PlanContent>
87
94
  {/* Message content */}
88
95
  {hasContent && (
89
- <Streamdown
96
+ <Response
90
97
  className="size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0"
91
- mode="static"
98
+ mode={message.status === "active" ? "streaming" : "static"}
99
+ isAnimating={message.status === "active"}
92
100
  >
93
101
  {message.content}
94
- </Streamdown>
102
+ </Response>
95
103
  )}
96
104
 
97
105
  {/* Tool calls */}
@@ -89,6 +89,10 @@ export interface RefinementPanelProps {
89
89
  * Processing state for HITL approval
90
90
  */
91
91
  isApprovalProcessing?: boolean;
92
+ /**
93
+ * Loading state — shows spinner on send button while streaming
94
+ */
95
+ loading?: boolean;
92
96
  /**
93
97
  * Placeholder text for input
94
98
  */
@@ -115,6 +119,7 @@ export const RefinementPanel = React.memo<RefinementPanelProps>(
115
119
  onApprovalReject,
116
120
  onApprovalEdit,
117
121
  isApprovalProcessing = false,
122
+ loading = false,
118
123
  placeholder = "Ask a question or describe a task...",
119
124
  className,
120
125
  }) => {
@@ -146,26 +151,20 @@ export const RefinementPanel = React.memo<RefinementPanelProps>(
146
151
 
147
152
  // Handle approve for HITL approval request
148
153
  const handleApprovalApprove = React.useCallback(() => {
149
- setApprovalCardState("approval-responded");
150
- setApprovalCardApproval({ approved: true });
151
154
  onApprovalApprove?.();
152
155
  }, [onApprovalApprove]);
153
156
 
154
- // Handle reject for HITL approval request
155
- const handleApprovalReject = React.useCallback((reason: string) => {
156
- setApprovalCardState("approval-responded");
157
- setApprovalCardApproval({ approved: false });
158
- onApprovalReject?.(reason);
159
- }, [onApprovalReject]);
160
-
161
157
  // Extract pending ask_user tool call from messages
162
158
  const pendingAskUser = React.useMemo(() => {
159
+ console.log("[RefinementPanel] Messages length:", messages.length);
163
160
  // Look from the end
164
161
  for (let i = messages.length - 1; i >= 0; i--) {
165
162
  const msg = messages[i];
166
- if (msg.toolCalls) {
163
+ if (msg.toolCalls && msg.toolCalls.length > 0) {
164
+ console.log(`[RefinementPanel] Message ${i} (${msg.id}) has ${msg.toolCalls.length} tool calls`, msg.toolCalls.map(tc => `${tc.name} [${tc.status}]`));
167
165
  const askUserTc = msg.toolCalls.find(tc => tc.name === "ask_user" && tc.status === "pending");
168
166
  if (askUserTc) {
167
+ console.log("[RefinementPanel] Found pending ask_user:", askUserTc);
169
168
  return askUserTc;
170
169
  }
171
170
  }
@@ -176,6 +175,7 @@ export const RefinementPanel = React.memo<RefinementPanelProps>(
176
175
  const activeApprovalRequest = React.useMemo(() => {
177
176
  if (approvalRequest) return approvalRequest;
178
177
  if (pendingAskUser) {
178
+ console.log("[RefinementPanel] ask_user args:", JSON.stringify(pendingAskUser.args));
179
179
  return {
180
180
  name: pendingAskUser.name,
181
181
  args: pendingAskUser.args,
@@ -184,21 +184,27 @@ export const RefinementPanel = React.memo<RefinementPanelProps>(
184
184
  return undefined;
185
185
  }, [approvalRequest, pendingAskUser]);
186
186
 
187
+ // Handle reject for HITL approval request
188
+ const handleApprovalReject = React.useCallback((reason: string) => {
189
+ if (activeApprovalRequest?.name === "ask_user") {
190
+ const dummyEvent = { preventDefault: () => {} } as React.FormEvent<HTMLFormElement>;
191
+ onSubmit({ text: "Skipped", files: [] }, dummyEvent);
192
+ } else {
193
+ onApprovalReject?.(reason);
194
+ }
195
+ }, [onApprovalReject, activeApprovalRequest, onSubmit]);
196
+
187
197
  // Handle edit for HITL approval request
188
198
  const handleApprovalEdit = React.useCallback((editedArgs: Record<string, unknown>) => {
189
- setApprovalCardState("approval-responded");
190
- setApprovalCardApproval({ approved: true });
191
- onApprovalEdit?.(editedArgs);
192
-
193
- // If there's a pendingAskUser and answers are provided, submit them!
194
- if (pendingAskUser && editedArgs.answers) {
199
+ if (activeApprovalRequest?.name === "ask_user" && editedArgs.answers) {
195
200
  const answers = Array.isArray(editedArgs.answers) ? editedArgs.answers : [editedArgs.answers];
196
201
  const text = answers.join(', ');
197
- // Submit the text response
198
202
  const dummyEvent = { preventDefault: () => {} } as React.FormEvent<HTMLFormElement>;
199
203
  onSubmit({ text, files: [] }, dummyEvent);
204
+ } else {
205
+ onApprovalEdit?.(editedArgs);
200
206
  }
201
- }, [onApprovalEdit, pendingAskUser, onSubmit]);
207
+ }, [onApprovalEdit, activeApprovalRequest, onSubmit]);
202
208
 
203
209
  // Reset file change state when fileChanges are cleared
204
210
  React.useEffect(() => {
@@ -253,18 +259,17 @@ export const RefinementPanel = React.memo<RefinementPanelProps>(
253
259
 
254
260
  return (
255
261
  <div className={`relative flex h-full flex-col ${className || ""}`}>
256
- <div className="flex-1 flex flex-col pb-4 min-h-0">
257
- <AIConversation
258
- messages={messages}
259
- showAvatars={true}
260
- className="relative"
261
- />
262
- </div>
262
+ <AIConversation
263
+ messages={messages}
264
+ showAvatars={true}
265
+ className="flex-1 min-h-0"
266
+ />
263
267
  <div className="sticky bottom-0 z-10 bg-background border-t">
264
268
  <PromptInput
265
269
  dialog={dialog}
266
270
  placeholder={placeholder}
267
271
  onSubmit={onSubmit}
272
+ loading={loading}
268
273
  />
269
274
  </div>
270
275
  </div>
@@ -43,14 +43,11 @@ export interface UseRefinementPanelReturn {
43
43
  /** Review configuration for HITL approval */
44
44
  reviewConfig?: ReviewConfig;
45
45
 
46
- /** Handle approval of HITL approval request */
47
- handleApprovalApprove?: () => Promise<void> | void;
46
+ /** Handle approval of HITL approval request (ask_user or review_content approve) */
47
+ handleAskUserApprove?: () => Promise<void> | void;
48
48
 
49
49
  /** Handle rejection of HITL approval request */
50
- handleApprovalReject?: (reason: string) => Promise<void> | void;
51
-
52
- /** Handle edit of HITL approval request */
53
- handleApprovalEdit?: (editedArgs: Record<string, unknown>) => Promise<void> | void;
50
+ handleAskUserReject?: (reason: string) => Promise<void> | void;
54
51
 
55
52
  /** Processing state for HITL approval */
56
53
  isApprovalProcessing?: boolean;
@@ -68,11 +65,19 @@ export interface UseRefinementPanelReturn {
68
65
  * @example
69
66
  * ```tsx
70
67
  * // Real application implementation
71
- * export function useRefinementPanel(): UseRefinementPanelReturn {
68
+ * export function useRefinementPanel(options?: { threadId?: string }): UseRefinementPanelReturn {
69
+ * const { threadId } = options || {};
72
70
  * const [messages, setMessages] = useState<RefinementMessage[]>([]);
73
71
  * const [fileChanges, setFileChanges] = useState<FileChangeData[]>([]);
74
72
  * const [loading, setLoading] = useState(false);
75
73
  *
74
+ * useEffect(() => {
75
+ * if (!threadId) return;
76
+ * fetch(`/api/v1/chat/threads/${threadId}/history`)
77
+ * .then((res) => res.json())
78
+ * .then((data) => setMessages(data.messages));
79
+ * }, [threadId]);
80
+ *
76
81
  * const handleSubmit = async (prompt: string) => {
77
82
  * setLoading(true);
78
83
  * try {
@@ -90,4 +95,4 @@ export interface UseRefinementPanelReturn {
90
95
  * }
91
96
  * ```
92
97
  */
93
- export function useRefinementPanel(): UseRefinementPanelReturn;
98
+ export function useRefinementPanel(options?: { threadId?: string }): UseRefinementPanelReturn;
@@ -22,6 +22,7 @@ export interface UseRefinementPanelReturn {
22
22
  }
23
23
 
24
24
  export interface UseRefinementPanelOptions {
25
+ threadId?: string;
25
26
  initialMessages?: RefinementMessage[];
26
27
  reviewMessages?: RefinementMessage[];
27
28
  reviewFileChanges?: FileChangeData[];
@@ -34,6 +35,7 @@ export function useRefinementPanelMock(
34
35
  options: UseRefinementPanelOptions = {}
35
36
  ): UseRefinementPanelReturn {
36
37
  const {
38
+ threadId,
37
39
  initialMessages = [],
38
40
  reviewMessages = [],
39
41
  reviewFileChanges = [],
@@ -42,6 +44,10 @@ export function useRefinementPanelMock(
42
44
  apiDelay = 800,
43
45
  } = options;
44
46
 
47
+ if (threadId) {
48
+ console.log(`[useRefinementPanelMock] threadId=${threadId} — history would be fetched from API in production`);
49
+ }
50
+
45
51
  const [messages, setMessages] = useState<RefinementMessage[]>(initialMessages);
46
52
  const [fileChanges, setFileChanges] = useState<FileChangeData[]>([]);
47
53
  const [loading, setLoading] = useState(false);