@gendive/chatllm 0.3.1 → 0.5.0

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.
@@ -57,6 +57,14 @@ interface AlternativeResponse {
57
57
  model: string;
58
58
  content: string;
59
59
  timestamp: number;
60
+ sources?: SourceItem[];
61
+ }
62
+ interface SourceItem {
63
+ id: string;
64
+ title: string;
65
+ url: string;
66
+ snippet?: string;
67
+ favicon?: string;
60
68
  }
61
69
  interface ChatMessage {
62
70
  id: string;
@@ -65,6 +73,7 @@ interface ChatMessage {
65
73
  model?: string;
66
74
  timestamp: number;
67
75
  alternatives?: AlternativeResponse[];
76
+ sources?: SourceItem[];
68
77
  }
69
78
  interface ChatSession {
70
79
  id: string;
@@ -139,7 +148,7 @@ interface ChatUIProps {
139
148
  /** CSS 클래스 */
140
149
  className?: string;
141
150
  /** 메시지 전송 핸들러 (커스텀 API 사용 시) */
142
- onSendMessage?: (params: SendMessageParams) => Promise<ReadableStream<Uint8Array> | string>;
151
+ onSendMessage?: (params: SendMessageParams) => Promise<SendMessageResponse>;
143
152
  /** 세션 변경 핸들러 */
144
153
  onSessionChange?: (session: ChatSession | null) => void;
145
154
  /** 에러 핸들러 */
@@ -155,6 +164,11 @@ interface SendMessageParams {
155
164
  apiKey?: string;
156
165
  systemPrompt?: string;
157
166
  }
167
+ /** onSendMessage 반환 타입 */
168
+ type SendMessageResponse = ReadableStream<Uint8Array> | string | {
169
+ content: string;
170
+ sources?: SourceItem[];
171
+ };
158
172
  interface ChatUIComponents {
159
173
  /** 사이드바 컴포넌트 */
160
174
  Sidebar?: React.ComponentType<SidebarProps>;
@@ -191,6 +205,8 @@ interface MessageListProps {
191
205
  onEdit: (message: ChatMessage) => void;
192
206
  onRegenerate: (id: string) => void;
193
207
  onQuote: (text: string) => void;
208
+ onAskOtherModel?: (messageId: string, targetModel: string) => void;
209
+ models?: ModelConfig[];
194
210
  copiedId: string | null;
195
211
  editingId: string | null;
196
212
  }
@@ -203,6 +219,8 @@ interface MessageBubbleProps {
203
219
  onEdit: () => void;
204
220
  onRegenerate?: () => void;
205
221
  onQuote?: (text: string) => void;
222
+ onAskOtherModel?: (targetModel: string) => void;
223
+ models?: ModelConfig[];
206
224
  alternatives?: AlternativeResponse[];
207
225
  activeAlternativeIndex?: number;
208
226
  onAlternativeChange?: (index: number) => void;
@@ -281,7 +299,9 @@ interface UseChatUIReturn {
281
299
  cancelEdit: () => void;
282
300
  saveEdit: (content: string) => Promise<void>;
283
301
  regenerate: (messageId: string) => Promise<void>;
302
+ askOtherModel: (messageId: string, targetModel: string) => Promise<void>;
284
303
  updatePersonalization: (config: Partial<PersonalizationConfig>) => void;
304
+ models: ModelConfig[];
285
305
  }
286
306
 
287
307
  /**
@@ -316,7 +336,7 @@ interface UseChatUIOptions {
316
336
  /** 압축 후 유지할 메시지 수 */
317
337
  keepRecentMessages?: number;
318
338
  /** 커스텀 메시지 전송 핸들러 */
319
- onSendMessage?: (params: SendMessageParams) => Promise<ReadableStream<Uint8Array> | string>;
339
+ onSendMessage?: (params: SendMessageParams) => Promise<SendMessageResponse>;
320
340
  /** 세션 변경 핸들러 */
321
341
  onSessionChange?: (session: ChatSession | null) => void;
322
342
  /** 에러 핸들러 */
@@ -487,4 +507,4 @@ interface SettingsModalProps {
487
507
  }
488
508
  declare const SettingsModal: React$1.FC<SettingsModalProps>;
489
509
 
490
- export { type ActionItem, type ActionMenuProps, type AlternativeResponse, ChatHeader, ChatInput, type ChatMessage, type ChatSession, ChatSidebar, ChatUI, type ChatUIComponents, type ChatUIProps, EmptyState, type EmptyStateProps, type HeaderProps, Icon, type IconName, type IconProps, IconSvg, type InputProps, LinkChip, type LinkChipProps, MarkdownRenderer, type MarkdownRendererProps, type MemoryItem, MemoryPanel, type MemoryPanelProps, MessageBubble, type MessageBubbleProps, MessageList, type MessageListProps, type ModelConfig, type ModelSelectorProps, type PersonalizationConfig, type PromptTemplate, type ProviderType, type ResponseStyle, type SendMessageParams, SettingsModal, type SettingsModalProps, type SettingsTab, type SidebarProps, type ThemeConfig, type ThemeMode, type UseChatUIOptions, type UseChatUIReturn, type UserProfile, useChatUI };
510
+ export { type ActionItem, type ActionMenuProps, type AlternativeResponse, ChatHeader, ChatInput, type ChatMessage, type ChatSession, ChatSidebar, ChatUI, type ChatUIComponents, type ChatUIProps, EmptyState, type EmptyStateProps, type HeaderProps, Icon, type IconName, type IconProps, IconSvg, type InputProps, LinkChip, type LinkChipProps, MarkdownRenderer, type MarkdownRendererProps, type MemoryItem, MemoryPanel, type MemoryPanelProps, MessageBubble, type MessageBubbleProps, MessageList, type MessageListProps, type ModelConfig, type ModelSelectorProps, type PersonalizationConfig, type PromptTemplate, type ProviderType, type ResponseStyle, type SendMessageParams, type SendMessageResponse, SettingsModal, type SettingsModalProps, type SettingsTab, type SidebarProps, type SourceItem, type ThemeConfig, type ThemeMode, type UseChatUIOptions, type UseChatUIReturn, type UserProfile, useChatUI };
@@ -57,6 +57,14 @@ interface AlternativeResponse {
57
57
  model: string;
58
58
  content: string;
59
59
  timestamp: number;
60
+ sources?: SourceItem[];
61
+ }
62
+ interface SourceItem {
63
+ id: string;
64
+ title: string;
65
+ url: string;
66
+ snippet?: string;
67
+ favicon?: string;
60
68
  }
61
69
  interface ChatMessage {
62
70
  id: string;
@@ -65,6 +73,7 @@ interface ChatMessage {
65
73
  model?: string;
66
74
  timestamp: number;
67
75
  alternatives?: AlternativeResponse[];
76
+ sources?: SourceItem[];
68
77
  }
69
78
  interface ChatSession {
70
79
  id: string;
@@ -139,7 +148,7 @@ interface ChatUIProps {
139
148
  /** CSS 클래스 */
140
149
  className?: string;
141
150
  /** 메시지 전송 핸들러 (커스텀 API 사용 시) */
142
- onSendMessage?: (params: SendMessageParams) => Promise<ReadableStream<Uint8Array> | string>;
151
+ onSendMessage?: (params: SendMessageParams) => Promise<SendMessageResponse>;
143
152
  /** 세션 변경 핸들러 */
144
153
  onSessionChange?: (session: ChatSession | null) => void;
145
154
  /** 에러 핸들러 */
@@ -155,6 +164,11 @@ interface SendMessageParams {
155
164
  apiKey?: string;
156
165
  systemPrompt?: string;
157
166
  }
167
+ /** onSendMessage 반환 타입 */
168
+ type SendMessageResponse = ReadableStream<Uint8Array> | string | {
169
+ content: string;
170
+ sources?: SourceItem[];
171
+ };
158
172
  interface ChatUIComponents {
159
173
  /** 사이드바 컴포넌트 */
160
174
  Sidebar?: React.ComponentType<SidebarProps>;
@@ -191,6 +205,8 @@ interface MessageListProps {
191
205
  onEdit: (message: ChatMessage) => void;
192
206
  onRegenerate: (id: string) => void;
193
207
  onQuote: (text: string) => void;
208
+ onAskOtherModel?: (messageId: string, targetModel: string) => void;
209
+ models?: ModelConfig[];
194
210
  copiedId: string | null;
195
211
  editingId: string | null;
196
212
  }
@@ -203,6 +219,8 @@ interface MessageBubbleProps {
203
219
  onEdit: () => void;
204
220
  onRegenerate?: () => void;
205
221
  onQuote?: (text: string) => void;
222
+ onAskOtherModel?: (targetModel: string) => void;
223
+ models?: ModelConfig[];
206
224
  alternatives?: AlternativeResponse[];
207
225
  activeAlternativeIndex?: number;
208
226
  onAlternativeChange?: (index: number) => void;
@@ -281,7 +299,9 @@ interface UseChatUIReturn {
281
299
  cancelEdit: () => void;
282
300
  saveEdit: (content: string) => Promise<void>;
283
301
  regenerate: (messageId: string) => Promise<void>;
302
+ askOtherModel: (messageId: string, targetModel: string) => Promise<void>;
284
303
  updatePersonalization: (config: Partial<PersonalizationConfig>) => void;
304
+ models: ModelConfig[];
285
305
  }
286
306
 
287
307
  /**
@@ -316,7 +336,7 @@ interface UseChatUIOptions {
316
336
  /** 압축 후 유지할 메시지 수 */
317
337
  keepRecentMessages?: number;
318
338
  /** 커스텀 메시지 전송 핸들러 */
319
- onSendMessage?: (params: SendMessageParams) => Promise<ReadableStream<Uint8Array> | string>;
339
+ onSendMessage?: (params: SendMessageParams) => Promise<SendMessageResponse>;
320
340
  /** 세션 변경 핸들러 */
321
341
  onSessionChange?: (session: ChatSession | null) => void;
322
342
  /** 에러 핸들러 */
@@ -487,4 +507,4 @@ interface SettingsModalProps {
487
507
  }
488
508
  declare const SettingsModal: React$1.FC<SettingsModalProps>;
489
509
 
490
- export { type ActionItem, type ActionMenuProps, type AlternativeResponse, ChatHeader, ChatInput, type ChatMessage, type ChatSession, ChatSidebar, ChatUI, type ChatUIComponents, type ChatUIProps, EmptyState, type EmptyStateProps, type HeaderProps, Icon, type IconName, type IconProps, IconSvg, type InputProps, LinkChip, type LinkChipProps, MarkdownRenderer, type MarkdownRendererProps, type MemoryItem, MemoryPanel, type MemoryPanelProps, MessageBubble, type MessageBubbleProps, MessageList, type MessageListProps, type ModelConfig, type ModelSelectorProps, type PersonalizationConfig, type PromptTemplate, type ProviderType, type ResponseStyle, type SendMessageParams, SettingsModal, type SettingsModalProps, type SettingsTab, type SidebarProps, type ThemeConfig, type ThemeMode, type UseChatUIOptions, type UseChatUIReturn, type UserProfile, useChatUI };
510
+ export { type ActionItem, type ActionMenuProps, type AlternativeResponse, ChatHeader, ChatInput, type ChatMessage, type ChatSession, ChatSidebar, ChatUI, type ChatUIComponents, type ChatUIProps, EmptyState, type EmptyStateProps, type HeaderProps, Icon, type IconName, type IconProps, IconSvg, type InputProps, LinkChip, type LinkChipProps, MarkdownRenderer, type MarkdownRendererProps, type MemoryItem, MemoryPanel, type MemoryPanelProps, MessageBubble, type MessageBubbleProps, MessageList, type MessageListProps, type ModelConfig, type ModelSelectorProps, type PersonalizationConfig, type PromptTemplate, type ProviderType, type ResponseStyle, type SendMessageParams, type SendMessageResponse, SettingsModal, type SettingsModalProps, type SettingsTab, type SidebarProps, type SourceItem, type ThemeConfig, type ThemeMode, type UseChatUIOptions, type UseChatUIReturn, type UserProfile, useChatUI };
@@ -408,6 +408,22 @@ ${contextSummary}` },
408
408
  );
409
409
  return;
410
410
  }
411
+ if (typeof result === "object" && "content" in result) {
412
+ setSessions(
413
+ (prev) => prev.map((s) => {
414
+ if (s.id === capturedSessionId) {
415
+ return {
416
+ ...s,
417
+ messages: s.messages.map(
418
+ (m) => m.id === assistantMessageId ? { ...m, content: result.content, sources: result.sources } : m
419
+ )
420
+ };
421
+ }
422
+ return s;
423
+ })
424
+ );
425
+ return;
426
+ }
411
427
  response = new Response(result);
412
428
  } else {
413
429
  response = await fetch(apiEndpoint, {
@@ -539,6 +555,158 @@ ${contextSummary}` },
539
555
  setInput(userMessage.content);
540
556
  await sendMessage(userMessage.content);
541
557
  }, [currentSession, currentSessionId, isLoading, sendMessage]);
558
+ const askOtherModel = (0, import_react.useCallback)(async (messageId, targetModel) => {
559
+ if (!currentSession || !currentSessionId || isLoading) return;
560
+ const assistantIndex = currentSession.messages.findIndex((m) => m.id === messageId);
561
+ if (assistantIndex === -1) return;
562
+ const assistantMessage = currentSession.messages[assistantIndex];
563
+ if (assistantMessage.role !== "assistant") return;
564
+ const userMessage = currentSession.messages[assistantIndex - 1];
565
+ if (!userMessage || userMessage.role !== "user") return;
566
+ setIsLoading(true);
567
+ abortControllerRef.current = new AbortController();
568
+ try {
569
+ const messagesToSend = currentSession.messages.slice(0, assistantIndex);
570
+ let chatMessages;
571
+ if (currentSession.contextSummary) {
572
+ const recentMessages = messagesToSend.slice(-keepRecentMessages);
573
+ chatMessages = [
574
+ { role: "system", content: `[\uC774\uC804 \uB300\uD654 \uC694\uC57D]
575
+ ${currentSession.contextSummary}` },
576
+ ...recentMessages.map((m) => ({ role: m.role, content: m.content }))
577
+ ];
578
+ } else {
579
+ chatMessages = messagesToSend.map((m) => ({ role: m.role, content: m.content }));
580
+ }
581
+ const baseSystemPrompt = buildSystemPrompt();
582
+ const messagesForApi = baseSystemPrompt ? [{ role: "system", content: baseSystemPrompt }, ...chatMessages] : chatMessages;
583
+ const modelConfig = models.find((m) => m.id === targetModel);
584
+ const provider = modelConfig?.provider || "ollama";
585
+ let responseContent = "";
586
+ let responseSources;
587
+ if (onSendMessage) {
588
+ const result = await onSendMessage({
589
+ messages: messagesForApi,
590
+ model: targetModel,
591
+ provider,
592
+ apiKey,
593
+ systemPrompt: baseSystemPrompt
594
+ });
595
+ if (typeof result === "string") {
596
+ responseContent = result;
597
+ } else if (typeof result === "object" && "content" in result) {
598
+ responseContent = result.content;
599
+ responseSources = result.sources;
600
+ } else {
601
+ const reader = result.getReader();
602
+ const decoder = new TextDecoder();
603
+ let buffer = "";
604
+ while (true) {
605
+ const { done, value } = await reader.read();
606
+ if (done) break;
607
+ buffer += decoder.decode(value, { stream: true });
608
+ const lines = buffer.split("\n");
609
+ buffer = lines.pop() || "";
610
+ for (const line of lines) {
611
+ if (line.startsWith("data: ")) {
612
+ const data = line.slice(6);
613
+ if (data === "[DONE]") continue;
614
+ try {
615
+ const parsed = JSON.parse(data);
616
+ if (parsed.content) responseContent += parsed.content;
617
+ } catch {
618
+ }
619
+ }
620
+ }
621
+ }
622
+ }
623
+ } else {
624
+ const response = await fetch(apiEndpoint, {
625
+ method: "POST",
626
+ headers: { "Content-Type": "application/json" },
627
+ body: JSON.stringify({
628
+ messages: messagesForApi,
629
+ model: targetModel,
630
+ provider,
631
+ apiKey: provider === "devdive" ? apiKey : void 0
632
+ }),
633
+ signal: abortControllerRef.current.signal
634
+ });
635
+ if (!response.ok) throw new Error("API error");
636
+ const reader = response.body?.getReader();
637
+ if (!reader) throw new Error("No reader");
638
+ const decoder = new TextDecoder();
639
+ let buffer = "";
640
+ while (true) {
641
+ const { done, value } = await reader.read();
642
+ if (done) break;
643
+ buffer += decoder.decode(value, { stream: true });
644
+ const lines = buffer.split("\n");
645
+ buffer = lines.pop() || "";
646
+ for (const line of lines) {
647
+ if (line.startsWith("data: ")) {
648
+ const data = line.slice(6);
649
+ if (data === "[DONE]") continue;
650
+ try {
651
+ const parsed = JSON.parse(data);
652
+ if (parsed.content) responseContent += parsed.content;
653
+ } catch {
654
+ }
655
+ }
656
+ }
657
+ }
658
+ }
659
+ const alternative = {
660
+ id: generateId("alt"),
661
+ model: targetModel,
662
+ content: responseContent,
663
+ timestamp: Date.now(),
664
+ sources: responseSources
665
+ };
666
+ const capturedSessionId = currentSessionId;
667
+ setSessions(
668
+ (prev) => prev.map((s) => {
669
+ if (s.id === capturedSessionId) {
670
+ return {
671
+ ...s,
672
+ messages: s.messages.map((m) => {
673
+ if (m.id === messageId) {
674
+ const existingAlts = m.alternatives || [];
675
+ return {
676
+ ...m,
677
+ alternatives: [...existingAlts, alternative]
678
+ };
679
+ }
680
+ return m;
681
+ }),
682
+ updatedAt: Date.now()
683
+ };
684
+ }
685
+ return s;
686
+ })
687
+ );
688
+ } catch (error) {
689
+ if (error instanceof Error && error.name === "AbortError") {
690
+ return;
691
+ }
692
+ const err = error instanceof Error ? error : new Error("Unknown error");
693
+ onError?.(err);
694
+ } finally {
695
+ setIsLoading(false);
696
+ abortControllerRef.current = null;
697
+ }
698
+ }, [
699
+ currentSession,
700
+ currentSessionId,
701
+ isLoading,
702
+ keepRecentMessages,
703
+ buildSystemPrompt,
704
+ models,
705
+ apiEndpoint,
706
+ apiKey,
707
+ onSendMessage,
708
+ onError
709
+ ]);
542
710
  return {
543
711
  // State
544
712
  sessions,
@@ -573,7 +741,9 @@ ${contextSummary}` },
573
741
  cancelEdit,
574
742
  saveEdit,
575
743
  regenerate,
576
- updatePersonalization
744
+ askOtherModel,
745
+ updatePersonalization,
746
+ models
577
747
  };
578
748
  };
579
749
 
@@ -2033,15 +2203,20 @@ var MessageBubble = ({
2033
2203
  onEdit,
2034
2204
  onRegenerate,
2035
2205
  onQuote,
2206
+ onAskOtherModel,
2207
+ models,
2036
2208
  alternatives,
2037
2209
  activeAlternativeIndex = 0,
2038
2210
  onAlternativeChange
2039
2211
  }) => {
2040
2212
  const [showActions, setShowActions] = (0, import_react6.useState)(false);
2213
+ const [showModelMenu, setShowModelMenu] = (0, import_react6.useState)(false);
2041
2214
  const isUser = message.role === "user";
2042
2215
  const isAssistant = message.role === "assistant";
2216
+ const otherModels = models?.filter((m) => m.id !== message.model) || [];
2043
2217
  const displayContent = alternatives && alternatives.length > 0 && activeAlternativeIndex > 0 ? alternatives[activeAlternativeIndex - 1]?.content || message.content : message.content;
2044
2218
  const displayModel = alternatives && alternatives.length > 0 && activeAlternativeIndex > 0 ? alternatives[activeAlternativeIndex - 1]?.model : message.model;
2219
+ const displaySources = alternatives && alternatives.length > 0 && activeAlternativeIndex > 0 ? alternatives[activeAlternativeIndex - 1]?.sources : message.sources;
2045
2220
  const handleMouseUp = () => {
2046
2221
  if (!onQuote) return;
2047
2222
  const selection = window.getSelection();
@@ -2166,6 +2341,43 @@ var MessageBubble = ({
2166
2341
  ]
2167
2342
  }
2168
2343
  ),
2344
+ displaySources && displaySources.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2345
+ "div",
2346
+ {
2347
+ style: {
2348
+ display: "flex",
2349
+ flexWrap: "wrap",
2350
+ gap: "8px",
2351
+ marginTop: "12px",
2352
+ paddingTop: "12px",
2353
+ borderTop: "1px solid var(--chatllm-border-light, #f3f4f6)"
2354
+ },
2355
+ children: [
2356
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2357
+ "span",
2358
+ {
2359
+ style: {
2360
+ fontSize: "12px",
2361
+ fontWeight: 500,
2362
+ color: "var(--chatllm-text-muted, #9ca3af)",
2363
+ marginRight: "4px"
2364
+ },
2365
+ children: "\uCD9C\uCC98:"
2366
+ }
2367
+ ),
2368
+ displaySources.map((source, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2369
+ LinkChip,
2370
+ {
2371
+ text: source.title,
2372
+ url: source.url,
2373
+ index: index + 1,
2374
+ showFavicon: true
2375
+ },
2376
+ source.id
2377
+ ))
2378
+ ]
2379
+ }
2380
+ ),
2169
2381
  alternatives && alternatives.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2170
2382
  "div",
2171
2383
  {
@@ -2230,7 +2442,110 @@ var MessageBubble = ({
2230
2442
  }
2231
2443
  ) }),
2232
2444
  isUser && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { onClick: onEdit, style: actionButtonStyle, title: "\uC218\uC815", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconSvg, { name: "edit-line", size: 16, color: "var(--chatllm-text-muted, #9ca3af)" }) }),
2233
- isAssistant && onRegenerate && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { onClick: onRegenerate, style: actionButtonStyle, title: "\uB2E4\uC2DC \uC0DD\uC131", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconSvg, { name: "refresh-line", size: 16, color: "var(--chatllm-text-muted, #9ca3af)" }) })
2445
+ isAssistant && onRegenerate && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { onClick: onRegenerate, style: actionButtonStyle, title: "\uB2E4\uC2DC \uC0DD\uC131", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconSvg, { name: "refresh-line", size: 16, color: "var(--chatllm-text-muted, #9ca3af)" }) }),
2446
+ isAssistant && onAskOtherModel && otherModels.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { position: "relative" }, children: [
2447
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2448
+ "button",
2449
+ {
2450
+ onClick: () => setShowModelMenu(!showModelMenu),
2451
+ style: actionButtonStyle,
2452
+ title: "\uB2E4\uB978 \uBAA8\uB378\uC5D0\uAC8C \uC9C8\uBB38",
2453
+ children: [
2454
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconSvg, { name: "robot-line", size: 16, color: "var(--chatllm-text-muted, #9ca3af)" }),
2455
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2456
+ IconSvg,
2457
+ {
2458
+ name: "arrow-down-s-line",
2459
+ size: 12,
2460
+ color: "var(--chatllm-text-muted, #9ca3af)",
2461
+ style: { marginLeft: "2px" }
2462
+ }
2463
+ )
2464
+ ]
2465
+ }
2466
+ ),
2467
+ showModelMenu && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2468
+ "div",
2469
+ {
2470
+ style: {
2471
+ position: "absolute",
2472
+ bottom: "100%",
2473
+ left: 0,
2474
+ marginBottom: "4px",
2475
+ backgroundColor: "var(--chatllm-bg, #ffffff)",
2476
+ border: "1px solid var(--chatllm-border, #e5e7eb)",
2477
+ borderRadius: "8px",
2478
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.1)",
2479
+ minWidth: "160px",
2480
+ zIndex: 100,
2481
+ overflow: "hidden"
2482
+ },
2483
+ onMouseLeave: () => setShowModelMenu(false),
2484
+ children: [
2485
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2486
+ "div",
2487
+ {
2488
+ style: {
2489
+ padding: "8px 12px",
2490
+ fontSize: "11px",
2491
+ fontWeight: 600,
2492
+ color: "var(--chatllm-text-muted, #9ca3af)",
2493
+ textTransform: "uppercase",
2494
+ borderBottom: "1px solid var(--chatllm-border-light, #f3f4f6)"
2495
+ },
2496
+ children: "\uB2E4\uB978 \uBAA8\uB378\uC5D0\uAC8C \uC9C8\uBB38"
2497
+ }
2498
+ ),
2499
+ otherModels.map((model) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2500
+ "button",
2501
+ {
2502
+ onClick: () => {
2503
+ onAskOtherModel(model.id);
2504
+ setShowModelMenu(false);
2505
+ },
2506
+ style: {
2507
+ width: "100%",
2508
+ padding: "10px 12px",
2509
+ display: "flex",
2510
+ alignItems: "center",
2511
+ gap: "8px",
2512
+ backgroundColor: "transparent",
2513
+ border: "none",
2514
+ cursor: "pointer",
2515
+ fontSize: "13px",
2516
+ color: "var(--chatllm-text, #1f2937)",
2517
+ textAlign: "left"
2518
+ },
2519
+ onMouseEnter: (e) => {
2520
+ e.currentTarget.style.backgroundColor = "var(--chatllm-bg-hover, #f3f4f6)";
2521
+ },
2522
+ onMouseLeave: (e) => {
2523
+ e.currentTarget.style.backgroundColor = "transparent";
2524
+ },
2525
+ children: [
2526
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconSvg, { name: "robot-line", size: 14, color: "var(--chatllm-primary, #3b82f6)" }),
2527
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { flex: 1 }, children: model.name }),
2528
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2529
+ "span",
2530
+ {
2531
+ style: {
2532
+ fontSize: "10px",
2533
+ padding: "2px 6px",
2534
+ backgroundColor: "var(--chatllm-bg-tertiary, #f3f4f6)",
2535
+ borderRadius: "4px",
2536
+ color: "var(--chatllm-text-muted, #9ca3af)"
2537
+ },
2538
+ children: model.provider
2539
+ }
2540
+ )
2541
+ ]
2542
+ },
2543
+ model.id
2544
+ ))
2545
+ ]
2546
+ }
2547
+ )
2548
+ ] })
2234
2549
  ]
2235
2550
  }
2236
2551
  )
@@ -2277,6 +2592,8 @@ var MessageList = ({
2277
2592
  onEdit,
2278
2593
  onRegenerate,
2279
2594
  onQuote,
2595
+ onAskOtherModel,
2596
+ models,
2280
2597
  copiedId,
2281
2598
  editingId
2282
2599
  }) => {
@@ -2341,6 +2658,8 @@ var MessageList = ({
2341
2658
  onEdit: () => onEdit(message),
2342
2659
  onRegenerate: message.role === "assistant" ? () => onRegenerate(message.id) : void 0,
2343
2660
  onQuote,
2661
+ onAskOtherModel: message.role === "assistant" && onAskOtherModel ? (targetModel) => onAskOtherModel(message.id, targetModel) : void 0,
2662
+ models,
2344
2663
  alternatives: message.alternatives
2345
2664
  },
2346
2665
  message.id
@@ -3644,7 +3963,9 @@ var ChatUI = ({
3644
3963
  cancelEdit,
3645
3964
  saveEdit,
3646
3965
  regenerate,
3647
- updatePersonalization
3966
+ askOtherModel,
3967
+ updatePersonalization,
3968
+ models: hookModels
3648
3969
  } = useChatUI(hookOptions);
3649
3970
  const greeting = currentPersonalization.userProfile.nickname ? `\uC548\uB155\uD558\uC138\uC694, ${currentPersonalization.userProfile.nickname}\uB2D8` : "\uC548\uB155\uD558\uC138\uC694";
3650
3971
  const handleTemplateClick = (template) => {
@@ -3762,6 +4083,8 @@ var ChatUI = ({
3762
4083
  onEdit: startEdit,
3763
4084
  onRegenerate: regenerate,
3764
4085
  onQuote: setQuotedText,
4086
+ onAskOtherModel: askOtherModel,
4087
+ models: hookModels,
3765
4088
  copiedId: copiedMessageId,
3766
4089
  editingId: editingMessageId
3767
4090
  }