@gendive/chatllm 0.22.1 → 0.23.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.
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  ArtifactCard: () => ArtifactCard,
34
+ ChatError: () => ChatError,
34
35
  ChatFloatingWidget: () => ChatFloatingWidget,
35
36
  ChatHeader: () => ChatHeader,
36
37
  ChatInput: () => ChatInput,
@@ -43,6 +44,7 @@ __export(index_exports, {
43
44
  ContentPartRenderer: () => ContentPartRenderer,
44
45
  DEFAULT_PROJECT_ID: () => DEFAULT_PROJECT_ID,
45
46
  DEFAULT_PROJECT_TITLE: () => DEFAULT_PROJECT_TITLE,
47
+ DEFAULT_RETRY_CONFIG: () => DEFAULT_RETRY_CONFIG,
46
48
  DeepResearchProgressUI: () => DeepResearchProgressUI,
47
49
  DevDiveAvatar: () => DevDiveAvatar,
48
50
  DevDiveFabCharacter: () => DevDiveFabCharacter,
@@ -66,28 +68,36 @@ __export(index_exports, {
66
68
  ResizeHandles: () => ResizeHandles,
67
69
  SettingsModal: () => SettingsModal,
68
70
  SkillProgressUI: () => SkillProgressUI,
71
+ classifyFetchError: () => classifyFetchError,
69
72
  convertSkillsToOpenAITools: () => convertSkillsToOpenAITools,
70
73
  convertToolsToSkills: () => convertToolsToSkills,
71
74
  createAdvancedResearchSkill: () => createAdvancedResearchSkill,
72
75
  createConversationSearchSkill: () => createConversationSearchSkill,
73
76
  createDeepResearchSkill: () => createDeepResearchSkill,
77
+ createTimeoutError: () => createTimeoutError,
74
78
  migrateSessionsToProjects: () => migrateSessionsToProjects,
79
+ parseSSELine: () => parseSSELine,
80
+ parseSSEResponse: () => parseSSEResponse,
75
81
  useChatUI: () => useChatUI,
82
+ useChecklist: () => useChecklist,
83
+ useContentParsers: () => useContentParsers,
76
84
  useDeepResearch: () => useDeepResearch,
77
85
  useDragResize: () => useDragResize,
78
86
  useFloatingWidget: () => useFloatingWidget,
79
87
  useImageError: () => useImageError,
80
88
  useObserver: () => useObserver,
81
89
  useProject: () => useProject,
82
- useSkills: () => useSkills
90
+ useSkills: () => useSkills,
91
+ useStreamingFetch: () => useStreamingFetch,
92
+ withRetry: () => withRetry
83
93
  });
84
94
  module.exports = __toCommonJS(index_exports);
85
95
 
86
96
  // src/react/ChatUI.tsx
87
- var import_react24 = __toESM(require("react"));
97
+ var import_react26 = __toESM(require("react"));
88
98
 
89
99
  // src/react/hooks/useChatUI.ts
90
- var import_react6 = require("react");
100
+ var import_react8 = require("react");
91
101
 
92
102
  // src/types.ts
93
103
  var DEFAULT_PERSONALIZATION = {
@@ -2144,6 +2154,462 @@ var parseContextRefs = (content) => {
2144
2154
  return { refs, cleanContent };
2145
2155
  };
2146
2156
 
2157
+ // src/react/utils/errors.ts
2158
+ var ChatError = class extends Error {
2159
+ code;
2160
+ retryable;
2161
+ statusCode;
2162
+ originalError;
2163
+ constructor(code, message, options) {
2164
+ super(message);
2165
+ this.name = "ChatError";
2166
+ this.code = code;
2167
+ this.statusCode = options?.statusCode;
2168
+ this.originalError = options?.originalError;
2169
+ this.retryable = options?.retryable ?? isRetryableCode(code);
2170
+ }
2171
+ };
2172
+ var classifyStatusCode = (status) => {
2173
+ if (status === 401 || status === 403) return "AUTH";
2174
+ if (status === 429) return "RATE_LIMIT";
2175
+ if (status >= 500) return "API";
2176
+ if (status >= 400) return "API";
2177
+ return "UNKNOWN";
2178
+ };
2179
+ var isRetryableCode = (code) => code === "NETWORK" || code === "RATE_LIMIT" || code === "API";
2180
+ var classifyFetchError = (error, response) => {
2181
+ if (error instanceof DOMException && error.name === "AbortError") {
2182
+ return new ChatError("ABORT", "\uC694\uCCAD\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", {
2183
+ originalError: error,
2184
+ retryable: false
2185
+ });
2186
+ }
2187
+ if (error instanceof Error && error.name === "AbortError") {
2188
+ return new ChatError("ABORT", "\uC694\uCCAD\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", {
2189
+ originalError: error,
2190
+ retryable: false
2191
+ });
2192
+ }
2193
+ if (error instanceof ChatError) return error;
2194
+ if (response && !response.ok) {
2195
+ const code = classifyStatusCode(response.status);
2196
+ const messages = {
2197
+ AUTH: "\uC778\uC99D\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. API \uD0A4\uB97C \uD655\uC778\uD574\uC8FC\uC138\uC694.",
2198
+ RATE_LIMIT: "\uC694\uCCAD \uD55C\uB3C4\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4. \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.",
2199
+ API: `\uC11C\uBC84 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4. (${response.status})`,
2200
+ NETWORK: "\uB124\uD2B8\uC6CC\uD06C \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.",
2201
+ TIMEOUT: "\uC694\uCCAD \uC2DC\uAC04\uC774 \uCD08\uACFC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.",
2202
+ ABORT: "\uC694\uCCAD\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.",
2203
+ UNKNOWN: "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4."
2204
+ };
2205
+ return new ChatError(code, messages[code], {
2206
+ statusCode: response.status,
2207
+ originalError: error
2208
+ });
2209
+ }
2210
+ if (error instanceof TypeError) {
2211
+ return new ChatError("NETWORK", "\uB124\uD2B8\uC6CC\uD06C \uC5F0\uACB0\uC744 \uD655\uC778\uD574\uC8FC\uC138\uC694.", {
2212
+ originalError: error
2213
+ });
2214
+ }
2215
+ const message = error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
2216
+ return new ChatError("UNKNOWN", message, { originalError: error });
2217
+ };
2218
+ var createTimeoutError = (timeoutMs) => new ChatError("TIMEOUT", `\uC2A4\uD2B8\uB9AC\uBC0D \uC751\uB2F5\uC774 ${timeoutMs / 1e3}\uCD08 \uB3D9\uC548 \uC5C6\uC2B5\uB2C8\uB2E4.`, {
2219
+ retryable: false
2220
+ });
2221
+
2222
+ // src/react/hooks/useStreamingFetch.ts
2223
+ var import_react6 = require("react");
2224
+ var DEFAULT_CHUNK_TIMEOUT = 3e4;
2225
+ var parseSSELine = (line) => {
2226
+ if (!line.trim()) return null;
2227
+ let data = line;
2228
+ if (line.startsWith("data: ")) {
2229
+ data = line.slice(6);
2230
+ if (data === "[DONE]") return null;
2231
+ }
2232
+ try {
2233
+ const parsed = JSON.parse(data);
2234
+ let usage = null;
2235
+ if (parsed.usage) {
2236
+ usage = {
2237
+ promptTokens: parsed.usage.prompt_tokens ?? parsed.usage.promptTokens ?? 0,
2238
+ completionTokens: parsed.usage.completion_tokens ?? parsed.usage.completionTokens ?? 0,
2239
+ totalTokens: parsed.usage.total_tokens ?? parsed.usage.totalTokens ?? 0
2240
+ };
2241
+ } else if (parsed.prompt_eval_count != null || parsed.eval_count != null) {
2242
+ usage = {
2243
+ promptTokens: parsed.prompt_eval_count ?? 0,
2244
+ completionTokens: parsed.eval_count ?? 0,
2245
+ totalTokens: (parsed.prompt_eval_count ?? 0) + (parsed.eval_count ?? 0)
2246
+ };
2247
+ }
2248
+ const delta = parsed.choices?.[0]?.delta ?? null;
2249
+ const finishReason = parsed.choices?.[0]?.finish_reason ?? null;
2250
+ const content = delta?.content ?? parsed.message?.content ?? parsed.content ?? parsed.text ?? "";
2251
+ const thinking = parsed.message?.thinking ?? "";
2252
+ return { content, thinking, finishReason, delta, usage, raw: parsed };
2253
+ } catch {
2254
+ return null;
2255
+ }
2256
+ };
2257
+ var parseSSEResponse = async (response) => {
2258
+ const reader = response.body?.getReader();
2259
+ if (!reader) return "";
2260
+ const decoder = new TextDecoder();
2261
+ let buffer = "";
2262
+ let result = "";
2263
+ try {
2264
+ while (true) {
2265
+ const { done, value } = await reader.read();
2266
+ if (done) break;
2267
+ buffer += decoder.decode(value, { stream: true });
2268
+ const lines = buffer.split("\n");
2269
+ buffer = lines.pop() || "";
2270
+ for (const line of lines) {
2271
+ const chunk = parseSSELine(line);
2272
+ if (chunk?.content) result += chunk.content;
2273
+ }
2274
+ }
2275
+ if (buffer.trim()) {
2276
+ const chunk = parseSSELine(buffer);
2277
+ if (chunk?.content) result += chunk.content;
2278
+ }
2279
+ } finally {
2280
+ reader.releaseLock();
2281
+ }
2282
+ return result;
2283
+ };
2284
+ var useStreamingFetch = (options = {}) => {
2285
+ const { chunkTimeout = DEFAULT_CHUNK_TIMEOUT } = options;
2286
+ const abortControllersRef = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
2287
+ const createAbortController = (0, import_react6.useCallback)((sessionId) => {
2288
+ const controller = new AbortController();
2289
+ abortControllersRef.current.set(sessionId, controller);
2290
+ return controller;
2291
+ }, []);
2292
+ const abort = (0, import_react6.useCallback)((sessionId) => {
2293
+ abortControllersRef.current.get(sessionId)?.abort();
2294
+ }, []);
2295
+ const cleanup = (0, import_react6.useCallback)((sessionId) => {
2296
+ abortControllersRef.current.delete(sessionId);
2297
+ }, []);
2298
+ const getSignal = (0, import_react6.useCallback)((sessionId) => {
2299
+ return abortControllersRef.current.get(sessionId)?.signal;
2300
+ }, []);
2301
+ const readWithTimeout = (0, import_react6.useCallback)(async (reader) => {
2302
+ let timerId;
2303
+ try {
2304
+ return await Promise.race([
2305
+ reader.read(),
2306
+ new Promise((_, reject) => {
2307
+ timerId = setTimeout(() => reject(createTimeoutError(chunkTimeout)), chunkTimeout);
2308
+ })
2309
+ ]);
2310
+ } finally {
2311
+ clearTimeout(timerId);
2312
+ }
2313
+ }, [chunkTimeout]);
2314
+ const streamResponse = (0, import_react6.useCallback)(async (response, onChunk, streamOptions) => {
2315
+ if (!response.ok) {
2316
+ throw classifyFetchError(new Error("API error"), response);
2317
+ }
2318
+ const reader = response.body?.getReader();
2319
+ if (!reader) throw new Error("No reader");
2320
+ const decoder = new TextDecoder();
2321
+ let buffer = "";
2322
+ let lastUsage = null;
2323
+ try {
2324
+ while (true) {
2325
+ const { done, value } = streamOptions?.noTimeout ? await reader.read() : await readWithTimeout(reader);
2326
+ if (done) break;
2327
+ buffer += decoder.decode(value, { stream: true });
2328
+ const lines = buffer.split("\n");
2329
+ buffer = lines.pop() || "";
2330
+ let shouldBreak = false;
2331
+ for (const line of lines) {
2332
+ const chunk = parseSSELine(line);
2333
+ if (!chunk) continue;
2334
+ if (chunk.usage) lastUsage = chunk.usage;
2335
+ const result = onChunk(chunk);
2336
+ if (result === "break") {
2337
+ shouldBreak = true;
2338
+ break;
2339
+ }
2340
+ }
2341
+ if (shouldBreak) break;
2342
+ }
2343
+ if (buffer.trim()) {
2344
+ const chunk = parseSSELine(buffer);
2345
+ if (chunk) {
2346
+ if (chunk.usage) lastUsage = chunk.usage;
2347
+ onChunk(chunk);
2348
+ }
2349
+ }
2350
+ } finally {
2351
+ reader.releaseLock();
2352
+ }
2353
+ return { usage: lastUsage };
2354
+ }, [readWithTimeout]);
2355
+ const fetchAndStream = (0, import_react6.useCallback)(async (fetchOptions, onChunk) => {
2356
+ const response = await fetch(fetchOptions.url, {
2357
+ method: "POST",
2358
+ headers: { "Content-Type": "application/json" },
2359
+ body: JSON.stringify(fetchOptions.body),
2360
+ signal: fetchOptions.signal
2361
+ });
2362
+ fetchOptions.onHeaders?.(response);
2363
+ return streamResponse(response, onChunk);
2364
+ }, [streamResponse]);
2365
+ return {
2366
+ abortControllers: abortControllersRef,
2367
+ createAbortController,
2368
+ abort,
2369
+ cleanup,
2370
+ getSignal,
2371
+ readWithTimeout,
2372
+ streamResponse,
2373
+ fetchAndStream,
2374
+ parseSSELine
2375
+ };
2376
+ };
2377
+
2378
+ // src/react/hooks/useChecklist.ts
2379
+ var import_react7 = require("react");
2380
+ var CHECKLIST_STEP_DELAY_MS = 100;
2381
+ var toActiveItems = (items) => items.map((it) => ({
2382
+ id: it.id,
2383
+ title: it.title,
2384
+ skill: it.skill,
2385
+ fileIndex: it.fileIndex,
2386
+ fileType: it.fileType,
2387
+ refImage: it.refImage,
2388
+ refStep: it.refStep
2389
+ }));
2390
+ var useChecklist = ({
2391
+ sessionsRef,
2392
+ sessions,
2393
+ setSessions,
2394
+ sendMessage,
2395
+ abortControllers,
2396
+ removeLoadingSession,
2397
+ pendingAttachmentDataRef,
2398
+ trackChecklistSkip,
2399
+ resolveChecklistRefImage,
2400
+ buildChecklistStepPrompt
2401
+ }) => {
2402
+ const skipNextChecklistParsingRef = (0, import_react7.useRef)(false);
2403
+ const activeChecklistRef = (0, import_react7.useRef)(null);
2404
+ const pendingChecklistRef = (0, import_react7.useRef)(null);
2405
+ const sendMessageRef = (0, import_react7.useRef)(sendMessage);
2406
+ sendMessageRef.current = sendMessage;
2407
+ const findSessionAndMessage = (0, import_react7.useCallback)(
2408
+ (messageId) => {
2409
+ const session = sessionsRef.current?.find(
2410
+ (s) => s.messages.some((m) => m.id === messageId)
2411
+ );
2412
+ if (!session) return null;
2413
+ const message = session.messages.find((m) => m.id === messageId);
2414
+ if (!message?.checklistBlock) return null;
2415
+ return { session, message };
2416
+ },
2417
+ [sessionsRef]
2418
+ );
2419
+ const executeStep = (0, import_react7.useCallback)(
2420
+ (items, stepIndex, messageId, sessionId, isFirst) => {
2421
+ skipNextChecklistParsingRef.current = true;
2422
+ setTimeout(() => {
2423
+ const item = items[stepIndex];
2424
+ const refUrl = resolveChecklistRefImage(item, messageId, sessionId);
2425
+ if (refUrl) {
2426
+ pendingAttachmentDataRef.current = [
2427
+ { name: "ref_image", mimeType: "image/png", base64: "", size: 0, url: refUrl }
2428
+ ];
2429
+ }
2430
+ sendMessageRef.current(
2431
+ buildChecklistStepPrompt(stepIndex, items.length, item, isFirst, refUrl),
2432
+ { hiddenUserMessage: true, isChecklistExecution: true, checklistStep: { index: stepIndex, title: item.title } }
2433
+ );
2434
+ }, CHECKLIST_STEP_DELAY_MS);
2435
+ },
2436
+ [resolveChecklistRefImage, buildChecklistStepPrompt, pendingAttachmentDataRef]
2437
+ );
2438
+ const updateChecklistItems = (0, import_react7.useCallback)(
2439
+ (sessionId, messageId, updater) => {
2440
+ setSessions(
2441
+ (prev) => prev.map((s) => {
2442
+ if (s.id !== sessionId) return s;
2443
+ return {
2444
+ ...s,
2445
+ messages: s.messages.map((m) => {
2446
+ if (m.id !== messageId || !m.checklistBlock) return m;
2447
+ const result = updater(m.checklistBlock.items, m.checklistBlock);
2448
+ return {
2449
+ ...m,
2450
+ checklistBlock: {
2451
+ ...m.checklistBlock,
2452
+ items: result.items,
2453
+ ...result.currentStep !== void 0 ? { currentStep: result.currentStep } : {},
2454
+ ...result.completed !== void 0 ? { completed: result.completed } : {}
2455
+ }
2456
+ };
2457
+ })
2458
+ };
2459
+ })
2460
+ );
2461
+ },
2462
+ [setSessions]
2463
+ );
2464
+ const handleChecklistStart = (0, import_react7.useCallback)(
2465
+ (messageId) => {
2466
+ const found = findSessionAndMessage(messageId);
2467
+ if (!found) return;
2468
+ const { session, message } = found;
2469
+ pendingChecklistRef.current = null;
2470
+ activeChecklistRef.current = {
2471
+ messageId,
2472
+ sessionId: session.id,
2473
+ items: toActiveItems(message.checklistBlock.items),
2474
+ currentStep: 0,
2475
+ stepResults: []
2476
+ };
2477
+ updateChecklistItems(session.id, messageId, (items) => ({
2478
+ items: items.map((it, idx) => ({
2479
+ ...it,
2480
+ status: idx === 0 ? "in_progress" : it.status
2481
+ })),
2482
+ currentStep: 0
2483
+ }));
2484
+ executeStep(message.checklistBlock.items, 0, messageId, session.id, true);
2485
+ },
2486
+ [findSessionAndMessage, updateChecklistItems, executeStep]
2487
+ );
2488
+ const handleChecklistAbort = (0, import_react7.useCallback)(() => {
2489
+ if (!activeChecklistRef.current) return;
2490
+ const checklist = activeChecklistRef.current;
2491
+ const stepIdx = checklist.currentStep;
2492
+ abortControllers.current?.get(checklist.sessionId)?.abort();
2493
+ updateChecklistItems(checklist.sessionId, checklist.messageId, (items) => ({
2494
+ items: items.map((it, idx) => ({
2495
+ ...it,
2496
+ status: idx === stepIdx ? "error" : it.status
2497
+ }))
2498
+ }));
2499
+ activeChecklistRef.current = null;
2500
+ removeLoadingSession(checklist.sessionId);
2501
+ }, [abortControllers, updateChecklistItems, removeLoadingSession]);
2502
+ const handleChecklistRetry = (0, import_react7.useCallback)(
2503
+ (messageId, stepIndex) => {
2504
+ const found = findSessionAndMessage(messageId);
2505
+ if (!found) return;
2506
+ const { session, message } = found;
2507
+ activeChecklistRef.current = {
2508
+ messageId,
2509
+ sessionId: session.id,
2510
+ items: toActiveItems(message.checklistBlock.items),
2511
+ currentStep: stepIndex,
2512
+ stepResults: message.checklistBlock.items.slice(0, stepIndex).map((it) => it.result || "")
2513
+ };
2514
+ updateChecklistItems(session.id, messageId, (items) => ({
2515
+ items: items.map((it, idx) => ({
2516
+ ...it,
2517
+ status: idx === stepIndex ? "in_progress" : idx > stepIndex ? "pending" : it.status
2518
+ })),
2519
+ currentStep: stepIndex
2520
+ }));
2521
+ executeStep(message.checklistBlock.items, stepIndex, messageId, session.id, stepIndex === 0);
2522
+ },
2523
+ [findSessionAndMessage, updateChecklistItems, executeStep]
2524
+ );
2525
+ const handleChecklistSkip = (0, import_react7.useCallback)(
2526
+ (messageId, stepIndex) => {
2527
+ const found = findSessionAndMessage(messageId);
2528
+ if (!found) return;
2529
+ const { session, message } = found;
2530
+ trackChecklistSkip();
2531
+ updateChecklistItems(session.id, messageId, (items) => ({
2532
+ items: items.map((it, idx) => ({
2533
+ ...it,
2534
+ status: idx === stepIndex ? "done" : it.status,
2535
+ result: idx === stepIndex ? "(\uAC74\uB108\uB700)" : it.result
2536
+ }))
2537
+ }));
2538
+ const nextPending = message.checklistBlock.items.findIndex(
2539
+ (it, idx) => idx > stepIndex && (it.status === "pending" || it.status === "error")
2540
+ );
2541
+ if (nextPending >= 0) {
2542
+ activeChecklistRef.current = {
2543
+ messageId,
2544
+ sessionId: session.id,
2545
+ items: toActiveItems(message.checklistBlock.items),
2546
+ currentStep: nextPending,
2547
+ stepResults: message.checklistBlock.items.slice(0, nextPending).map((it) => it.result || "(\uAC74\uB108\uB700)")
2548
+ };
2549
+ updateChecklistItems(session.id, messageId, (items) => ({
2550
+ items: items.map((it, idx) => ({
2551
+ ...it,
2552
+ status: idx === nextPending ? "in_progress" : it.status
2553
+ })),
2554
+ currentStep: nextPending
2555
+ }));
2556
+ executeStep(message.checklistBlock.items, nextPending, messageId, session.id, false);
2557
+ } else {
2558
+ const allResults = message.checklistBlock.items.map((it, i) => {
2559
+ const result = i === stepIndex ? "(\uAC74\uB108\uB700)" : it.result || "(\uAC74\uB108\uB700)";
2560
+ return `### ${i + 1}. ${it.title}
2561
+ ${result}`;
2562
+ }).join("\n\n");
2563
+ updateChecklistItems(session.id, messageId, () => ({
2564
+ items: message.checklistBlock.items.map((it, idx) => ({
2565
+ ...it,
2566
+ status: idx === stepIndex ? "done" : it.status,
2567
+ result: idx === stepIndex ? "(\uAC74\uB108\uB700)" : it.result
2568
+ })),
2569
+ completed: true
2570
+ }));
2571
+ skipNextChecklistParsingRef.current = true;
2572
+ setTimeout(() => {
2573
+ sendMessageRef.current(
2574
+ `\uBAA8\uB4E0 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uB2E8\uACC4\uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC544\uB798\uB294 \uAC01 \uB2E8\uACC4\uBCC4 \uACB0\uACFC\uC785\uB2C8\uB2E4:
2575
+
2576
+ ${allResults}
2577
+
2578
+ \uC704 \uACB0\uACFC\uB97C \uC885\uD569\uD558\uC5EC \uCD5C\uC885 \uACB0\uACFC\uBB3C\uC744 \uC644\uC131\uD574\uC8FC\uC138\uC694. checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \uB9C8\uC138\uC694.`,
2579
+ { hiddenUserMessage: true, isChecklistExecution: true }
2580
+ );
2581
+ }, CHECKLIST_STEP_DELAY_MS);
2582
+ }
2583
+ },
2584
+ [findSessionAndMessage, updateChecklistItems, executeStep, trackChecklistSkip]
2585
+ );
2586
+ const activeChecklistMessage = (0, import_react7.useMemo)(() => {
2587
+ const active = activeChecklistRef.current;
2588
+ if (!active) {
2589
+ for (const session2 of sessions) {
2590
+ const msg = session2.messages.find(
2591
+ (m) => m.checklistBlock && !m.checklistBlock.completed
2592
+ );
2593
+ if (msg) return msg;
2594
+ }
2595
+ return null;
2596
+ }
2597
+ const session = sessions.find((s) => s.id === active.sessionId);
2598
+ if (!session) return null;
2599
+ return session.messages.find((m) => m.id === active.messageId) || null;
2600
+ }, [sessions]);
2601
+ return {
2602
+ activeChecklistRef,
2603
+ pendingChecklistRef,
2604
+ skipNextChecklistParsingRef,
2605
+ handleChecklistStart,
2606
+ handleChecklistAbort,
2607
+ handleChecklistRetry,
2608
+ handleChecklistSkip,
2609
+ activeChecklistMessage
2610
+ };
2611
+ };
2612
+
2147
2613
  // src/react/utils/sessionCache.ts
2148
2614
  var buildCacheKey = (storageKey, sessionId) => `${storageKey}_cache_${sessionId}`;
2149
2615
  var writeSessionCache = (storageKey, session) => {
@@ -2176,38 +2642,14 @@ var removeSessionCache = (storageKey, sessionId) => {
2176
2642
  };
2177
2643
 
2178
2644
  // src/react/hooks/useChatUI.ts
2179
- var parseSSEResponse = async (response) => {
2180
- const reader = response.body?.getReader();
2181
- if (!reader) return "";
2182
- const decoder = new TextDecoder();
2183
- let buffer = "";
2184
- let result = "";
2185
- while (true) {
2186
- const { done, value } = await reader.read();
2187
- if (done) break;
2188
- buffer += decoder.decode(value, { stream: true });
2189
- const lines = buffer.split("\n");
2190
- buffer = lines.pop() || "";
2191
- for (const line of lines) {
2192
- if (line.startsWith("data: ")) {
2193
- const data = line.slice(6);
2194
- if (data === "[DONE]") continue;
2195
- try {
2196
- const parsed = JSON.parse(data);
2197
- const chunk = parsed.content ?? parsed.text ?? "";
2198
- if (chunk) result += chunk;
2199
- } catch {
2200
- }
2201
- }
2202
- }
2203
- }
2204
- return result;
2205
- };
2206
2645
  var DEFAULT_STORAGE_KEY2 = "chatllm_sessions";
2207
2646
  var DEFAULT_COMPRESSION_THRESHOLD = 20;
2208
2647
  var DEFAULT_KEEP_RECENT = 6;
2209
2648
  var DEFAULT_RECOMPRESSION_THRESHOLD = 10;
2210
2649
  var DEFAULT_TOKEN_LIMIT = 8e3;
2650
+ var DEFAULT_STREAM_CHUNK_TIMEOUT = 3e4;
2651
+ var DEFAULT_MAX_TOOL_CALL_DEPTH = 5;
2652
+ var DEFAULT_MAX_TOOL_RESULT_SIZE = 1e4;
2211
2653
  var DEFAULT_SESSION_CONTEXT_MAX_CHARS = 4e3;
2212
2654
  var DEFAULT_SESSION_CONTEXT_MAX_ITEMS = 10;
2213
2655
  var SESSION_CONTEXT_ITEM_MAX_CHARS = 500;
@@ -2334,6 +2776,12 @@ var useChatUI = (options) => {
2334
2776
  onSendMessage,
2335
2777
  onSessionChange,
2336
2778
  onError,
2779
+ streamChunkTimeout = DEFAULT_STREAM_CHUNK_TIMEOUT,
2780
+ retry: retryConfig,
2781
+ onAbort,
2782
+ onTokenUsage,
2783
+ maxToolCallDepth = DEFAULT_MAX_TOOL_CALL_DEPTH,
2784
+ maxToolResultSize = DEFAULT_MAX_TOOL_RESULT_SIZE,
2337
2785
  onTitleChange,
2338
2786
  generateTitle: generateTitleCallback,
2339
2787
  // Memory options
@@ -2395,74 +2843,78 @@ var useChatUI = (options) => {
2395
2843
  onChecklistStepModel
2396
2844
  } = options;
2397
2845
  const enableAutoExtraction = enableAutoExtractionProp ?? !useExternalStorage;
2398
- const [sessions, setSessions] = (0, import_react6.useState)([]);
2399
- const [currentSessionId, setCurrentSessionId] = (0, import_react6.useState)(null);
2400
- const [input, setInput] = (0, import_react6.useState)("");
2401
- const [loadingSessionIds, setLoadingSessionIds] = (0, import_react6.useState)(/* @__PURE__ */ new Set());
2846
+ const [sessions, setSessions] = (0, import_react8.useState)([]);
2847
+ const [currentSessionId, setCurrentSessionId] = (0, import_react8.useState)(null);
2848
+ const [input, setInput] = (0, import_react8.useState)("");
2849
+ const [loadingSessionIds, setLoadingSessionIds] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
2402
2850
  const isLoading = currentSessionId !== null && loadingSessionIds.has(currentSessionId);
2403
- const addLoadingSession = (0, import_react6.useCallback)((id) => setLoadingSessionIds((prev) => new Set(prev).add(id)), []);
2404
- const removeLoadingSession = (0, import_react6.useCallback)((id) => setLoadingSessionIds((prev) => {
2851
+ const addLoadingSession = (0, import_react8.useCallback)((id) => setLoadingSessionIds((prev) => new Set(prev).add(id)), []);
2852
+ const removeLoadingSession = (0, import_react8.useCallback)((id) => setLoadingSessionIds((prev) => {
2405
2853
  const next = new Set(prev);
2406
2854
  next.delete(id);
2407
2855
  return next;
2408
2856
  }), []);
2409
- const [selectedModel, setSelectedModel] = (0, import_react6.useState)(initialModel || models[0]?.id || "");
2410
- const [sidebarOpen, setSidebarOpen] = (0, import_react6.useState)(true);
2411
- const [settingsOpen, setSettingsOpen] = (0, import_react6.useState)(false);
2412
- const [quotedText, setQuotedText] = (0, import_react6.useState)(null);
2413
- const [selectedAction, setSelectedAction] = (0, import_react6.useState)(null);
2414
- const [copiedMessageId, setCopiedMessageId] = (0, import_react6.useState)(null);
2415
- const [editingMessageId, setEditingMessageId] = (0, import_react6.useState)(null);
2416
- const [personalization, setPersonalization] = (0, import_react6.useState)({
2857
+ const [selectedModel, setSelectedModel] = (0, import_react8.useState)(initialModel || models[0]?.id || "");
2858
+ const [sidebarOpen, setSidebarOpen] = (0, import_react8.useState)(true);
2859
+ const [settingsOpen, setSettingsOpen] = (0, import_react8.useState)(false);
2860
+ const [quotedText, setQuotedText] = (0, import_react8.useState)(null);
2861
+ const [selectedAction, setSelectedAction] = (0, import_react8.useState)(null);
2862
+ const [copiedMessageId, setCopiedMessageId] = (0, import_react8.useState)(null);
2863
+ const [editingMessageId, setEditingMessageId] = (0, import_react8.useState)(null);
2864
+ const [personalization, setPersonalization] = (0, import_react8.useState)({
2417
2865
  ...DEFAULT_PERSONALIZATION,
2418
2866
  ...initialPersonalization
2419
2867
  });
2420
- const [activeAlternatives, setActiveAlternatives] = (0, import_react6.useState)({});
2421
- const [loadingAlternativeFor, setLoadingAlternativeFor] = (0, import_react6.useState)(null);
2422
- const [isSessionsLoading, setIsSessionsLoading] = (0, import_react6.useState)(false);
2423
- const [isSessionLoading, setIsSessionLoading] = (0, import_react6.useState)(false);
2424
- const [isDeepResearchMode, setIsDeepResearchMode] = (0, import_react6.useState)(false);
2425
- const [attachments, setAttachments] = (0, import_react6.useState)([]);
2426
- const [isModelsLoading, setIsModelsLoading] = (0, import_react6.useState)(false);
2427
- const [loadedModels, setLoadedModels] = (0, import_react6.useState)(null);
2428
- const [deepResearchProgress, setDeepResearchProgress] = (0, import_react6.useState)(
2868
+ const [activeAlternatives, setActiveAlternatives] = (0, import_react8.useState)({});
2869
+ const [loadingAlternativeFor, setLoadingAlternativeFor] = (0, import_react8.useState)(null);
2870
+ const [isSessionsLoading, setIsSessionsLoading] = (0, import_react8.useState)(false);
2871
+ const [isSessionLoading, setIsSessionLoading] = (0, import_react8.useState)(false);
2872
+ const [isDeepResearchMode, setIsDeepResearchMode] = (0, import_react8.useState)(false);
2873
+ const [attachments, setAttachments] = (0, import_react8.useState)([]);
2874
+ const [isModelsLoading, setIsModelsLoading] = (0, import_react8.useState)(false);
2875
+ const [loadedModels, setLoadedModels] = (0, import_react8.useState)(null);
2876
+ const [deepResearchProgress, setDeepResearchProgress] = (0, import_react8.useState)(
2429
2877
  null
2430
2878
  );
2431
- const sessionsRef = (0, import_react6.useRef)(sessions);
2432
- (0, import_react6.useEffect)(() => {
2879
+ const sessionsRef = (0, import_react8.useRef)(sessions);
2880
+ (0, import_react8.useEffect)(() => {
2433
2881
  sessionsRef.current = sessions;
2434
2882
  }, [sessions]);
2435
- const modelsRef = (0, import_react6.useRef)(models);
2436
- (0, import_react6.useEffect)(() => {
2883
+ const modelsRef = (0, import_react8.useRef)(models);
2884
+ (0, import_react8.useEffect)(() => {
2437
2885
  modelsRef.current = models;
2438
2886
  }, [models]);
2439
- const onSendMessageRef = (0, import_react6.useRef)(onSendMessage);
2440
- const onResponseHeadersRef = (0, import_react6.useRef)(options.onResponseHeaders);
2441
- const onSessionChangeRef = (0, import_react6.useRef)(onSessionChange);
2442
- const onErrorRef = (0, import_react6.useRef)(onError);
2443
- const onTitleChangeRef = (0, import_react6.useRef)(onTitleChange);
2444
- const generateTitleRef = (0, import_react6.useRef)(generateTitleCallback);
2445
- const onPersonalizationChangeRef = (0, import_react6.useRef)(options.onPersonalizationChange);
2446
- const onPersonalizationSaveRef = (0, import_react6.useRef)(options.onPersonalizationSave);
2447
- const onLoadSessionsRef = (0, import_react6.useRef)(onLoadSessions);
2448
- const onCreateSessionRef = (0, import_react6.useRef)(onCreateSession);
2449
- const onLoadSessionRef = (0, import_react6.useRef)(onLoadSession);
2450
- const onDeleteSessionCallbackRef = (0, import_react6.useRef)(onDeleteSessionCallback);
2451
- const onUpdateSessionTitleRef = (0, import_react6.useRef)(onUpdateSessionTitle);
2452
- const onSaveMessagesRef = (0, import_react6.useRef)(onSaveMessages);
2453
- const onToolCallRef = (0, import_react6.useRef)(onToolCall);
2454
- const onSkillCompleteRef = (0, import_react6.useRef)(onSkillComplete);
2455
- const onSessionContextChangeRef = (0, import_react6.useRef)(onSessionContextChange);
2456
- const onLoadModelsRef = (0, import_react6.useRef)(onLoadModels);
2457
- const onUploadImageRef = (0, import_react6.useRef)(onUploadImage);
2458
- const onImageErrorRef = (0, import_react6.useRef)(onImageError);
2459
- const fileUploaderRef = (0, import_react6.useRef)(fileUploader);
2460
- const globalMemoryRef = (0, import_react6.useRef)(null);
2461
- (0, import_react6.useEffect)(() => {
2887
+ const onSendMessageRef = (0, import_react8.useRef)(onSendMessage);
2888
+ const onResponseHeadersRef = (0, import_react8.useRef)(options.onResponseHeaders);
2889
+ const onSessionChangeRef = (0, import_react8.useRef)(onSessionChange);
2890
+ const onErrorRef = (0, import_react8.useRef)(onError);
2891
+ const onAbortRef = (0, import_react8.useRef)(onAbort);
2892
+ const onTokenUsageRef = (0, import_react8.useRef)(onTokenUsage);
2893
+ const onTitleChangeRef = (0, import_react8.useRef)(onTitleChange);
2894
+ const generateTitleRef = (0, import_react8.useRef)(generateTitleCallback);
2895
+ const onPersonalizationChangeRef = (0, import_react8.useRef)(options.onPersonalizationChange);
2896
+ const onPersonalizationSaveRef = (0, import_react8.useRef)(options.onPersonalizationSave);
2897
+ const onLoadSessionsRef = (0, import_react8.useRef)(onLoadSessions);
2898
+ const onCreateSessionRef = (0, import_react8.useRef)(onCreateSession);
2899
+ const onLoadSessionRef = (0, import_react8.useRef)(onLoadSession);
2900
+ const onDeleteSessionCallbackRef = (0, import_react8.useRef)(onDeleteSessionCallback);
2901
+ const onUpdateSessionTitleRef = (0, import_react8.useRef)(onUpdateSessionTitle);
2902
+ const onSaveMessagesRef = (0, import_react8.useRef)(onSaveMessages);
2903
+ const onToolCallRef = (0, import_react8.useRef)(onToolCall);
2904
+ const onSkillCompleteRef = (0, import_react8.useRef)(onSkillComplete);
2905
+ const onSessionContextChangeRef = (0, import_react8.useRef)(onSessionContextChange);
2906
+ const onLoadModelsRef = (0, import_react8.useRef)(onLoadModels);
2907
+ const onUploadImageRef = (0, import_react8.useRef)(onUploadImage);
2908
+ const onImageErrorRef = (0, import_react8.useRef)(onImageError);
2909
+ const fileUploaderRef = (0, import_react8.useRef)(fileUploader);
2910
+ const globalMemoryRef = (0, import_react8.useRef)(null);
2911
+ (0, import_react8.useEffect)(() => {
2462
2912
  onSendMessageRef.current = onSendMessage;
2463
2913
  onResponseHeadersRef.current = options.onResponseHeaders;
2464
2914
  onSessionChangeRef.current = onSessionChange;
2465
2915
  onErrorRef.current = onError;
2916
+ onAbortRef.current = onAbort;
2917
+ onTokenUsageRef.current = onTokenUsage;
2466
2918
  onTitleChangeRef.current = onTitleChange;
2467
2919
  generateTitleRef.current = generateTitleCallback;
2468
2920
  onPersonalizationChangeRef.current = options.onPersonalizationChange;
@@ -2481,15 +2933,19 @@ var useChatUI = (options) => {
2481
2933
  fileUploaderRef.current = fileUploader;
2482
2934
  onLoadModelsRef.current = onLoadModels;
2483
2935
  });
2484
- const abortControllersRef = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
2485
- const pendingInitialLoadRef = (0, import_react6.useRef)(null);
2486
- const skipNextPollParsingRef = (0, import_react6.useRef)(false);
2487
- const skipNextSkillParsingRef = (0, import_react6.useRef)(false);
2488
- const skipNextChecklistParsingRef = (0, import_react6.useRef)(false);
2489
- const activeChecklistRef = (0, import_react6.useRef)(null);
2490
- const pendingChecklistRef = (0, import_react6.useRef)(null);
2491
- const pendingAttachmentDataRef = (0, import_react6.useRef)(null);
2492
- const lastExtractionMsgCountRef = (0, import_react6.useRef)(0);
2936
+ const abortControllersRef = (0, import_react8.useRef)(/* @__PURE__ */ new Map());
2937
+ const { streamResponse } = useStreamingFetch({ chunkTimeout: streamChunkTimeout });
2938
+ const pendingInitialLoadRef = (0, import_react8.useRef)(null);
2939
+ const skipNextPollParsingRef = (0, import_react8.useRef)(false);
2940
+ const toolCallDepthRef = (0, import_react8.useRef)(0);
2941
+ const skipNextSkillParsingRef = (0, import_react8.useRef)(false);
2942
+ const sendMessageForChecklistRef = (0, import_react8.useRef)(
2943
+ async () => {
2944
+ console.warn("[ChatUI] sendMessageForChecklistRef called before sendMessage initialized");
2945
+ }
2946
+ );
2947
+ const pendingAttachmentDataRef = (0, import_react8.useRef)(null);
2948
+ const lastExtractionMsgCountRef = (0, import_react8.useRef)(0);
2493
2949
  const resolveChecklistRefImage = (item, checklistMessageId, sessionId) => {
2494
2950
  const session = sessionsRef.current.find((s) => s.id === sessionId);
2495
2951
  if (!session) return null;
@@ -2556,7 +3012,7 @@ ${hints.join(" ")}` : "";
2556
3012
  return `${stepLabel}
2557
3013
  ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \uB9C8\uC138\uC694.`;
2558
3014
  };
2559
- const memoryOptions = (0, import_react6.useMemo)(
3015
+ const memoryOptions = (0, import_react8.useMemo)(
2560
3016
  () => ({
2561
3017
  storageType: globalMemoryConfig?.storageType || "localStorage",
2562
3018
  storageKey: globalMemoryConfig?.localStorage?.key || `${storageKey}_memory`,
@@ -2570,11 +3026,11 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
2570
3026
  const globalMemoryRaw = useGlobalMemory(memoryOptions);
2571
3027
  const globalMemory = useGlobalMemoryEnabled ? globalMemoryRaw : null;
2572
3028
  globalMemoryRef.current = globalMemory;
2573
- const stableToolCall = (0, import_react6.useCallback)(
3029
+ const stableToolCall = (0, import_react8.useCallback)(
2574
3030
  (name, params) => onToolCallRef.current(name, params),
2575
3031
  []
2576
3032
  );
2577
- const mergedSkills = (0, import_react6.useMemo)(() => {
3033
+ const mergedSkills = (0, import_react8.useMemo)(() => {
2578
3034
  if (!tools || !onToolCall) return skills || {};
2579
3035
  const toolSkills = convertToolsToSkills(tools, stableToolCall);
2580
3036
  return { ...skills || {}, ...toolSkills };
@@ -2610,9 +3066,9 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
2610
3066
  onDeleteProjectFile,
2611
3067
  onError
2612
3068
  });
2613
- const [projectSettingsOpen, setProjectSettingsOpen] = (0, import_react6.useState)(false);
3069
+ const [projectSettingsOpen, setProjectSettingsOpen] = (0, import_react8.useState)(false);
2614
3070
  const projectMemoryKey = enableProjects && projectHook.currentProjectId ? `${storageKey}_project_memory_${projectHook.currentProjectId}` : `${storageKey}_project_memory_none`;
2615
- const projectMemoryOptions = (0, import_react6.useMemo)(
3071
+ const projectMemoryOptions = (0, import_react8.useMemo)(
2616
3072
  () => ({
2617
3073
  storageType: globalMemoryConfig?.storageType || "localStorage",
2618
3074
  storageKey: projectMemoryKey,
@@ -2624,7 +3080,7 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
2624
3080
  );
2625
3081
  const projectMemoryRaw = useGlobalMemory(projectMemoryOptions);
2626
3082
  const projectMemory = enableProjects ? projectMemoryRaw : null;
2627
- const unwrapResponseHeaders = (0, import_react6.useCallback)((result) => {
3083
+ const unwrapResponseHeaders = (0, import_react8.useCallback)((result) => {
2628
3084
  if (typeof result === "object" && result !== null && "response" in result && "headers" in result) {
2629
3085
  const wrapped = result;
2630
3086
  onResponseHeadersRef.current?.(wrapped.headers);
@@ -2632,7 +3088,7 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
2632
3088
  }
2633
3089
  return result;
2634
3090
  }, []);
2635
- const emitFetchHeaders = (0, import_react6.useCallback)((response) => {
3091
+ const emitFetchHeaders = (0, import_react8.useCallback)((response) => {
2636
3092
  if (onResponseHeadersRef.current && response.headers) {
2637
3093
  const headerMap = {};
2638
3094
  response.headers.forEach((v, k) => {
@@ -2641,7 +3097,7 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
2641
3097
  onResponseHeadersRef.current(headerMap);
2642
3098
  }
2643
3099
  }, []);
2644
- const callInternalLLM = (0, import_react6.useCallback)(async (prompt, model) => {
3100
+ const callInternalLLM = (0, import_react8.useCallback)(async (prompt, model) => {
2645
3101
  if (onSendMessageRef.current) {
2646
3102
  const modelConfig = modelsRef.current.find((m) => m.id === model);
2647
3103
  const provider = modelConfig?.provider || "ollama";
@@ -2679,11 +3135,11 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
2679
3135
  const currentSession = sessions.find((s) => s.id === currentSessionId) || null;
2680
3136
  const messages = currentSession?.messages.filter((m) => !m.hidden) || [];
2681
3137
  const compressionState = currentSession?.compressionState || null;
2682
- const visibleSessions = (0, import_react6.useMemo)(() => {
3138
+ const visibleSessions = (0, import_react8.useMemo)(() => {
2683
3139
  if (!enableProjects || !projectHook.currentProjectId) return sessions;
2684
3140
  return sessions.filter((s) => s.projectId === projectHook.currentProjectId);
2685
3141
  }, [sessions, enableProjects, projectHook.currentProjectId]);
2686
- (0, import_react6.useEffect)(() => {
3142
+ (0, import_react8.useEffect)(() => {
2687
3143
  if (typeof window === "undefined") return;
2688
3144
  if (useExternalStorage && onLoadSessionsRef.current) {
2689
3145
  setIsSessionsLoading(true);
@@ -2745,7 +3201,7 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
2745
3201
  }
2746
3202
  }
2747
3203
  }, [storageKey, useExternalStorage, initialModel, startWithNewSession]);
2748
- (0, import_react6.useEffect)(() => {
3204
+ (0, import_react8.useEffect)(() => {
2749
3205
  if (!onLoadModelsRef.current) return;
2750
3206
  setIsModelsLoading(true);
2751
3207
  onLoadModelsRef.current().then((modelList) => {
@@ -2760,21 +3216,21 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
2760
3216
  });
2761
3217
  }, []);
2762
3218
  const effectiveModels = loadedModels || models;
2763
- (0, import_react6.useEffect)(() => {
3219
+ (0, import_react8.useEffect)(() => {
2764
3220
  if (typeof window === "undefined") return;
2765
3221
  if (useExternalStorage) return;
2766
3222
  if (sessions.length > 0) {
2767
3223
  localStorage.setItem(storageKey, JSON.stringify(sessions));
2768
3224
  }
2769
3225
  }, [sessions, storageKey, useExternalStorage]);
2770
- (0, import_react6.useEffect)(() => {
3226
+ (0, import_react8.useEffect)(() => {
2771
3227
  if (typeof window === "undefined") return;
2772
3228
  localStorage.setItem(`${storageKey}_personalization`, JSON.stringify(personalization));
2773
3229
  }, [personalization, storageKey]);
2774
- (0, import_react6.useEffect)(() => {
3230
+ (0, import_react8.useEffect)(() => {
2775
3231
  onSessionChangeRef.current?.(currentSession);
2776
3232
  }, [currentSession]);
2777
- const buildSystemPrompt = (0, import_react6.useCallback)((session) => {
3233
+ const buildSystemPrompt = (0, import_react8.useCallback)((session) => {
2778
3234
  const parts = [];
2779
3235
  const { userProfile, responseStyle, language } = personalization;
2780
3236
  const identityName = assistantName || "AI \uC5B4\uC2DC\uC2A4\uD134\uD2B8";
@@ -3133,7 +3589,7 @@ AI (\uD655\uC815):
3133
3589
  }
3134
3590
  return parts.length > 0 ? parts.join("\n") : "";
3135
3591
  }, [personalization, globalMemory, useGlobalMemoryEnabled, enablePoll, enableChecklist, buildSkillsPrompt, enableProjects, projectHook.currentProject, projectMemory, resolvedSkills, assistantName, onBuildSystemPrompt, compactSystemPrompt, selectedModel]);
3136
- const promoteToSessionContext = (0, import_react6.useCallback)((sessionId, skillName, content, metadata, label) => {
3592
+ const promoteToSessionContext = (0, import_react8.useCallback)((sessionId, skillName, content, metadata, label) => {
3137
3593
  const item = createSessionContextItem(skillName, content, metadata, label);
3138
3594
  setSessions(
3139
3595
  (prev) => prev.map((s) => {
@@ -3144,7 +3600,7 @@ AI (\uD655\uC815):
3144
3600
  })
3145
3601
  );
3146
3602
  }, []);
3147
- const compressContext = (0, import_react6.useCallback)(async (messagesToCompress, model) => {
3603
+ const compressContext = (0, import_react8.useCallback)(async (messagesToCompress, model) => {
3148
3604
  const conversationText = messagesToCompress.map((m) => `${m.role === "user" ? "\uC0AC\uC6A9\uC790" : "AI"}: ${m.content}`).join("\n\n");
3149
3605
  const summaryPrompt = `\uB2E4\uC74C \uB300\uD654 \uB0B4\uC6A9\uC744 \uD575\uC2EC \uC815\uBCF4\uB9CC \uC720\uC9C0\uD558\uBA74\uC11C \uAC04\uACB0\uD558\uAC8C \uC694\uC57D\uD574\uC8FC\uC138\uC694.
3150
3606
  \uC911\uC694\uD55C \uACB0\uC815\uC0AC\uD56D, \uC0AC\uC6A9\uC790 \uC694\uAD6C\uC0AC\uD56D, \uB9E5\uB77D \uC815\uBCF4\uB97C \uBCF4\uC874\uD558\uC138\uC694.
@@ -3159,7 +3615,7 @@ ${conversationText}
3159
3615
  return "";
3160
3616
  }
3161
3617
  }, [callInternalLLM]);
3162
- const incrementalCompressContext = (0, import_react6.useCallback)(
3618
+ const incrementalCompressContext = (0, import_react8.useCallback)(
3163
3619
  async (existingSummary, newMessages, model) => {
3164
3620
  const newConversation = newMessages.map((m) => `${m.role === "user" ? "\uC0AC\uC6A9\uC790" : "AI"}: ${m.content}`).join("\n\n");
3165
3621
  const mergePrompt = `\uAE30\uC874 \uB300\uD654 \uC694\uC57D\uACFC \uC0C8\uB85C\uC6B4 \uB300\uD654 \uB0B4\uC6A9\uC744 \uD1B5\uD569\uD558\uC5EC \uD558\uB098\uC758 \uC694\uC57D\uC73C\uB85C \uB9CC\uB4E4\uC5B4\uC8FC\uC138\uC694.
@@ -3186,10 +3642,10 @@ ${newConversation}
3186
3642
  },
3187
3643
  [callInternalLLM]
3188
3644
  );
3189
- const estimateTokens = (0, import_react6.useCallback)((messages2) => {
3645
+ const estimateTokens = (0, import_react8.useCallback)((messages2) => {
3190
3646
  return messages2.reduce((sum, m) => sum + Math.ceil(m.content.length / 4), 0);
3191
3647
  }, []);
3192
- const newSession = (0, import_react6.useCallback)(async () => {
3648
+ const newSession = (0, import_react8.useCallback)(async () => {
3193
3649
  const projectId = enableProjects ? projectHook.currentProjectId || DEFAULT_PROJECT_ID : void 0;
3194
3650
  if (useExternalStorage && onCreateSessionRef.current) {
3195
3651
  setIsSessionLoading(true);
@@ -3227,7 +3683,7 @@ ${newConversation}
3227
3683
  setSessions((prev) => [newSess, ...prev]);
3228
3684
  setCurrentSessionId(newSess.id);
3229
3685
  }, [selectedModel, useExternalStorage, enableProjects, projectHook.currentProjectId]);
3230
- const selectSession = (0, import_react6.useCallback)(async (id) => {
3686
+ const selectSession = (0, import_react8.useCallback)(async (id) => {
3231
3687
  if (useExternalStorage && onLoadSessionRef.current) {
3232
3688
  setIsSessionLoading(true);
3233
3689
  try {
@@ -3331,13 +3787,13 @@ ${newConversation}
3331
3787
  setSelectedModel(session.model);
3332
3788
  }
3333
3789
  }, [sessions, useExternalStorage, storageKey, initialModel]);
3334
- (0, import_react6.useEffect)(() => {
3790
+ (0, import_react8.useEffect)(() => {
3335
3791
  if (!pendingInitialLoadRef.current || !useExternalStorage) return;
3336
3792
  const id = pendingInitialLoadRef.current;
3337
3793
  pendingInitialLoadRef.current = null;
3338
3794
  selectSession(id);
3339
3795
  }, [currentSessionId, selectSession, useExternalStorage]);
3340
- const deleteSession = (0, import_react6.useCallback)(async (id) => {
3796
+ const deleteSession = (0, import_react8.useCallback)(async (id) => {
3341
3797
  if (useExternalStorage && onDeleteSessionCallbackRef.current) {
3342
3798
  try {
3343
3799
  await onDeleteSessionCallbackRef.current(id);
@@ -3365,7 +3821,7 @@ ${newConversation}
3365
3821
  return filtered;
3366
3822
  });
3367
3823
  }, [currentSessionId, storageKey, useExternalStorage]);
3368
- const renameSession = (0, import_react6.useCallback)(async (id, newTitle) => {
3824
+ const renameSession = (0, import_react8.useCallback)(async (id, newTitle) => {
3369
3825
  if (!newTitle.trim()) return;
3370
3826
  if (useExternalStorage && onUpdateSessionTitleRef.current) {
3371
3827
  try {
@@ -3388,7 +3844,7 @@ ${newConversation}
3388
3844
  );
3389
3845
  onTitleChangeRef.current?.(id, newTitle.trim());
3390
3846
  }, [useExternalStorage]);
3391
- const setModel = (0, import_react6.useCallback)((model) => {
3847
+ const setModel = (0, import_react8.useCallback)((model) => {
3392
3848
  setSelectedModel(model);
3393
3849
  if (currentSessionId) {
3394
3850
  setSessions(
@@ -3396,10 +3852,10 @@ ${newConversation}
3396
3852
  );
3397
3853
  }
3398
3854
  }, [currentSessionId]);
3399
- const toggleSidebar = (0, import_react6.useCallback)(() => setSidebarOpen((prev) => !prev), []);
3400
- const openSettings = (0, import_react6.useCallback)(() => setSettingsOpen(true), []);
3401
- const closeSettings = (0, import_react6.useCallback)(() => setSettingsOpen(false), []);
3402
- const copyMessage = (0, import_react6.useCallback)((content, id) => {
3855
+ const toggleSidebar = (0, import_react8.useCallback)(() => setSidebarOpen((prev) => !prev), []);
3856
+ const openSettings = (0, import_react8.useCallback)(() => setSettingsOpen(true), []);
3857
+ const closeSettings = (0, import_react8.useCallback)(() => setSettingsOpen(false), []);
3858
+ const copyMessage = (0, import_react8.useCallback)((content, id) => {
3403
3859
  if (typeof navigator !== "undefined") {
3404
3860
  navigator.clipboard.writeText(content).then(() => {
3405
3861
  setCopiedMessageId(id);
@@ -3407,30 +3863,30 @@ ${newConversation}
3407
3863
  });
3408
3864
  }
3409
3865
  }, []);
3410
- const startEdit = (0, import_react6.useCallback)((message) => {
3866
+ const startEdit = (0, import_react8.useCallback)((message) => {
3411
3867
  if (message.role === "user") {
3412
3868
  setEditingMessageId(message.id);
3413
3869
  }
3414
3870
  }, []);
3415
- const cancelEdit = (0, import_react6.useCallback)(() => {
3871
+ const cancelEdit = (0, import_react8.useCallback)(() => {
3416
3872
  setEditingMessageId(null);
3417
3873
  }, []);
3418
- const stopGeneration = (0, import_react6.useCallback)(() => {
3874
+ const stopGeneration = (0, import_react8.useCallback)(() => {
3419
3875
  if (currentSessionId) {
3420
3876
  abortControllersRef.current.get(currentSessionId)?.abort();
3421
3877
  }
3422
3878
  }, [currentSessionId]);
3423
- const updatePersonalization = (0, import_react6.useCallback)((config) => {
3879
+ const updatePersonalization = (0, import_react8.useCallback)((config) => {
3424
3880
  setPersonalization((prev) => {
3425
3881
  const next = { ...prev, ...config };
3426
3882
  onPersonalizationChangeRef.current?.(next);
3427
3883
  return next;
3428
3884
  });
3429
3885
  }, []);
3430
- const savePersonalization = (0, import_react6.useCallback)(() => {
3886
+ const savePersonalization = (0, import_react8.useCallback)(() => {
3431
3887
  onPersonalizationSaveRef.current?.(personalization);
3432
3888
  }, [personalization]);
3433
- const addAttachments = (0, import_react6.useCallback)((files) => {
3889
+ const addAttachments = (0, import_react8.useCallback)((files) => {
3434
3890
  const newAttachments = files.map((file) => {
3435
3891
  const isImage = file.type.startsWith("image/");
3436
3892
  return {
@@ -3445,7 +3901,7 @@ ${newConversation}
3445
3901
  });
3446
3902
  setAttachments((prev) => [...prev, ...newAttachments]);
3447
3903
  }, []);
3448
- const removeAttachment = (0, import_react6.useCallback)((id) => {
3904
+ const removeAttachment = (0, import_react8.useCallback)((id) => {
3449
3905
  setAttachments((prev) => {
3450
3906
  const target = prev.find((a) => a.id === id);
3451
3907
  if (target?.previewUrl) {
@@ -3454,17 +3910,17 @@ ${newConversation}
3454
3910
  return prev.filter((a) => a.id !== id);
3455
3911
  });
3456
3912
  }, []);
3457
- const toggleDeepResearchMode = (0, import_react6.useCallback)(() => {
3913
+ const toggleDeepResearchMode = (0, import_react8.useCallback)(() => {
3458
3914
  setIsDeepResearchMode((prev) => !prev);
3459
3915
  }, []);
3460
- const trackBehavior = (0, import_react6.useCallback)(async (key, updater) => {
3916
+ const trackBehavior = (0, import_react8.useCallback)(async (key, updater) => {
3461
3917
  if (!globalMemory) return;
3462
3918
  const fullKey = `behavior.${key}`;
3463
3919
  const prev = globalMemory.get(fullKey) ?? {};
3464
3920
  const next = updater(prev);
3465
3921
  await globalMemory.set(fullKey, next, { category: "behavior" });
3466
3922
  }, [globalMemory]);
3467
- const trackMessageLength = (0, import_react6.useCallback)((length) => {
3923
+ const trackMessageLength = (0, import_react8.useCallback)((length) => {
3468
3924
  trackBehavior("messageLength", (prev) => {
3469
3925
  const samples = (prev.messageLengthSamples || []).slice(-19);
3470
3926
  samples.push(length);
@@ -3472,39 +3928,69 @@ ${newConversation}
3472
3928
  return { ...prev, messageLengthSamples: samples, avgMessageLength: avg };
3473
3929
  });
3474
3930
  }, [trackBehavior]);
3475
- const trackSkillUsage = (0, import_react6.useCallback)((skillName) => {
3931
+ const trackSkillUsage = (0, import_react8.useCallback)((skillName) => {
3476
3932
  trackBehavior("skillUsage", (prev) => ({
3477
3933
  ...prev,
3478
3934
  [skillName]: (prev[skillName] || 0) + 1
3479
3935
  }));
3480
3936
  }, [trackBehavior]);
3481
- const trackChecklistSkip = (0, import_react6.useCallback)(() => {
3937
+ const trackChecklistSkip = (0, import_react8.useCallback)(() => {
3482
3938
  trackBehavior("checklistSkipRate", (prev) => ({
3483
3939
  total: (prev.total || 0) + 1,
3484
3940
  skipped: (prev.skipped || 0) + 1
3485
3941
  }));
3486
3942
  }, [trackBehavior]);
3487
- const trackChecklistComplete = (0, import_react6.useCallback)(() => {
3943
+ const trackChecklistComplete = (0, import_react8.useCallback)(() => {
3488
3944
  trackBehavior("checklistSkipRate", (prev) => ({
3489
3945
  total: (prev.total || 0) + 1,
3490
3946
  skipped: prev.skipped || 0
3491
3947
  }));
3492
3948
  }, [trackBehavior]);
3493
- const trackRegenerate = (0, import_react6.useCallback)(() => {
3949
+ const {
3950
+ activeChecklistRef,
3951
+ pendingChecklistRef,
3952
+ skipNextChecklistParsingRef,
3953
+ handleChecklistStart,
3954
+ handleChecklistAbort,
3955
+ handleChecklistRetry,
3956
+ handleChecklistSkip
3957
+ } = useChecklist({
3958
+ sessionsRef,
3959
+ sessions,
3960
+ setSessions,
3961
+ sendMessage: (...args) => sendMessageForChecklistRef.current(...args),
3962
+ abortControllers: abortControllersRef,
3963
+ removeLoadingSession,
3964
+ pendingAttachmentDataRef,
3965
+ trackChecklistSkip,
3966
+ resolveChecklistRefImage,
3967
+ buildChecklistStepPrompt
3968
+ });
3969
+ const trackRegenerate = (0, import_react8.useCallback)(() => {
3494
3970
  trackBehavior("regenerateRate", (prev) => ({
3495
3971
  ...prev,
3496
3972
  regenerated: (prev.regenerated || 0) + 1
3497
3973
  }));
3498
3974
  }, [trackBehavior]);
3499
- const trackMessageSent = (0, import_react6.useCallback)(() => {
3975
+ const trackMessageSent = (0, import_react8.useCallback)(() => {
3500
3976
  trackBehavior("regenerateRate", (prev) => ({
3501
3977
  total: (prev.total || 0) + 1,
3502
3978
  regenerated: prev.regenerated || 0
3503
3979
  }));
3504
3980
  }, [trackBehavior]);
3505
- const sendMessage = (0, import_react6.useCallback)(async (content, options2) => {
3981
+ const sendMessage = (0, import_react8.useCallback)(async (content, options2) => {
3506
3982
  const messageContent = content || input;
3507
3983
  if (!messageContent.trim() && attachments.length === 0 || isLoading) return;
3984
+ if (options2?.hiddenUserMessage) {
3985
+ toolCallDepthRef.current += 1;
3986
+ if (toolCallDepthRef.current > maxToolCallDepth) {
3987
+ toolCallDepthRef.current = 0;
3988
+ onErrorRef.current?.(new Error(`\uB3C4\uAD6C \uD638\uCD9C \uAE4A\uC774 \uC81C\uD55C \uCD08\uACFC (\uCD5C\uB300 ${maxToolCallDepth}\uD68C)`));
3989
+ return;
3990
+ }
3991
+ } else {
3992
+ toolCallDepthRef.current = 0;
3993
+ }
3508
3994
  let sessionId = currentSessionId;
3509
3995
  if (!sessionId) {
3510
3996
  if (useExternalStorage && onCreateSessionRef.current) {
@@ -3790,10 +4276,11 @@ ${finalContent}`;
3790
4276
  return;
3791
4277
  }
3792
4278
  abortControllersRef.current.set(capturedSessionId, new AbortController());
4279
+ let accumulatedContent = "";
4280
+ let lastUsage = null;
3793
4281
  try {
3794
4282
  const shouldSkipSkillParsing = skipNextSkillParsingRef.current;
3795
4283
  skipNextSkillParsingRef.current = false;
3796
- let accumulatedContent = "";
3797
4284
  let checklistStepImageUrl = null;
3798
4285
  let messagesToSend = [...existingMessages, userMessage];
3799
4286
  const recompressionThreshold = DEFAULT_RECOMPRESSION_THRESHOLD;
@@ -3999,119 +4486,71 @@ ${attachmentContext}
3999
4486
  emitFetchHeaders(response);
4000
4487
  }
4001
4488
  if (response) {
4002
- if (!response.ok) throw new Error("API error");
4003
- const reader = response.body?.getReader();
4004
- if (!reader) throw new Error("No reader");
4005
- const decoder = new TextDecoder();
4006
- let buffer = "";
4007
4489
  let skillTagDetected = false;
4008
4490
  let checklistTagDetected = false;
4009
4491
  let toolCallAcc = null;
4010
- while (true) {
4011
- const { done, value } = await reader.read();
4012
- if (done) break;
4013
- buffer += decoder.decode(value, { stream: true });
4014
- const lines = buffer.split("\n");
4015
- buffer = lines.pop() || "";
4016
- for (const line of lines) {
4017
- if (!line.trim()) continue;
4018
- let data = line;
4019
- if (line.startsWith("data: ")) {
4020
- data = line.slice(6);
4021
- if (data === "[DONE]") continue;
4022
- }
4023
- try {
4024
- const parsed = JSON.parse(data);
4025
- const delta = parsed.choices?.[0]?.delta;
4026
- const finishReason = parsed.choices?.[0]?.finish_reason;
4027
- if (useNativeTools && delta?.tool_calls) {
4028
- toolCallAcc = accumulateToolCallDelta(toolCallAcc, delta);
4029
- }
4030
- if (useNativeTools && finishReason === "tool_calls" && toolCallAcc) {
4031
- accumulatedContent += toolCallToSkillUseXml(toolCallAcc);
4032
- skillTagDetected = true;
4033
- break;
4034
- }
4035
- const content2 = delta?.content || parsed.message?.content || parsed.content || parsed.text || "";
4036
- const thinking = parsed.message?.thinking || "";
4037
- if (content2 || thinking) {
4038
- if (content2) accumulatedContent += content2;
4039
- if (!shouldSkipSkillParsing && accumulatedContent.includes("</skill_use>")) {
4040
- const endIdx = accumulatedContent.indexOf("</skill_use>");
4041
- accumulatedContent = accumulatedContent.substring(0, endIdx + "</skill_use>".length);
4042
- skillTagDetected = true;
4043
- }
4044
- if (!skipNextChecklistParsingRef.current && accumulatedContent.includes("</checklist>")) {
4045
- const endIdx = accumulatedContent.indexOf("</checklist>");
4046
- accumulatedContent = accumulatedContent.substring(0, endIdx + "</checklist>".length);
4047
- checklistTagDetected = true;
4048
- }
4049
- const displayContent = skillTagDetected ? accumulatedContent : null;
4050
- setSessions(
4051
- (prev) => prev.map((s) => {
4052
- if (s.id === capturedSessionId) {
4053
- return {
4054
- ...s,
4055
- messages: s.messages.map((m) => {
4056
- if (m.id !== assistantMessageId) return m;
4057
- if (displayContent) {
4058
- return { ...m, content: displayContent };
4059
- }
4060
- let newContent = m.content;
4061
- if (thinking) {
4062
- if (!newContent.includes("<thinking>")) {
4063
- newContent = "<thinking>" + thinking;
4064
- } else if (!newContent.includes("</thinking>")) {
4065
- newContent += thinking;
4066
- }
4067
- }
4068
- if (content2) {
4069
- if (newContent.includes("<thinking>") && !newContent.includes("</thinking>")) {
4070
- newContent += "</thinking>\n\n";
4071
- }
4072
- newContent += content2;
4073
- }
4074
- return { ...m, content: newContent };
4075
- })
4076
- };
4077
- }
4078
- return s;
4079
- })
4080
- );
4081
- if (skillTagDetected || checklistTagDetected) break;
4082
- }
4083
- } catch {
4084
- }
4492
+ const streamResult = await streamResponse(response, (chunk) => {
4493
+ if (useNativeTools && chunk.delta?.tool_calls) {
4494
+ toolCallAcc = accumulateToolCallDelta(toolCallAcc, chunk.delta);
4085
4495
  }
4086
- if (skillTagDetected || checklistTagDetected) break;
4087
- }
4088
- if (useNativeTools && toolCallAcc && !skillTagDetected) {
4089
- accumulatedContent += toolCallToSkillUseXml(toolCallAcc);
4090
- skillTagDetected = true;
4091
- }
4092
- if (buffer.trim()) {
4093
- try {
4094
- const parsed = JSON.parse(buffer);
4095
- const content2 = parsed.message?.content || parsed.content || parsed.text;
4096
- if (content2) {
4097
- accumulatedContent += content2;
4098
- setSessions(
4099
- (prev) => prev.map((s) => {
4100
- if (s.id === capturedSessionId) {
4101
- return {
4102
- ...s,
4103
- messages: s.messages.map(
4104
- (m) => m.id === assistantMessageId ? { ...m, content: m.content + content2 } : m
4105
- )
4106
- };
4107
- }
4108
- return s;
4109
- })
4110
- );
4111
- }
4112
- } catch {
4496
+ if (useNativeTools && chunk.finishReason === "tool_calls" && toolCallAcc) {
4497
+ accumulatedContent += toolCallToSkillUseXml(toolCallAcc);
4498
+ skillTagDetected = true;
4499
+ return "break";
4113
4500
  }
4114
- }
4501
+ const { content: content2, thinking } = chunk;
4502
+ if (content2 || thinking) {
4503
+ if (content2) accumulatedContent += content2;
4504
+ if (!shouldSkipSkillParsing && accumulatedContent.includes("</skill_use>")) {
4505
+ const endIdx = accumulatedContent.indexOf("</skill_use>");
4506
+ accumulatedContent = accumulatedContent.substring(0, endIdx + "</skill_use>".length);
4507
+ skillTagDetected = true;
4508
+ }
4509
+ if (!skipNextChecklistParsingRef.current && accumulatedContent.includes("</checklist>")) {
4510
+ const endIdx = accumulatedContent.indexOf("</checklist>");
4511
+ accumulatedContent = accumulatedContent.substring(0, endIdx + "</checklist>".length);
4512
+ checklistTagDetected = true;
4513
+ }
4514
+ const displayContent = skillTagDetected ? accumulatedContent : null;
4515
+ setSessions(
4516
+ (prev) => prev.map((s) => {
4517
+ if (s.id === capturedSessionId) {
4518
+ return {
4519
+ ...s,
4520
+ messages: s.messages.map((m) => {
4521
+ if (m.id !== assistantMessageId) return m;
4522
+ if (displayContent) {
4523
+ return { ...m, content: displayContent };
4524
+ }
4525
+ let newContent = m.content;
4526
+ if (thinking) {
4527
+ if (!newContent.includes("<thinking>")) {
4528
+ newContent = "<thinking>" + thinking;
4529
+ } else if (!newContent.includes("</thinking>")) {
4530
+ newContent += thinking;
4531
+ }
4532
+ }
4533
+ if (content2) {
4534
+ if (newContent.includes("<thinking>") && !newContent.includes("</thinking>")) {
4535
+ newContent += "</thinking>\n\n";
4536
+ }
4537
+ newContent += content2;
4538
+ }
4539
+ return { ...m, content: newContent };
4540
+ })
4541
+ };
4542
+ }
4543
+ return s;
4544
+ })
4545
+ );
4546
+ if (skillTagDetected || checklistTagDetected) return "break";
4547
+ }
4548
+ });
4549
+ lastUsage = streamResult.usage;
4550
+ if (useNativeTools && toolCallAcc && !skillTagDetected) {
4551
+ accumulatedContent += toolCallToSkillUseXml(toolCallAcc);
4552
+ skillTagDetected = true;
4553
+ }
4115
4554
  }
4116
4555
  const saveMessagesOnEarlyReturn = (overrideContentParts) => {
4117
4556
  if (!useExternalStorage || !capturedSessionId) return;
@@ -4472,9 +4911,10 @@ ${result.content}
4472
4911
  }
4473
4912
  skipNextSkillParsingRef.current = true;
4474
4913
  saveMessagesOnEarlyReturn();
4914
+ const truncatedResult = result.content.length > maxToolResultSize ? result.content.substring(0, maxToolResultSize) + "\n\n...(truncated)" : result.content;
4475
4915
  const resultPrompt = `\uC2A4\uD0AC "${detectedSkill.name}" \uC2E4\uD589 \uACB0\uACFC:
4476
4916
 
4477
- ${result.content}
4917
+ ${truncatedResult}
4478
4918
 
4479
4919
  \uC704 \uACB0\uACFC\uB97C \uBC14\uD0D5\uC73C\uB85C \uC0AC\uC6A9\uC790\uC758 \uC6D0\uB798 \uC9C8\uBB38\uC5D0 \uB2F5\uBCC0\uD574\uC8FC\uC138\uC694. skill_use \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \uB9C8\uC138\uC694.`;
4480
4920
  setTimeout(() => {
@@ -4800,6 +5240,32 @@ ${stepSummary}
4800
5240
  };
4801
5241
  })
4802
5242
  );
5243
+ if (lastUsage && capturedSessionId) {
5244
+ const usage = lastUsage;
5245
+ setSessions(
5246
+ (prev) => prev.map((s) => {
5247
+ if (s.id !== capturedSessionId) return s;
5248
+ const prevStats = s.tokenStats ?? {
5249
+ totalPromptTokens: 0,
5250
+ totalCompletionTokens: 0,
5251
+ totalTokens: 0,
5252
+ messageCount: 0,
5253
+ lastUpdated: 0
5254
+ };
5255
+ return {
5256
+ ...s,
5257
+ tokenStats: {
5258
+ totalPromptTokens: prevStats.totalPromptTokens + usage.promptTokens,
5259
+ totalCompletionTokens: prevStats.totalCompletionTokens + usage.completionTokens,
5260
+ totalTokens: prevStats.totalTokens + usage.totalTokens,
5261
+ messageCount: prevStats.messageCount + 1,
5262
+ lastUpdated: Date.now()
5263
+ }
5264
+ };
5265
+ })
5266
+ );
5267
+ onTokenUsageRef.current?.(capturedSessionId, usage);
5268
+ }
4803
5269
  if (useExternalStorage && capturedSessionId) {
4804
5270
  const assistantContentForSave = accumulatedContent;
4805
5271
  if (assistantContentForSave && onSaveMessagesRef.current) {
@@ -4839,18 +5305,32 @@ ${stepSummary}
4839
5305
  observer.processMessages(capturedSessionId, newMessages);
4840
5306
  }
4841
5307
  } catch (error) {
4842
- if (error instanceof Error && error.name === "AbortError") {
5308
+ const classified = classifyFetchError(error);
5309
+ if (classified.code === "ABORT") {
5310
+ if (accumulatedContent) {
5311
+ setSessions(
5312
+ (prev) => prev.map((s) => {
5313
+ if (s.id !== capturedSessionId) return s;
5314
+ return {
5315
+ ...s,
5316
+ messages: s.messages.map(
5317
+ (m) => m.id === assistantMessageId ? { ...m, content: accumulatedContent } : m
5318
+ )
5319
+ };
5320
+ })
5321
+ );
5322
+ onAbortRef.current?.(capturedSessionId, accumulatedContent);
5323
+ }
4843
5324
  return;
4844
5325
  }
4845
- const err = error instanceof Error ? error : new Error("Unknown error");
4846
- onErrorRef.current?.(err);
5326
+ onErrorRef.current?.(classified);
4847
5327
  setSessions(
4848
5328
  (prev) => prev.map((s) => {
4849
5329
  if (s.id === capturedSessionId) {
4850
5330
  return {
4851
5331
  ...s,
4852
5332
  messages: s.messages.map(
4853
- (m) => m.id === assistantMessageId ? { ...m, content: "\uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694." } : m
5333
+ (m) => m.id === assistantMessageId ? { ...m, content: accumulatedContent || "\uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694." } : m
4854
5334
  )
4855
5335
  };
4856
5336
  }
@@ -4883,241 +5363,8 @@ ${stepSummary}
4883
5363
  attachments,
4884
5364
  continueAfterToolResult
4885
5365
  ]);
4886
- const handleChecklistStart = (0, import_react6.useCallback)(
4887
- (messageId) => {
4888
- const session = sessionsRef.current.find(
4889
- (s) => s.messages.some((m) => m.id === messageId)
4890
- );
4891
- if (!session) return;
4892
- const message = session.messages.find((m) => m.id === messageId);
4893
- if (!message?.checklistBlock) return;
4894
- pendingChecklistRef.current = null;
4895
- activeChecklistRef.current = {
4896
- messageId,
4897
- sessionId: session.id,
4898
- items: message.checklistBlock.items.map((it) => ({ id: it.id, title: it.title, skill: it.skill, fileIndex: it.fileIndex, fileType: it.fileType, refImage: it.refImage, refStep: it.refStep })),
4899
- currentStep: 0,
4900
- stepResults: []
4901
- };
4902
- setSessions(
4903
- (prev) => prev.map((s) => {
4904
- if (s.id !== session.id) return s;
4905
- return {
4906
- ...s,
4907
- messages: s.messages.map((m) => {
4908
- if (m.id !== messageId || !m.checklistBlock) return m;
4909
- const updatedItems = m.checklistBlock.items.map((it, idx) => ({
4910
- ...it,
4911
- status: idx === 0 ? "in_progress" : it.status
4912
- }));
4913
- return {
4914
- ...m,
4915
- checklistBlock: { ...m.checklistBlock, items: updatedItems, currentStep: 0 }
4916
- };
4917
- })
4918
- };
4919
- })
4920
- );
4921
- skipNextChecklistParsingRef.current = true;
4922
- setTimeout(() => {
4923
- const items = message.checklistBlock.items;
4924
- const refUrl = resolveChecklistRefImage(items[0], messageId, session.id);
4925
- if (refUrl) pendingAttachmentDataRef.current = [{ name: "ref_image", mimeType: "image/png", base64: "", size: 0, url: refUrl }];
4926
- sendMessage(
4927
- buildChecklistStepPrompt(0, items.length, items[0], true, refUrl),
4928
- { hiddenUserMessage: true, isChecklistExecution: true, checklistStep: { index: 0, title: items[0].title } }
4929
- );
4930
- }, 100);
4931
- },
4932
- [sendMessage]
4933
- );
4934
- const handleChecklistAbort = (0, import_react6.useCallback)(() => {
4935
- if (!activeChecklistRef.current) return;
4936
- const checklist = activeChecklistRef.current;
4937
- const stepIdx = checklist.currentStep;
4938
- abortControllersRef.current.get(checklist.sessionId)?.abort();
4939
- setSessions(
4940
- (prev) => prev.map((s) => {
4941
- if (s.id !== checklist.sessionId) return s;
4942
- return {
4943
- ...s,
4944
- messages: s.messages.map((m) => {
4945
- if (m.id !== checklist.messageId || !m.checklistBlock) return m;
4946
- return {
4947
- ...m,
4948
- checklistBlock: {
4949
- ...m.checklistBlock,
4950
- items: m.checklistBlock.items.map((it, idx) => ({
4951
- ...it,
4952
- status: idx === stepIdx ? "error" : it.status
4953
- }))
4954
- }
4955
- };
4956
- })
4957
- };
4958
- })
4959
- );
4960
- activeChecklistRef.current = null;
4961
- removeLoadingSession(checklist.sessionId);
4962
- }, [removeLoadingSession]);
4963
- const handleChecklistRetry = (0, import_react6.useCallback)(
4964
- (messageId, stepIndex) => {
4965
- const session = sessionsRef.current.find(
4966
- (s) => s.messages.some((m) => m.id === messageId)
4967
- );
4968
- if (!session) return;
4969
- const message = session.messages.find((m) => m.id === messageId);
4970
- if (!message?.checklistBlock) return;
4971
- activeChecklistRef.current = {
4972
- messageId,
4973
- sessionId: session.id,
4974
- items: message.checklistBlock.items.map((it) => ({ id: it.id, title: it.title, skill: it.skill, fileIndex: it.fileIndex, fileType: it.fileType, refImage: it.refImage, refStep: it.refStep })),
4975
- currentStep: stepIndex,
4976
- stepResults: message.checklistBlock.items.slice(0, stepIndex).map((it) => it.result || "")
4977
- };
4978
- setSessions(
4979
- (prev) => prev.map((s) => {
4980
- if (s.id !== session.id) return s;
4981
- return {
4982
- ...s,
4983
- messages: s.messages.map((m) => {
4984
- if (m.id !== messageId || !m.checklistBlock) return m;
4985
- return {
4986
- ...m,
4987
- checklistBlock: {
4988
- ...m.checklistBlock,
4989
- items: m.checklistBlock.items.map((it, idx) => ({
4990
- ...it,
4991
- status: idx === stepIndex ? "in_progress" : idx > stepIndex ? "pending" : it.status
4992
- })),
4993
- currentStep: stepIndex
4994
- }
4995
- };
4996
- })
4997
- };
4998
- })
4999
- );
5000
- skipNextChecklistParsingRef.current = true;
5001
- setTimeout(() => {
5002
- const items = message.checklistBlock.items;
5003
- const refUrl = resolveChecklistRefImage(items[stepIndex], messageId, session.id);
5004
- if (refUrl) pendingAttachmentDataRef.current = [{ name: "ref_image", mimeType: "image/png", base64: "", size: 0, url: refUrl }];
5005
- sendMessage(
5006
- buildChecklistStepPrompt(stepIndex, items.length, items[stepIndex], stepIndex === 0, refUrl),
5007
- { hiddenUserMessage: true, isChecklistExecution: true, checklistStep: { index: stepIndex, title: items[stepIndex].title } }
5008
- );
5009
- }, 100);
5010
- },
5011
- [sendMessage]
5012
- );
5013
- const handleChecklistSkip = (0, import_react6.useCallback)(
5014
- (messageId, stepIndex) => {
5015
- const session = sessionsRef.current.find(
5016
- (s) => s.messages.some((m) => m.id === messageId)
5017
- );
5018
- if (!session) return;
5019
- const message = session.messages.find((m) => m.id === messageId);
5020
- if (!message?.checklistBlock) return;
5021
- trackChecklistSkip();
5022
- setSessions(
5023
- (prev) => prev.map((s) => {
5024
- if (s.id !== session.id) return s;
5025
- return {
5026
- ...s,
5027
- messages: s.messages.map((m) => {
5028
- if (m.id !== messageId || !m.checklistBlock) return m;
5029
- return {
5030
- ...m,
5031
- checklistBlock: {
5032
- ...m.checklistBlock,
5033
- items: m.checklistBlock.items.map((it, idx) => ({
5034
- ...it,
5035
- status: idx === stepIndex ? "done" : it.status,
5036
- result: idx === stepIndex ? "(\uAC74\uB108\uB700)" : it.result
5037
- }))
5038
- }
5039
- };
5040
- })
5041
- };
5042
- })
5043
- );
5044
- const nextPending = message.checklistBlock.items.findIndex(
5045
- (it, idx) => idx > stepIndex && (it.status === "pending" || it.status === "error")
5046
- );
5047
- if (nextPending >= 0) {
5048
- activeChecklistRef.current = {
5049
- messageId,
5050
- sessionId: session.id,
5051
- items: message.checklistBlock.items.map((it) => ({ id: it.id, title: it.title, skill: it.skill, fileIndex: it.fileIndex, fileType: it.fileType, refImage: it.refImage, refStep: it.refStep })),
5052
- currentStep: nextPending,
5053
- stepResults: message.checklistBlock.items.slice(0, nextPending).map((it) => it.result || "(\uAC74\uB108\uB700)")
5054
- };
5055
- setSessions(
5056
- (prev) => prev.map((s) => {
5057
- if (s.id !== session.id) return s;
5058
- return {
5059
- ...s,
5060
- messages: s.messages.map((m) => {
5061
- if (m.id !== messageId || !m.checklistBlock) return m;
5062
- return {
5063
- ...m,
5064
- checklistBlock: {
5065
- ...m.checklistBlock,
5066
- items: m.checklistBlock.items.map((it, idx) => ({
5067
- ...it,
5068
- status: idx === nextPending ? "in_progress" : it.status
5069
- })),
5070
- currentStep: nextPending
5071
- }
5072
- };
5073
- })
5074
- };
5075
- })
5076
- );
5077
- skipNextChecklistParsingRef.current = true;
5078
- setTimeout(() => {
5079
- const items = message.checklistBlock.items;
5080
- const refUrl = resolveChecklistRefImage(items[nextPending], messageId, session.id);
5081
- if (refUrl) pendingAttachmentDataRef.current = [{ name: "ref_image", mimeType: "image/png", base64: "", size: 0, url: refUrl }];
5082
- sendMessage(
5083
- buildChecklistStepPrompt(nextPending, items.length, items[nextPending], false, refUrl),
5084
- { hiddenUserMessage: true, isChecklistExecution: true, checklistStep: { index: nextPending, title: items[nextPending].title } }
5085
- );
5086
- }, 100);
5087
- } else {
5088
- const allResults = message.checklistBlock.items.map((it, i) => {
5089
- const result = i === stepIndex ? "(\uAC74\uB108\uB700)" : it.result || "(\uAC74\uB108\uB700)";
5090
- return `### ${i + 1}. ${it.title}
5091
- ${result}`;
5092
- }).join("\n\n");
5093
- setSessions(
5094
- (prev) => prev.map((s) => {
5095
- if (s.id !== session.id) return s;
5096
- return {
5097
- ...s,
5098
- messages: s.messages.map((m) => {
5099
- if (m.id !== messageId || !m.checklistBlock) return m;
5100
- return { ...m, checklistBlock: { ...m.checklistBlock, completed: true } };
5101
- })
5102
- };
5103
- })
5104
- );
5105
- skipNextChecklistParsingRef.current = true;
5106
- setTimeout(() => {
5107
- sendMessage(
5108
- `\uBAA8\uB4E0 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uB2E8\uACC4\uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC544\uB798\uB294 \uAC01 \uB2E8\uACC4\uBCC4 \uACB0\uACFC\uC785\uB2C8\uB2E4:
5109
-
5110
- ${allResults}
5111
-
5112
- \uC704 \uACB0\uACFC\uB97C \uC885\uD569\uD558\uC5EC \uCD5C\uC885 \uACB0\uACFC\uBB3C\uC744 \uC644\uC131\uD574\uC8FC\uC138\uC694. checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \uB9C8\uC138\uC694.`,
5113
- { hiddenUserMessage: true, isChecklistExecution: true }
5114
- );
5115
- }, 100);
5116
- }
5117
- },
5118
- [sendMessage]
5119
- );
5120
- const handlePollSubmit = (0, import_react6.useCallback)(
5366
+ sendMessageForChecklistRef.current = sendMessage;
5367
+ const handlePollSubmit = (0, import_react8.useCallback)(
5121
5368
  (messageId, responses) => {
5122
5369
  const currentSess = sessions.find((s) => s.id === currentSessionId);
5123
5370
  const targetMessage = currentSess?.messages.find((m) => m.id === messageId);
@@ -5187,7 +5434,7 @@ ${formattedParts.join("\n")}
5187
5434
  },
5188
5435
  [sessions, currentSessionId, selectedModel, sendMessage]
5189
5436
  );
5190
- const saveEdit = (0, import_react6.useCallback)(async (content) => {
5437
+ const saveEdit = (0, import_react8.useCallback)(async (content) => {
5191
5438
  if (!editingMessageId || !currentSession || !currentSessionId) return;
5192
5439
  const messageIndex = currentSession.messages.findIndex((m) => m.id === editingMessageId);
5193
5440
  if (messageIndex === -1) return;
@@ -5204,7 +5451,7 @@ ${formattedParts.join("\n")}
5204
5451
  setEditingMessageId(null);
5205
5452
  await sendMessage(content);
5206
5453
  }, [editingMessageId, currentSession, currentSessionId, sendMessage]);
5207
- const regenerate = (0, import_react6.useCallback)(async (messageId) => {
5454
+ const regenerate = (0, import_react8.useCallback)(async (messageId) => {
5208
5455
  console.log("[ChatUI] Regenerate called:", { messageId, currentSessionId, isLoading });
5209
5456
  if (!currentSession || !currentSessionId || isLoading) {
5210
5457
  console.log("[ChatUI] Regenerate early return - missing session or loading");
@@ -5243,137 +5490,76 @@ ${formattedParts.join("\n")}
5243
5490
  ...s.messages,
5244
5491
  {
5245
5492
  id: assistantMessageId,
5246
- role: "assistant",
5247
- content: "",
5248
- model: selectedModel,
5249
- timestamp: Date.now()
5250
- }
5251
- ]
5252
- };
5253
- }
5254
- return s;
5255
- })
5256
- );
5257
- const messagesUpToUser = currentSession.messages.slice(0, assistantIndex);
5258
- const chatMessages = messagesUpToUser.map((m) => ({
5259
- role: m.role,
5260
- content: m.content,
5261
- ...m.contentParts && { contentParts: m.contentParts }
5262
- }));
5263
- const baseSystemPrompt = buildSystemPrompt(currentSession);
5264
- const messagesForApi = baseSystemPrompt ? [{ role: "system", content: baseSystemPrompt }, ...chatMessages] : chatMessages;
5265
- const modelConfig = models.find((m) => m.id === selectedModel);
5266
- const provider = modelConfig?.provider || "ollama";
5267
- const isOllama = provider === "ollama" || apiEndpoint.includes("ollama") || apiEndpoint.includes("11434");
5268
- const requestBody = isOllama ? { model: selectedModel, messages: messagesForApi, stream: true } : {
5269
- messages: messagesForApi,
5270
- model: selectedModel,
5271
- provider,
5272
- apiKey: provider === "devdive" ? apiKey : void 0,
5273
- stream: true
5274
- };
5275
- console.log("[ChatUI] Regenerate fetch:", { apiEndpoint, isOllama, model: selectedModel });
5276
- const response = await fetch(apiEndpoint, {
5277
- method: "POST",
5278
- headers: { "Content-Type": "application/json" },
5279
- body: JSON.stringify(requestBody),
5280
- signal: abortControllersRef.current.get(capturedSessionId).signal
5281
- });
5282
- console.log("[ChatUI] Regenerate response status:", response.status);
5283
- if (!response.ok) throw new Error(`API error: ${response.status}`);
5284
- const reader = response.body?.getReader();
5285
- if (!reader) throw new Error("No reader");
5286
- const decoder = new TextDecoder();
5287
- let buffer = "";
5288
- while (true) {
5289
- const { done, value } = await reader.read();
5290
- if (done) break;
5291
- buffer += decoder.decode(value, { stream: true });
5292
- const lines = buffer.split("\n");
5293
- buffer = lines.pop() || "";
5294
- for (const line of lines) {
5295
- if (!line.trim()) continue;
5296
- let data = line;
5297
- if (line.startsWith("data: ")) {
5298
- data = line.slice(6);
5299
- if (data === "[DONE]") continue;
5300
- }
5301
- try {
5302
- const parsed = JSON.parse(data);
5303
- const content = parsed.message?.content || parsed.content || parsed.text || "";
5304
- const thinking = parsed.message?.thinking || "";
5305
- if (content || thinking) {
5306
- setSessions(
5307
- (prev) => prev.map((s) => {
5308
- if (s.id === capturedSessionId) {
5309
- return {
5310
- ...s,
5311
- messages: s.messages.map((m) => {
5312
- if (m.id !== assistantMessageId) return m;
5313
- let newContent = m.content;
5314
- if (thinking) {
5315
- if (!newContent.includes("<thinking>")) {
5316
- newContent = "<thinking>" + thinking;
5317
- } else if (!newContent.includes("</thinking>")) {
5318
- newContent += thinking;
5319
- }
5320
- }
5321
- if (content) {
5322
- if (newContent.includes("<thinking>") && !newContent.includes("</thinking>")) {
5323
- newContent += "</thinking>\n\n";
5324
- }
5325
- newContent += content;
5326
- }
5327
- return { ...m, content: newContent };
5328
- })
5329
- };
5330
- }
5331
- return s;
5332
- })
5333
- );
5334
- }
5335
- } catch {
5336
- }
5337
- }
5338
- }
5339
- if (buffer.trim()) {
5340
- try {
5341
- const parsed = JSON.parse(buffer);
5342
- const content = parsed.message?.content || parsed.content || parsed.text || "";
5343
- const thinking = parsed.message?.thinking || "";
5344
- if (content || thinking) {
5345
- setSessions(
5346
- (prev) => prev.map((s) => {
5347
- if (s.id === capturedSessionId) {
5348
- return {
5349
- ...s,
5350
- messages: s.messages.map((m) => {
5351
- if (m.id !== assistantMessageId) return m;
5352
- let newContent = m.content;
5353
- if (thinking) {
5354
- if (!newContent.includes("<thinking>")) {
5355
- newContent = "<thinking>" + thinking;
5356
- } else if (!newContent.includes("</thinking>")) {
5357
- newContent += thinking;
5358
- }
5359
- }
5360
- if (content) {
5361
- if (newContent.includes("<thinking>") && !newContent.includes("</thinking>")) {
5362
- newContent += "</thinking>\n\n";
5363
- }
5364
- newContent += content;
5365
- }
5366
- return { ...m, content: newContent };
5367
- })
5368
- };
5493
+ role: "assistant",
5494
+ content: "",
5495
+ model: selectedModel,
5496
+ timestamp: Date.now()
5369
5497
  }
5370
- return s;
5371
- })
5372
- );
5498
+ ]
5499
+ };
5373
5500
  }
5374
- } catch {
5501
+ return s;
5502
+ })
5503
+ );
5504
+ const messagesUpToUser = currentSession.messages.slice(0, assistantIndex);
5505
+ const chatMessages = messagesUpToUser.map((m) => ({
5506
+ role: m.role,
5507
+ content: m.content,
5508
+ ...m.contentParts && { contentParts: m.contentParts }
5509
+ }));
5510
+ const baseSystemPrompt = buildSystemPrompt(currentSession);
5511
+ const messagesForApi = baseSystemPrompt ? [{ role: "system", content: baseSystemPrompt }, ...chatMessages] : chatMessages;
5512
+ const modelConfig = models.find((m) => m.id === selectedModel);
5513
+ const provider = modelConfig?.provider || "ollama";
5514
+ const isOllama = provider === "ollama" || apiEndpoint.includes("ollama") || apiEndpoint.includes("11434");
5515
+ const requestBody = isOllama ? { model: selectedModel, messages: messagesForApi, stream: true } : {
5516
+ messages: messagesForApi,
5517
+ model: selectedModel,
5518
+ provider,
5519
+ apiKey: provider === "devdive" ? apiKey : void 0,
5520
+ stream: true
5521
+ };
5522
+ console.log("[ChatUI] Regenerate fetch:", { apiEndpoint, isOllama, model: selectedModel });
5523
+ const response = await fetch(apiEndpoint, {
5524
+ method: "POST",
5525
+ headers: { "Content-Type": "application/json" },
5526
+ body: JSON.stringify(requestBody),
5527
+ signal: abortControllersRef.current.get(capturedSessionId).signal
5528
+ });
5529
+ console.log("[ChatUI] Regenerate response status:", response.status);
5530
+ await streamResponse(response, (chunk) => {
5531
+ const { content, thinking } = chunk;
5532
+ if (content || thinking) {
5533
+ setSessions(
5534
+ (prev) => prev.map((s) => {
5535
+ if (s.id === capturedSessionId) {
5536
+ return {
5537
+ ...s,
5538
+ messages: s.messages.map((m) => {
5539
+ if (m.id !== assistantMessageId) return m;
5540
+ let newContent = m.content;
5541
+ if (thinking) {
5542
+ if (!newContent.includes("<thinking>")) {
5543
+ newContent = "<thinking>" + thinking;
5544
+ } else if (!newContent.includes("</thinking>")) {
5545
+ newContent += thinking;
5546
+ }
5547
+ }
5548
+ if (content) {
5549
+ if (newContent.includes("<thinking>") && !newContent.includes("</thinking>")) {
5550
+ newContent += "</thinking>\n\n";
5551
+ }
5552
+ newContent += content;
5553
+ }
5554
+ return { ...m, content: newContent };
5555
+ })
5556
+ };
5557
+ }
5558
+ return s;
5559
+ })
5560
+ );
5375
5561
  }
5376
- }
5562
+ }, { noTimeout: true });
5377
5563
  } catch (error) {
5378
5564
  if (error instanceof Error && error.name === "AbortError") {
5379
5565
  return;
@@ -5384,7 +5570,7 @@ ${formattedParts.join("\n")}
5384
5570
  removeLoadingSession(capturedSessionId);
5385
5571
  }
5386
5572
  }, [currentSession, currentSessionId, loadingSessionIds, selectedModel, models, apiEndpoint, apiKey, buildSystemPrompt]);
5387
- const askOtherModel = (0, import_react6.useCallback)(async (messageId, targetModel) => {
5573
+ const askOtherModel = (0, import_react8.useCallback)(async (messageId, targetModel) => {
5388
5574
  if (!currentSession || !currentSessionId || isLoading) return;
5389
5575
  const assistantIndex = currentSession.messages.findIndex((m) => m.id === messageId);
5390
5576
  if (assistantIndex === -1) return;
@@ -5439,30 +5625,7 @@ ${currentSession.contextSummary}` },
5439
5625
  responseContent = result.content;
5440
5626
  responseSources = result.sources;
5441
5627
  } else {
5442
- const reader = result.getReader();
5443
- const decoder = new TextDecoder();
5444
- let buffer = "";
5445
- while (true) {
5446
- const { done, value } = await reader.read();
5447
- if (done) break;
5448
- buffer += decoder.decode(value, { stream: true });
5449
- const lines = buffer.split("\n");
5450
- buffer = lines.pop() || "";
5451
- for (const line of lines) {
5452
- if (line.startsWith("data: ")) {
5453
- const data = line.slice(6);
5454
- if (data === "[DONE]") continue;
5455
- try {
5456
- const parsed = JSON.parse(data);
5457
- {
5458
- const chunk = parsed.content ?? parsed.text ?? "";
5459
- if (chunk) responseContent += chunk;
5460
- }
5461
- } catch {
5462
- }
5463
- }
5464
- }
5465
- }
5628
+ responseContent = await parseSSEResponse(new Response(result));
5466
5629
  }
5467
5630
  } else {
5468
5631
  const isOllama = provider === "ollama" || apiEndpoint.includes("ollama") || apiEndpoint.includes("11434");
@@ -5480,32 +5643,9 @@ ${currentSession.contextSummary}` },
5480
5643
  signal: abortControllersRef.current.get(currentSessionId).signal
5481
5644
  });
5482
5645
  emitFetchHeaders(response);
5483
- if (!response.ok) throw new Error("API error");
5484
- const reader = response.body?.getReader();
5485
- if (!reader) throw new Error("No reader");
5486
- const decoder = new TextDecoder();
5487
- let buffer = "";
5488
- while (true) {
5489
- const { done, value } = await reader.read();
5490
- if (done) break;
5491
- buffer += decoder.decode(value, { stream: true });
5492
- const lines = buffer.split("\n");
5493
- buffer = lines.pop() || "";
5494
- for (const line of lines) {
5495
- if (line.startsWith("data: ")) {
5496
- const data = line.slice(6);
5497
- if (data === "[DONE]") continue;
5498
- try {
5499
- const parsed = JSON.parse(data);
5500
- {
5501
- const chunk = parsed.content ?? parsed.text ?? "";
5502
- if (chunk) responseContent += chunk;
5503
- }
5504
- } catch {
5505
- }
5506
- }
5507
- }
5508
- }
5646
+ await streamResponse(response, (chunk) => {
5647
+ if (chunk.content) responseContent += chunk.content;
5648
+ }, { noTimeout: true });
5509
5649
  }
5510
5650
  const alternative = {
5511
5651
  id: generateId5("alt"),
@@ -5565,13 +5705,13 @@ ${currentSession.contextSummary}` },
5565
5705
  onSendMessage,
5566
5706
  onError
5567
5707
  ]);
5568
- const setActiveAlternative = (0, import_react6.useCallback)((assistantMessageId, index) => {
5708
+ const setActiveAlternative = (0, import_react8.useCallback)((assistantMessageId, index) => {
5569
5709
  setActiveAlternatives((prev) => ({
5570
5710
  ...prev,
5571
5711
  [assistantMessageId]: index
5572
5712
  }));
5573
5713
  }, []);
5574
- const getActiveAlternative = (0, import_react6.useCallback)((assistantMessageId) => {
5714
+ const getActiveAlternative = (0, import_react8.useCallback)((assistantMessageId) => {
5575
5715
  return activeAlternatives[assistantMessageId] ?? 0;
5576
5716
  }, [activeAlternatives]);
5577
5717
  return {
@@ -5629,6 +5769,10 @@ ${currentSession.contextSummary}` },
5629
5769
  }));
5630
5770
  await infoExtraction.extractInfo(recentMessages);
5631
5771
  },
5772
+ getTokenStats: (0, import_react8.useCallback)((sessionId) => {
5773
+ const session = sessions.find((s) => s.id === sessionId);
5774
+ return session?.tokenStats ?? null;
5775
+ }, [sessions]),
5632
5776
  // External Storage Loading States
5633
5777
  /**
5634
5778
  * @description 세션 목록 로딩 상태
@@ -5784,12 +5928,12 @@ ${result.content}
5784
5928
  };
5785
5929
 
5786
5930
  // src/react/contexts/ImageErrorContext.ts
5787
- var import_react7 = require("react");
5788
- var ImageErrorContext = (0, import_react7.createContext)(null);
5789
- var useImageError = () => (0, import_react7.useContext)(ImageErrorContext);
5931
+ var import_react9 = require("react");
5932
+ var ImageErrorContext = (0, import_react9.createContext)(null);
5933
+ var useImageError = () => (0, import_react9.useContext)(ImageErrorContext);
5790
5934
 
5791
5935
  // src/react/components/ChatSidebar.tsx
5792
- var import_react11 = require("react");
5936
+ var import_react13 = require("react");
5793
5937
 
5794
5938
  // src/react/components/Icon.tsx
5795
5939
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -5889,10 +6033,10 @@ var IconSvg = ({
5889
6033
  };
5890
6034
 
5891
6035
  // src/react/components/MarkdownRenderer.tsx
5892
- var import_react9 = __toESM(require("react"));
6036
+ var import_react11 = __toESM(require("react"));
5893
6037
 
5894
6038
  // src/react/components/LinkChip.tsx
5895
- var import_react8 = require("react");
6039
+ var import_react10 = require("react");
5896
6040
  var import_jsx_runtime2 = require("react/jsx-runtime");
5897
6041
  var getDomain = (url) => {
5898
6042
  try {
@@ -5946,7 +6090,7 @@ var LinkChip = ({
5946
6090
  index,
5947
6091
  style
5948
6092
  }) => {
5949
- const [isHovered, setIsHovered] = (0, import_react8.useState)(false);
6093
+ const [isHovered, setIsHovered] = (0, import_react10.useState)(false);
5950
6094
  const domain = getDomain(url);
5951
6095
  const shortName = getShortName(domain);
5952
6096
  const domainColor = getDomainColor(domain);
@@ -6316,8 +6460,8 @@ var parseTableRow = (row) => {
6316
6460
  return row.split("|").slice(1, -1).map((cell) => cell.trim());
6317
6461
  };
6318
6462
  var MarkdownTable = ({ data }) => {
6319
- const [copied, setCopied] = import_react9.default.useState(false);
6320
- const [isHovered, setIsHovered] = import_react9.default.useState(false);
6463
+ const [copied, setCopied] = import_react11.default.useState(false);
6464
+ const [isHovered, setIsHovered] = import_react11.default.useState(false);
6321
6465
  const handleCopy = async () => {
6322
6466
  const headerLine = data.headers.join(" ");
6323
6467
  const bodyLines = data.rows.map((row) => row.join(" "));
@@ -6429,7 +6573,7 @@ var ThinkingSpinner = () => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
6429
6573
  }
6430
6574
  );
6431
6575
  var ThinkingBlock = ({ content, defaultOpen = false }) => {
6432
- const [isOpen, setIsOpen] = import_react9.default.useState(defaultOpen);
6576
+ const [isOpen, setIsOpen] = import_react11.default.useState(defaultOpen);
6433
6577
  const isStreaming = content.trim().endsWith("...");
6434
6578
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
6435
6579
  "details",
@@ -6504,7 +6648,7 @@ var ThinkingBlock = ({ content, defaultOpen = false }) => {
6504
6648
  );
6505
6649
  };
6506
6650
  var CodeBlock = ({ language, code }) => {
6507
- const [copied, setCopied] = import_react9.default.useState(false);
6651
+ const [copied, setCopied] = import_react11.default.useState(false);
6508
6652
  const handleCopy = async () => {
6509
6653
  try {
6510
6654
  await navigator.clipboard.writeText(code);
@@ -6587,10 +6731,10 @@ var CodeBlock = ({ language, code }) => {
6587
6731
  );
6588
6732
  };
6589
6733
  var ImageWithCopyButton = ({ src, alt, imageKey }) => {
6590
- const [isHovered, setIsHovered] = import_react9.default.useState(false);
6591
- const [imgSrc, setImgSrc] = import_react9.default.useState(src);
6734
+ const [isHovered, setIsHovered] = import_react11.default.useState(false);
6735
+ const [imgSrc, setImgSrc] = import_react11.default.useState(src);
6592
6736
  const onImageError = useImageError();
6593
- const handleImageError = import_react9.default.useCallback(async () => {
6737
+ const handleImageError = import_react11.default.useCallback(async () => {
6594
6738
  if (onImageError) {
6595
6739
  const newUrl = await onImageError(imgSrc);
6596
6740
  if (newUrl) {
@@ -6599,8 +6743,8 @@ var ImageWithCopyButton = ({ src, alt, imageKey }) => {
6599
6743
  }
6600
6744
  }
6601
6745
  }, [onImageError, imgSrc]);
6602
- const [copyState, setCopyState] = import_react9.default.useState("idle");
6603
- const imgRef = import_react9.default.useRef(null);
6746
+ const [copyState, setCopyState] = import_react11.default.useState("idle");
6747
+ const imgRef = import_react11.default.useRef(null);
6604
6748
  const getImageBlob = async () => {
6605
6749
  const img = imgRef.current;
6606
6750
  if (img && img.complete && img.naturalWidth > 0) {
@@ -6816,7 +6960,7 @@ var ImageWithCopyButton = ({ src, alt, imageKey }) => {
6816
6960
  );
6817
6961
  };
6818
6962
  var ChoiceButtons = ({ choices, title, onChoiceClick }) => {
6819
- const [hoveredIndex, setHoveredIndex] = import_react9.default.useState(null);
6963
+ const [hoveredIndex, setHoveredIndex] = import_react11.default.useState(null);
6820
6964
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
6821
6965
  "div",
6822
6966
  {
@@ -6946,11 +7090,11 @@ var MarkdownRenderer = ({
6946
7090
  sources,
6947
7091
  inline = false
6948
7092
  }) => {
6949
- const inlineRendered = (0, import_react9.useMemo)(() => {
7093
+ const inlineRendered = (0, import_react11.useMemo)(() => {
6950
7094
  if (!inline) return null;
6951
7095
  return parseInlineElements(content, "inline-md", sources);
6952
7096
  }, [inline, content, sources]);
6953
- const rendered = (0, import_react9.useMemo)(() => {
7097
+ const rendered = (0, import_react11.useMemo)(() => {
6954
7098
  if (inline) return null;
6955
7099
  const elements = [];
6956
7100
  let processedContent = content;
@@ -7103,7 +7247,7 @@ var MarkdownRenderer = ({
7103
7247
  borderRadius: "0 8px 8px 0",
7104
7248
  color: "var(--chatllm-text, #374151)"
7105
7249
  },
7106
- children: blockquoteLines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react9.default.Fragment, { children: [
7250
+ children: blockquoteLines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react11.default.Fragment, { children: [
7107
7251
  parseInlineElements(line, `bq-line-${i}`, sources),
7108
7252
  i < blockquoteLines.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("br", {})
7109
7253
  ] }, i))
@@ -7347,7 +7491,7 @@ var MarkdownRenderer = ({
7347
7491
  };
7348
7492
 
7349
7493
  // src/react/components/ProjectSelector.tsx
7350
- var import_react10 = require("react");
7494
+ var import_react12 = require("react");
7351
7495
  var import_jsx_runtime4 = require("react/jsx-runtime");
7352
7496
  var ProjectSelector = ({
7353
7497
  projects,
@@ -7356,10 +7500,10 @@ var ProjectSelector = ({
7356
7500
  onNewProject,
7357
7501
  onProjectSettings
7358
7502
  }) => {
7359
- const [isOpen, setIsOpen] = (0, import_react10.useState)(false);
7360
- const dropdownRef = (0, import_react10.useRef)(null);
7503
+ const [isOpen, setIsOpen] = (0, import_react12.useState)(false);
7504
+ const dropdownRef = (0, import_react12.useRef)(null);
7361
7505
  const currentProject = projects.find((p) => p.id === currentProjectId);
7362
- (0, import_react10.useEffect)(() => {
7506
+ (0, import_react12.useEffect)(() => {
7363
7507
  const handleClickOutside = (e) => {
7364
7508
  if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
7365
7509
  setIsOpen(false);
@@ -7593,10 +7737,10 @@ var ChatSidebar = ({
7593
7737
  isLoading = false
7594
7738
  }) => {
7595
7739
  const sidebarWidth = typeof widthProp === "number" ? `${widthProp}px` : widthProp || "288px";
7596
- const [editingId, setEditingId] = (0, import_react11.useState)(null);
7597
- const [editingTitle, setEditingTitle] = (0, import_react11.useState)("");
7598
- const inputRef = (0, import_react11.useRef)(null);
7599
- (0, import_react11.useEffect)(() => {
7740
+ const [editingId, setEditingId] = (0, import_react13.useState)(null);
7741
+ const [editingTitle, setEditingTitle] = (0, import_react13.useState)("");
7742
+ const inputRef = (0, import_react13.useRef)(null);
7743
+ (0, import_react13.useEffect)(() => {
7600
7744
  if (editingId && inputRef.current) {
7601
7745
  inputRef.current.focus();
7602
7746
  inputRef.current.select();
@@ -7968,7 +8112,7 @@ var ChatSidebar = ({
7968
8112
  };
7969
8113
 
7970
8114
  // src/react/components/ChatHeader.tsx
7971
- var import_react12 = require("react");
8115
+ var import_react14 = require("react");
7972
8116
  var import_jsx_runtime6 = require("react/jsx-runtime");
7973
8117
  var ChatHeader = ({
7974
8118
  title,
@@ -7982,7 +8126,7 @@ var ChatHeader = ({
7982
8126
  showSettings = true,
7983
8127
  renderHeaderExtra
7984
8128
  }) => {
7985
- const [modelDropdownOpen, setModelDropdownOpen] = (0, import_react12.useState)(false);
8129
+ const [modelDropdownOpen, setModelDropdownOpen] = (0, import_react14.useState)(false);
7986
8130
  const currentModel = models.find((m) => m.id === model);
7987
8131
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
7988
8132
  "header",
@@ -8237,7 +8381,7 @@ var ChatHeader = ({
8237
8381
  };
8238
8382
 
8239
8383
  // src/react/components/ChatInput.tsx
8240
- var import_react13 = __toESM(require("react"));
8384
+ var import_react15 = __toESM(require("react"));
8241
8385
  var import_jsx_runtime7 = require("react/jsx-runtime");
8242
8386
  var ChatInput = ({
8243
8387
  value,
@@ -8265,20 +8409,20 @@ var ChatInput = ({
8265
8409
  onDisclaimerClick,
8266
8410
  inline = false
8267
8411
  }) => {
8268
- const [mainMenuOpen, setMainMenuOpen] = import_react13.default.useState(false);
8269
- const textareaRef = (0, import_react13.useRef)(null);
8270
- const fileInputRef = (0, import_react13.useRef)(null);
8271
- const [actionMenuOpen, setActionMenuOpen] = (0, import_react13.useState)(false);
8272
- const [isDragOver, setIsDragOver] = (0, import_react13.useState)(false);
8273
- const mainMenuRef = (0, import_react13.useRef)(null);
8274
- const actionMenuRef = (0, import_react13.useRef)(null);
8275
- (0, import_react13.useEffect)(() => {
8412
+ const [mainMenuOpen, setMainMenuOpen] = import_react15.default.useState(false);
8413
+ const textareaRef = (0, import_react15.useRef)(null);
8414
+ const fileInputRef = (0, import_react15.useRef)(null);
8415
+ const [actionMenuOpen, setActionMenuOpen] = (0, import_react15.useState)(false);
8416
+ const [isDragOver, setIsDragOver] = (0, import_react15.useState)(false);
8417
+ const mainMenuRef = (0, import_react15.useRef)(null);
8418
+ const actionMenuRef = (0, import_react15.useRef)(null);
8419
+ (0, import_react15.useEffect)(() => {
8276
8420
  if (textareaRef.current) {
8277
8421
  textareaRef.current.style.height = "auto";
8278
8422
  textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 200)}px`;
8279
8423
  }
8280
8424
  }, [value]);
8281
- (0, import_react13.useEffect)(() => {
8425
+ (0, import_react15.useEffect)(() => {
8282
8426
  const handleClickOutside = (event) => {
8283
8427
  if (mainMenuRef.current && !mainMenuRef.current.contains(event.target)) {
8284
8428
  setMainMenuOpen(false);
@@ -9000,13 +9144,13 @@ var iconButtonStyle = {
9000
9144
  };
9001
9145
 
9002
9146
  // src/react/components/MessageList.tsx
9003
- var import_react21 = __toESM(require("react"));
9147
+ var import_react23 = __toESM(require("react"));
9004
9148
 
9005
9149
  // src/react/components/MessageBubble.tsx
9006
- var import_react20 = require("react");
9150
+ var import_react22 = require("react");
9007
9151
 
9008
9152
  // src/react/components/DeepResearchProgressUI.tsx
9009
- var import_react14 = __toESM(require("react"));
9153
+ var import_react16 = __toESM(require("react"));
9010
9154
  var import_jsx_runtime8 = require("react/jsx-runtime");
9011
9155
  var StatusIcon = ({ status }) => {
9012
9156
  switch (status) {
@@ -9070,7 +9214,7 @@ var PhaseProgress = ({ phase }) => {
9070
9214
  gap: "8px",
9071
9215
  marginBottom: "16px"
9072
9216
  },
9073
- children: phases.slice(0, 4).map((p, index) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react14.default.Fragment, { children: [
9217
+ children: phases.slice(0, 4).map((p, index) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react16.default.Fragment, { children: [
9074
9218
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
9075
9219
  "div",
9076
9220
  {
@@ -9289,7 +9433,7 @@ var DeepResearchProgressUI = ({ progress }) => {
9289
9433
  };
9290
9434
 
9291
9435
  // src/react/components/PollCard.tsx
9292
- var import_react15 = require("react");
9436
+ var import_react17 = require("react");
9293
9437
  var import_jsx_runtime9 = require("react/jsx-runtime");
9294
9438
  var renderInlineMarkdown = (text) => {
9295
9439
  const parts = [];
@@ -9328,12 +9472,12 @@ var PollCard = ({
9328
9472
  onSubmit,
9329
9473
  onSkip
9330
9474
  }) => {
9331
- const [activeTab, setActiveTab] = (0, import_react15.useState)(0);
9332
- const [selections, setSelections] = (0, import_react15.useState)({});
9333
- const [otherTexts, setOtherTexts] = (0, import_react15.useState)({});
9334
- const [otherSelected, setOtherSelected] = (0, import_react15.useState)({});
9475
+ const [activeTab, setActiveTab] = (0, import_react17.useState)(0);
9476
+ const [selections, setSelections] = (0, import_react17.useState)({});
9477
+ const [otherTexts, setOtherTexts] = (0, import_react17.useState)({});
9478
+ const [otherSelected, setOtherSelected] = (0, import_react17.useState)({});
9335
9479
  const currentQuestion = questions[activeTab];
9336
- const handleOptionToggle = (0, import_react15.useCallback)(
9480
+ const handleOptionToggle = (0, import_react17.useCallback)(
9337
9481
  (questionId, optionId, multiSelect) => {
9338
9482
  setSelections((prev) => {
9339
9483
  const current = prev[questionId] || /* @__PURE__ */ new Set();
@@ -9355,7 +9499,7 @@ var PollCard = ({
9355
9499
  },
9356
9500
  []
9357
9501
  );
9358
- const handleOtherToggle = (0, import_react15.useCallback)((questionId, multiSelect) => {
9502
+ const handleOtherToggle = (0, import_react17.useCallback)((questionId, multiSelect) => {
9359
9503
  if (multiSelect) {
9360
9504
  setOtherSelected((prev) => ({ ...prev, [questionId]: !prev[questionId] }));
9361
9505
  } else {
@@ -9363,7 +9507,7 @@ var PollCard = ({
9363
9507
  setOtherSelected((prev) => ({ ...prev, [questionId]: true }));
9364
9508
  }
9365
9509
  }, []);
9366
- const handleSubmit = (0, import_react15.useCallback)(() => {
9510
+ const handleSubmit = (0, import_react17.useCallback)(() => {
9367
9511
  const responses = questions.map((q) => {
9368
9512
  const selected = selections[q.id] || /* @__PURE__ */ new Set();
9369
9513
  const other = otherSelected[q.id] && otherTexts[q.id]?.trim();
@@ -9376,7 +9520,7 @@ var PollCard = ({
9376
9520
  });
9377
9521
  onSubmit(responses);
9378
9522
  }, [questions, selections, otherSelected, otherTexts, onSubmit]);
9379
- const handleSkip = (0, import_react15.useCallback)(() => {
9523
+ const handleSkip = (0, import_react17.useCallback)(() => {
9380
9524
  const responses = questions.map((q) => ({
9381
9525
  questionId: q.id,
9382
9526
  selectedOptions: [],
@@ -9385,7 +9529,7 @@ var PollCard = ({
9385
9529
  onSubmit(responses);
9386
9530
  onSkip?.();
9387
9531
  }, [questions, onSubmit, onSkip]);
9388
- (0, import_react15.useEffect)(() => {
9532
+ (0, import_react17.useEffect)(() => {
9389
9533
  if (typeof window === "undefined") return;
9390
9534
  const handleKeyDown = (e) => {
9391
9535
  if (e.key !== "Escape") return;
@@ -9404,9 +9548,9 @@ var PollCard = ({
9404
9548
  const currentHasSelection = getSelectionCount(currentQuestion.id) > 0;
9405
9549
  const isLastTab = activeTab === questions.length - 1;
9406
9550
  const totalSelected = questions.reduce((sum, q) => sum + getSelectionCount(q.id), 0);
9407
- const [visibleCount, setVisibleCount] = (0, import_react15.useState)(0);
9408
- const [cardVisible, setCardVisible] = (0, import_react15.useState)(false);
9409
- (0, import_react15.useEffect)(() => {
9551
+ const [visibleCount, setVisibleCount] = (0, import_react17.useState)(0);
9552
+ const [cardVisible, setCardVisible] = (0, import_react17.useState)(false);
9553
+ (0, import_react17.useEffect)(() => {
9410
9554
  const fadeTimer = setTimeout(() => setCardVisible(true), 50);
9411
9555
  const totalItems = currentQuestion.options.length + 1;
9412
9556
  const timers = [fadeTimer];
@@ -9415,7 +9559,7 @@ var PollCard = ({
9415
9559
  }
9416
9560
  return () => timers.forEach(clearTimeout);
9417
9561
  }, [currentQuestion.options.length]);
9418
- (0, import_react15.useEffect)(() => {
9562
+ (0, import_react17.useEffect)(() => {
9419
9563
  setVisibleCount(0);
9420
9564
  const totalItems = currentQuestion.options.length + 1;
9421
9565
  const timers = [];
@@ -9424,7 +9568,7 @@ var PollCard = ({
9424
9568
  }
9425
9569
  return () => timers.forEach(clearTimeout);
9426
9570
  }, [activeTab, currentQuestion.options.length]);
9427
- const handleNext = (0, import_react15.useCallback)(() => {
9571
+ const handleNext = (0, import_react17.useCallback)(() => {
9428
9572
  if (!isLastTab) {
9429
9573
  setActiveTab((prev) => prev + 1);
9430
9574
  } else {
@@ -9842,15 +9986,15 @@ var SkillProgressUI = ({
9842
9986
  };
9843
9987
 
9844
9988
  // src/react/components/ImageContentCard.tsx
9845
- var import_react16 = require("react");
9989
+ var import_react18 = require("react");
9846
9990
  var import_jsx_runtime11 = require("react/jsx-runtime");
9847
9991
  var ImageContentCard = ({ part }) => {
9848
- const [isExpanded, setIsExpanded] = (0, import_react16.useState)(false);
9849
- const [isLoaded, setIsLoaded] = (0, import_react16.useState)(false);
9850
- const [hasError, setHasError] = (0, import_react16.useState)(false);
9851
- const [imgSrc, setImgSrc] = (0, import_react16.useState)(part.url);
9992
+ const [isExpanded, setIsExpanded] = (0, import_react18.useState)(false);
9993
+ const [isLoaded, setIsLoaded] = (0, import_react18.useState)(false);
9994
+ const [hasError, setHasError] = (0, import_react18.useState)(false);
9995
+ const [imgSrc, setImgSrc] = (0, import_react18.useState)(part.url);
9852
9996
  const onImageError = useImageError();
9853
- const handleImageError = (0, import_react16.useCallback)(async () => {
9997
+ const handleImageError = (0, import_react18.useCallback)(async () => {
9854
9998
  if (onImageError) {
9855
9999
  const newUrl = await onImageError(imgSrc, part.fileName);
9856
10000
  if (newUrl) {
@@ -10063,7 +10207,7 @@ var FileContentCard = ({ part }) => {
10063
10207
  };
10064
10208
 
10065
10209
  // src/react/components/ToolStatusCard.tsx
10066
- var import_react17 = require("react");
10210
+ var import_react19 = require("react");
10067
10211
  var import_jsx_runtime13 = require("react/jsx-runtime");
10068
10212
  var mapIcon2 = (icon) => {
10069
10213
  const iconMap = {
@@ -10096,7 +10240,7 @@ var ToolStatusCard = ({
10096
10240
  sources,
10097
10241
  errorMessage
10098
10242
  }) => {
10099
- const [isExpanded, setIsExpanded] = (0, import_react17.useState)(false);
10243
+ const [isExpanded, setIsExpanded] = (0, import_react19.useState)(false);
10100
10244
  const displayLabel = label || getDefaultLabel(toolName);
10101
10245
  const statusText = getStatusSuffix(displayLabel, status);
10102
10246
  const hasSources = sources && sources.length > 0;
@@ -10227,7 +10371,7 @@ var ToolStatusCard = ({
10227
10371
  };
10228
10372
 
10229
10373
  // src/react/components/ArtifactCard.tsx
10230
- var import_react18 = require("react");
10374
+ var import_react20 = require("react");
10231
10375
  var import_jsx_runtime14 = require("react/jsx-runtime");
10232
10376
  var getThemeStyles = () => {
10233
10377
  const root = document.documentElement;
@@ -10268,10 +10412,10 @@ var getAutoResizeScript = (id) => `
10268
10412
  });
10269
10413
  </script>`;
10270
10414
  var HtmlArtifact = ({ code }) => {
10271
- const iframeRef = (0, import_react18.useRef)(null);
10272
- const [height, setHeight] = (0, import_react18.useState)(200);
10273
- const artifactId = (0, import_react18.useRef)(`artifact-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`);
10274
- (0, import_react18.useEffect)(() => {
10415
+ const iframeRef = (0, import_react20.useRef)(null);
10416
+ const [height, setHeight] = (0, import_react20.useState)(200);
10417
+ const artifactId = (0, import_react20.useRef)(`artifact-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`);
10418
+ (0, import_react20.useEffect)(() => {
10275
10419
  const id = artifactId.current;
10276
10420
  const handleMessage = (e) => {
10277
10421
  if (e.data?.type === "artifact-resize" && e.data.id === id && typeof e.data.height === "number") {
@@ -10317,10 +10461,10 @@ var SvgArtifact = ({ code }) => {
10317
10461
  );
10318
10462
  };
10319
10463
  var MermaidArtifact = ({ code }) => {
10320
- const containerRef = (0, import_react18.useRef)(null);
10321
- const [svgHtml, setSvgHtml] = (0, import_react18.useState)(null);
10322
- const [error, setError] = (0, import_react18.useState)(null);
10323
- const renderMermaid = (0, import_react18.useCallback)(async () => {
10464
+ const containerRef = (0, import_react20.useRef)(null);
10465
+ const [svgHtml, setSvgHtml] = (0, import_react20.useState)(null);
10466
+ const [error, setError] = (0, import_react20.useState)(null);
10467
+ const renderMermaid = (0, import_react20.useCallback)(async () => {
10324
10468
  try {
10325
10469
  let mermaid;
10326
10470
  try {
@@ -10336,7 +10480,7 @@ var MermaidArtifact = ({ code }) => {
10336
10480
  setError(e instanceof Error ? e.message : "Mermaid \uB80C\uB354\uB9C1 \uC2E4\uD328");
10337
10481
  }
10338
10482
  }, [code]);
10339
- (0, import_react18.useEffect)(() => {
10483
+ (0, import_react20.useEffect)(() => {
10340
10484
  renderMermaid();
10341
10485
  }, [renderMermaid]);
10342
10486
  if (error) {
@@ -10412,9 +10556,9 @@ var svgToPngBlob = (svgString, scale = 2) => {
10412
10556
  });
10413
10557
  };
10414
10558
  var ArtifactActions = ({ code, language, containerRef }) => {
10415
- const [showCode, setShowCode] = (0, import_react18.useState)(false);
10416
- const [copied, setCopied] = (0, import_react18.useState)(false);
10417
- const [saving, setSaving] = (0, import_react18.useState)(false);
10559
+ const [showCode, setShowCode] = (0, import_react20.useState)(false);
10560
+ const [copied, setCopied] = (0, import_react20.useState)(false);
10561
+ const [saving, setSaving] = (0, import_react20.useState)(false);
10418
10562
  const handleCopy = async () => {
10419
10563
  try {
10420
10564
  await navigator.clipboard.writeText(code);
@@ -10579,7 +10723,7 @@ var ArtifactActions = ({ code, language, containerRef }) => {
10579
10723
  ] });
10580
10724
  };
10581
10725
  var ArtifactCard = ({ part, index = 0 }) => {
10582
- const contentRef = (0, import_react18.useRef)(null);
10726
+ const contentRef = (0, import_react20.useRef)(null);
10583
10727
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
10584
10728
  "div",
10585
10729
  {
@@ -10751,7 +10895,7 @@ var ContentPartRenderer = ({
10751
10895
  };
10752
10896
 
10753
10897
  // src/react/components/ChecklistCard.tsx
10754
- var import_react19 = require("react");
10898
+ var import_react21 = require("react");
10755
10899
  var import_jsx_runtime16 = require("react/jsx-runtime");
10756
10900
  var ChecklistCard = ({
10757
10901
  items,
@@ -10761,8 +10905,8 @@ var ChecklistCard = ({
10761
10905
  onSkipStep,
10762
10906
  onStart
10763
10907
  }) => {
10764
- const [expandedItems, setExpandedItems] = (0, import_react19.useState)(/* @__PURE__ */ new Set());
10765
- const { doneCount, isRunning, hasError, isWaiting } = (0, import_react19.useMemo)(() => {
10908
+ const [expandedItems, setExpandedItems] = (0, import_react21.useState)(/* @__PURE__ */ new Set());
10909
+ const { doneCount, isRunning, hasError, isWaiting } = (0, import_react21.useMemo)(() => {
10766
10910
  const done = items.filter((it) => it.status === "done").length;
10767
10911
  const running = items.some((it) => it.status === "in_progress");
10768
10912
  const waiting = items.every((it) => it.status === "pending");
@@ -11259,9 +11403,9 @@ var MessageBubble = ({
11259
11403
  onToggleChecklistPanel,
11260
11404
  onChecklistStart
11261
11405
  }) => {
11262
- const [showActions, setShowActions] = (0, import_react20.useState)(false);
11263
- const [showModelMenu, setShowModelMenu] = (0, import_react20.useState)(false);
11264
- const [stepExpanded, setStepExpanded] = (0, import_react20.useState)(false);
11406
+ const [showActions, setShowActions] = (0, import_react22.useState)(false);
11407
+ const [showModelMenu, setShowModelMenu] = (0, import_react22.useState)(false);
11408
+ const [stepExpanded, setStepExpanded] = (0, import_react22.useState)(false);
11265
11409
  const isUser = message.role === "user";
11266
11410
  const isAssistant = message.role === "assistant";
11267
11411
  const relevantAlternatives = isUser ? alternatives : message.alternatives;
@@ -12012,14 +12156,14 @@ var MessageList = ({
12012
12156
  onExport,
12013
12157
  onToggleChecklistPanel
12014
12158
  }) => {
12015
- const messagesEndRef = (0, import_react21.useRef)(null);
12016
- const containerRef = (0, import_react21.useRef)(null);
12017
- const [selectedText, setSelectedText] = (0, import_react21.useState)("");
12018
- const [selectionPosition, setSelectionPosition] = (0, import_react21.useState)(null);
12019
- const [showScrollButton, setShowScrollButton] = (0, import_react21.useState)(false);
12020
- const userScrollLockRef = (0, import_react21.useRef)(false);
12159
+ const messagesEndRef = (0, import_react23.useRef)(null);
12160
+ const containerRef = (0, import_react23.useRef)(null);
12161
+ const [selectedText, setSelectedText] = (0, import_react23.useState)("");
12162
+ const [selectionPosition, setSelectionPosition] = (0, import_react23.useState)(null);
12163
+ const [showScrollButton, setShowScrollButton] = (0, import_react23.useState)(false);
12164
+ const userScrollLockRef = (0, import_react23.useRef)(false);
12021
12165
  const SCROLL_THRESHOLD = 100;
12022
- (0, import_react21.useEffect)(() => {
12166
+ (0, import_react23.useEffect)(() => {
12023
12167
  const container = containerRef.current;
12024
12168
  if (!container) return;
12025
12169
  const handleWheel = (e) => {
@@ -12056,7 +12200,7 @@ var MessageList = ({
12056
12200
  container.removeEventListener("touchmove", handleTouchMove);
12057
12201
  };
12058
12202
  }, []);
12059
- const handleScroll = (0, import_react21.useCallback)(() => {
12203
+ const handleScroll = (0, import_react23.useCallback)(() => {
12060
12204
  if (!containerRef.current) return;
12061
12205
  const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
12062
12206
  const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
@@ -12065,14 +12209,14 @@ var MessageList = ({
12065
12209
  setShowScrollButton(false);
12066
12210
  }
12067
12211
  }, []);
12068
- (0, import_react21.useEffect)(() => {
12212
+ (0, import_react23.useEffect)(() => {
12069
12213
  if (userScrollLockRef.current) return;
12070
12214
  containerRef.current?.scrollTo({
12071
12215
  top: containerRef.current.scrollHeight,
12072
12216
  behavior: "smooth"
12073
12217
  });
12074
12218
  }, [messages]);
12075
- const scrollToBottom = (0, import_react21.useCallback)(() => {
12219
+ const scrollToBottom = (0, import_react23.useCallback)(() => {
12076
12220
  userScrollLockRef.current = false;
12077
12221
  setShowScrollButton(false);
12078
12222
  containerRef.current?.scrollTo({
@@ -12080,7 +12224,7 @@ var MessageList = ({
12080
12224
  behavior: "smooth"
12081
12225
  });
12082
12226
  }, []);
12083
- const handleMouseUp = (0, import_react21.useCallback)(() => {
12227
+ const handleMouseUp = (0, import_react23.useCallback)(() => {
12084
12228
  const selection = typeof window !== "undefined" ? window.getSelection() : null;
12085
12229
  const text = selection?.toString().trim();
12086
12230
  if (text && text.length > 0) {
@@ -12181,7 +12325,7 @@ var MessageList = ({
12181
12325
  },
12182
12326
  message.id
12183
12327
  );
12184
- return renderMessage ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react21.default.Fragment, { children: renderMessage(message, bubbleElement) }, message.id) : bubbleElement;
12328
+ return renderMessage ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react23.default.Fragment, { children: renderMessage(message, bubbleElement) }, message.id) : bubbleElement;
12185
12329
  }),
12186
12330
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { ref: messagesEndRef })
12187
12331
  ]
@@ -12275,7 +12419,7 @@ var MessageList = ({
12275
12419
  };
12276
12420
 
12277
12421
  // src/react/components/SettingsModal.tsx
12278
- var import_react22 = require("react");
12422
+ var import_react24 = require("react");
12279
12423
  var import_jsx_runtime19 = require("react/jsx-runtime");
12280
12424
  var DEFAULT_IMPORT_MEMORY_PROMPT = `Export all of my stored memories and any context you've learned about me from past conversations. Preserve my words verbatim where possible, especially for instructions and preferences.
12281
12425
 
@@ -12340,9 +12484,9 @@ var SettingsModal = ({
12340
12484
  onImportMemory,
12341
12485
  importMemoryPrompt
12342
12486
  }) => {
12343
- const [activeTab, setActiveTab] = (0, import_react22.useState)("general");
12344
- const [localApiKey, setLocalApiKey] = (0, import_react22.useState)(apiKey);
12345
- (0, import_react22.useEffect)(() => {
12487
+ const [activeTab, setActiveTab] = (0, import_react24.useState)("general");
12488
+ const [localApiKey, setLocalApiKey] = (0, import_react24.useState)(apiKey);
12489
+ (0, import_react24.useEffect)(() => {
12346
12490
  setLocalApiKey(apiKey);
12347
12491
  }, [apiKey]);
12348
12492
  if (!isOpen) return null;
@@ -12818,11 +12962,11 @@ var memoryCategoryColors = {
12818
12962
  preference: "#8b5cf6"
12819
12963
  };
12820
12964
  var MemoryTabContent = ({ items, contextSummary, onDelete, onClearAll, title = "AI \uBA54\uBAA8\uB9AC", emptyMessage = "\uC800\uC7A5\uB41C \uBA54\uBAA8\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4", emptyDescription = "\uB300\uD654\uAC00 \uC9C4\uD589\uB418\uBA74 AI\uAC00 \uC790\uB3D9\uC73C\uB85C \uC815\uBCF4\uB97C \uD559\uC2B5\uD569\uB2C8\uB2E4", onImportMemory, importMemoryPrompt }) => {
12821
- const [activeFilter, setActiveFilter] = (0, import_react22.useState)("all");
12822
- const [expandedId, setExpandedId] = (0, import_react22.useState)(null);
12823
- const [importModalOpen, setImportModalOpen] = (0, import_react22.useState)(false);
12824
- const [importText, setImportText] = (0, import_react22.useState)("");
12825
- const [promptCopied, setPromptCopied] = (0, import_react22.useState)(false);
12965
+ const [activeFilter, setActiveFilter] = (0, import_react24.useState)("all");
12966
+ const [expandedId, setExpandedId] = (0, import_react24.useState)(null);
12967
+ const [importModalOpen, setImportModalOpen] = (0, import_react24.useState)(false);
12968
+ const [importText, setImportText] = (0, import_react24.useState)("");
12969
+ const [promptCopied, setPromptCopied] = (0, import_react24.useState)(false);
12826
12970
  const filteredItems = activeFilter === "all" ? items : items.filter((item) => item.category === activeFilter);
12827
12971
  const formatDate = (timestamp) => {
12828
12972
  const date = new Date(timestamp);
@@ -13279,7 +13423,7 @@ var MemoryTabContent = ({ items, contextSummary, onDelete, onClearAll, title = "
13279
13423
  };
13280
13424
 
13281
13425
  // src/react/components/ProjectSettingsModal.tsx
13282
- var import_react23 = require("react");
13426
+ var import_react25 = require("react");
13283
13427
  var import_jsx_runtime20 = require("react/jsx-runtime");
13284
13428
  var COLOR_PRESETS = [
13285
13429
  "#3584FA",
@@ -13300,12 +13444,12 @@ var ProjectSettingsModal = ({
13300
13444
  onDeleteFile,
13301
13445
  onDeleteProject
13302
13446
  }) => {
13303
- const [activeTab, setActiveTab] = (0, import_react23.useState)("general");
13304
- const [title, setTitle] = (0, import_react23.useState)("");
13305
- const [description, setDescription] = (0, import_react23.useState)("");
13306
- const [instructions, setInstructions] = (0, import_react23.useState)("");
13307
- const [color, setColor] = (0, import_react23.useState)("#3584FA");
13308
- (0, import_react23.useEffect)(() => {
13447
+ const [activeTab, setActiveTab] = (0, import_react25.useState)("general");
13448
+ const [title, setTitle] = (0, import_react25.useState)("");
13449
+ const [description, setDescription] = (0, import_react25.useState)("");
13450
+ const [instructions, setInstructions] = (0, import_react25.useState)("");
13451
+ const [color, setColor] = (0, import_react25.useState)("#3584FA");
13452
+ (0, import_react25.useEffect)(() => {
13309
13453
  if (project) {
13310
13454
  setTitle(project.title);
13311
13455
  setDescription(project.description || "");
@@ -14770,28 +14914,28 @@ var ChatUIView = ({
14770
14914
  handleChecklistSkip,
14771
14915
  activeChecklistMessage
14772
14916
  } = state;
14773
- const [disclaimerOpen, setDisclaimerOpen] = import_react24.default.useState(false);
14774
- const [isMobile, setIsMobile] = import_react24.default.useState(false);
14775
- import_react24.default.useEffect(() => {
14917
+ const [disclaimerOpen, setDisclaimerOpen] = import_react26.default.useState(false);
14918
+ const [isMobile, setIsMobile] = import_react26.default.useState(false);
14919
+ import_react26.default.useEffect(() => {
14776
14920
  if (typeof window === "undefined") return;
14777
14921
  const check = () => setIsMobile(window.innerWidth < 768);
14778
14922
  check();
14779
14923
  window.addEventListener("resize", check);
14780
14924
  return () => window.removeEventListener("resize", check);
14781
14925
  }, []);
14782
- const [checklistPanelDismissed, setChecklistPanelDismissed] = import_react24.default.useState(false);
14926
+ const [checklistPanelDismissed, setChecklistPanelDismissed] = import_react26.default.useState(false);
14783
14927
  const isChecklistPanelOpen = !!activeChecklistMessage && !checklistPanelDismissed;
14784
- const prevChecklistIdRef = import_react24.default.useRef(void 0);
14785
- import_react24.default.useEffect(() => {
14928
+ const prevChecklistIdRef = import_react26.default.useRef(void 0);
14929
+ import_react26.default.useEffect(() => {
14786
14930
  const currentId = activeChecklistMessage?.checklistBlock?.id;
14787
14931
  if (currentId && currentId !== prevChecklistIdRef.current && !activeChecklistMessage?.checklistBlock?.completed) {
14788
14932
  setChecklistPanelDismissed(false);
14789
14933
  }
14790
14934
  prevChecklistIdRef.current = currentId;
14791
14935
  }, [activeChecklistMessage?.checklistBlock?.id, activeChecklistMessage?.checklistBlock?.completed]);
14792
- const [welcomeExiting, setWelcomeExiting] = import_react24.default.useState(false);
14793
- const prevMessageCountRef = import_react24.default.useRef(messages.length);
14794
- import_react24.default.useEffect(() => {
14936
+ const [welcomeExiting, setWelcomeExiting] = import_react26.default.useState(false);
14937
+ const prevMessageCountRef = import_react26.default.useRef(messages.length);
14938
+ import_react26.default.useEffect(() => {
14795
14939
  let timer;
14796
14940
  if (prevMessageCountRef.current === 0 && messages.length > 0) {
14797
14941
  setWelcomeExiting(true);
@@ -14815,7 +14959,7 @@ var ChatUIView = ({
14815
14959
  const handleChoiceClick = (choice) => {
14816
14960
  setInput(choice.text);
14817
14961
  };
14818
- const memoryItems = import_react24.default.useMemo(() => {
14962
+ const memoryItems = import_react26.default.useMemo(() => {
14819
14963
  if (!globalMemory?.state.entries) return [];
14820
14964
  const items = [];
14821
14965
  for (const [key, entry] of globalMemory.state.entries) {
@@ -14829,7 +14973,7 @@ var ChatUIView = ({
14829
14973
  }
14830
14974
  return items;
14831
14975
  }, [globalMemory?.state.entries]);
14832
- const projectMemoryItems = import_react24.default.useMemo(() => {
14976
+ const projectMemoryItems = import_react26.default.useMemo(() => {
14833
14977
  if (!projectMemory?.state.entries) return [];
14834
14978
  const items = [];
14835
14979
  for (const [key, entry] of projectMemory.state.entries) {
@@ -15351,13 +15495,13 @@ var ChatUI = (props) => {
15351
15495
  };
15352
15496
 
15353
15497
  // src/react/ChatFloatingWidget.tsx
15354
- var import_react32 = require("react");
15498
+ var import_react34 = require("react");
15355
15499
 
15356
15500
  // src/react/hooks/useFloatingWidget.ts
15357
- var import_react26 = require("react");
15501
+ var import_react28 = require("react");
15358
15502
 
15359
15503
  // src/react/hooks/useDragResize.ts
15360
- var import_react25 = require("react");
15504
+ var import_react27 = require("react");
15361
15505
  var DRAG_THRESHOLD = 5;
15362
15506
  var FAB_SIZE = 56;
15363
15507
  var EDGE_MARGIN = 24;
@@ -15425,8 +15569,8 @@ var useDragResize = (options) => {
15425
15569
  maxWidth = DEFAULT_MAX_WIDTH,
15426
15570
  minHeight = DEFAULT_MIN_HEIGHT
15427
15571
  } = options;
15428
- const [isMobile, setIsMobile] = (0, import_react25.useState)(false);
15429
- (0, import_react25.useEffect)(() => {
15572
+ const [isMobile, setIsMobile] = (0, import_react27.useState)(false);
15573
+ (0, import_react27.useEffect)(() => {
15430
15574
  if (typeof window === "undefined") return;
15431
15575
  const mql = window.matchMedia("(max-width: 767px)");
15432
15576
  setIsMobile(mql.matches);
@@ -15441,19 +15585,19 @@ var useDragResize = (options) => {
15441
15585
  panelWidth: initialWidth,
15442
15586
  panelHeight: initialHeight
15443
15587
  });
15444
- const [fabPos, setFabPos] = (0, import_react25.useState)({ x: persisted.fabX, y: persisted.fabY });
15445
- const [panelSize, setPanelSize] = (0, import_react25.useState)({ width: persisted.panelWidth, height: persisted.panelHeight });
15446
- const [isDragging, setIsDragging] = (0, import_react25.useState)(false);
15447
- const [isResizing, setIsResizing] = (0, import_react25.useState)(false);
15448
- const [isDizzy, setIsDizzy] = (0, import_react25.useState)(false);
15449
- const [viewport, setViewport] = (0, import_react25.useState)(() => ({
15588
+ const [fabPos, setFabPos] = (0, import_react27.useState)({ x: persisted.fabX, y: persisted.fabY });
15589
+ const [panelSize, setPanelSize] = (0, import_react27.useState)({ width: persisted.panelWidth, height: persisted.panelHeight });
15590
+ const [isDragging, setIsDragging] = (0, import_react27.useState)(false);
15591
+ const [isResizing, setIsResizing] = (0, import_react27.useState)(false);
15592
+ const [isDizzy, setIsDizzy] = (0, import_react27.useState)(false);
15593
+ const [viewport, setViewport] = (0, import_react27.useState)(() => ({
15450
15594
  width: typeof window !== "undefined" ? window.innerWidth : 1024,
15451
15595
  height: typeof window !== "undefined" ? window.innerHeight : 768
15452
15596
  }));
15453
15597
  const vw = viewport.width;
15454
15598
  const vh = viewport.height;
15455
- const initializedRef = (0, import_react25.useRef)(false);
15456
- (0, import_react25.useEffect)(() => {
15599
+ const initializedRef = (0, import_react27.useRef)(false);
15600
+ (0, import_react27.useEffect)(() => {
15457
15601
  if (initializedRef.current || typeof window === "undefined") return;
15458
15602
  initializedRef.current = true;
15459
15603
  const correctFab = getInitialFabPosition(initialPosition);
@@ -15466,40 +15610,40 @@ var useDragResize = (options) => {
15466
15610
  setFabPos({ x: loaded.fabX, y: loaded.fabY });
15467
15611
  setPanelSize({ width: loaded.panelWidth, height: loaded.panelHeight });
15468
15612
  }, [initialPosition, storageKey, initialWidth, initialHeight]);
15469
- const dragStartRef = (0, import_react25.useRef)(null);
15470
- const isDraggingRef = (0, import_react25.useRef)(false);
15471
- const wasDragRef = (0, import_react25.useRef)(false);
15472
- const userDraggedRef = (0, import_react25.useRef)(false);
15473
- const dragDeltaRef = (0, import_react25.useRef)({ dx: 0, dy: 0 });
15474
- const fabElRef = (0, import_react25.useRef)(null);
15475
- const dragCleanupRef = (0, import_react25.useRef)(null);
15476
- const snapTimerRef = (0, import_react25.useRef)(null);
15477
- const prevDragRef = (0, import_react25.useRef)(null);
15478
- const shakeScoreRef = (0, import_react25.useRef)(0);
15479
- const prevVelocityRef = (0, import_react25.useRef)(null);
15480
- const isDizzyRef = (0, import_react25.useRef)(false);
15481
- const dizzyTimerRef = (0, import_react25.useRef)(null);
15482
- const lastVelocityRef = (0, import_react25.useRef)({ vx: 0, vy: 0, speed: 0 });
15483
- const dragRafRef = (0, import_react25.useRef)(null);
15484
- const animCleanupRef = (0, import_react25.useRef)(null);
15485
- const snapEnabledRef = (0, import_react25.useRef)(snapEnabled);
15486
- const storageKeyRef = (0, import_react25.useRef)(storageKey);
15487
- const panelSizeRef = (0, import_react25.useRef)(panelSize);
15488
- const fabPosRef = (0, import_react25.useRef)(fabPos);
15489
- (0, import_react25.useEffect)(() => {
15613
+ const dragStartRef = (0, import_react27.useRef)(null);
15614
+ const isDraggingRef = (0, import_react27.useRef)(false);
15615
+ const wasDragRef = (0, import_react27.useRef)(false);
15616
+ const userDraggedRef = (0, import_react27.useRef)(false);
15617
+ const dragDeltaRef = (0, import_react27.useRef)({ dx: 0, dy: 0 });
15618
+ const fabElRef = (0, import_react27.useRef)(null);
15619
+ const dragCleanupRef = (0, import_react27.useRef)(null);
15620
+ const snapTimerRef = (0, import_react27.useRef)(null);
15621
+ const prevDragRef = (0, import_react27.useRef)(null);
15622
+ const shakeScoreRef = (0, import_react27.useRef)(0);
15623
+ const prevVelocityRef = (0, import_react27.useRef)(null);
15624
+ const isDizzyRef = (0, import_react27.useRef)(false);
15625
+ const dizzyTimerRef = (0, import_react27.useRef)(null);
15626
+ const lastVelocityRef = (0, import_react27.useRef)({ vx: 0, vy: 0, speed: 0 });
15627
+ const dragRafRef = (0, import_react27.useRef)(null);
15628
+ const animCleanupRef = (0, import_react27.useRef)(null);
15629
+ const snapEnabledRef = (0, import_react27.useRef)(snapEnabled);
15630
+ const storageKeyRef = (0, import_react27.useRef)(storageKey);
15631
+ const panelSizeRef = (0, import_react27.useRef)(panelSize);
15632
+ const fabPosRef = (0, import_react27.useRef)(fabPos);
15633
+ (0, import_react27.useEffect)(() => {
15490
15634
  snapEnabledRef.current = snapEnabled;
15491
15635
  }, [snapEnabled]);
15492
- (0, import_react25.useEffect)(() => {
15636
+ (0, import_react27.useEffect)(() => {
15493
15637
  storageKeyRef.current = storageKey;
15494
15638
  }, [storageKey]);
15495
- (0, import_react25.useEffect)(() => {
15639
+ (0, import_react27.useEffect)(() => {
15496
15640
  panelSizeRef.current = panelSize;
15497
15641
  }, [panelSize]);
15498
- (0, import_react25.useEffect)(() => {
15642
+ (0, import_react27.useEffect)(() => {
15499
15643
  fabPosRef.current = fabPos;
15500
15644
  }, [fabPos]);
15501
- const resizeStartRef = (0, import_react25.useRef)(null);
15502
- (0, import_react25.useEffect)(() => {
15645
+ const resizeStartRef = (0, import_react27.useRef)(null);
15646
+ (0, import_react27.useEffect)(() => {
15503
15647
  return () => {
15504
15648
  dragCleanupRef.current?.();
15505
15649
  if (snapTimerRef.current) clearTimeout(snapTimerRef.current);
@@ -15507,7 +15651,7 @@ var useDragResize = (options) => {
15507
15651
  if (dragRafRef.current) cancelAnimationFrame(dragRafRef.current);
15508
15652
  };
15509
15653
  }, []);
15510
- const handleFabPointerDown = (0, import_react25.useCallback)((e) => {
15654
+ const handleFabPointerDown = (0, import_react27.useCallback)((e) => {
15511
15655
  if (disabled || isMobile) return;
15512
15656
  dragCleanupRef.current?.();
15513
15657
  const el = e.currentTarget;
@@ -15689,7 +15833,7 @@ var useDragResize = (options) => {
15689
15833
  }, [disabled, isMobile]);
15690
15834
  const fabCenterX = fabPos.x + FAB_SIZE / 2;
15691
15835
  const panelDirection = fabCenterX > vw / 2 ? "left" : "right";
15692
- const panelPositionStyle = (0, import_react25.useMemo)(() => {
15836
+ const panelPositionStyle = (0, import_react27.useMemo)(() => {
15693
15837
  if (isMobile || disabled) return {};
15694
15838
  const fabIsTop = fabPos.y < vh / 2;
15695
15839
  let panelBottom;
@@ -15721,7 +15865,7 @@ var useDragResize = (options) => {
15721
15865
  borderRadius: "var(--floating-panel-radius, 16px)"
15722
15866
  };
15723
15867
  }, [isMobile, disabled, fabPos.x, fabPos.y, panelSize.width, panelSize.height, vw, vh, panelDirection, minHeight]);
15724
- const handleResizePointerDown = (0, import_react25.useCallback)((edge, e) => {
15868
+ const handleResizePointerDown = (0, import_react27.useCallback)((edge, e) => {
15725
15869
  if (disabled || isMobile) return;
15726
15870
  e.preventDefault();
15727
15871
  e.stopPropagation();
@@ -15737,7 +15881,7 @@ var useDragResize = (options) => {
15737
15881
  };
15738
15882
  setIsResizing(true);
15739
15883
  }, [disabled, isMobile]);
15740
- const handleResizePointerMove = (0, import_react25.useCallback)((e) => {
15884
+ const handleResizePointerMove = (0, import_react27.useCallback)((e) => {
15741
15885
  if (!resizeStartRef.current) return;
15742
15886
  const { startX, startY, width, height, edge, fabY } = resizeStartRef.current;
15743
15887
  const dx = e.clientX - startX;
@@ -15761,7 +15905,7 @@ var useDragResize = (options) => {
15761
15905
  newHeight = Math.max(minHeight, Math.min(maxH, newHeight));
15762
15906
  setPanelSize({ width: newWidth, height: newHeight });
15763
15907
  }, [minWidth, maxWidth, minHeight]);
15764
- const handleResizePointerUp = (0, import_react25.useCallback)((e) => {
15908
+ const handleResizePointerUp = (0, import_react27.useCallback)((e) => {
15765
15909
  if (!resizeStartRef.current) return;
15766
15910
  e.currentTarget.releasePointerCapture(e.pointerId);
15767
15911
  resizeStartRef.current = null;
@@ -15770,7 +15914,7 @@ var useDragResize = (options) => {
15770
15914
  const fp = fabPosRef.current;
15771
15915
  persistState(storageKeyRef.current, { fabX: fp.x, fabY: fp.y, panelWidth: ps.width, panelHeight: ps.height });
15772
15916
  }, []);
15773
- (0, import_react25.useEffect)(() => {
15917
+ (0, import_react27.useEffect)(() => {
15774
15918
  if (typeof window === "undefined") return;
15775
15919
  const handleResize = () => {
15776
15920
  const newVw = window.innerWidth;
@@ -15832,9 +15976,9 @@ var useFloatingWidget = (options) => {
15832
15976
  maxWidth,
15833
15977
  minHeight
15834
15978
  } = options || {};
15835
- const [isOpen, setIsOpen] = (0, import_react26.useState)(defaultOpen);
15836
- const [activeTab, setActiveTab] = (0, import_react26.useState)(defaultTab);
15837
- const panelRef = (0, import_react26.useRef)(null);
15979
+ const [isOpen, setIsOpen] = (0, import_react28.useState)(defaultOpen);
15980
+ const [activeTab, setActiveTab] = (0, import_react28.useState)(defaultTab);
15981
+ const panelRef = (0, import_react28.useRef)(null);
15838
15982
  const dragResize = useDragResize({
15839
15983
  initialPosition: position,
15840
15984
  initialWidth: width,
@@ -15846,27 +15990,27 @@ var useFloatingWidget = (options) => {
15846
15990
  maxWidth,
15847
15991
  minHeight
15848
15992
  });
15849
- const onOpenRef = (0, import_react26.useRef)(onOpen);
15850
- const onCloseRef = (0, import_react26.useRef)(onClose);
15851
- const onTabChangeRef = (0, import_react26.useRef)(onTabChange);
15852
- (0, import_react26.useEffect)(() => {
15993
+ const onOpenRef = (0, import_react28.useRef)(onOpen);
15994
+ const onCloseRef = (0, import_react28.useRef)(onClose);
15995
+ const onTabChangeRef = (0, import_react28.useRef)(onTabChange);
15996
+ (0, import_react28.useEffect)(() => {
15853
15997
  onOpenRef.current = onOpen;
15854
15998
  }, [onOpen]);
15855
- (0, import_react26.useEffect)(() => {
15999
+ (0, import_react28.useEffect)(() => {
15856
16000
  onCloseRef.current = onClose;
15857
16001
  }, [onClose]);
15858
- (0, import_react26.useEffect)(() => {
16002
+ (0, import_react28.useEffect)(() => {
15859
16003
  onTabChangeRef.current = onTabChange;
15860
16004
  }, [onTabChange]);
15861
- const open = (0, import_react26.useCallback)(() => {
16005
+ const open = (0, import_react28.useCallback)(() => {
15862
16006
  setIsOpen(true);
15863
16007
  onOpenRef.current?.();
15864
16008
  }, []);
15865
- const close = (0, import_react26.useCallback)(() => {
16009
+ const close = (0, import_react28.useCallback)(() => {
15866
16010
  setIsOpen(false);
15867
16011
  onCloseRef.current?.();
15868
16012
  }, []);
15869
- const toggle = (0, import_react26.useCallback)(() => {
16013
+ const toggle = (0, import_react28.useCallback)(() => {
15870
16014
  setIsOpen((prev) => {
15871
16015
  const next = !prev;
15872
16016
  if (next) onOpenRef.current?.();
@@ -15874,15 +16018,15 @@ var useFloatingWidget = (options) => {
15874
16018
  return next;
15875
16019
  });
15876
16020
  }, []);
15877
- const setTab = (0, import_react26.useCallback)((tabKey) => {
16021
+ const setTab = (0, import_react28.useCallback)((tabKey) => {
15878
16022
  setActiveTab(tabKey);
15879
16023
  onTabChangeRef.current?.(tabKey);
15880
16024
  }, []);
15881
- const handleFabInteraction = (0, import_react26.useCallback)(() => {
16025
+ const handleFabInteraction = (0, import_react28.useCallback)(() => {
15882
16026
  if (dragResize.isDragging) return;
15883
16027
  toggle();
15884
16028
  }, [dragResize.isDragging, toggle]);
15885
- (0, import_react26.useEffect)(() => {
16029
+ (0, import_react28.useEffect)(() => {
15886
16030
  if (!isOpen) return;
15887
16031
  const handleKeyDown = (e) => {
15888
16032
  if (e.key === "Escape") {
@@ -15892,7 +16036,7 @@ var useFloatingWidget = (options) => {
15892
16036
  document.addEventListener("keydown", handleKeyDown);
15893
16037
  return () => document.removeEventListener("keydown", handleKeyDown);
15894
16038
  }, [isOpen, close]);
15895
- (0, import_react26.useEffect)(() => {
16039
+ (0, import_react28.useEffect)(() => {
15896
16040
  if (!isOpen) return;
15897
16041
  const handleClickOutside = (e) => {
15898
16042
  if (dragResize.isDragging || dragResize.isResizing) return;
@@ -15908,10 +16052,10 @@ var useFloatingWidget = (options) => {
15908
16052
  };
15909
16053
 
15910
16054
  // src/react/components/floating/FloatingFab.tsx
15911
- var import_react28 = require("react");
16055
+ var import_react30 = require("react");
15912
16056
 
15913
16057
  // src/react/components/floating/DevDiveCharacter.tsx
15914
- var import_react27 = require("react");
16058
+ var import_react29 = require("react");
15915
16059
  var import_jsx_runtime24 = require("react/jsx-runtime");
15916
16060
  var THEMES = {
15917
16061
  dark: { body: "#2ecc71", stroke: "#27ae60", highlight: "#3ddc84", face: "#1a1a2e", eyeLight: "#fff" },
@@ -15932,32 +16076,32 @@ var DevDiveFabCharacter = ({
15932
16076
  isComplete = false
15933
16077
  }) => {
15934
16078
  const c = THEMES[theme];
15935
- const [eyeOffset, setEyeOffset] = (0, import_react27.useState)({ x: 0, y: 0 });
15936
- const [isBlinking, setIsBlinking] = (0, import_react27.useState)(false);
15937
- const [isHappy, setIsHappy] = (0, import_react27.useState)(false);
15938
- const [isPressed, setIsPressed] = (0, import_react27.useState)(false);
15939
- const [mouthOpen, setMouthOpen] = (0, import_react27.useState)(false);
15940
- const [isSleepy, setIsSleepy] = (0, import_react27.useState)(false);
15941
- const [isSurprised, setIsSurprised] = (0, import_react27.useState)(false);
15942
- const [isWinking, setIsWinking] = (0, import_react27.useState)(false);
15943
- const [isCatMouth, setIsCatMouth] = (0, import_react27.useState)(false);
15944
- const [isEntering, setIsEntering] = (0, import_react27.useState)(true);
15945
- const svgRef = (0, import_react27.useRef)(null);
15946
- const cleanupRef = (0, import_react27.useRef)(null);
15947
- const lastActivityRef = (0, import_react27.useRef)(Date.now());
15948
- const isSleepyRef = (0, import_react27.useRef)(false);
15949
- const markActivity = (0, import_react27.useCallback)(() => {
16079
+ const [eyeOffset, setEyeOffset] = (0, import_react29.useState)({ x: 0, y: 0 });
16080
+ const [isBlinking, setIsBlinking] = (0, import_react29.useState)(false);
16081
+ const [isHappy, setIsHappy] = (0, import_react29.useState)(false);
16082
+ const [isPressed, setIsPressed] = (0, import_react29.useState)(false);
16083
+ const [mouthOpen, setMouthOpen] = (0, import_react29.useState)(false);
16084
+ const [isSleepy, setIsSleepy] = (0, import_react29.useState)(false);
16085
+ const [isSurprised, setIsSurprised] = (0, import_react29.useState)(false);
16086
+ const [isWinking, setIsWinking] = (0, import_react29.useState)(false);
16087
+ const [isCatMouth, setIsCatMouth] = (0, import_react29.useState)(false);
16088
+ const [isEntering, setIsEntering] = (0, import_react29.useState)(true);
16089
+ const svgRef = (0, import_react29.useRef)(null);
16090
+ const cleanupRef = (0, import_react29.useRef)(null);
16091
+ const lastActivityRef = (0, import_react29.useRef)(Date.now());
16092
+ const isSleepyRef = (0, import_react29.useRef)(false);
16093
+ const markActivity = (0, import_react29.useCallback)(() => {
15950
16094
  lastActivityRef.current = Date.now();
15951
16095
  if (isSleepyRef.current) {
15952
16096
  isSleepyRef.current = false;
15953
16097
  setIsSleepy(false);
15954
16098
  }
15955
16099
  }, []);
15956
- (0, import_react27.useEffect)(() => {
16100
+ (0, import_react29.useEffect)(() => {
15957
16101
  const t = setTimeout(() => setIsEntering(false), 600);
15958
16102
  return () => clearTimeout(t);
15959
16103
  }, []);
15960
- (0, import_react27.useEffect)(() => {
16104
+ (0, import_react29.useEffect)(() => {
15961
16105
  let timer;
15962
16106
  let blinkTimer;
15963
16107
  const scheduleBlink = () => {
@@ -15973,7 +16117,7 @@ var DevDiveFabCharacter = ({
15973
16117
  clearTimeout(blinkTimer);
15974
16118
  };
15975
16119
  }, []);
15976
- (0, import_react27.useEffect)(() => {
16120
+ (0, import_react29.useEffect)(() => {
15977
16121
  if (!isTalking) {
15978
16122
  setMouthOpen(false);
15979
16123
  return;
@@ -15986,7 +16130,7 @@ var DevDiveFabCharacter = ({
15986
16130
  toggle();
15987
16131
  return () => clearTimeout(timer);
15988
16132
  }, [isTalking]);
15989
- (0, import_react27.useEffect)(() => {
16133
+ (0, import_react29.useEffect)(() => {
15990
16134
  if (typeof window === "undefined") return;
15991
16135
  let rafId = 0;
15992
16136
  const onMouseMove = (e) => {
@@ -16010,7 +16154,7 @@ var DevDiveFabCharacter = ({
16010
16154
  cancelAnimationFrame(rafId);
16011
16155
  };
16012
16156
  }, []);
16013
- (0, import_react27.useEffect)(() => {
16157
+ (0, import_react29.useEffect)(() => {
16014
16158
  let upTimer;
16015
16159
  let surpriseTimer;
16016
16160
  const rafId = requestAnimationFrame(() => {
@@ -16053,10 +16197,10 @@ var DevDiveFabCharacter = ({
16053
16197
  cleanupRef.current?.();
16054
16198
  };
16055
16199
  }, [markActivity]);
16056
- (0, import_react27.useEffect)(() => {
16200
+ (0, import_react29.useEffect)(() => {
16057
16201
  markActivity();
16058
16202
  }, [isOpen, isTalking, markActivity]);
16059
- (0, import_react27.useEffect)(() => {
16203
+ (0, import_react29.useEffect)(() => {
16060
16204
  if (isOpen || isTalking || isDizzy) return;
16061
16205
  const check = setInterval(() => {
16062
16206
  if (Date.now() - lastActivityRef.current > IDLE_TIMEOUT_MS && !isSleepyRef.current) {
@@ -16066,8 +16210,8 @@ var DevDiveFabCharacter = ({
16066
16210
  }, 5e3);
16067
16211
  return () => clearInterval(check);
16068
16212
  }, [isOpen, isTalking, isDizzy]);
16069
- const prevCompleteRef = (0, import_react27.useRef)(false);
16070
- (0, import_react27.useEffect)(() => {
16213
+ const prevCompleteRef = (0, import_react29.useRef)(false);
16214
+ (0, import_react29.useEffect)(() => {
16071
16215
  const wasComplete = prevCompleteRef.current;
16072
16216
  prevCompleteRef.current = isComplete;
16073
16217
  if (wasComplete || !isComplete) return;
@@ -16075,7 +16219,7 @@ var DevDiveFabCharacter = ({
16075
16219
  const t = setTimeout(() => setIsWinking(false), WINK_DURATION_MS);
16076
16220
  return () => clearTimeout(t);
16077
16221
  }, [isComplete]);
16078
- (0, import_react27.useEffect)(() => {
16222
+ (0, import_react29.useEffect)(() => {
16079
16223
  if (isOpen || isTalking || isDizzy || isError || isSleepy) {
16080
16224
  setIsCatMouth(false);
16081
16225
  return;
@@ -16267,12 +16411,12 @@ var FloatingFab = ({
16267
16411
  }) => {
16268
16412
  const isRight = position.includes("right");
16269
16413
  const isBottom = position.includes("bottom");
16270
- const fabRef = (0, import_react28.useRef)(null);
16414
+ const fabRef = (0, import_react30.useRef)(null);
16271
16415
  const isCharacterMode = !icon;
16272
- const [bubbleOnRight, setBubbleOnRight] = (0, import_react28.useState)(!isRight);
16416
+ const [bubbleOnRight, setBubbleOnRight] = (0, import_react30.useState)(!isRight);
16273
16417
  const posLeft = positionStyle?.left;
16274
16418
  const posTop = positionStyle?.top;
16275
- (0, import_react28.useEffect)(() => {
16419
+ (0, import_react30.useEffect)(() => {
16276
16420
  if (!fabRef.current || typeof window === "undefined") {
16277
16421
  setBubbleOnRight(!isRight);
16278
16422
  return;
@@ -16280,14 +16424,14 @@ var FloatingFab = ({
16280
16424
  const rect = fabRef.current.getBoundingClientRect();
16281
16425
  setBubbleOnRight(rect.left + rect.width / 2 < window.innerWidth / 2);
16282
16426
  }, [isRight, posLeft, posTop]);
16283
- const [bubbleText, setBubbleText] = (0, import_react28.useState)(null);
16284
- const [bubbleExiting, setBubbleExiting] = (0, import_react28.useState)(false);
16285
- const bubbleTextRef = (0, import_react28.useRef)(bubbleText);
16427
+ const [bubbleText, setBubbleText] = (0, import_react30.useState)(null);
16428
+ const [bubbleExiting, setBubbleExiting] = (0, import_react30.useState)(false);
16429
+ const bubbleTextRef = (0, import_react30.useRef)(bubbleText);
16286
16430
  bubbleTextRef.current = bubbleText;
16287
- const [displayText, setDisplayText] = (0, import_react28.useState)(null);
16288
- const [isTyping, setIsTyping] = (0, import_react28.useState)(false);
16289
- const typingTimerRef = (0, import_react28.useRef)(null);
16290
- (0, import_react28.useEffect)(() => {
16431
+ const [displayText, setDisplayText] = (0, import_react30.useState)(null);
16432
+ const [isTyping, setIsTyping] = (0, import_react30.useState)(false);
16433
+ const typingTimerRef = (0, import_react30.useRef)(null);
16434
+ (0, import_react30.useEffect)(() => {
16291
16435
  if (notification) {
16292
16436
  setBubbleText(notification);
16293
16437
  setBubbleExiting(false);
@@ -16323,9 +16467,9 @@ var FloatingFab = ({
16323
16467
  }, 300);
16324
16468
  return () => clearTimeout(timer);
16325
16469
  }, [notification]);
16326
- const notifContentRef = (0, import_react28.useRef)(null);
16327
- const [needsMarquee, setNeedsMarquee] = (0, import_react28.useState)(false);
16328
- (0, import_react28.useEffect)(() => {
16470
+ const notifContentRef = (0, import_react30.useRef)(null);
16471
+ const [needsMarquee, setNeedsMarquee] = (0, import_react30.useState)(false);
16472
+ (0, import_react30.useEffect)(() => {
16329
16473
  if (isTyping || !notification) {
16330
16474
  setNeedsMarquee(false);
16331
16475
  return;
@@ -16467,7 +16611,7 @@ var FloatingFab = ({
16467
16611
  };
16468
16612
 
16469
16613
  // src/react/components/floating/FloatingPanel.tsx
16470
- var import_react29 = require("react");
16614
+ var import_react31 = require("react");
16471
16615
  var import_jsx_runtime26 = require("react/jsx-runtime");
16472
16616
  var FloatingPanel = ({
16473
16617
  isOpen,
@@ -16485,8 +16629,8 @@ var FloatingPanel = ({
16485
16629
  }) => {
16486
16630
  const isRight = position.includes("right");
16487
16631
  const themeClass = theme?.mode === "dark" ? "chatllm-dark" : "";
16488
- const [isMobile, setIsMobile] = (0, import_react29.useState)(false);
16489
- (0, import_react29.useEffect)(() => {
16632
+ const [isMobile, setIsMobile] = (0, import_react31.useState)(false);
16633
+ (0, import_react31.useEffect)(() => {
16490
16634
  if (typeof window === "undefined") return;
16491
16635
  const mql = window.matchMedia("(max-width: 767px)");
16492
16636
  setIsMobile(mql.matches);
@@ -16494,10 +16638,10 @@ var FloatingPanel = ({
16494
16638
  mql.addEventListener("change", handler);
16495
16639
  return () => mql.removeEventListener("change", handler);
16496
16640
  }, []);
16497
- const [shouldRender, setShouldRender] = (0, import_react29.useState)(isOpen);
16498
- const [isVisible, setIsVisible] = (0, import_react29.useState)(isOpen);
16499
- const rafRef = (0, import_react29.useRef)(0);
16500
- (0, import_react29.useEffect)(() => {
16641
+ const [shouldRender, setShouldRender] = (0, import_react31.useState)(isOpen);
16642
+ const [isVisible, setIsVisible] = (0, import_react31.useState)(isOpen);
16643
+ const rafRef = (0, import_react31.useRef)(0);
16644
+ (0, import_react31.useEffect)(() => {
16501
16645
  if (isOpen) {
16502
16646
  setShouldRender(true);
16503
16647
  rafRef.current = requestAnimationFrame(() => {
@@ -16512,7 +16656,7 @@ var FloatingPanel = ({
16512
16656
  }
16513
16657
  return () => cancelAnimationFrame(rafRef.current);
16514
16658
  }, [isOpen]);
16515
- (0, import_react29.useEffect)(() => {
16659
+ (0, import_react31.useEffect)(() => {
16516
16660
  if (!isOpen || !isMobile) return;
16517
16661
  const prev = document.body.style.overflow;
16518
16662
  document.body.style.overflow = "hidden";
@@ -16673,10 +16817,10 @@ var FloatingTabBar = ({
16673
16817
  };
16674
16818
 
16675
16819
  // src/react/components/floating/CompactChatView.tsx
16676
- var import_react31 = require("react");
16820
+ var import_react33 = require("react");
16677
16821
 
16678
16822
  // src/react/components/floating/CompactSessionMenu.tsx
16679
- var import_react30 = require("react");
16823
+ var import_react32 = require("react");
16680
16824
  var import_jsx_runtime28 = require("react/jsx-runtime");
16681
16825
  var CompactSessionMenu = ({
16682
16826
  sessions,
@@ -16688,13 +16832,13 @@ var CompactSessionMenu = ({
16688
16832
  onClose,
16689
16833
  isLoading = false
16690
16834
  }) => {
16691
- const menuRef = (0, import_react30.useRef)(null);
16692
- const inputRef = (0, import_react30.useRef)(null);
16693
- const onCloseRef = (0, import_react30.useRef)(onClose);
16835
+ const menuRef = (0, import_react32.useRef)(null);
16836
+ const inputRef = (0, import_react32.useRef)(null);
16837
+ const onCloseRef = (0, import_react32.useRef)(onClose);
16694
16838
  onCloseRef.current = onClose;
16695
- const [editingId, setEditingId] = (0, import_react30.useState)(null);
16696
- const [editingTitle, setEditingTitle] = (0, import_react30.useState)("");
16697
- (0, import_react30.useEffect)(() => {
16839
+ const [editingId, setEditingId] = (0, import_react32.useState)(null);
16840
+ const [editingTitle, setEditingTitle] = (0, import_react32.useState)("");
16841
+ (0, import_react32.useEffect)(() => {
16698
16842
  const handleMouseDown = (e) => {
16699
16843
  if (menuRef.current && !menuRef.current.contains(e.target)) {
16700
16844
  onCloseRef.current();
@@ -16703,7 +16847,7 @@ var CompactSessionMenu = ({
16703
16847
  document.addEventListener("mousedown", handleMouseDown);
16704
16848
  return () => document.removeEventListener("mousedown", handleMouseDown);
16705
16849
  }, []);
16706
- (0, import_react30.useEffect)(() => {
16850
+ (0, import_react32.useEffect)(() => {
16707
16851
  if (editingId && inputRef.current) {
16708
16852
  inputRef.current.focus();
16709
16853
  inputRef.current.select();
@@ -17018,16 +17162,16 @@ var CompactChatView = ({
17018
17162
  const handleChoiceClick = (choice) => {
17019
17163
  setInput(choice.text);
17020
17164
  };
17021
- const [showSessionMenu, setShowSessionMenu] = (0, import_react31.useState)(false);
17022
- const handleSessionSelect = (0, import_react31.useCallback)((id) => {
17165
+ const [showSessionMenu, setShowSessionMenu] = (0, import_react33.useState)(false);
17166
+ const handleSessionSelect = (0, import_react33.useCallback)((id) => {
17023
17167
  selectSession(id);
17024
17168
  setShowSessionMenu(false);
17025
17169
  }, [selectSession]);
17026
- const handleNewSession = (0, import_react31.useCallback)(() => {
17170
+ const handleNewSession = (0, import_react33.useCallback)(() => {
17027
17171
  newSession();
17028
17172
  setShowSessionMenu(false);
17029
17173
  }, [newSession]);
17030
- const handleCloseMenu = (0, import_react31.useCallback)(() => {
17174
+ const handleCloseMenu = (0, import_react33.useCallback)(() => {
17031
17175
  setShowSessionMenu(false);
17032
17176
  }, []);
17033
17177
  const greeting = personalization?.userProfile?.nickname ? `${personalization.userProfile.nickname}\uB2D8, \uBB34\uC5C7\uC774\uB4E0 \uBB3C\uC5B4\uBCF4\uC138\uC694` : "\uBB34\uC5C7\uC774\uB4E0 \uBB3C\uC5B4\uBCF4\uC138\uC694";
@@ -17347,11 +17491,11 @@ var ChatFloatingWidget = ({
17347
17491
  maxWidth,
17348
17492
  minHeight
17349
17493
  });
17350
- const notifObj = (0, import_react32.useMemo)(() => {
17494
+ const notifObj = (0, import_react34.useMemo)(() => {
17351
17495
  if (!notification) return null;
17352
17496
  return typeof notification === "string" ? { text: notification } : notification;
17353
17497
  }, [notification]);
17354
- const handleFabClick = (0, import_react32.useCallback)(() => {
17498
+ const handleFabClick = (0, import_react34.useCallback)(() => {
17355
17499
  if (notifObj?.onClick) {
17356
17500
  notifObj.onClick();
17357
17501
  if (!isOpen) handleFabInteraction();
@@ -17365,7 +17509,7 @@ var ChatFloatingWidget = ({
17365
17509
  }
17366
17510
  handleFabInteraction();
17367
17511
  }, [notifObj, isOpen, handleFabInteraction, setTab, tabs]);
17368
- const allTabs = (0, import_react32.useMemo)(() => {
17512
+ const allTabs = (0, import_react34.useMemo)(() => {
17369
17513
  const chatTab = {
17370
17514
  key: "chat",
17371
17515
  label: "\uCC44\uD305",
@@ -17379,16 +17523,16 @@ var ChatFloatingWidget = ({
17379
17523
  }));
17380
17524
  return [chatTab, ...customTabs];
17381
17525
  }, [tabs]);
17382
- const [unreadBadge, setUnreadBadge] = (0, import_react32.useState)(0);
17383
- const seenMessageIdsRef = (0, import_react32.useRef)(/* @__PURE__ */ new Set());
17384
- (0, import_react32.useEffect)(() => {
17526
+ const [unreadBadge, setUnreadBadge] = (0, import_react34.useState)(0);
17527
+ const seenMessageIdsRef = (0, import_react34.useRef)(/* @__PURE__ */ new Set());
17528
+ (0, import_react34.useEffect)(() => {
17385
17529
  if (seenMessageIdsRef.current.size === 0 && chatState.messages.length > 0) {
17386
17530
  for (const m of chatState.messages) {
17387
17531
  seenMessageIdsRef.current.add(m.id);
17388
17532
  }
17389
17533
  }
17390
17534
  }, [chatState.messages]);
17391
- (0, import_react32.useEffect)(() => {
17535
+ (0, import_react34.useEffect)(() => {
17392
17536
  if (isOpen) {
17393
17537
  for (const m of chatState.messages) {
17394
17538
  seenMessageIdsRef.current.add(m.id);
@@ -17405,7 +17549,7 @@ var ChatFloatingWidget = ({
17405
17549
  }
17406
17550
  }
17407
17551
  }, [chatState.messages, isOpen]);
17408
- (0, import_react32.useEffect)(() => {
17552
+ (0, import_react34.useEffect)(() => {
17409
17553
  if (isOpen) {
17410
17554
  setUnreadBadge(0);
17411
17555
  for (const m of chatState.messages) {
@@ -17413,13 +17557,13 @@ var ChatFloatingWidget = ({
17413
17557
  }
17414
17558
  }
17415
17559
  }, [isOpen, chatState.messages]);
17416
- const totalBadge = (0, import_react32.useMemo)(() => {
17560
+ const totalBadge = (0, import_react34.useMemo)(() => {
17417
17561
  return tabs.reduce((sum, t) => sum + (t.badge || 0), 0) + unreadBadge;
17418
17562
  }, [tabs, unreadBadge]);
17419
- const isTalking = (0, import_react32.useMemo)(() => !!notification || chatState.isLoading, [notification, chatState.isLoading]);
17420
- const prevLoadingRef = (0, import_react32.useRef)(chatState.isLoading);
17421
- const [isComplete, setIsComplete] = (0, import_react32.useState)(false);
17422
- (0, import_react32.useEffect)(() => {
17563
+ const isTalking = (0, import_react34.useMemo)(() => !!notification || chatState.isLoading, [notification, chatState.isLoading]);
17564
+ const prevLoadingRef = (0, import_react34.useRef)(chatState.isLoading);
17565
+ const [isComplete, setIsComplete] = (0, import_react34.useState)(false);
17566
+ (0, import_react34.useEffect)(() => {
17423
17567
  const wasLoading = prevLoadingRef.current;
17424
17568
  prevLoadingRef.current = chatState.isLoading;
17425
17569
  if (!wasLoading || chatState.isLoading || chatState.messages.length === 0) return;
@@ -17535,7 +17679,7 @@ var ChatFloatingWidget = ({
17535
17679
  };
17536
17680
 
17537
17681
  // src/react/hooks/useDeepResearch.ts
17538
- var import_react33 = require("react");
17682
+ var import_react35 = require("react");
17539
17683
  var REPORT_GENERATION_PROMPT2 = `\uB2F9\uC2E0\uC740 \uB9AC\uC11C\uCE58 \uBCF4\uACE0\uC11C \uC791\uC131 \uC804\uBB38\uAC00\uC785\uB2C8\uB2E4.
17540
17684
 
17541
17685
  <collected_sources>
@@ -17586,10 +17730,10 @@ var QUERY_ANALYSIS_PROMPT2 = `\uC0AC\uC6A9\uC790 \uC9C8\uBB38\uC744 \uBD84\uC11D
17586
17730
  - JSON \uC678 \uB2E4\uB978 \uD14D\uC2A4\uD2B8 \uCD9C\uB825 \uAE08\uC9C0`;
17587
17731
  var useDeepResearch = (options) => {
17588
17732
  const { onWebSearch, onExtractContent, apiEndpoint, apiKey, model, provider } = options;
17589
- const [isResearching, setIsResearching] = (0, import_react33.useState)(false);
17590
- const [progress, setProgress] = (0, import_react33.useState)(null);
17591
- const abortControllerRef = (0, import_react33.useRef)(null);
17592
- const callLLM2 = (0, import_react33.useCallback)(
17733
+ const [isResearching, setIsResearching] = (0, import_react35.useState)(false);
17734
+ const [progress, setProgress] = (0, import_react35.useState)(null);
17735
+ const abortControllerRef = (0, import_react35.useRef)(null);
17736
+ const callLLM2 = (0, import_react35.useCallback)(
17593
17737
  async (prompt, stream = false) => {
17594
17738
  const response = await fetch(apiEndpoint, {
17595
17739
  method: "POST",
@@ -17616,7 +17760,7 @@ var useDeepResearch = (options) => {
17616
17760
  },
17617
17761
  [apiEndpoint, apiKey, model, provider]
17618
17762
  );
17619
- const analyzeQuery2 = (0, import_react33.useCallback)(
17763
+ const analyzeQuery2 = (0, import_react35.useCallback)(
17620
17764
  async (query) => {
17621
17765
  const prompt = QUERY_ANALYSIS_PROMPT2.replace("{question}", query);
17622
17766
  const response = await callLLM2(prompt);
@@ -17635,7 +17779,7 @@ var useDeepResearch = (options) => {
17635
17779
  },
17636
17780
  [callLLM2]
17637
17781
  );
17638
- const runSubAgent2 = (0, import_react33.useCallback)(
17782
+ const runSubAgent2 = (0, import_react35.useCallback)(
17639
17783
  async (topic, queries, agentId, updateProgress) => {
17640
17784
  updateProgress({ status: "searching", searchCount: 0, resultsCount: 0 });
17641
17785
  const allResults = [];
@@ -17674,7 +17818,7 @@ var useDeepResearch = (options) => {
17674
17818
  },
17675
17819
  [onWebSearch, onExtractContent]
17676
17820
  );
17677
- const generateReport2 = (0, import_react33.useCallback)(
17821
+ const generateReport2 = (0, import_react35.useCallback)(
17678
17822
  async (query, results, onStreamContent) => {
17679
17823
  const allSources = [];
17680
17824
  const sourcesForPrompt = [];
@@ -17732,7 +17876,7 @@ var useDeepResearch = (options) => {
17732
17876
  },
17733
17877
  [callLLM2]
17734
17878
  );
17735
- const runDeepResearch = (0, import_react33.useCallback)(
17879
+ const runDeepResearch = (0, import_react35.useCallback)(
17736
17880
  async (query, onStreamContent) => {
17737
17881
  abortControllerRef.current = new AbortController();
17738
17882
  setIsResearching(true);
@@ -17835,7 +17979,7 @@ var useDeepResearch = (options) => {
17835
17979
  },
17836
17980
  [analyzeQuery2, runSubAgent2, generateReport2]
17837
17981
  );
17838
- const stopResearch = (0, import_react33.useCallback)(() => {
17982
+ const stopResearch = (0, import_react35.useCallback)(() => {
17839
17983
  abortControllerRef.current?.abort();
17840
17984
  setIsResearching(false);
17841
17985
  setProgress(null);
@@ -17848,6 +17992,122 @@ var useDeepResearch = (options) => {
17848
17992
  };
17849
17993
  };
17850
17994
 
17995
+ // src/react/hooks/useContentParsers.ts
17996
+ var import_react36 = require("react");
17997
+ var useContentParsers = ({
17998
+ sessionsRef,
17999
+ setSessions
18000
+ }) => {
18001
+ const applyPollParsing = (0, import_react36.useCallback)(
18002
+ (sessionId, messageId, content, skipParsing) => {
18003
+ const { pollBlock, cleanContent } = parsePollFromContent(content);
18004
+ setSessions(
18005
+ (prev) => prev.map((s) => {
18006
+ if (s.id !== sessionId) return s;
18007
+ return {
18008
+ ...s,
18009
+ messages: s.messages.map((m) => {
18010
+ if (m.id !== messageId) return m;
18011
+ if (skipParsing) {
18012
+ return { ...m, content: cleanContent };
18013
+ }
18014
+ if (pollBlock) {
18015
+ return { ...m, content: cleanContent, pollBlock };
18016
+ }
18017
+ return m;
18018
+ })
18019
+ };
18020
+ })
18021
+ );
18022
+ return skipParsing ? null : pollBlock;
18023
+ },
18024
+ [setSessions]
18025
+ );
18026
+ const applyChecklistParsing = (0, import_react36.useCallback)(
18027
+ (sessionId, messageId, content, skipParsing) => {
18028
+ if (skipParsing) return null;
18029
+ const { checklistBlock, cleanContent } = parseChecklistFromContent(content);
18030
+ if (!checklistBlock) return null;
18031
+ setSessions(
18032
+ (prev) => prev.map((s) => {
18033
+ if (s.id !== sessionId) return s;
18034
+ return {
18035
+ ...s,
18036
+ messages: s.messages.map((m) => {
18037
+ if (m.id !== messageId) return m;
18038
+ return { ...m, content: cleanContent, checklistBlock };
18039
+ })
18040
+ };
18041
+ })
18042
+ );
18043
+ return checklistBlock;
18044
+ },
18045
+ [setSessions]
18046
+ );
18047
+ const applyArtifactParsing = (0, import_react36.useCallback)(
18048
+ (sessionId, messageId, content) => {
18049
+ if (!hasArtifactTag(content)) return false;
18050
+ let found = false;
18051
+ setSessions(
18052
+ (prev) => prev.map((s) => {
18053
+ if (s.id !== sessionId) return s;
18054
+ return {
18055
+ ...s,
18056
+ messages: s.messages.map((m) => {
18057
+ if (m.id !== messageId) return m;
18058
+ const { artifacts, cleanContent } = parseArtifactsFromContent(m.content);
18059
+ if (artifacts.length === 0) return m;
18060
+ found = true;
18061
+ const existingParts = m.contentParts || [];
18062
+ const textPart = cleanContent.trim() ? [{ type: "text", content: cleanContent }] : [];
18063
+ return {
18064
+ ...m,
18065
+ content: cleanContent,
18066
+ contentParts: [
18067
+ ...textPart,
18068
+ ...existingParts.filter(
18069
+ (p) => p.type !== "text" && p.type !== "artifact"
18070
+ ),
18071
+ ...artifacts
18072
+ ]
18073
+ };
18074
+ })
18075
+ };
18076
+ })
18077
+ );
18078
+ return found;
18079
+ },
18080
+ [setSessions]
18081
+ );
18082
+ const parseContextReferences = (0, import_react36.useCallback)(
18083
+ (content, sessionContext) => {
18084
+ const { refs, cleanContent } = parseContextRefs(content);
18085
+ if (refs.length === 0 || !sessionContext) {
18086
+ return { refs, cleanContent, refContents: null };
18087
+ }
18088
+ const assembled = refs.map((refId) => {
18089
+ const item = sessionContext.references.find(
18090
+ (r) => r.id === refId
18091
+ );
18092
+ return item ? `[${item.label || item.skillName}]
18093
+ ${item.summary}` : null;
18094
+ }).filter(Boolean).join("\n\n");
18095
+ return {
18096
+ refs,
18097
+ cleanContent,
18098
+ refContents: assembled || null
18099
+ };
18100
+ },
18101
+ []
18102
+ );
18103
+ return {
18104
+ applyPollParsing,
18105
+ applyChecklistParsing,
18106
+ applyArtifactParsing,
18107
+ parseContextReferences
18108
+ };
18109
+ };
18110
+
17851
18111
  // src/react/utils/conversationSearchAdapter.ts
17852
18112
  var formatSearchResults = (results) => {
17853
18113
  if (results.length === 0) {
@@ -18038,7 +18298,7 @@ var EmptyState = ({
18038
18298
  };
18039
18299
 
18040
18300
  // src/react/components/MemoryPanel.tsx
18041
- var import_react34 = require("react");
18301
+ var import_react37 = require("react");
18042
18302
  var import_jsx_runtime33 = require("react/jsx-runtime");
18043
18303
  var categoryLabels = {
18044
18304
  fact: "\uC0AC\uC6A9\uC790 \uC815\uBCF4",
@@ -18058,8 +18318,8 @@ var MemoryPanel = ({
18058
18318
  isOpen,
18059
18319
  onToggle
18060
18320
  }) => {
18061
- const [expandedId, setExpandedId] = (0, import_react34.useState)(null);
18062
- const [activeTab, setActiveTab] = (0, import_react34.useState)("all");
18321
+ const [expandedId, setExpandedId] = (0, import_react37.useState)(null);
18322
+ const [activeTab, setActiveTab] = (0, import_react37.useState)("all");
18063
18323
  const filteredItems = activeTab === "all" ? items : items.filter((item) => item.category === activeTab);
18064
18324
  const formatDate = (timestamp) => {
18065
18325
  const date = new Date(timestamp);
@@ -18375,9 +18635,53 @@ var MemoryPanel = ({
18375
18635
  }
18376
18636
  );
18377
18637
  };
18638
+
18639
+ // src/react/utils/retry.ts
18640
+ var DEFAULT_RETRY_CONFIG = {
18641
+ maxRetries: 3,
18642
+ initialDelayMs: 1e3,
18643
+ maxDelayMs: 3e4,
18644
+ backoffMultiplier: 2
18645
+ };
18646
+ var calculateDelay = (attempt, config) => {
18647
+ const exponential = config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt);
18648
+ const capped = Math.min(exponential, config.maxDelayMs);
18649
+ const jitter = 0.5 + Math.random() * 0.5;
18650
+ return Math.floor(capped * jitter);
18651
+ };
18652
+ var sleep = (ms, signal) => new Promise((resolve, reject) => {
18653
+ if (signal?.aborted) {
18654
+ reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
18655
+ return;
18656
+ }
18657
+ const timer = setTimeout(resolve, ms);
18658
+ signal?.addEventListener("abort", () => {
18659
+ clearTimeout(timer);
18660
+ reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
18661
+ }, { once: true });
18662
+ });
18663
+ var withRetry = async (fn, config, signal) => {
18664
+ const merged = { ...DEFAULT_RETRY_CONFIG, ...config };
18665
+ let lastError;
18666
+ for (let attempt = 0; attempt <= merged.maxRetries; attempt++) {
18667
+ try {
18668
+ return await fn();
18669
+ } catch (error) {
18670
+ lastError = error;
18671
+ if (error instanceof DOMException && error.name === "AbortError") throw error;
18672
+ if (error instanceof Error && error.name === "AbortError") throw error;
18673
+ if (signal?.aborted) throw error;
18674
+ if (attempt >= merged.maxRetries) throw error;
18675
+ if (error instanceof ChatError && !error.retryable) throw error;
18676
+ await sleep(calculateDelay(attempt, merged), signal);
18677
+ }
18678
+ }
18679
+ throw lastError;
18680
+ };
18378
18681
  // Annotate the CommonJS export names for ESM import in node:
18379
18682
  0 && (module.exports = {
18380
18683
  ArtifactCard,
18684
+ ChatError,
18381
18685
  ChatFloatingWidget,
18382
18686
  ChatHeader,
18383
18687
  ChatInput,
@@ -18390,6 +18694,7 @@ var MemoryPanel = ({
18390
18694
  ContentPartRenderer,
18391
18695
  DEFAULT_PROJECT_ID,
18392
18696
  DEFAULT_PROJECT_TITLE,
18697
+ DEFAULT_RETRY_CONFIG,
18393
18698
  DeepResearchProgressUI,
18394
18699
  DevDiveAvatar,
18395
18700
  DevDiveFabCharacter,
@@ -18413,20 +18718,28 @@ var MemoryPanel = ({
18413
18718
  ResizeHandles,
18414
18719
  SettingsModal,
18415
18720
  SkillProgressUI,
18721
+ classifyFetchError,
18416
18722
  convertSkillsToOpenAITools,
18417
18723
  convertToolsToSkills,
18418
18724
  createAdvancedResearchSkill,
18419
18725
  createConversationSearchSkill,
18420
18726
  createDeepResearchSkill,
18727
+ createTimeoutError,
18421
18728
  migrateSessionsToProjects,
18729
+ parseSSELine,
18730
+ parseSSEResponse,
18422
18731
  useChatUI,
18732
+ useChecklist,
18733
+ useContentParsers,
18423
18734
  useDeepResearch,
18424
18735
  useDragResize,
18425
18736
  useFloatingWidget,
18426
18737
  useImageError,
18427
18738
  useObserver,
18428
18739
  useProject,
18429
- useSkills
18740
+ useSkills,
18741
+ useStreamingFetch,
18742
+ withRetry
18430
18743
  });
18431
18744
  /**
18432
18745
  * @description localStorage 기반 메모리 저장소 어댑터