@yushaw/sanqian-chat 0.1.1

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.
@@ -0,0 +1,1171 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/renderer/index.ts
21
+ var renderer_exports = {};
22
+ __export(renderer_exports, {
23
+ ChatInput: () => ChatInput,
24
+ FloatingChat: () => FloatingChat,
25
+ I18nProvider: () => I18nProvider,
26
+ MessageBubble: () => MessageBubble,
27
+ MessageList: () => MessageList,
28
+ ThemeProvider: () => ThemeProvider,
29
+ createIpcAdapter: () => createIpcAdapter,
30
+ createSdkAdapter: () => createSdkAdapter,
31
+ getTranslations: () => getTranslations,
32
+ useChat: () => useChat,
33
+ useI18n: () => useI18n,
34
+ useStandaloneI18n: () => useStandaloneI18n,
35
+ useStandaloneTheme: () => useStandaloneTheme,
36
+ useTheme: () => useTheme
37
+ });
38
+ module.exports = __toCommonJS(renderer_exports);
39
+
40
+ // src/renderer/hooks/useChat.ts
41
+ var import_react = require("react");
42
+ var TYPEWRITER_DELAYS = { VERY_FAST: 2, FAST: 5, NORMAL: 10, SLOW: 20 };
43
+ var TYPEWRITER_THRESHOLDS = { VERY_FAST: 100, FAST: 50, NORMAL: 20 };
44
+ function useChat(options) {
45
+ const { adapter, onError, onConversationChange } = options;
46
+ const [messages, setMessages] = (0, import_react.useState)([]);
47
+ const [isLoading, setIsLoading] = (0, import_react.useState)(false);
48
+ const [isStreaming, setIsStreaming] = (0, import_react.useState)(false);
49
+ const [error, setError] = (0, import_react.useState)(null);
50
+ const [conversationId, setConversationId] = (0, import_react.useState)(options.conversationId ?? null);
51
+ const [conversationTitle, setConversationTitle] = (0, import_react.useState)(null);
52
+ const [pendingInterrupt, setPendingInterrupt] = (0, import_react.useState)(null);
53
+ const cancelRef = (0, import_react.useRef)(null);
54
+ const isMountedRef = (0, import_react.useRef)(true);
55
+ const messagesRef = (0, import_react.useRef)(messages);
56
+ const conversationIdRef = (0, import_react.useRef)(conversationId);
57
+ const currentRunIdRef = (0, import_react.useRef)(null);
58
+ const currentBlocksRef = (0, import_react.useRef)([]);
59
+ const currentTextBlockIndexRef = (0, import_react.useRef)(-1);
60
+ const needsContentClearRef = (0, import_react.useRef)(false);
61
+ const fullContentRef = (0, import_react.useRef)("");
62
+ const tokenQueueRef = (0, import_react.useRef)([]);
63
+ const displayedContentRef = (0, import_react.useRef)("");
64
+ const typewriterIntervalRef = (0, import_react.useRef)(null);
65
+ const currentAssistantMessageIdRef = (0, import_react.useRef)(null);
66
+ (0, import_react.useEffect)(() => {
67
+ messagesRef.current = messages;
68
+ }, [messages]);
69
+ (0, import_react.useEffect)(() => {
70
+ conversationIdRef.current = conversationId;
71
+ }, [conversationId]);
72
+ (0, import_react.useEffect)(() => {
73
+ isMountedRef.current = true;
74
+ return () => {
75
+ isMountedRef.current = false;
76
+ cancelRef.current?.();
77
+ if (typewriterIntervalRef.current) clearTimeout(typewriterIntervalRef.current);
78
+ };
79
+ }, []);
80
+ const flushTypewriter = (0, import_react.useCallback)(() => {
81
+ if (typewriterIntervalRef.current) {
82
+ clearTimeout(typewriterIntervalRef.current);
83
+ typewriterIntervalRef.current = null;
84
+ }
85
+ if (tokenQueueRef.current.length > 0 && currentAssistantMessageIdRef.current) {
86
+ displayedContentRef.current += tokenQueueRef.current.join("");
87
+ tokenQueueRef.current = [];
88
+ setMessages((prev) => {
89
+ const idx = prev.findIndex((m) => m.id === currentAssistantMessageIdRef.current);
90
+ if (idx === -1) return prev;
91
+ const updated = [...prev];
92
+ updated[idx] = { ...prev[idx], content: displayedContentRef.current, blocks: [...currentBlocksRef.current] };
93
+ return updated;
94
+ });
95
+ }
96
+ }, []);
97
+ const handleStreamEvent = (0, import_react.useCallback)((event, assistantMessageId) => {
98
+ if (!isMountedRef.current) return;
99
+ currentAssistantMessageIdRef.current = assistantMessageId;
100
+ switch (event.type) {
101
+ case "text": {
102
+ const content = event.content;
103
+ if (!content) break;
104
+ if (needsContentClearRef.current || fullContentRef.current === "") {
105
+ flushTypewriter();
106
+ needsContentClearRef.current = false;
107
+ fullContentRef.current = "";
108
+ displayedContentRef.current = "";
109
+ currentBlocksRef.current.forEach((b) => {
110
+ if (b.type === "text") b.isIntermediate = true;
111
+ });
112
+ currentBlocksRef.current.push({ type: "text", content: "", timestamp: Date.now(), isIntermediate: false });
113
+ currentTextBlockIndexRef.current = currentBlocksRef.current.length - 1;
114
+ }
115
+ const textToAdd = fullContentRef.current === "" ? content.trimStart() : content;
116
+ fullContentRef.current += textToAdd;
117
+ if (currentTextBlockIndexRef.current >= 0) {
118
+ currentBlocksRef.current[currentTextBlockIndexRef.current].content += textToAdd;
119
+ }
120
+ tokenQueueRef.current.push(...textToAdd.split(""));
121
+ if (!typewriterIntervalRef.current) {
122
+ const tick = () => {
123
+ const char = tokenQueueRef.current.shift();
124
+ if (char !== void 0) {
125
+ displayedContentRef.current += char;
126
+ setMessages((prev) => {
127
+ const idx = prev.findIndex((m) => m.id === assistantMessageId);
128
+ if (idx === -1) return prev;
129
+ const updated = [...prev];
130
+ updated[idx] = { ...prev[idx], content: displayedContentRef.current, isStreaming: true, blocks: [...currentBlocksRef.current] };
131
+ return updated;
132
+ });
133
+ const qLen = tokenQueueRef.current.length;
134
+ const delay = qLen > TYPEWRITER_THRESHOLDS.VERY_FAST ? TYPEWRITER_DELAYS.VERY_FAST : qLen > TYPEWRITER_THRESHOLDS.FAST ? TYPEWRITER_DELAYS.FAST : qLen > TYPEWRITER_THRESHOLDS.NORMAL ? TYPEWRITER_DELAYS.NORMAL : TYPEWRITER_DELAYS.SLOW;
135
+ if (typewriterIntervalRef.current !== null) {
136
+ typewriterIntervalRef.current = setTimeout(tick, delay);
137
+ }
138
+ } else {
139
+ typewriterIntervalRef.current = null;
140
+ }
141
+ };
142
+ typewriterIntervalRef.current = setTimeout(tick, TYPEWRITER_DELAYS.SLOW);
143
+ }
144
+ break;
145
+ }
146
+ case "thinking": {
147
+ const content = event.content;
148
+ if (!content) break;
149
+ let thinkingBlockIdx = currentBlocksRef.current.findIndex((b) => b.type === "thinking" && !b.isIntermediate);
150
+ if (thinkingBlockIdx === -1) {
151
+ currentBlocksRef.current.push({ type: "thinking", content: "", timestamp: Date.now(), isIntermediate: false });
152
+ thinkingBlockIdx = currentBlocksRef.current.length - 1;
153
+ }
154
+ currentBlocksRef.current[thinkingBlockIdx].content += content;
155
+ setMessages((prev) => {
156
+ const idx = prev.findIndex((m) => m.id === assistantMessageId);
157
+ if (idx === -1) return prev;
158
+ const updated = [...prev];
159
+ const msg = prev[idx];
160
+ updated[idx] = {
161
+ ...msg,
162
+ thinking: (msg.thinking || "") + content,
163
+ currentThinking: (msg.currentThinking || "") + content,
164
+ isThinkingStreaming: true,
165
+ blocks: [...currentBlocksRef.current]
166
+ };
167
+ return updated;
168
+ });
169
+ break;
170
+ }
171
+ case "tool_call": {
172
+ flushTypewriter();
173
+ const tc = event.tool_call;
174
+ if (!tc) break;
175
+ needsContentClearRef.current = true;
176
+ let args = {};
177
+ try {
178
+ args = JSON.parse(tc.function?.arguments || "{}");
179
+ } catch (e) {
180
+ console.warn("[useChat] Failed to parse tool arguments:", e);
181
+ }
182
+ currentBlocksRef.current.push({
183
+ type: "tool_call",
184
+ content: "",
185
+ timestamp: Date.now(),
186
+ toolName: tc.function?.name || "",
187
+ toolArgs: args,
188
+ toolCallId: tc.id,
189
+ toolStatus: "running",
190
+ isIntermediate: true
191
+ });
192
+ setMessages((prev) => {
193
+ const idx = prev.findIndex((m) => m.id === assistantMessageId);
194
+ if (idx === -1) return prev;
195
+ const msg = prev[idx];
196
+ const newTc = { id: tc.id, name: tc.function?.name || "", arguments: args, status: "running" };
197
+ const updated = [...prev];
198
+ updated[idx] = { ...msg, toolCalls: [...msg.toolCalls || [], newTc], blocks: [...currentBlocksRef.current] };
199
+ return updated;
200
+ });
201
+ break;
202
+ }
203
+ case "tool_result": {
204
+ flushTypewriter();
205
+ const toolId = event.tool_call_id;
206
+ const result = typeof event.result === "string" ? event.result : JSON.stringify(event.result);
207
+ const blockIdx = currentBlocksRef.current.findIndex((b) => b.type === "tool_call" && b.toolCallId === toolId);
208
+ if (blockIdx !== -1) currentBlocksRef.current[blockIdx].toolStatus = "completed";
209
+ currentBlocksRef.current.push({ type: "tool_result", content: result, timestamp: Date.now(), toolCallId: toolId, isIntermediate: true });
210
+ setMessages((prev) => {
211
+ const idx = prev.findIndex((m) => m.id === assistantMessageId);
212
+ if (idx === -1) return prev;
213
+ const msg = prev[idx];
214
+ const updated = [...prev];
215
+ updated[idx] = {
216
+ ...msg,
217
+ toolCalls: msg.toolCalls?.map((t) => t.id === toolId ? { ...t, status: "completed", result: event.result } : t),
218
+ blocks: [...currentBlocksRef.current]
219
+ };
220
+ return updated;
221
+ });
222
+ break;
223
+ }
224
+ case "done": {
225
+ flushTypewriter();
226
+ const finalContent = fullContentRef.current;
227
+ if (currentTextBlockIndexRef.current >= 0 && currentTextBlockIndexRef.current < currentBlocksRef.current.length) {
228
+ currentBlocksRef.current[currentTextBlockIndexRef.current].content = finalContent;
229
+ currentBlocksRef.current[currentTextBlockIndexRef.current].isIntermediate = false;
230
+ }
231
+ setMessages((prev) => {
232
+ const idx = prev.findIndex((m) => m.id === assistantMessageId);
233
+ if (idx === -1) return prev;
234
+ const updated = [...prev];
235
+ const msg = prev[idx];
236
+ updated[idx] = {
237
+ ...msg,
238
+ content: finalContent || msg.content,
239
+ isStreaming: false,
240
+ isComplete: true,
241
+ isThinkingStreaming: false,
242
+ blocks: [...currentBlocksRef.current]
243
+ };
244
+ return updated;
245
+ });
246
+ currentBlocksRef.current = [];
247
+ currentTextBlockIndexRef.current = -1;
248
+ fullContentRef.current = "";
249
+ needsContentClearRef.current = false;
250
+ tokenQueueRef.current = [];
251
+ displayedContentRef.current = "";
252
+ setConversationId(event.conversationId);
253
+ if (event.title) setConversationTitle(event.title);
254
+ onConversationChange?.(event.conversationId, event.title);
255
+ setIsStreaming(false);
256
+ setIsLoading(false);
257
+ break;
258
+ }
259
+ case "error": {
260
+ flushTypewriter();
261
+ setMessages((prev) => {
262
+ const idx = prev.findIndex((m) => m.id === assistantMessageId);
263
+ if (idx === -1) return prev;
264
+ const updated = [...prev];
265
+ updated[idx] = { ...prev[idx], isStreaming: false, isComplete: true, content: prev[idx].content || `Error: ${event.error}` };
266
+ return updated;
267
+ });
268
+ currentBlocksRef.current = [];
269
+ currentTextBlockIndexRef.current = -1;
270
+ fullContentRef.current = "";
271
+ setError(event.error);
272
+ onError?.(new Error(event.error));
273
+ setIsStreaming(false);
274
+ setIsLoading(false);
275
+ break;
276
+ }
277
+ case "interrupt": {
278
+ flushTypewriter();
279
+ currentRunIdRef.current = event.run_id ?? null;
280
+ const payload = event.interrupt_payload;
281
+ if (payload) {
282
+ setPendingInterrupt(payload);
283
+ }
284
+ break;
285
+ }
286
+ }
287
+ }, [onError, onConversationChange, flushTypewriter]);
288
+ const sendMessage = (0, import_react.useCallback)(async (content) => {
289
+ if (!content.trim()) return;
290
+ setError(null);
291
+ if (typewriterIntervalRef.current) {
292
+ clearTimeout(typewriterIntervalRef.current);
293
+ typewriterIntervalRef.current = null;
294
+ }
295
+ currentBlocksRef.current = [];
296
+ currentTextBlockIndexRef.current = -1;
297
+ fullContentRef.current = "";
298
+ needsContentClearRef.current = false;
299
+ tokenQueueRef.current = [];
300
+ displayedContentRef.current = "";
301
+ const userMessage = { id: crypto.randomUUID(), role: "user", content: content.trim(), timestamp: (/* @__PURE__ */ new Date()).toISOString() };
302
+ const assistantMessage = { id: crypto.randomUUID(), role: "assistant", content: "", timestamp: (/* @__PURE__ */ new Date()).toISOString(), isStreaming: true, toolCalls: [], blocks: [] };
303
+ setMessages((prev) => [...prev, userMessage, assistantMessage]);
304
+ setIsLoading(true);
305
+ setIsStreaming(true);
306
+ try {
307
+ if (!adapter.isConnected()) await adapter.connect();
308
+ const apiMessages = messagesRef.current.filter((m) => m.role === "user" || m.role === "assistant").map((m) => ({ role: m.role, content: m.content })).concat({ role: "user", content: content.trim() });
309
+ const { cancel } = await adapter.chatStream(apiMessages, conversationIdRef.current ?? void 0, (event) => handleStreamEvent(event, assistantMessage.id));
310
+ cancelRef.current = cancel;
311
+ } catch (err) {
312
+ const errorMessage = err instanceof Error ? err.message : "Failed to send message";
313
+ setError(errorMessage);
314
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
315
+ setMessages((prev) => {
316
+ const idx = prev.findIndex((m) => m.id === assistantMessage.id);
317
+ if (idx === -1) return prev;
318
+ const updated = [...prev];
319
+ updated[idx] = { ...prev[idx], isStreaming: false, content: `Error: ${errorMessage}` };
320
+ return updated;
321
+ });
322
+ setIsLoading(false);
323
+ setIsStreaming(false);
324
+ }
325
+ }, [adapter, handleStreamEvent, onError]);
326
+ const stopStreaming = (0, import_react.useCallback)(() => {
327
+ cancelRef.current?.();
328
+ cancelRef.current = null;
329
+ flushTypewriter();
330
+ setMessages((prev) => {
331
+ const last = [...prev].reverse().find((m) => m.role === "assistant");
332
+ if (!last?.isStreaming) return prev;
333
+ return prev.map((m) => m.id === last.id ? { ...m, content: fullContentRef.current || m.content, isStreaming: false, isComplete: true } : m);
334
+ });
335
+ currentBlocksRef.current = [];
336
+ fullContentRef.current = "";
337
+ setIsStreaming(false);
338
+ setIsLoading(false);
339
+ }, [flushTypewriter]);
340
+ const clearMessages = (0, import_react.useCallback)(() => {
341
+ cancelRef.current?.();
342
+ if (typewriterIntervalRef.current) clearTimeout(typewriterIntervalRef.current);
343
+ setMessages([]);
344
+ setError(null);
345
+ setIsLoading(false);
346
+ setIsStreaming(false);
347
+ currentBlocksRef.current = [];
348
+ fullContentRef.current = "";
349
+ }, []);
350
+ const loadConversation = (0, import_react.useCallback)(async (id) => {
351
+ try {
352
+ setIsLoading(true);
353
+ setError(null);
354
+ const detail = await adapter.getConversation(id);
355
+ if (!isMountedRef.current) return;
356
+ setMessages(detail.messages);
357
+ setConversationId(detail.id);
358
+ setConversationTitle(detail.title);
359
+ } catch (err) {
360
+ const msg = err instanceof Error ? err.message : "Failed to load conversation";
361
+ setError(msg);
362
+ onError?.(err instanceof Error ? err : new Error(msg));
363
+ } finally {
364
+ if (isMountedRef.current) setIsLoading(false);
365
+ }
366
+ }, [adapter, onError]);
367
+ const newConversation = (0, import_react.useCallback)(() => {
368
+ cancelRef.current?.();
369
+ if (typewriterIntervalRef.current) clearTimeout(typewriterIntervalRef.current);
370
+ setMessages([]);
371
+ setConversationId(null);
372
+ setConversationTitle(null);
373
+ setError(null);
374
+ setIsLoading(false);
375
+ setIsStreaming(false);
376
+ setPendingInterrupt(null);
377
+ currentBlocksRef.current = [];
378
+ fullContentRef.current = "";
379
+ }, []);
380
+ const sendHitlResponse = (0, import_react.useCallback)((response) => {
381
+ adapter.sendHitlResponse?.(response, currentRunIdRef.current ?? void 0);
382
+ }, [adapter]);
383
+ const approveHitl = (0, import_react.useCallback)((remember = false) => {
384
+ sendHitlResponse({ approved: true, remember });
385
+ setPendingInterrupt(null);
386
+ }, [sendHitlResponse]);
387
+ const rejectHitl = (0, import_react.useCallback)((remember = false) => {
388
+ sendHitlResponse({ approved: false, remember });
389
+ setPendingInterrupt(null);
390
+ setIsLoading(false);
391
+ }, [sendHitlResponse]);
392
+ const submitHitlInput = (0, import_react.useCallback)((response) => {
393
+ sendHitlResponse(response);
394
+ setPendingInterrupt(null);
395
+ if (response.cancelled || response.timed_out) setIsLoading(false);
396
+ }, [sendHitlResponse]);
397
+ return {
398
+ messages,
399
+ isLoading,
400
+ isStreaming,
401
+ error,
402
+ conversationId,
403
+ conversationTitle,
404
+ pendingInterrupt,
405
+ sendMessage,
406
+ stopStreaming,
407
+ clearMessages,
408
+ setError,
409
+ approveHitl,
410
+ rejectHitl,
411
+ submitHitlInput,
412
+ loadConversation,
413
+ newConversation
414
+ };
415
+ }
416
+
417
+ // src/renderer/hooks/useTheme.tsx
418
+ var import_react2 = require("react");
419
+ var import_jsx_runtime = require("react/jsx-runtime");
420
+ var ThemeContext = (0, import_react2.createContext)(null);
421
+ function ThemeProvider({
422
+ children,
423
+ defaultTheme = "system",
424
+ storageKey = "sanqian-chat-theme"
425
+ }) {
426
+ const [theme, setThemeState] = (0, import_react2.useState)(() => {
427
+ if (typeof window !== "undefined") {
428
+ const stored = localStorage.getItem(storageKey);
429
+ if (stored === "light" || stored === "dark" || stored === "system") {
430
+ return stored;
431
+ }
432
+ }
433
+ return defaultTheme;
434
+ });
435
+ const [resolvedTheme, setResolvedTheme] = (0, import_react2.useState)("light");
436
+ (0, import_react2.useEffect)(() => {
437
+ const applyTheme = (resolved) => {
438
+ const root = document.documentElement;
439
+ if (resolved === "dark") {
440
+ root.classList.add("dark");
441
+ } else {
442
+ root.classList.remove("dark");
443
+ }
444
+ setResolvedTheme(resolved);
445
+ };
446
+ if (theme === "system") {
447
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
448
+ applyTheme(mediaQuery.matches ? "dark" : "light");
449
+ const handler = (e) => {
450
+ applyTheme(e.matches ? "dark" : "light");
451
+ };
452
+ mediaQuery.addEventListener("change", handler);
453
+ return () => mediaQuery.removeEventListener("change", handler);
454
+ } else {
455
+ applyTheme(theme);
456
+ }
457
+ }, [theme]);
458
+ const setTheme = (0, import_react2.useCallback)((newTheme) => {
459
+ setThemeState(newTheme);
460
+ if (typeof window !== "undefined") {
461
+ localStorage.setItem(storageKey, newTheme);
462
+ }
463
+ }, [storageKey]);
464
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThemeContext.Provider, { value: { theme, resolvedTheme, setTheme }, children });
465
+ }
466
+ function useTheme() {
467
+ const context = (0, import_react2.useContext)(ThemeContext);
468
+ if (!context) {
469
+ throw new Error("useTheme must be used within a ThemeProvider");
470
+ }
471
+ return context;
472
+ }
473
+ function useStandaloneTheme(defaultTheme = "system") {
474
+ const [theme, setThemeState] = (0, import_react2.useState)(defaultTheme);
475
+ const [resolvedTheme, setResolvedTheme] = (0, import_react2.useState)("light");
476
+ (0, import_react2.useEffect)(() => {
477
+ const applyTheme = (resolved) => {
478
+ const root = document.documentElement;
479
+ if (resolved === "dark") {
480
+ root.classList.add("dark");
481
+ } else {
482
+ root.classList.remove("dark");
483
+ }
484
+ setResolvedTheme(resolved);
485
+ };
486
+ if (theme === "system") {
487
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
488
+ applyTheme(mediaQuery.matches ? "dark" : "light");
489
+ const handler = (e) => {
490
+ applyTheme(e.matches ? "dark" : "light");
491
+ };
492
+ mediaQuery.addEventListener("change", handler);
493
+ return () => mediaQuery.removeEventListener("change", handler);
494
+ } else {
495
+ applyTheme(theme);
496
+ }
497
+ }, [theme]);
498
+ const setTheme = (0, import_react2.useCallback)((newTheme) => {
499
+ setThemeState(newTheme);
500
+ }, []);
501
+ return { theme, resolvedTheme, setTheme };
502
+ }
503
+
504
+ // src/renderer/hooks/useI18n.tsx
505
+ var import_react3 = require("react");
506
+ var import_jsx_runtime2 = require("react/jsx-runtime");
507
+ var translations = {
508
+ en: {
509
+ hitl: {
510
+ approvalRequired: "Approval Required",
511
+ inputRequired: "Input Required",
512
+ tool: "Tool",
513
+ approve: "Approve",
514
+ reject: "Reject",
515
+ submit: "Submit",
516
+ cancel: "Cancel"
517
+ },
518
+ input: {
519
+ placeholder: "Type a message...",
520
+ send: "Send",
521
+ stop: "Stop"
522
+ },
523
+ message: {
524
+ thinking: "Thinking...",
525
+ error: "Error",
526
+ loading: "Loading..."
527
+ },
528
+ connection: {
529
+ connecting: "Connecting...",
530
+ connected: "Connected",
531
+ disconnected: "Disconnected",
532
+ reconnecting: "Reconnecting...",
533
+ error: "Connection error"
534
+ },
535
+ conversation: {
536
+ new: "New conversation",
537
+ untitled: "Untitled",
538
+ delete: "Delete",
539
+ deleteConfirm: "Are you sure you want to delete this conversation?"
540
+ }
541
+ },
542
+ zh: {
543
+ hitl: {
544
+ approvalRequired: "\u9700\u8981\u5BA1\u6279",
545
+ inputRequired: "\u9700\u8981\u8F93\u5165",
546
+ tool: "\u5DE5\u5177",
547
+ approve: "\u6279\u51C6",
548
+ reject: "\u62D2\u7EDD",
549
+ submit: "\u63D0\u4EA4",
550
+ cancel: "\u53D6\u6D88"
551
+ },
552
+ input: {
553
+ placeholder: "\u8F93\u5165\u6D88\u606F...",
554
+ send: "\u53D1\u9001",
555
+ stop: "\u505C\u6B62"
556
+ },
557
+ message: {
558
+ thinking: "\u601D\u8003\u4E2D...",
559
+ error: "\u9519\u8BEF",
560
+ loading: "\u52A0\u8F7D\u4E2D..."
561
+ },
562
+ connection: {
563
+ connecting: "\u8FDE\u63A5\u4E2D...",
564
+ connected: "\u5DF2\u8FDE\u63A5",
565
+ disconnected: "\u5DF2\u65AD\u5F00",
566
+ reconnecting: "\u91CD\u65B0\u8FDE\u63A5\u4E2D...",
567
+ error: "\u8FDE\u63A5\u9519\u8BEF"
568
+ },
569
+ conversation: {
570
+ new: "\u65B0\u5BF9\u8BDD",
571
+ untitled: "\u672A\u547D\u540D",
572
+ delete: "\u5220\u9664",
573
+ deleteConfirm: "\u786E\u5B9A\u8981\u5220\u9664\u8FD9\u4E2A\u5BF9\u8BDD\u5417\uFF1F"
574
+ }
575
+ }
576
+ };
577
+ var I18nContext = (0, import_react3.createContext)(null);
578
+ function I18nProvider({
579
+ children,
580
+ defaultLocale = "en",
581
+ storageKey = "sanqian-chat-locale",
582
+ customTranslations
583
+ }) {
584
+ const [locale, setLocaleState] = (0, import_react3.useState)(() => {
585
+ if (typeof window !== "undefined") {
586
+ const stored = localStorage.getItem(storageKey);
587
+ if (stored === "en" || stored === "zh") {
588
+ return stored;
589
+ }
590
+ const browserLang = navigator.language.toLowerCase();
591
+ if (browserLang.startsWith("zh")) {
592
+ return "zh";
593
+ }
594
+ }
595
+ return defaultLocale;
596
+ });
597
+ const setLocale = (0, import_react3.useCallback)((newLocale) => {
598
+ setLocaleState(newLocale);
599
+ if (typeof window !== "undefined") {
600
+ localStorage.setItem(storageKey, newLocale);
601
+ }
602
+ }, [storageKey]);
603
+ const t = (0, import_react3.useMemo)(() => {
604
+ const base = translations[locale];
605
+ const custom = customTranslations?.[locale];
606
+ if (!custom) return base;
607
+ return {
608
+ hitl: { ...base.hitl, ...custom.hitl },
609
+ input: { ...base.input, ...custom.input },
610
+ message: { ...base.message, ...custom.message },
611
+ connection: { ...base.connection, ...custom.connection },
612
+ conversation: { ...base.conversation, ...custom.conversation }
613
+ };
614
+ }, [locale, customTranslations]);
615
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(I18nContext.Provider, { value: { locale, setLocale, t }, children });
616
+ }
617
+ function useI18n() {
618
+ const context = (0, import_react3.useContext)(I18nContext);
619
+ if (!context) {
620
+ throw new Error("useI18n must be used within an I18nProvider");
621
+ }
622
+ return context;
623
+ }
624
+ function useStandaloneI18n(defaultLocale = "en") {
625
+ const [locale, setLocale] = (0, import_react3.useState)(defaultLocale);
626
+ const t = (0, import_react3.useMemo)(() => translations[locale], [locale]);
627
+ return { locale, setLocale, t };
628
+ }
629
+ function getTranslations(locale) {
630
+ return translations[locale];
631
+ }
632
+
633
+ // src/renderer/adapters/ipc.ts
634
+ function createIpcAdapter() {
635
+ let connectionStatus = "disconnected";
636
+ const connectionListeners = /* @__PURE__ */ new Set();
637
+ const streamCallbacks = /* @__PURE__ */ new Map();
638
+ let streamEventCleanup = null;
639
+ let currentRunId = null;
640
+ const api = window.sanqianChat;
641
+ if (!api) {
642
+ throw new Error("sanqianChat API not available. Ensure preload script is configured.");
643
+ }
644
+ const updateStatus = (status, error, errorCode) => {
645
+ connectionStatus = status;
646
+ connectionListeners.forEach((cb) => cb(status, error, errorCode));
647
+ };
648
+ streamEventCleanup = api.onStreamEvent((streamId, event) => {
649
+ const callback = streamCallbacks.get(streamId);
650
+ if (callback && isValidStreamEvent(event)) {
651
+ callback(event);
652
+ if (event.type === "done" || event.type === "error") {
653
+ streamCallbacks.delete(streamId);
654
+ }
655
+ }
656
+ });
657
+ return {
658
+ async connect() {
659
+ updateStatus("connecting");
660
+ try {
661
+ const result = await api.connect();
662
+ if (result.success) {
663
+ updateStatus("connected");
664
+ } else {
665
+ throw new Error(result.error || "Connection failed");
666
+ }
667
+ } catch (e) {
668
+ updateStatus("error", e instanceof Error ? e.message : "Connection failed", "CONNECTION_FAILED");
669
+ throw e;
670
+ }
671
+ },
672
+ async disconnect() {
673
+ updateStatus("disconnected");
674
+ },
675
+ isConnected() {
676
+ return connectionStatus === "connected";
677
+ },
678
+ getConnectionStatus() {
679
+ return connectionStatus;
680
+ },
681
+ onConnectionChange(callback) {
682
+ connectionListeners.add(callback);
683
+ callback(connectionStatus);
684
+ return () => connectionListeners.delete(callback);
685
+ },
686
+ async listConversations(options) {
687
+ const result = await api.listConversations(options);
688
+ if (!result.success) throw new Error(result.error || "Failed to list");
689
+ const data = result.data;
690
+ return {
691
+ conversations: data.conversations.map((c) => ({
692
+ id: c.conversation_id,
693
+ title: c.title || "Untitled",
694
+ createdAt: c.created_at || "",
695
+ updatedAt: c.updated_at || "",
696
+ messageCount: c.message_count || 0
697
+ })),
698
+ total: data.total
699
+ };
700
+ },
701
+ async getConversation(id, options) {
702
+ const result = await api.getConversation({ conversationId: id, messageLimit: options?.messageLimit });
703
+ if (!result.success) throw new Error(result.error || "Failed to get");
704
+ const data = result.data;
705
+ return {
706
+ id: data.conversation_id,
707
+ title: data.title || "Untitled",
708
+ createdAt: data.created_at || "",
709
+ updatedAt: data.updated_at || "",
710
+ messageCount: data.message_count || 0,
711
+ messages: (data.messages || []).map((m, i) => ({
712
+ id: `msg-${i}`,
713
+ role: m.role,
714
+ content: m.content,
715
+ timestamp: m.created_at || (/* @__PURE__ */ new Date()).toISOString()
716
+ }))
717
+ };
718
+ },
719
+ async deleteConversation(id) {
720
+ const result = await api.deleteConversation({ conversationId: id });
721
+ if (!result.success) throw new Error(result.error || "Failed to delete");
722
+ },
723
+ async chatStream(messages, conversationId, onEvent) {
724
+ const streamId = crypto.randomUUID();
725
+ streamCallbacks.set(streamId, (event) => {
726
+ if (event.type === "interrupt") {
727
+ currentRunId = event.run_id || null;
728
+ }
729
+ onEvent(event);
730
+ });
731
+ try {
732
+ await api.stream({ streamId, messages, conversationId });
733
+ return {
734
+ cancel: async () => {
735
+ await api.cancelStream({ streamId });
736
+ streamCallbacks.delete(streamId);
737
+ }
738
+ };
739
+ } catch (e) {
740
+ streamCallbacks.delete(streamId);
741
+ throw e;
742
+ }
743
+ },
744
+ sendHitlResponse(response, runId) {
745
+ api.sendHitlResponse({ response, runId: runId || currentRunId || void 0 });
746
+ },
747
+ cleanup() {
748
+ streamEventCleanup?.();
749
+ connectionListeners.clear();
750
+ streamCallbacks.clear();
751
+ }
752
+ };
753
+ }
754
+ function isValidStreamEvent(event) {
755
+ if (!event || typeof event !== "object") return false;
756
+ const e = event;
757
+ return typeof e.type === "string";
758
+ }
759
+
760
+ // src/core/adapter.ts
761
+ function createSdkAdapter(config) {
762
+ let connectionStatus = "disconnected";
763
+ const connectionListeners = /* @__PURE__ */ new Set();
764
+ let currentRunId = null;
765
+ const updateStatus = (status, error, errorCode) => {
766
+ connectionStatus = status;
767
+ connectionListeners.forEach((cb) => cb(status, error, errorCode));
768
+ };
769
+ return {
770
+ async connect() {
771
+ const sdk = config.getSdk();
772
+ if (!sdk) throw new Error("SDK not available");
773
+ updateStatus("connecting");
774
+ try {
775
+ await sdk.ensureReady();
776
+ updateStatus("connected");
777
+ } catch (e) {
778
+ updateStatus("error", e instanceof Error ? e.message : "Connection failed", "CONNECTION_FAILED");
779
+ throw e;
780
+ }
781
+ },
782
+ async disconnect() {
783
+ updateStatus("disconnected");
784
+ },
785
+ isConnected() {
786
+ const sdk = config.getSdk();
787
+ return sdk?.isConnected() ?? false;
788
+ },
789
+ getConnectionStatus() {
790
+ return connectionStatus;
791
+ },
792
+ onConnectionChange(callback) {
793
+ connectionListeners.add(callback);
794
+ callback(connectionStatus);
795
+ return () => connectionListeners.delete(callback);
796
+ },
797
+ async listConversations(options) {
798
+ const sdk = config.getSdk();
799
+ const agentId = config.getAgentId();
800
+ if (!sdk || !agentId) throw new Error("SDK or agent not ready");
801
+ const result = await sdk.listConversations({ agentId, ...options });
802
+ return {
803
+ conversations: result.conversations.map((c) => ({
804
+ id: c.conversation_id,
805
+ title: c.title || "Untitled",
806
+ createdAt: c.created_at || "",
807
+ updatedAt: c.updated_at || "",
808
+ messageCount: c.message_count
809
+ })),
810
+ total: result.total
811
+ };
812
+ },
813
+ async getConversation(id, options) {
814
+ const sdk = config.getSdk();
815
+ if (!sdk) throw new Error("SDK not ready");
816
+ const detail = await sdk.getConversation(id, { messageLimit: options?.messageLimit });
817
+ return {
818
+ id: detail.conversation_id,
819
+ title: detail.title || "Untitled",
820
+ createdAt: detail.created_at || "",
821
+ updatedAt: detail.updated_at || "",
822
+ messageCount: detail.message_count,
823
+ messages: (detail.messages || []).map((m, i) => ({
824
+ id: `msg-${i}`,
825
+ role: m.role,
826
+ content: m.content,
827
+ timestamp: m.created_at || (/* @__PURE__ */ new Date()).toISOString()
828
+ }))
829
+ };
830
+ },
831
+ async deleteConversation(id) {
832
+ const sdk = config.getSdk();
833
+ if (!sdk) throw new Error("SDK not ready");
834
+ await sdk.deleteConversation(id);
835
+ },
836
+ async chatStream(messages, conversationId, onEvent) {
837
+ const sdk = config.getSdk();
838
+ const agentId = config.getAgentId();
839
+ if (!sdk || !agentId) throw new Error("SDK or agent not ready");
840
+ await sdk.ensureReady();
841
+ const sdkMessages = messages.map((m) => ({ role: m.role, content: m.content }));
842
+ const stream = sdk.chatStream(agentId, sdkMessages, { conversationId });
843
+ const controller = new AbortController();
844
+ const signal = controller.signal;
845
+ (async () => {
846
+ try {
847
+ for await (const event of stream) {
848
+ if (signal.aborted) break;
849
+ switch (event.type) {
850
+ case "text":
851
+ onEvent({ type: "text", content: event.content || "" });
852
+ break;
853
+ case "thinking":
854
+ onEvent({ type: "thinking", content: event.content || "" });
855
+ break;
856
+ case "tool_call":
857
+ if (event.tool_call) {
858
+ onEvent({ type: "tool_call", tool_call: event.tool_call });
859
+ }
860
+ break;
861
+ case "tool_result":
862
+ onEvent({ type: "tool_result", tool_call_id: event.tool_call_id || "", result: event.result });
863
+ break;
864
+ case "done":
865
+ onEvent({ type: "done", conversationId: event.conversationId || "", title: event.title });
866
+ break;
867
+ case "error":
868
+ onEvent({ type: "error", error: event.error || "Unknown error" });
869
+ break;
870
+ default:
871
+ const evt = event;
872
+ if (evt.type === "interrupt") {
873
+ currentRunId = evt.run_id || null;
874
+ onEvent({
875
+ type: "interrupt",
876
+ interrupt_type: evt.interrupt_type || "",
877
+ interrupt_payload: evt.interrupt_payload,
878
+ run_id: evt.run_id
879
+ });
880
+ }
881
+ break;
882
+ }
883
+ }
884
+ } catch (e) {
885
+ if (!signal.aborted) {
886
+ onEvent({ type: "error", error: e instanceof Error ? e.message : "Stream error" });
887
+ }
888
+ }
889
+ })();
890
+ return {
891
+ cancel: () => {
892
+ controller.abort();
893
+ }
894
+ };
895
+ },
896
+ sendHitlResponse(response, runId) {
897
+ const sdk = config.getSdk();
898
+ if (!sdk) return;
899
+ const id = runId || currentRunId;
900
+ if (id) {
901
+ sdk.sendHitlResponse(id, response);
902
+ }
903
+ }
904
+ };
905
+ }
906
+
907
+ // src/renderer/components/MessageList.tsx
908
+ var import_react4 = require("react");
909
+ var import_jsx_runtime3 = require("react/jsx-runtime");
910
+ var SCROLL_THRESHOLD = 100;
911
+ var MessageList = (0, import_react4.memo)(function MessageList2({
912
+ messages,
913
+ className = "",
914
+ renderMessage,
915
+ autoScroll = true,
916
+ scrollBehavior = "smooth"
917
+ }) {
918
+ const containerRef = (0, import_react4.useRef)(null);
919
+ const isNearBottomRef = (0, import_react4.useRef)(true);
920
+ const checkIfNearBottom = (0, import_react4.useCallback)(() => {
921
+ const container = containerRef.current;
922
+ if (!container) return true;
923
+ return container.scrollTop <= SCROLL_THRESHOLD;
924
+ }, []);
925
+ const handleScroll = (0, import_react4.useCallback)(() => {
926
+ isNearBottomRef.current = checkIfNearBottom();
927
+ }, [checkIfNearBottom]);
928
+ const scrollToBottom = (0, import_react4.useCallback)(
929
+ (behavior = scrollBehavior) => {
930
+ containerRef.current?.scrollTo({ top: 0, behavior });
931
+ },
932
+ [scrollBehavior]
933
+ );
934
+ (0, import_react4.useEffect)(() => {
935
+ if (autoScroll && isNearBottomRef.current) {
936
+ scrollToBottom();
937
+ }
938
+ }, [messages, autoScroll, scrollToBottom]);
939
+ (0, import_react4.useEffect)(() => {
940
+ scrollToBottom("instant");
941
+ isNearBottomRef.current = true;
942
+ }, []);
943
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
944
+ "div",
945
+ {
946
+ ref: containerRef,
947
+ className: `${className} flex flex-col-reverse overflow-y-auto`,
948
+ role: "log",
949
+ "aria-live": "polite",
950
+ onScroll: handleScroll,
951
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex flex-col", children: messages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: renderMessage(message, index) }, message.id)) })
952
+ }
953
+ );
954
+ });
955
+
956
+ // src/renderer/components/MessageBubble.tsx
957
+ var import_react5 = require("react");
958
+ var import_jsx_runtime4 = require("react/jsx-runtime");
959
+ var MessageBubble = (0, import_react5.memo)(function MessageBubble2({
960
+ message,
961
+ className = "",
962
+ children,
963
+ renderContent
964
+ }) {
965
+ const isStreaming = message.isStreaming ?? false;
966
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className, "data-role": message.role, "data-streaming": isStreaming, children: [
967
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "message-content", children: renderContent ? renderContent(message.content, isStreaming) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
968
+ message.content,
969
+ isStreaming && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "streaming-cursor", children: "\u258C" })
970
+ ] }) }),
971
+ children
972
+ ] });
973
+ });
974
+
975
+ // src/renderer/components/ChatInput.tsx
976
+ var import_react6 = require("react");
977
+ var import_jsx_runtime5 = require("react/jsx-runtime");
978
+ var ChatInput = (0, import_react6.memo)(function ChatInput2({
979
+ onSend,
980
+ onStop,
981
+ placeholder = "Type a message...",
982
+ disabled = false,
983
+ isStreaming = false,
984
+ isLoading = false,
985
+ className = "",
986
+ textareaClassName = "",
987
+ sendButtonClassName = "",
988
+ stopButtonClassName = "",
989
+ sendButtonContent = "Send",
990
+ stopButtonContent = "Stop",
991
+ maxRows = 6,
992
+ autoFocus = false,
993
+ focusRef
994
+ }) {
995
+ const [text, setText] = (0, import_react6.useState)("");
996
+ const textareaRef = (0, import_react6.useRef)(null);
997
+ const canSend = text.trim().length > 0 && !disabled && !isLoading;
998
+ const maxHeight = maxRows * 20;
999
+ (0, import_react6.useEffect)(() => {
1000
+ if (focusRef) {
1001
+ focusRef.current = () => textareaRef.current?.focus();
1002
+ }
1003
+ }, [focusRef]);
1004
+ (0, import_react6.useEffect)(() => {
1005
+ if (autoFocus) {
1006
+ const timer = setTimeout(() => textareaRef.current?.focus(), 100);
1007
+ return () => clearTimeout(timer);
1008
+ }
1009
+ }, [autoFocus]);
1010
+ (0, import_react6.useEffect)(() => {
1011
+ const textarea = textareaRef.current;
1012
+ if (!textarea) return;
1013
+ textarea.style.height = "auto";
1014
+ textarea.style.height = `${Math.min(textarea.scrollHeight, maxHeight)}px`;
1015
+ }, [text, maxHeight]);
1016
+ const handleSubmit = (0, import_react6.useCallback)(
1017
+ (e) => {
1018
+ e?.preventDefault();
1019
+ if (!canSend) return;
1020
+ onSend(text.trim());
1021
+ setText("");
1022
+ if (textareaRef.current) textareaRef.current.style.height = "auto";
1023
+ },
1024
+ [text, canSend, onSend]
1025
+ );
1026
+ const handleKeyDown = (0, import_react6.useCallback)(
1027
+ (e) => {
1028
+ if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
1029
+ e.preventDefault();
1030
+ handleSubmit();
1031
+ }
1032
+ },
1033
+ [handleSubmit]
1034
+ );
1035
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("form", { onSubmit: handleSubmit, className, children: [
1036
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1037
+ "textarea",
1038
+ {
1039
+ ref: textareaRef,
1040
+ value: text,
1041
+ onChange: (e) => setText(e.target.value),
1042
+ onKeyDown: handleKeyDown,
1043
+ placeholder,
1044
+ disabled,
1045
+ rows: 1,
1046
+ className: textareaClassName
1047
+ }
1048
+ ),
1049
+ isStreaming ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: onStop, className: stopButtonClassName, children: stopButtonContent }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "submit", disabled: !canSend, className: sendButtonClassName, children: sendButtonContent })
1050
+ ] });
1051
+ });
1052
+
1053
+ // src/renderer/components/FloatingChat.tsx
1054
+ var import_react7 = require("react");
1055
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1056
+ var FloatingChat = (0, import_react7.memo)(function FloatingChat2({
1057
+ messages,
1058
+ isLoading,
1059
+ isStreaming,
1060
+ error,
1061
+ pendingInterrupt,
1062
+ onSendMessage,
1063
+ onStopStreaming,
1064
+ onApproveHitl,
1065
+ onRejectHitl,
1066
+ onHide,
1067
+ className = "",
1068
+ placeholder,
1069
+ locale = "en",
1070
+ renderMessage,
1071
+ renderContent,
1072
+ renderHitl,
1073
+ header,
1074
+ footer
1075
+ }) {
1076
+ const t = getTranslations(locale);
1077
+ const inputPlaceholder = placeholder ?? t.input.placeholder;
1078
+ const defaultRenderMessage = (0, import_react7.useCallback)(
1079
+ (message) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1080
+ MessageBubble,
1081
+ {
1082
+ message,
1083
+ className: `p-3 my-1 rounded-lg ${message.role === "user" ? "bg-blue-100 dark:bg-blue-900 ml-8" : "bg-gray-100 dark:bg-gray-800 mr-8"}`,
1084
+ renderContent
1085
+ }
1086
+ ),
1087
+ [renderContent]
1088
+ );
1089
+ const defaultRenderHitl = (0, import_react7.useCallback)(
1090
+ (interrupt, onApprove, onReject) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg m-2", children: [
1091
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "font-medium mb-2", children: interrupt.type === "approval_request" ? t.hitl.approvalRequired : t.hitl.inputRequired }),
1092
+ interrupt.tool && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "text-sm mb-2", children: [
1093
+ t.hitl.tool,
1094
+ ": ",
1095
+ interrupt.tool
1096
+ ] }),
1097
+ interrupt.reason && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-sm mb-2", children: interrupt.reason }),
1098
+ interrupt.question && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-sm mb-2", children: interrupt.question }),
1099
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex gap-2 mt-3", children: [
1100
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1101
+ "button",
1102
+ {
1103
+ onClick: onApprove,
1104
+ className: "px-3 py-1 bg-green-500 text-white rounded hover:bg-green-600",
1105
+ children: t.hitl.approve
1106
+ }
1107
+ ),
1108
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1109
+ "button",
1110
+ {
1111
+ onClick: onReject,
1112
+ className: "px-3 py-1 bg-red-500 text-white rounded hover:bg-red-600",
1113
+ children: t.hitl.reject
1114
+ }
1115
+ )
1116
+ ] })
1117
+ ] }),
1118
+ [t]
1119
+ );
1120
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `flex flex-col h-full ${className}`, children: [
1121
+ header && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex-shrink-0", children: header }),
1122
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1123
+ MessageList,
1124
+ {
1125
+ messages,
1126
+ className: "flex-1 p-2 min-h-0",
1127
+ renderMessage: renderMessage || defaultRenderMessage
1128
+ }
1129
+ ),
1130
+ pendingInterrupt && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex-shrink-0", children: (renderHitl || defaultRenderHitl)(
1131
+ pendingInterrupt,
1132
+ () => onApproveHitl?.(),
1133
+ () => onRejectHitl?.()
1134
+ ) }),
1135
+ error && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex-shrink-0 p-2 text-red-500 text-sm bg-red-50 dark:bg-red-900/20", children: error }),
1136
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex-shrink-0 p-2 border-t dark:border-gray-700", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1137
+ ChatInput,
1138
+ {
1139
+ onSend: onSendMessage,
1140
+ onStop: onStopStreaming,
1141
+ placeholder: inputPlaceholder,
1142
+ isStreaming,
1143
+ isLoading,
1144
+ disabled: !!pendingInterrupt,
1145
+ autoFocus: true,
1146
+ className: "flex gap-2",
1147
+ textareaClassName: "flex-1 resize-none rounded-lg border dark:border-gray-600 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-800",
1148
+ sendButtonClassName: "px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50",
1149
+ stopButtonClassName: "px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600"
1150
+ }
1151
+ ) }),
1152
+ footer && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex-shrink-0", children: footer })
1153
+ ] });
1154
+ });
1155
+ // Annotate the CommonJS export names for ESM import in node:
1156
+ 0 && (module.exports = {
1157
+ ChatInput,
1158
+ FloatingChat,
1159
+ I18nProvider,
1160
+ MessageBubble,
1161
+ MessageList,
1162
+ ThemeProvider,
1163
+ createIpcAdapter,
1164
+ createSdkAdapter,
1165
+ getTranslations,
1166
+ useChat,
1167
+ useI18n,
1168
+ useStandaloneI18n,
1169
+ useStandaloneTheme,
1170
+ useTheme
1171
+ });