@gendive/chatllm 0.22.0 → 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;
@@ -2269,15 +2711,22 @@ var convertAttachmentsToBase64 = async (attachments) => Promise.all(
2269
2711
  size: att.size
2270
2712
  }))
2271
2713
  );
2272
- var convertAttachmentsWithUploader = async (attachments, uploader) => Promise.all(
2273
- attachments.map(async (att) => ({
2274
- name: att.name,
2275
- mimeType: att.mimeType,
2276
- base64: "",
2277
- url: await uploader(att.file),
2278
- size: att.size,
2279
- source: "uploader"
2280
- }))
2714
+ var convertAttachmentsWithUploaderCached = async (attachments, uploader, cache) => Promise.all(
2715
+ attachments.map(async (att) => {
2716
+ let url = cache.get(att.id);
2717
+ if (!url) {
2718
+ url = await uploader(att.file);
2719
+ cache.set(att.id, url);
2720
+ }
2721
+ return {
2722
+ name: att.name,
2723
+ mimeType: att.mimeType,
2724
+ base64: "",
2725
+ url,
2726
+ size: att.size,
2727
+ source: "uploader"
2728
+ };
2729
+ })
2281
2730
  );
2282
2731
  var findPreviousResultImage = (messages) => {
2283
2732
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -2327,6 +2776,12 @@ var useChatUI = (options) => {
2327
2776
  onSendMessage,
2328
2777
  onSessionChange,
2329
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,
2330
2785
  onTitleChange,
2331
2786
  generateTitle: generateTitleCallback,
2332
2787
  // Memory options
@@ -2388,74 +2843,78 @@ var useChatUI = (options) => {
2388
2843
  onChecklistStepModel
2389
2844
  } = options;
2390
2845
  const enableAutoExtraction = enableAutoExtractionProp ?? !useExternalStorage;
2391
- const [sessions, setSessions] = (0, import_react6.useState)([]);
2392
- const [currentSessionId, setCurrentSessionId] = (0, import_react6.useState)(null);
2393
- const [input, setInput] = (0, import_react6.useState)("");
2394
- 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());
2395
2850
  const isLoading = currentSessionId !== null && loadingSessionIds.has(currentSessionId);
2396
- const addLoadingSession = (0, import_react6.useCallback)((id) => setLoadingSessionIds((prev) => new Set(prev).add(id)), []);
2397
- 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) => {
2398
2853
  const next = new Set(prev);
2399
2854
  next.delete(id);
2400
2855
  return next;
2401
2856
  }), []);
2402
- const [selectedModel, setSelectedModel] = (0, import_react6.useState)(initialModel || models[0]?.id || "");
2403
- const [sidebarOpen, setSidebarOpen] = (0, import_react6.useState)(true);
2404
- const [settingsOpen, setSettingsOpen] = (0, import_react6.useState)(false);
2405
- const [quotedText, setQuotedText] = (0, import_react6.useState)(null);
2406
- const [selectedAction, setSelectedAction] = (0, import_react6.useState)(null);
2407
- const [copiedMessageId, setCopiedMessageId] = (0, import_react6.useState)(null);
2408
- const [editingMessageId, setEditingMessageId] = (0, import_react6.useState)(null);
2409
- 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)({
2410
2865
  ...DEFAULT_PERSONALIZATION,
2411
2866
  ...initialPersonalization
2412
2867
  });
2413
- const [activeAlternatives, setActiveAlternatives] = (0, import_react6.useState)({});
2414
- const [loadingAlternativeFor, setLoadingAlternativeFor] = (0, import_react6.useState)(null);
2415
- const [isSessionsLoading, setIsSessionsLoading] = (0, import_react6.useState)(false);
2416
- const [isSessionLoading, setIsSessionLoading] = (0, import_react6.useState)(false);
2417
- const [isDeepResearchMode, setIsDeepResearchMode] = (0, import_react6.useState)(false);
2418
- const [attachments, setAttachments] = (0, import_react6.useState)([]);
2419
- const [isModelsLoading, setIsModelsLoading] = (0, import_react6.useState)(false);
2420
- const [loadedModels, setLoadedModels] = (0, import_react6.useState)(null);
2421
- 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)(
2422
2877
  null
2423
2878
  );
2424
- const sessionsRef = (0, import_react6.useRef)(sessions);
2425
- (0, import_react6.useEffect)(() => {
2879
+ const sessionsRef = (0, import_react8.useRef)(sessions);
2880
+ (0, import_react8.useEffect)(() => {
2426
2881
  sessionsRef.current = sessions;
2427
2882
  }, [sessions]);
2428
- const modelsRef = (0, import_react6.useRef)(models);
2429
- (0, import_react6.useEffect)(() => {
2883
+ const modelsRef = (0, import_react8.useRef)(models);
2884
+ (0, import_react8.useEffect)(() => {
2430
2885
  modelsRef.current = models;
2431
2886
  }, [models]);
2432
- const onSendMessageRef = (0, import_react6.useRef)(onSendMessage);
2433
- const onResponseHeadersRef = (0, import_react6.useRef)(options.onResponseHeaders);
2434
- const onSessionChangeRef = (0, import_react6.useRef)(onSessionChange);
2435
- const onErrorRef = (0, import_react6.useRef)(onError);
2436
- const onTitleChangeRef = (0, import_react6.useRef)(onTitleChange);
2437
- const generateTitleRef = (0, import_react6.useRef)(generateTitleCallback);
2438
- const onPersonalizationChangeRef = (0, import_react6.useRef)(options.onPersonalizationChange);
2439
- const onPersonalizationSaveRef = (0, import_react6.useRef)(options.onPersonalizationSave);
2440
- const onLoadSessionsRef = (0, import_react6.useRef)(onLoadSessions);
2441
- const onCreateSessionRef = (0, import_react6.useRef)(onCreateSession);
2442
- const onLoadSessionRef = (0, import_react6.useRef)(onLoadSession);
2443
- const onDeleteSessionCallbackRef = (0, import_react6.useRef)(onDeleteSessionCallback);
2444
- const onUpdateSessionTitleRef = (0, import_react6.useRef)(onUpdateSessionTitle);
2445
- const onSaveMessagesRef = (0, import_react6.useRef)(onSaveMessages);
2446
- const onToolCallRef = (0, import_react6.useRef)(onToolCall);
2447
- const onSkillCompleteRef = (0, import_react6.useRef)(onSkillComplete);
2448
- const onSessionContextChangeRef = (0, import_react6.useRef)(onSessionContextChange);
2449
- const onLoadModelsRef = (0, import_react6.useRef)(onLoadModels);
2450
- const onUploadImageRef = (0, import_react6.useRef)(onUploadImage);
2451
- const onImageErrorRef = (0, import_react6.useRef)(onImageError);
2452
- const fileUploaderRef = (0, import_react6.useRef)(fileUploader);
2453
- const globalMemoryRef = (0, import_react6.useRef)(null);
2454
- (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)(() => {
2455
2912
  onSendMessageRef.current = onSendMessage;
2456
2913
  onResponseHeadersRef.current = options.onResponseHeaders;
2457
2914
  onSessionChangeRef.current = onSessionChange;
2458
2915
  onErrorRef.current = onError;
2916
+ onAbortRef.current = onAbort;
2917
+ onTokenUsageRef.current = onTokenUsage;
2459
2918
  onTitleChangeRef.current = onTitleChange;
2460
2919
  generateTitleRef.current = generateTitleCallback;
2461
2920
  onPersonalizationChangeRef.current = options.onPersonalizationChange;
@@ -2474,15 +2933,19 @@ var useChatUI = (options) => {
2474
2933
  fileUploaderRef.current = fileUploader;
2475
2934
  onLoadModelsRef.current = onLoadModels;
2476
2935
  });
2477
- const abortControllersRef = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
2478
- const pendingInitialLoadRef = (0, import_react6.useRef)(null);
2479
- const skipNextPollParsingRef = (0, import_react6.useRef)(false);
2480
- const skipNextSkillParsingRef = (0, import_react6.useRef)(false);
2481
- const skipNextChecklistParsingRef = (0, import_react6.useRef)(false);
2482
- const activeChecklistRef = (0, import_react6.useRef)(null);
2483
- const pendingChecklistRef = (0, import_react6.useRef)(null);
2484
- const pendingAttachmentDataRef = (0, import_react6.useRef)(null);
2485
- 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);
2486
2949
  const resolveChecklistRefImage = (item, checklistMessageId, sessionId) => {
2487
2950
  const session = sessionsRef.current.find((s) => s.id === sessionId);
2488
2951
  if (!session) return null;
@@ -2549,7 +3012,7 @@ ${hints.join(" ")}` : "";
2549
3012
  return `${stepLabel}
2550
3013
  ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \uB9C8\uC138\uC694.`;
2551
3014
  };
2552
- const memoryOptions = (0, import_react6.useMemo)(
3015
+ const memoryOptions = (0, import_react8.useMemo)(
2553
3016
  () => ({
2554
3017
  storageType: globalMemoryConfig?.storageType || "localStorage",
2555
3018
  storageKey: globalMemoryConfig?.localStorage?.key || `${storageKey}_memory`,
@@ -2563,11 +3026,11 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
2563
3026
  const globalMemoryRaw = useGlobalMemory(memoryOptions);
2564
3027
  const globalMemory = useGlobalMemoryEnabled ? globalMemoryRaw : null;
2565
3028
  globalMemoryRef.current = globalMemory;
2566
- const stableToolCall = (0, import_react6.useCallback)(
3029
+ const stableToolCall = (0, import_react8.useCallback)(
2567
3030
  (name, params) => onToolCallRef.current(name, params),
2568
3031
  []
2569
3032
  );
2570
- const mergedSkills = (0, import_react6.useMemo)(() => {
3033
+ const mergedSkills = (0, import_react8.useMemo)(() => {
2571
3034
  if (!tools || !onToolCall) return skills || {};
2572
3035
  const toolSkills = convertToolsToSkills(tools, stableToolCall);
2573
3036
  return { ...skills || {}, ...toolSkills };
@@ -2603,9 +3066,9 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
2603
3066
  onDeleteProjectFile,
2604
3067
  onError
2605
3068
  });
2606
- const [projectSettingsOpen, setProjectSettingsOpen] = (0, import_react6.useState)(false);
3069
+ const [projectSettingsOpen, setProjectSettingsOpen] = (0, import_react8.useState)(false);
2607
3070
  const projectMemoryKey = enableProjects && projectHook.currentProjectId ? `${storageKey}_project_memory_${projectHook.currentProjectId}` : `${storageKey}_project_memory_none`;
2608
- const projectMemoryOptions = (0, import_react6.useMemo)(
3071
+ const projectMemoryOptions = (0, import_react8.useMemo)(
2609
3072
  () => ({
2610
3073
  storageType: globalMemoryConfig?.storageType || "localStorage",
2611
3074
  storageKey: projectMemoryKey,
@@ -2617,7 +3080,7 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
2617
3080
  );
2618
3081
  const projectMemoryRaw = useGlobalMemory(projectMemoryOptions);
2619
3082
  const projectMemory = enableProjects ? projectMemoryRaw : null;
2620
- const unwrapResponseHeaders = (0, import_react6.useCallback)((result) => {
3083
+ const unwrapResponseHeaders = (0, import_react8.useCallback)((result) => {
2621
3084
  if (typeof result === "object" && result !== null && "response" in result && "headers" in result) {
2622
3085
  const wrapped = result;
2623
3086
  onResponseHeadersRef.current?.(wrapped.headers);
@@ -2625,7 +3088,7 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
2625
3088
  }
2626
3089
  return result;
2627
3090
  }, []);
2628
- const emitFetchHeaders = (0, import_react6.useCallback)((response) => {
3091
+ const emitFetchHeaders = (0, import_react8.useCallback)((response) => {
2629
3092
  if (onResponseHeadersRef.current && response.headers) {
2630
3093
  const headerMap = {};
2631
3094
  response.headers.forEach((v, k) => {
@@ -2634,7 +3097,7 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
2634
3097
  onResponseHeadersRef.current(headerMap);
2635
3098
  }
2636
3099
  }, []);
2637
- const callInternalLLM = (0, import_react6.useCallback)(async (prompt, model) => {
3100
+ const callInternalLLM = (0, import_react8.useCallback)(async (prompt, model) => {
2638
3101
  if (onSendMessageRef.current) {
2639
3102
  const modelConfig = modelsRef.current.find((m) => m.id === model);
2640
3103
  const provider = modelConfig?.provider || "ollama";
@@ -2672,11 +3135,11 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
2672
3135
  const currentSession = sessions.find((s) => s.id === currentSessionId) || null;
2673
3136
  const messages = currentSession?.messages.filter((m) => !m.hidden) || [];
2674
3137
  const compressionState = currentSession?.compressionState || null;
2675
- const visibleSessions = (0, import_react6.useMemo)(() => {
3138
+ const visibleSessions = (0, import_react8.useMemo)(() => {
2676
3139
  if (!enableProjects || !projectHook.currentProjectId) return sessions;
2677
3140
  return sessions.filter((s) => s.projectId === projectHook.currentProjectId);
2678
3141
  }, [sessions, enableProjects, projectHook.currentProjectId]);
2679
- (0, import_react6.useEffect)(() => {
3142
+ (0, import_react8.useEffect)(() => {
2680
3143
  if (typeof window === "undefined") return;
2681
3144
  if (useExternalStorage && onLoadSessionsRef.current) {
2682
3145
  setIsSessionsLoading(true);
@@ -2738,7 +3201,7 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
2738
3201
  }
2739
3202
  }
2740
3203
  }, [storageKey, useExternalStorage, initialModel, startWithNewSession]);
2741
- (0, import_react6.useEffect)(() => {
3204
+ (0, import_react8.useEffect)(() => {
2742
3205
  if (!onLoadModelsRef.current) return;
2743
3206
  setIsModelsLoading(true);
2744
3207
  onLoadModelsRef.current().then((modelList) => {
@@ -2753,21 +3216,21 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
2753
3216
  });
2754
3217
  }, []);
2755
3218
  const effectiveModels = loadedModels || models;
2756
- (0, import_react6.useEffect)(() => {
3219
+ (0, import_react8.useEffect)(() => {
2757
3220
  if (typeof window === "undefined") return;
2758
3221
  if (useExternalStorage) return;
2759
3222
  if (sessions.length > 0) {
2760
3223
  localStorage.setItem(storageKey, JSON.stringify(sessions));
2761
3224
  }
2762
3225
  }, [sessions, storageKey, useExternalStorage]);
2763
- (0, import_react6.useEffect)(() => {
3226
+ (0, import_react8.useEffect)(() => {
2764
3227
  if (typeof window === "undefined") return;
2765
3228
  localStorage.setItem(`${storageKey}_personalization`, JSON.stringify(personalization));
2766
3229
  }, [personalization, storageKey]);
2767
- (0, import_react6.useEffect)(() => {
3230
+ (0, import_react8.useEffect)(() => {
2768
3231
  onSessionChangeRef.current?.(currentSession);
2769
3232
  }, [currentSession]);
2770
- const buildSystemPrompt = (0, import_react6.useCallback)((session) => {
3233
+ const buildSystemPrompt = (0, import_react8.useCallback)((session) => {
2771
3234
  const parts = [];
2772
3235
  const { userProfile, responseStyle, language } = personalization;
2773
3236
  const identityName = assistantName || "AI \uC5B4\uC2DC\uC2A4\uD134\uD2B8";
@@ -3126,7 +3589,7 @@ AI (\uD655\uC815):
3126
3589
  }
3127
3590
  return parts.length > 0 ? parts.join("\n") : "";
3128
3591
  }, [personalization, globalMemory, useGlobalMemoryEnabled, enablePoll, enableChecklist, buildSkillsPrompt, enableProjects, projectHook.currentProject, projectMemory, resolvedSkills, assistantName, onBuildSystemPrompt, compactSystemPrompt, selectedModel]);
3129
- const promoteToSessionContext = (0, import_react6.useCallback)((sessionId, skillName, content, metadata, label) => {
3592
+ const promoteToSessionContext = (0, import_react8.useCallback)((sessionId, skillName, content, metadata, label) => {
3130
3593
  const item = createSessionContextItem(skillName, content, metadata, label);
3131
3594
  setSessions(
3132
3595
  (prev) => prev.map((s) => {
@@ -3137,7 +3600,7 @@ AI (\uD655\uC815):
3137
3600
  })
3138
3601
  );
3139
3602
  }, []);
3140
- const compressContext = (0, import_react6.useCallback)(async (messagesToCompress, model) => {
3603
+ const compressContext = (0, import_react8.useCallback)(async (messagesToCompress, model) => {
3141
3604
  const conversationText = messagesToCompress.map((m) => `${m.role === "user" ? "\uC0AC\uC6A9\uC790" : "AI"}: ${m.content}`).join("\n\n");
3142
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.
3143
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.
@@ -3152,7 +3615,7 @@ ${conversationText}
3152
3615
  return "";
3153
3616
  }
3154
3617
  }, [callInternalLLM]);
3155
- const incrementalCompressContext = (0, import_react6.useCallback)(
3618
+ const incrementalCompressContext = (0, import_react8.useCallback)(
3156
3619
  async (existingSummary, newMessages, model) => {
3157
3620
  const newConversation = newMessages.map((m) => `${m.role === "user" ? "\uC0AC\uC6A9\uC790" : "AI"}: ${m.content}`).join("\n\n");
3158
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.
@@ -3179,10 +3642,10 @@ ${newConversation}
3179
3642
  },
3180
3643
  [callInternalLLM]
3181
3644
  );
3182
- const estimateTokens = (0, import_react6.useCallback)((messages2) => {
3645
+ const estimateTokens = (0, import_react8.useCallback)((messages2) => {
3183
3646
  return messages2.reduce((sum, m) => sum + Math.ceil(m.content.length / 4), 0);
3184
3647
  }, []);
3185
- const newSession = (0, import_react6.useCallback)(async () => {
3648
+ const newSession = (0, import_react8.useCallback)(async () => {
3186
3649
  const projectId = enableProjects ? projectHook.currentProjectId || DEFAULT_PROJECT_ID : void 0;
3187
3650
  if (useExternalStorage && onCreateSessionRef.current) {
3188
3651
  setIsSessionLoading(true);
@@ -3220,7 +3683,7 @@ ${newConversation}
3220
3683
  setSessions((prev) => [newSess, ...prev]);
3221
3684
  setCurrentSessionId(newSess.id);
3222
3685
  }, [selectedModel, useExternalStorage, enableProjects, projectHook.currentProjectId]);
3223
- const selectSession = (0, import_react6.useCallback)(async (id) => {
3686
+ const selectSession = (0, import_react8.useCallback)(async (id) => {
3224
3687
  if (useExternalStorage && onLoadSessionRef.current) {
3225
3688
  setIsSessionLoading(true);
3226
3689
  try {
@@ -3324,13 +3787,13 @@ ${newConversation}
3324
3787
  setSelectedModel(session.model);
3325
3788
  }
3326
3789
  }, [sessions, useExternalStorage, storageKey, initialModel]);
3327
- (0, import_react6.useEffect)(() => {
3790
+ (0, import_react8.useEffect)(() => {
3328
3791
  if (!pendingInitialLoadRef.current || !useExternalStorage) return;
3329
3792
  const id = pendingInitialLoadRef.current;
3330
3793
  pendingInitialLoadRef.current = null;
3331
3794
  selectSession(id);
3332
3795
  }, [currentSessionId, selectSession, useExternalStorage]);
3333
- const deleteSession = (0, import_react6.useCallback)(async (id) => {
3796
+ const deleteSession = (0, import_react8.useCallback)(async (id) => {
3334
3797
  if (useExternalStorage && onDeleteSessionCallbackRef.current) {
3335
3798
  try {
3336
3799
  await onDeleteSessionCallbackRef.current(id);
@@ -3358,7 +3821,7 @@ ${newConversation}
3358
3821
  return filtered;
3359
3822
  });
3360
3823
  }, [currentSessionId, storageKey, useExternalStorage]);
3361
- const renameSession = (0, import_react6.useCallback)(async (id, newTitle) => {
3824
+ const renameSession = (0, import_react8.useCallback)(async (id, newTitle) => {
3362
3825
  if (!newTitle.trim()) return;
3363
3826
  if (useExternalStorage && onUpdateSessionTitleRef.current) {
3364
3827
  try {
@@ -3381,7 +3844,7 @@ ${newConversation}
3381
3844
  );
3382
3845
  onTitleChangeRef.current?.(id, newTitle.trim());
3383
3846
  }, [useExternalStorage]);
3384
- const setModel = (0, import_react6.useCallback)((model) => {
3847
+ const setModel = (0, import_react8.useCallback)((model) => {
3385
3848
  setSelectedModel(model);
3386
3849
  if (currentSessionId) {
3387
3850
  setSessions(
@@ -3389,10 +3852,10 @@ ${newConversation}
3389
3852
  );
3390
3853
  }
3391
3854
  }, [currentSessionId]);
3392
- const toggleSidebar = (0, import_react6.useCallback)(() => setSidebarOpen((prev) => !prev), []);
3393
- const openSettings = (0, import_react6.useCallback)(() => setSettingsOpen(true), []);
3394
- const closeSettings = (0, import_react6.useCallback)(() => setSettingsOpen(false), []);
3395
- 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) => {
3396
3859
  if (typeof navigator !== "undefined") {
3397
3860
  navigator.clipboard.writeText(content).then(() => {
3398
3861
  setCopiedMessageId(id);
@@ -3400,30 +3863,30 @@ ${newConversation}
3400
3863
  });
3401
3864
  }
3402
3865
  }, []);
3403
- const startEdit = (0, import_react6.useCallback)((message) => {
3866
+ const startEdit = (0, import_react8.useCallback)((message) => {
3404
3867
  if (message.role === "user") {
3405
3868
  setEditingMessageId(message.id);
3406
3869
  }
3407
3870
  }, []);
3408
- const cancelEdit = (0, import_react6.useCallback)(() => {
3871
+ const cancelEdit = (0, import_react8.useCallback)(() => {
3409
3872
  setEditingMessageId(null);
3410
3873
  }, []);
3411
- const stopGeneration = (0, import_react6.useCallback)(() => {
3874
+ const stopGeneration = (0, import_react8.useCallback)(() => {
3412
3875
  if (currentSessionId) {
3413
3876
  abortControllersRef.current.get(currentSessionId)?.abort();
3414
3877
  }
3415
3878
  }, [currentSessionId]);
3416
- const updatePersonalization = (0, import_react6.useCallback)((config) => {
3879
+ const updatePersonalization = (0, import_react8.useCallback)((config) => {
3417
3880
  setPersonalization((prev) => {
3418
3881
  const next = { ...prev, ...config };
3419
3882
  onPersonalizationChangeRef.current?.(next);
3420
3883
  return next;
3421
3884
  });
3422
3885
  }, []);
3423
- const savePersonalization = (0, import_react6.useCallback)(() => {
3886
+ const savePersonalization = (0, import_react8.useCallback)(() => {
3424
3887
  onPersonalizationSaveRef.current?.(personalization);
3425
3888
  }, [personalization]);
3426
- const addAttachments = (0, import_react6.useCallback)((files) => {
3889
+ const addAttachments = (0, import_react8.useCallback)((files) => {
3427
3890
  const newAttachments = files.map((file) => {
3428
3891
  const isImage = file.type.startsWith("image/");
3429
3892
  return {
@@ -3438,7 +3901,7 @@ ${newConversation}
3438
3901
  });
3439
3902
  setAttachments((prev) => [...prev, ...newAttachments]);
3440
3903
  }, []);
3441
- const removeAttachment = (0, import_react6.useCallback)((id) => {
3904
+ const removeAttachment = (0, import_react8.useCallback)((id) => {
3442
3905
  setAttachments((prev) => {
3443
3906
  const target = prev.find((a) => a.id === id);
3444
3907
  if (target?.previewUrl) {
@@ -3447,17 +3910,17 @@ ${newConversation}
3447
3910
  return prev.filter((a) => a.id !== id);
3448
3911
  });
3449
3912
  }, []);
3450
- const toggleDeepResearchMode = (0, import_react6.useCallback)(() => {
3913
+ const toggleDeepResearchMode = (0, import_react8.useCallback)(() => {
3451
3914
  setIsDeepResearchMode((prev) => !prev);
3452
3915
  }, []);
3453
- const trackBehavior = (0, import_react6.useCallback)(async (key, updater) => {
3916
+ const trackBehavior = (0, import_react8.useCallback)(async (key, updater) => {
3454
3917
  if (!globalMemory) return;
3455
3918
  const fullKey = `behavior.${key}`;
3456
3919
  const prev = globalMemory.get(fullKey) ?? {};
3457
3920
  const next = updater(prev);
3458
3921
  await globalMemory.set(fullKey, next, { category: "behavior" });
3459
3922
  }, [globalMemory]);
3460
- const trackMessageLength = (0, import_react6.useCallback)((length) => {
3923
+ const trackMessageLength = (0, import_react8.useCallback)((length) => {
3461
3924
  trackBehavior("messageLength", (prev) => {
3462
3925
  const samples = (prev.messageLengthSamples || []).slice(-19);
3463
3926
  samples.push(length);
@@ -3465,39 +3928,69 @@ ${newConversation}
3465
3928
  return { ...prev, messageLengthSamples: samples, avgMessageLength: avg };
3466
3929
  });
3467
3930
  }, [trackBehavior]);
3468
- const trackSkillUsage = (0, import_react6.useCallback)((skillName) => {
3931
+ const trackSkillUsage = (0, import_react8.useCallback)((skillName) => {
3469
3932
  trackBehavior("skillUsage", (prev) => ({
3470
3933
  ...prev,
3471
3934
  [skillName]: (prev[skillName] || 0) + 1
3472
3935
  }));
3473
3936
  }, [trackBehavior]);
3474
- const trackChecklistSkip = (0, import_react6.useCallback)(() => {
3937
+ const trackChecklistSkip = (0, import_react8.useCallback)(() => {
3475
3938
  trackBehavior("checklistSkipRate", (prev) => ({
3476
3939
  total: (prev.total || 0) + 1,
3477
3940
  skipped: (prev.skipped || 0) + 1
3478
3941
  }));
3479
3942
  }, [trackBehavior]);
3480
- const trackChecklistComplete = (0, import_react6.useCallback)(() => {
3943
+ const trackChecklistComplete = (0, import_react8.useCallback)(() => {
3481
3944
  trackBehavior("checklistSkipRate", (prev) => ({
3482
3945
  total: (prev.total || 0) + 1,
3483
3946
  skipped: prev.skipped || 0
3484
3947
  }));
3485
3948
  }, [trackBehavior]);
3486
- 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)(() => {
3487
3970
  trackBehavior("regenerateRate", (prev) => ({
3488
3971
  ...prev,
3489
3972
  regenerated: (prev.regenerated || 0) + 1
3490
3973
  }));
3491
3974
  }, [trackBehavior]);
3492
- const trackMessageSent = (0, import_react6.useCallback)(() => {
3975
+ const trackMessageSent = (0, import_react8.useCallback)(() => {
3493
3976
  trackBehavior("regenerateRate", (prev) => ({
3494
3977
  total: (prev.total || 0) + 1,
3495
3978
  regenerated: prev.regenerated || 0
3496
3979
  }));
3497
3980
  }, [trackBehavior]);
3498
- const sendMessage = (0, import_react6.useCallback)(async (content, options2) => {
3981
+ const sendMessage = (0, import_react8.useCallback)(async (content, options2) => {
3499
3982
  const messageContent = content || input;
3500
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
+ }
3501
3994
  let sessionId = currentSessionId;
3502
3995
  if (!sessionId) {
3503
3996
  if (useExternalStorage && onCreateSessionRef.current) {
@@ -3551,6 +4044,7 @@ ${finalContent}`;
3551
4044
  const actionPrompt = selectedAction?.systemPrompt;
3552
4045
  const isHidden = options2?.hiddenUserMessage ?? false;
3553
4046
  const currentAttachments = attachments;
4047
+ const uploadedUrlMap = /* @__PURE__ */ new Map();
3554
4048
  let userContentParts;
3555
4049
  if (currentAttachments.length > 0) {
3556
4050
  userContentParts = [];
@@ -3559,8 +4053,19 @@ ${finalContent}`;
3559
4053
  }
3560
4054
  for (const att of currentAttachments) {
3561
4055
  if (att.type === "image" && att.file) {
3562
- const dataUri = await fileToDataUri(att.file);
3563
- const imageUrl = onUploadImageRef.current ? await onUploadImageRef.current(dataUri, att.name) : dataUri;
4056
+ let imageUrl;
4057
+ try {
4058
+ if (fileUploaderRef.current) {
4059
+ imageUrl = await fileUploaderRef.current(att.file);
4060
+ } else {
4061
+ const dataUri = await fileToDataUri(att.file);
4062
+ imageUrl = onUploadImageRef.current ? await onUploadImageRef.current(dataUri, att.name) : dataUri;
4063
+ }
4064
+ } catch (err) {
4065
+ console.error("[chatllm] image upload failed, falling back to data URI:", err);
4066
+ imageUrl = await fileToDataUri(att.file);
4067
+ }
4068
+ uploadedUrlMap.set(att.id, imageUrl);
3564
4069
  userContentParts.push({ type: "image", url: imageUrl, alt: att.name, fileName: att.name });
3565
4070
  } else {
3566
4071
  userContentParts.push({ type: "file", name: att.name, url: att.previewUrl || "", mimeType: att.mimeType, size: att.size });
@@ -3659,7 +4164,7 @@ ${finalContent}`;
3659
4164
  })
3660
4165
  );
3661
4166
  try {
3662
- const filesToPass = fileUploaderRef.current ? await convertAttachmentsWithUploader(matchedFiles, fileUploaderRef.current) : skillConfig.autoConvertBase64 ? await convertAttachmentsToBase64(matchedFiles) : matchedFiles;
4167
+ const filesToPass = fileUploaderRef.current ? await convertAttachmentsWithUploaderCached(matchedFiles, fileUploaderRef.current, uploadedUrlMap) : skillConfig.autoConvertBase64 ? await convertAttachmentsToBase64(matchedFiles) : matchedFiles;
3663
4168
  const result = await skillConfig.execute({ files: filesToPass, userMessage: finalContent });
3664
4169
  const attachResultType = result.metadata?.type || result.metadata?.resultType || "text";
3665
4170
  const toolResultPart = {
@@ -3718,7 +4223,7 @@ ${finalContent}`;
3718
4223
  if (hasImageAttachments && hasUserText) {
3719
4224
  const imageAttachments = currentAttachments.filter((a) => a.type === "image");
3720
4225
  try {
3721
- pendingAttachmentDataRef.current = fileUploaderRef.current ? await convertAttachmentsWithUploader(imageAttachments, fileUploaderRef.current) : await convertAttachmentsToBase64(imageAttachments);
4226
+ pendingAttachmentDataRef.current = fileUploaderRef.current ? await convertAttachmentsWithUploaderCached(imageAttachments, fileUploaderRef.current, uploadedUrlMap) : await convertAttachmentsToBase64(imageAttachments);
3722
4227
  } catch (err) {
3723
4228
  console.error("[chatllm] pendingAttachment conversion failed:", err);
3724
4229
  pendingAttachmentDataRef.current = null;
@@ -3771,10 +4276,11 @@ ${finalContent}`;
3771
4276
  return;
3772
4277
  }
3773
4278
  abortControllersRef.current.set(capturedSessionId, new AbortController());
4279
+ let accumulatedContent = "";
4280
+ let lastUsage = null;
3774
4281
  try {
3775
4282
  const shouldSkipSkillParsing = skipNextSkillParsingRef.current;
3776
4283
  skipNextSkillParsingRef.current = false;
3777
- let accumulatedContent = "";
3778
4284
  let checklistStepImageUrl = null;
3779
4285
  let messagesToSend = [...existingMessages, userMessage];
3780
4286
  const recompressionThreshold = DEFAULT_RECOMPRESSION_THRESHOLD;
@@ -3980,118 +4486,70 @@ ${attachmentContext}
3980
4486
  emitFetchHeaders(response);
3981
4487
  }
3982
4488
  if (response) {
3983
- if (!response.ok) throw new Error("API error");
3984
- const reader = response.body?.getReader();
3985
- if (!reader) throw new Error("No reader");
3986
- const decoder = new TextDecoder();
3987
- let buffer = "";
3988
4489
  let skillTagDetected = false;
3989
4490
  let checklistTagDetected = false;
3990
4491
  let toolCallAcc = null;
3991
- while (true) {
3992
- const { done, value } = await reader.read();
3993
- if (done) break;
3994
- buffer += decoder.decode(value, { stream: true });
3995
- const lines = buffer.split("\n");
3996
- buffer = lines.pop() || "";
3997
- for (const line of lines) {
3998
- if (!line.trim()) continue;
3999
- let data = line;
4000
- if (line.startsWith("data: ")) {
4001
- data = line.slice(6);
4002
- if (data === "[DONE]") continue;
4492
+ const streamResult = await streamResponse(response, (chunk) => {
4493
+ if (useNativeTools && chunk.delta?.tool_calls) {
4494
+ toolCallAcc = accumulateToolCallDelta(toolCallAcc, chunk.delta);
4495
+ }
4496
+ if (useNativeTools && chunk.finishReason === "tool_calls" && toolCallAcc) {
4497
+ accumulatedContent += toolCallToSkillUseXml(toolCallAcc);
4498
+ skillTagDetected = true;
4499
+ return "break";
4500
+ }
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;
4003
4508
  }
4004
- try {
4005
- const parsed = JSON.parse(data);
4006
- const delta = parsed.choices?.[0]?.delta;
4007
- const finishReason = parsed.choices?.[0]?.finish_reason;
4008
- if (useNativeTools && delta?.tool_calls) {
4009
- toolCallAcc = accumulateToolCallDelta(toolCallAcc, delta);
4010
- }
4011
- if (useNativeTools && finishReason === "tool_calls" && toolCallAcc) {
4012
- accumulatedContent += toolCallToSkillUseXml(toolCallAcc);
4013
- skillTagDetected = true;
4014
- break;
4015
- }
4016
- const content2 = delta?.content || parsed.message?.content || parsed.content || parsed.text || "";
4017
- const thinking = parsed.message?.thinking || "";
4018
- if (content2 || thinking) {
4019
- if (content2) accumulatedContent += content2;
4020
- if (!shouldSkipSkillParsing && accumulatedContent.includes("</skill_use>")) {
4021
- const endIdx = accumulatedContent.indexOf("</skill_use>");
4022
- accumulatedContent = accumulatedContent.substring(0, endIdx + "</skill_use>".length);
4023
- skillTagDetected = true;
4024
- }
4025
- if (!skipNextChecklistParsingRef.current && accumulatedContent.includes("</checklist>")) {
4026
- const endIdx = accumulatedContent.indexOf("</checklist>");
4027
- accumulatedContent = accumulatedContent.substring(0, endIdx + "</checklist>".length);
4028
- checklistTagDetected = true;
4029
- }
4030
- const displayContent = skillTagDetected ? accumulatedContent : null;
4031
- setSessions(
4032
- (prev) => prev.map((s) => {
4033
- if (s.id === capturedSessionId) {
4034
- return {
4035
- ...s,
4036
- messages: s.messages.map((m) => {
4037
- if (m.id !== assistantMessageId) return m;
4038
- if (displayContent) {
4039
- return { ...m, content: displayContent };
4040
- }
4041
- let newContent = m.content;
4042
- if (thinking) {
4043
- if (!newContent.includes("<thinking>")) {
4044
- newContent = "<thinking>" + thinking;
4045
- } else if (!newContent.includes("</thinking>")) {
4046
- newContent += thinking;
4047
- }
4048
- }
4049
- if (content2) {
4050
- if (newContent.includes("<thinking>") && !newContent.includes("</thinking>")) {
4051
- newContent += "</thinking>\n\n";
4052
- }
4053
- newContent += content2;
4054
- }
4055
- return { ...m, content: newContent };
4056
- })
4057
- };
4058
- }
4059
- return s;
4060
- })
4061
- );
4062
- if (skillTagDetected || checklistTagDetected) break;
4063
- }
4064
- } catch {
4509
+ if (!skipNextChecklistParsingRef.current && accumulatedContent.includes("</checklist>")) {
4510
+ const endIdx = accumulatedContent.indexOf("</checklist>");
4511
+ accumulatedContent = accumulatedContent.substring(0, endIdx + "</checklist>".length);
4512
+ checklistTagDetected = true;
4065
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";
4066
4547
  }
4067
- if (skillTagDetected || checklistTagDetected) break;
4068
- }
4069
- if (useNativeTools && toolCallAcc && !skillTagDetected) {
4070
- accumulatedContent += toolCallToSkillUseXml(toolCallAcc);
4071
- skillTagDetected = true;
4072
- }
4073
- if (buffer.trim()) {
4074
- try {
4075
- const parsed = JSON.parse(buffer);
4076
- const content2 = parsed.message?.content || parsed.content || parsed.text;
4077
- if (content2) {
4078
- accumulatedContent += content2;
4079
- setSessions(
4080
- (prev) => prev.map((s) => {
4081
- if (s.id === capturedSessionId) {
4082
- return {
4083
- ...s,
4084
- messages: s.messages.map(
4085
- (m) => m.id === assistantMessageId ? { ...m, content: m.content + content2 } : m
4086
- )
4087
- };
4088
- }
4089
- return s;
4090
- })
4091
- );
4092
- }
4093
- } catch {
4094
- }
4548
+ });
4549
+ lastUsage = streamResult.usage;
4550
+ if (useNativeTools && toolCallAcc && !skillTagDetected) {
4551
+ accumulatedContent += toolCallToSkillUseXml(toolCallAcc);
4552
+ skillTagDetected = true;
4095
4553
  }
4096
4554
  }
4097
4555
  const saveMessagesOnEarlyReturn = (overrideContentParts) => {
@@ -4453,9 +4911,10 @@ ${result.content}
4453
4911
  }
4454
4912
  skipNextSkillParsingRef.current = true;
4455
4913
  saveMessagesOnEarlyReturn();
4914
+ const truncatedResult = result.content.length > maxToolResultSize ? result.content.substring(0, maxToolResultSize) + "\n\n...(truncated)" : result.content;
4456
4915
  const resultPrompt = `\uC2A4\uD0AC "${detectedSkill.name}" \uC2E4\uD589 \uACB0\uACFC:
4457
4916
 
4458
- ${result.content}
4917
+ ${truncatedResult}
4459
4918
 
4460
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.`;
4461
4920
  setTimeout(() => {
@@ -4781,6 +5240,32 @@ ${stepSummary}
4781
5240
  };
4782
5241
  })
4783
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
+ }
4784
5269
  if (useExternalStorage && capturedSessionId) {
4785
5270
  const assistantContentForSave = accumulatedContent;
4786
5271
  if (assistantContentForSave && onSaveMessagesRef.current) {
@@ -4820,18 +5305,32 @@ ${stepSummary}
4820
5305
  observer.processMessages(capturedSessionId, newMessages);
4821
5306
  }
4822
5307
  } catch (error) {
4823
- 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
+ }
4824
5324
  return;
4825
5325
  }
4826
- const err = error instanceof Error ? error : new Error("Unknown error");
4827
- onErrorRef.current?.(err);
5326
+ onErrorRef.current?.(classified);
4828
5327
  setSessions(
4829
5328
  (prev) => prev.map((s) => {
4830
5329
  if (s.id === capturedSessionId) {
4831
5330
  return {
4832
5331
  ...s,
4833
5332
  messages: s.messages.map(
4834
- (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
4835
5334
  )
4836
5335
  };
4837
5336
  }
@@ -4864,241 +5363,8 @@ ${stepSummary}
4864
5363
  attachments,
4865
5364
  continueAfterToolResult
4866
5365
  ]);
4867
- const handleChecklistStart = (0, import_react6.useCallback)(
4868
- (messageId) => {
4869
- const session = sessionsRef.current.find(
4870
- (s) => s.messages.some((m) => m.id === messageId)
4871
- );
4872
- if (!session) return;
4873
- const message = session.messages.find((m) => m.id === messageId);
4874
- if (!message?.checklistBlock) return;
4875
- pendingChecklistRef.current = null;
4876
- activeChecklistRef.current = {
4877
- messageId,
4878
- sessionId: session.id,
4879
- 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 })),
4880
- currentStep: 0,
4881
- stepResults: []
4882
- };
4883
- setSessions(
4884
- (prev) => prev.map((s) => {
4885
- if (s.id !== session.id) return s;
4886
- return {
4887
- ...s,
4888
- messages: s.messages.map((m) => {
4889
- if (m.id !== messageId || !m.checklistBlock) return m;
4890
- const updatedItems = m.checklistBlock.items.map((it, idx) => ({
4891
- ...it,
4892
- status: idx === 0 ? "in_progress" : it.status
4893
- }));
4894
- return {
4895
- ...m,
4896
- checklistBlock: { ...m.checklistBlock, items: updatedItems, currentStep: 0 }
4897
- };
4898
- })
4899
- };
4900
- })
4901
- );
4902
- skipNextChecklistParsingRef.current = true;
4903
- setTimeout(() => {
4904
- const items = message.checklistBlock.items;
4905
- const refUrl = resolveChecklistRefImage(items[0], messageId, session.id);
4906
- if (refUrl) pendingAttachmentDataRef.current = [{ name: "ref_image", mimeType: "image/png", base64: "", size: 0, url: refUrl }];
4907
- sendMessage(
4908
- buildChecklistStepPrompt(0, items.length, items[0], true, refUrl),
4909
- { hiddenUserMessage: true, isChecklistExecution: true, checklistStep: { index: 0, title: items[0].title } }
4910
- );
4911
- }, 100);
4912
- },
4913
- [sendMessage]
4914
- );
4915
- const handleChecklistAbort = (0, import_react6.useCallback)(() => {
4916
- if (!activeChecklistRef.current) return;
4917
- const checklist = activeChecklistRef.current;
4918
- const stepIdx = checklist.currentStep;
4919
- abortControllersRef.current.get(checklist.sessionId)?.abort();
4920
- setSessions(
4921
- (prev) => prev.map((s) => {
4922
- if (s.id !== checklist.sessionId) return s;
4923
- return {
4924
- ...s,
4925
- messages: s.messages.map((m) => {
4926
- if (m.id !== checklist.messageId || !m.checklistBlock) return m;
4927
- return {
4928
- ...m,
4929
- checklistBlock: {
4930
- ...m.checklistBlock,
4931
- items: m.checklistBlock.items.map((it, idx) => ({
4932
- ...it,
4933
- status: idx === stepIdx ? "error" : it.status
4934
- }))
4935
- }
4936
- };
4937
- })
4938
- };
4939
- })
4940
- );
4941
- activeChecklistRef.current = null;
4942
- removeLoadingSession(checklist.sessionId);
4943
- }, [removeLoadingSession]);
4944
- const handleChecklistRetry = (0, import_react6.useCallback)(
4945
- (messageId, stepIndex) => {
4946
- const session = sessionsRef.current.find(
4947
- (s) => s.messages.some((m) => m.id === messageId)
4948
- );
4949
- if (!session) return;
4950
- const message = session.messages.find((m) => m.id === messageId);
4951
- if (!message?.checklistBlock) return;
4952
- activeChecklistRef.current = {
4953
- messageId,
4954
- sessionId: session.id,
4955
- 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 })),
4956
- currentStep: stepIndex,
4957
- stepResults: message.checklistBlock.items.slice(0, stepIndex).map((it) => it.result || "")
4958
- };
4959
- setSessions(
4960
- (prev) => prev.map((s) => {
4961
- if (s.id !== session.id) return s;
4962
- return {
4963
- ...s,
4964
- messages: s.messages.map((m) => {
4965
- if (m.id !== messageId || !m.checklistBlock) return m;
4966
- return {
4967
- ...m,
4968
- checklistBlock: {
4969
- ...m.checklistBlock,
4970
- items: m.checklistBlock.items.map((it, idx) => ({
4971
- ...it,
4972
- status: idx === stepIndex ? "in_progress" : idx > stepIndex ? "pending" : it.status
4973
- })),
4974
- currentStep: stepIndex
4975
- }
4976
- };
4977
- })
4978
- };
4979
- })
4980
- );
4981
- skipNextChecklistParsingRef.current = true;
4982
- setTimeout(() => {
4983
- const items = message.checklistBlock.items;
4984
- const refUrl = resolveChecklistRefImage(items[stepIndex], messageId, session.id);
4985
- if (refUrl) pendingAttachmentDataRef.current = [{ name: "ref_image", mimeType: "image/png", base64: "", size: 0, url: refUrl }];
4986
- sendMessage(
4987
- buildChecklistStepPrompt(stepIndex, items.length, items[stepIndex], stepIndex === 0, refUrl),
4988
- { hiddenUserMessage: true, isChecklistExecution: true, checklistStep: { index: stepIndex, title: items[stepIndex].title } }
4989
- );
4990
- }, 100);
4991
- },
4992
- [sendMessage]
4993
- );
4994
- const handleChecklistSkip = (0, import_react6.useCallback)(
4995
- (messageId, stepIndex) => {
4996
- const session = sessionsRef.current.find(
4997
- (s) => s.messages.some((m) => m.id === messageId)
4998
- );
4999
- if (!session) return;
5000
- const message = session.messages.find((m) => m.id === messageId);
5001
- if (!message?.checklistBlock) return;
5002
- trackChecklistSkip();
5003
- setSessions(
5004
- (prev) => prev.map((s) => {
5005
- if (s.id !== session.id) return s;
5006
- return {
5007
- ...s,
5008
- messages: s.messages.map((m) => {
5009
- if (m.id !== messageId || !m.checklistBlock) return m;
5010
- return {
5011
- ...m,
5012
- checklistBlock: {
5013
- ...m.checklistBlock,
5014
- items: m.checklistBlock.items.map((it, idx) => ({
5015
- ...it,
5016
- status: idx === stepIndex ? "done" : it.status,
5017
- result: idx === stepIndex ? "(\uAC74\uB108\uB700)" : it.result
5018
- }))
5019
- }
5020
- };
5021
- })
5022
- };
5023
- })
5024
- );
5025
- const nextPending = message.checklistBlock.items.findIndex(
5026
- (it, idx) => idx > stepIndex && (it.status === "pending" || it.status === "error")
5027
- );
5028
- if (nextPending >= 0) {
5029
- activeChecklistRef.current = {
5030
- messageId,
5031
- sessionId: session.id,
5032
- 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 })),
5033
- currentStep: nextPending,
5034
- stepResults: message.checklistBlock.items.slice(0, nextPending).map((it) => it.result || "(\uAC74\uB108\uB700)")
5035
- };
5036
- setSessions(
5037
- (prev) => prev.map((s) => {
5038
- if (s.id !== session.id) return s;
5039
- return {
5040
- ...s,
5041
- messages: s.messages.map((m) => {
5042
- if (m.id !== messageId || !m.checklistBlock) return m;
5043
- return {
5044
- ...m,
5045
- checklistBlock: {
5046
- ...m.checklistBlock,
5047
- items: m.checklistBlock.items.map((it, idx) => ({
5048
- ...it,
5049
- status: idx === nextPending ? "in_progress" : it.status
5050
- })),
5051
- currentStep: nextPending
5052
- }
5053
- };
5054
- })
5055
- };
5056
- })
5057
- );
5058
- skipNextChecklistParsingRef.current = true;
5059
- setTimeout(() => {
5060
- const items = message.checklistBlock.items;
5061
- const refUrl = resolveChecklistRefImage(items[nextPending], messageId, session.id);
5062
- if (refUrl) pendingAttachmentDataRef.current = [{ name: "ref_image", mimeType: "image/png", base64: "", size: 0, url: refUrl }];
5063
- sendMessage(
5064
- buildChecklistStepPrompt(nextPending, items.length, items[nextPending], false, refUrl),
5065
- { hiddenUserMessage: true, isChecklistExecution: true, checklistStep: { index: nextPending, title: items[nextPending].title } }
5066
- );
5067
- }, 100);
5068
- } else {
5069
- const allResults = message.checklistBlock.items.map((it, i) => {
5070
- const result = i === stepIndex ? "(\uAC74\uB108\uB700)" : it.result || "(\uAC74\uB108\uB700)";
5071
- return `### ${i + 1}. ${it.title}
5072
- ${result}`;
5073
- }).join("\n\n");
5074
- setSessions(
5075
- (prev) => prev.map((s) => {
5076
- if (s.id !== session.id) return s;
5077
- return {
5078
- ...s,
5079
- messages: s.messages.map((m) => {
5080
- if (m.id !== messageId || !m.checklistBlock) return m;
5081
- return { ...m, checklistBlock: { ...m.checklistBlock, completed: true } };
5082
- })
5083
- };
5084
- })
5085
- );
5086
- skipNextChecklistParsingRef.current = true;
5087
- setTimeout(() => {
5088
- sendMessage(
5089
- `\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:
5090
-
5091
- ${allResults}
5092
-
5093
- \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.`,
5094
- { hiddenUserMessage: true, isChecklistExecution: true }
5095
- );
5096
- }, 100);
5097
- }
5098
- },
5099
- [sendMessage]
5100
- );
5101
- const handlePollSubmit = (0, import_react6.useCallback)(
5366
+ sendMessageForChecklistRef.current = sendMessage;
5367
+ const handlePollSubmit = (0, import_react8.useCallback)(
5102
5368
  (messageId, responses) => {
5103
5369
  const currentSess = sessions.find((s) => s.id === currentSessionId);
5104
5370
  const targetMessage = currentSess?.messages.find((m) => m.id === messageId);
@@ -5168,7 +5434,7 @@ ${formattedParts.join("\n")}
5168
5434
  },
5169
5435
  [sessions, currentSessionId, selectedModel, sendMessage]
5170
5436
  );
5171
- const saveEdit = (0, import_react6.useCallback)(async (content) => {
5437
+ const saveEdit = (0, import_react8.useCallback)(async (content) => {
5172
5438
  if (!editingMessageId || !currentSession || !currentSessionId) return;
5173
5439
  const messageIndex = currentSession.messages.findIndex((m) => m.id === editingMessageId);
5174
5440
  if (messageIndex === -1) return;
@@ -5185,7 +5451,7 @@ ${formattedParts.join("\n")}
5185
5451
  setEditingMessageId(null);
5186
5452
  await sendMessage(content);
5187
5453
  }, [editingMessageId, currentSession, currentSessionId, sendMessage]);
5188
- const regenerate = (0, import_react6.useCallback)(async (messageId) => {
5454
+ const regenerate = (0, import_react8.useCallback)(async (messageId) => {
5189
5455
  console.log("[ChatUI] Regenerate called:", { messageId, currentSessionId, isLoading });
5190
5456
  if (!currentSession || !currentSessionId || isLoading) {
5191
5457
  console.log("[ChatUI] Regenerate early return - missing session or loading");
@@ -5224,137 +5490,76 @@ ${formattedParts.join("\n")}
5224
5490
  ...s.messages,
5225
5491
  {
5226
5492
  id: assistantMessageId,
5227
- role: "assistant",
5228
- content: "",
5229
- model: selectedModel,
5230
- timestamp: Date.now()
5231
- }
5232
- ]
5233
- };
5234
- }
5235
- return s;
5236
- })
5237
- );
5238
- const messagesUpToUser = currentSession.messages.slice(0, assistantIndex);
5239
- const chatMessages = messagesUpToUser.map((m) => ({
5240
- role: m.role,
5241
- content: m.content,
5242
- ...m.contentParts && { contentParts: m.contentParts }
5243
- }));
5244
- const baseSystemPrompt = buildSystemPrompt(currentSession);
5245
- const messagesForApi = baseSystemPrompt ? [{ role: "system", content: baseSystemPrompt }, ...chatMessages] : chatMessages;
5246
- const modelConfig = models.find((m) => m.id === selectedModel);
5247
- const provider = modelConfig?.provider || "ollama";
5248
- const isOllama = provider === "ollama" || apiEndpoint.includes("ollama") || apiEndpoint.includes("11434");
5249
- const requestBody = isOllama ? { model: selectedModel, messages: messagesForApi, stream: true } : {
5250
- messages: messagesForApi,
5251
- model: selectedModel,
5252
- provider,
5253
- apiKey: provider === "devdive" ? apiKey : void 0,
5254
- stream: true
5255
- };
5256
- console.log("[ChatUI] Regenerate fetch:", { apiEndpoint, isOllama, model: selectedModel });
5257
- const response = await fetch(apiEndpoint, {
5258
- method: "POST",
5259
- headers: { "Content-Type": "application/json" },
5260
- body: JSON.stringify(requestBody),
5261
- signal: abortControllersRef.current.get(capturedSessionId).signal
5262
- });
5263
- console.log("[ChatUI] Regenerate response status:", response.status);
5264
- if (!response.ok) throw new Error(`API error: ${response.status}`);
5265
- const reader = response.body?.getReader();
5266
- if (!reader) throw new Error("No reader");
5267
- const decoder = new TextDecoder();
5268
- let buffer = "";
5269
- while (true) {
5270
- const { done, value } = await reader.read();
5271
- if (done) break;
5272
- buffer += decoder.decode(value, { stream: true });
5273
- const lines = buffer.split("\n");
5274
- buffer = lines.pop() || "";
5275
- for (const line of lines) {
5276
- if (!line.trim()) continue;
5277
- let data = line;
5278
- if (line.startsWith("data: ")) {
5279
- data = line.slice(6);
5280
- if (data === "[DONE]") continue;
5281
- }
5282
- try {
5283
- const parsed = JSON.parse(data);
5284
- const content = parsed.message?.content || parsed.content || parsed.text || "";
5285
- const thinking = parsed.message?.thinking || "";
5286
- if (content || thinking) {
5287
- setSessions(
5288
- (prev) => prev.map((s) => {
5289
- if (s.id === capturedSessionId) {
5290
- return {
5291
- ...s,
5292
- messages: s.messages.map((m) => {
5293
- if (m.id !== assistantMessageId) return m;
5294
- let newContent = m.content;
5295
- if (thinking) {
5296
- if (!newContent.includes("<thinking>")) {
5297
- newContent = "<thinking>" + thinking;
5298
- } else if (!newContent.includes("</thinking>")) {
5299
- newContent += thinking;
5300
- }
5301
- }
5302
- if (content) {
5303
- if (newContent.includes("<thinking>") && !newContent.includes("</thinking>")) {
5304
- newContent += "</thinking>\n\n";
5305
- }
5306
- newContent += content;
5307
- }
5308
- return { ...m, content: newContent };
5309
- })
5310
- };
5311
- }
5312
- return s;
5313
- })
5314
- );
5315
- }
5316
- } catch {
5317
- }
5318
- }
5319
- }
5320
- if (buffer.trim()) {
5321
- try {
5322
- const parsed = JSON.parse(buffer);
5323
- const content = parsed.message?.content || parsed.content || parsed.text || "";
5324
- const thinking = parsed.message?.thinking || "";
5325
- if (content || thinking) {
5326
- setSessions(
5327
- (prev) => prev.map((s) => {
5328
- if (s.id === capturedSessionId) {
5329
- return {
5330
- ...s,
5331
- messages: s.messages.map((m) => {
5332
- if (m.id !== assistantMessageId) return m;
5333
- let newContent = m.content;
5334
- if (thinking) {
5335
- if (!newContent.includes("<thinking>")) {
5336
- newContent = "<thinking>" + thinking;
5337
- } else if (!newContent.includes("</thinking>")) {
5338
- newContent += thinking;
5339
- }
5340
- }
5341
- if (content) {
5342
- if (newContent.includes("<thinking>") && !newContent.includes("</thinking>")) {
5343
- newContent += "</thinking>\n\n";
5344
- }
5345
- newContent += content;
5346
- }
5347
- return { ...m, content: newContent };
5348
- })
5349
- };
5493
+ role: "assistant",
5494
+ content: "",
5495
+ model: selectedModel,
5496
+ timestamp: Date.now()
5350
5497
  }
5351
- return s;
5352
- })
5353
- );
5498
+ ]
5499
+ };
5354
5500
  }
5355
- } 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
+ );
5356
5561
  }
5357
- }
5562
+ }, { noTimeout: true });
5358
5563
  } catch (error) {
5359
5564
  if (error instanceof Error && error.name === "AbortError") {
5360
5565
  return;
@@ -5365,7 +5570,7 @@ ${formattedParts.join("\n")}
5365
5570
  removeLoadingSession(capturedSessionId);
5366
5571
  }
5367
5572
  }, [currentSession, currentSessionId, loadingSessionIds, selectedModel, models, apiEndpoint, apiKey, buildSystemPrompt]);
5368
- const askOtherModel = (0, import_react6.useCallback)(async (messageId, targetModel) => {
5573
+ const askOtherModel = (0, import_react8.useCallback)(async (messageId, targetModel) => {
5369
5574
  if (!currentSession || !currentSessionId || isLoading) return;
5370
5575
  const assistantIndex = currentSession.messages.findIndex((m) => m.id === messageId);
5371
5576
  if (assistantIndex === -1) return;
@@ -5420,30 +5625,7 @@ ${currentSession.contextSummary}` },
5420
5625
  responseContent = result.content;
5421
5626
  responseSources = result.sources;
5422
5627
  } else {
5423
- const reader = result.getReader();
5424
- const decoder = new TextDecoder();
5425
- let buffer = "";
5426
- while (true) {
5427
- const { done, value } = await reader.read();
5428
- if (done) break;
5429
- buffer += decoder.decode(value, { stream: true });
5430
- const lines = buffer.split("\n");
5431
- buffer = lines.pop() || "";
5432
- for (const line of lines) {
5433
- if (line.startsWith("data: ")) {
5434
- const data = line.slice(6);
5435
- if (data === "[DONE]") continue;
5436
- try {
5437
- const parsed = JSON.parse(data);
5438
- {
5439
- const chunk = parsed.content ?? parsed.text ?? "";
5440
- if (chunk) responseContent += chunk;
5441
- }
5442
- } catch {
5443
- }
5444
- }
5445
- }
5446
- }
5628
+ responseContent = await parseSSEResponse(new Response(result));
5447
5629
  }
5448
5630
  } else {
5449
5631
  const isOllama = provider === "ollama" || apiEndpoint.includes("ollama") || apiEndpoint.includes("11434");
@@ -5461,32 +5643,9 @@ ${currentSession.contextSummary}` },
5461
5643
  signal: abortControllersRef.current.get(currentSessionId).signal
5462
5644
  });
5463
5645
  emitFetchHeaders(response);
5464
- if (!response.ok) throw new Error("API error");
5465
- const reader = response.body?.getReader();
5466
- if (!reader) throw new Error("No reader");
5467
- const decoder = new TextDecoder();
5468
- let buffer = "";
5469
- while (true) {
5470
- const { done, value } = await reader.read();
5471
- if (done) break;
5472
- buffer += decoder.decode(value, { stream: true });
5473
- const lines = buffer.split("\n");
5474
- buffer = lines.pop() || "";
5475
- for (const line of lines) {
5476
- if (line.startsWith("data: ")) {
5477
- const data = line.slice(6);
5478
- if (data === "[DONE]") continue;
5479
- try {
5480
- const parsed = JSON.parse(data);
5481
- {
5482
- const chunk = parsed.content ?? parsed.text ?? "";
5483
- if (chunk) responseContent += chunk;
5484
- }
5485
- } catch {
5486
- }
5487
- }
5488
- }
5489
- }
5646
+ await streamResponse(response, (chunk) => {
5647
+ if (chunk.content) responseContent += chunk.content;
5648
+ }, { noTimeout: true });
5490
5649
  }
5491
5650
  const alternative = {
5492
5651
  id: generateId5("alt"),
@@ -5546,13 +5705,13 @@ ${currentSession.contextSummary}` },
5546
5705
  onSendMessage,
5547
5706
  onError
5548
5707
  ]);
5549
- const setActiveAlternative = (0, import_react6.useCallback)((assistantMessageId, index) => {
5708
+ const setActiveAlternative = (0, import_react8.useCallback)((assistantMessageId, index) => {
5550
5709
  setActiveAlternatives((prev) => ({
5551
5710
  ...prev,
5552
5711
  [assistantMessageId]: index
5553
5712
  }));
5554
5713
  }, []);
5555
- const getActiveAlternative = (0, import_react6.useCallback)((assistantMessageId) => {
5714
+ const getActiveAlternative = (0, import_react8.useCallback)((assistantMessageId) => {
5556
5715
  return activeAlternatives[assistantMessageId] ?? 0;
5557
5716
  }, [activeAlternatives]);
5558
5717
  return {
@@ -5610,6 +5769,10 @@ ${currentSession.contextSummary}` },
5610
5769
  }));
5611
5770
  await infoExtraction.extractInfo(recentMessages);
5612
5771
  },
5772
+ getTokenStats: (0, import_react8.useCallback)((sessionId) => {
5773
+ const session = sessions.find((s) => s.id === sessionId);
5774
+ return session?.tokenStats ?? null;
5775
+ }, [sessions]),
5613
5776
  // External Storage Loading States
5614
5777
  /**
5615
5778
  * @description 세션 목록 로딩 상태
@@ -5765,12 +5928,12 @@ ${result.content}
5765
5928
  };
5766
5929
 
5767
5930
  // src/react/contexts/ImageErrorContext.ts
5768
- var import_react7 = require("react");
5769
- var ImageErrorContext = (0, import_react7.createContext)(null);
5770
- 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);
5771
5934
 
5772
5935
  // src/react/components/ChatSidebar.tsx
5773
- var import_react11 = require("react");
5936
+ var import_react13 = require("react");
5774
5937
 
5775
5938
  // src/react/components/Icon.tsx
5776
5939
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -5870,10 +6033,10 @@ var IconSvg = ({
5870
6033
  };
5871
6034
 
5872
6035
  // src/react/components/MarkdownRenderer.tsx
5873
- var import_react9 = __toESM(require("react"));
6036
+ var import_react11 = __toESM(require("react"));
5874
6037
 
5875
6038
  // src/react/components/LinkChip.tsx
5876
- var import_react8 = require("react");
6039
+ var import_react10 = require("react");
5877
6040
  var import_jsx_runtime2 = require("react/jsx-runtime");
5878
6041
  var getDomain = (url) => {
5879
6042
  try {
@@ -5927,7 +6090,7 @@ var LinkChip = ({
5927
6090
  index,
5928
6091
  style
5929
6092
  }) => {
5930
- const [isHovered, setIsHovered] = (0, import_react8.useState)(false);
6093
+ const [isHovered, setIsHovered] = (0, import_react10.useState)(false);
5931
6094
  const domain = getDomain(url);
5932
6095
  const shortName = getShortName(domain);
5933
6096
  const domainColor = getDomainColor(domain);
@@ -6297,8 +6460,8 @@ var parseTableRow = (row) => {
6297
6460
  return row.split("|").slice(1, -1).map((cell) => cell.trim());
6298
6461
  };
6299
6462
  var MarkdownTable = ({ data }) => {
6300
- const [copied, setCopied] = import_react9.default.useState(false);
6301
- 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);
6302
6465
  const handleCopy = async () => {
6303
6466
  const headerLine = data.headers.join(" ");
6304
6467
  const bodyLines = data.rows.map((row) => row.join(" "));
@@ -6410,7 +6573,7 @@ var ThinkingSpinner = () => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
6410
6573
  }
6411
6574
  );
6412
6575
  var ThinkingBlock = ({ content, defaultOpen = false }) => {
6413
- const [isOpen, setIsOpen] = import_react9.default.useState(defaultOpen);
6576
+ const [isOpen, setIsOpen] = import_react11.default.useState(defaultOpen);
6414
6577
  const isStreaming = content.trim().endsWith("...");
6415
6578
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
6416
6579
  "details",
@@ -6485,7 +6648,7 @@ var ThinkingBlock = ({ content, defaultOpen = false }) => {
6485
6648
  );
6486
6649
  };
6487
6650
  var CodeBlock = ({ language, code }) => {
6488
- const [copied, setCopied] = import_react9.default.useState(false);
6651
+ const [copied, setCopied] = import_react11.default.useState(false);
6489
6652
  const handleCopy = async () => {
6490
6653
  try {
6491
6654
  await navigator.clipboard.writeText(code);
@@ -6568,10 +6731,10 @@ var CodeBlock = ({ language, code }) => {
6568
6731
  );
6569
6732
  };
6570
6733
  var ImageWithCopyButton = ({ src, alt, imageKey }) => {
6571
- const [isHovered, setIsHovered] = import_react9.default.useState(false);
6572
- 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);
6573
6736
  const onImageError = useImageError();
6574
- const handleImageError = import_react9.default.useCallback(async () => {
6737
+ const handleImageError = import_react11.default.useCallback(async () => {
6575
6738
  if (onImageError) {
6576
6739
  const newUrl = await onImageError(imgSrc);
6577
6740
  if (newUrl) {
@@ -6580,8 +6743,8 @@ var ImageWithCopyButton = ({ src, alt, imageKey }) => {
6580
6743
  }
6581
6744
  }
6582
6745
  }, [onImageError, imgSrc]);
6583
- const [copyState, setCopyState] = import_react9.default.useState("idle");
6584
- const imgRef = import_react9.default.useRef(null);
6746
+ const [copyState, setCopyState] = import_react11.default.useState("idle");
6747
+ const imgRef = import_react11.default.useRef(null);
6585
6748
  const getImageBlob = async () => {
6586
6749
  const img = imgRef.current;
6587
6750
  if (img && img.complete && img.naturalWidth > 0) {
@@ -6797,7 +6960,7 @@ var ImageWithCopyButton = ({ src, alt, imageKey }) => {
6797
6960
  );
6798
6961
  };
6799
6962
  var ChoiceButtons = ({ choices, title, onChoiceClick }) => {
6800
- const [hoveredIndex, setHoveredIndex] = import_react9.default.useState(null);
6963
+ const [hoveredIndex, setHoveredIndex] = import_react11.default.useState(null);
6801
6964
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
6802
6965
  "div",
6803
6966
  {
@@ -6927,11 +7090,11 @@ var MarkdownRenderer = ({
6927
7090
  sources,
6928
7091
  inline = false
6929
7092
  }) => {
6930
- const inlineRendered = (0, import_react9.useMemo)(() => {
7093
+ const inlineRendered = (0, import_react11.useMemo)(() => {
6931
7094
  if (!inline) return null;
6932
7095
  return parseInlineElements(content, "inline-md", sources);
6933
7096
  }, [inline, content, sources]);
6934
- const rendered = (0, import_react9.useMemo)(() => {
7097
+ const rendered = (0, import_react11.useMemo)(() => {
6935
7098
  if (inline) return null;
6936
7099
  const elements = [];
6937
7100
  let processedContent = content;
@@ -7084,7 +7247,7 @@ var MarkdownRenderer = ({
7084
7247
  borderRadius: "0 8px 8px 0",
7085
7248
  color: "var(--chatllm-text, #374151)"
7086
7249
  },
7087
- 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: [
7088
7251
  parseInlineElements(line, `bq-line-${i}`, sources),
7089
7252
  i < blockquoteLines.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("br", {})
7090
7253
  ] }, i))
@@ -7328,7 +7491,7 @@ var MarkdownRenderer = ({
7328
7491
  };
7329
7492
 
7330
7493
  // src/react/components/ProjectSelector.tsx
7331
- var import_react10 = require("react");
7494
+ var import_react12 = require("react");
7332
7495
  var import_jsx_runtime4 = require("react/jsx-runtime");
7333
7496
  var ProjectSelector = ({
7334
7497
  projects,
@@ -7337,10 +7500,10 @@ var ProjectSelector = ({
7337
7500
  onNewProject,
7338
7501
  onProjectSettings
7339
7502
  }) => {
7340
- const [isOpen, setIsOpen] = (0, import_react10.useState)(false);
7341
- const dropdownRef = (0, import_react10.useRef)(null);
7503
+ const [isOpen, setIsOpen] = (0, import_react12.useState)(false);
7504
+ const dropdownRef = (0, import_react12.useRef)(null);
7342
7505
  const currentProject = projects.find((p) => p.id === currentProjectId);
7343
- (0, import_react10.useEffect)(() => {
7506
+ (0, import_react12.useEffect)(() => {
7344
7507
  const handleClickOutside = (e) => {
7345
7508
  if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
7346
7509
  setIsOpen(false);
@@ -7574,10 +7737,10 @@ var ChatSidebar = ({
7574
7737
  isLoading = false
7575
7738
  }) => {
7576
7739
  const sidebarWidth = typeof widthProp === "number" ? `${widthProp}px` : widthProp || "288px";
7577
- const [editingId, setEditingId] = (0, import_react11.useState)(null);
7578
- const [editingTitle, setEditingTitle] = (0, import_react11.useState)("");
7579
- const inputRef = (0, import_react11.useRef)(null);
7580
- (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)(() => {
7581
7744
  if (editingId && inputRef.current) {
7582
7745
  inputRef.current.focus();
7583
7746
  inputRef.current.select();
@@ -7949,7 +8112,7 @@ var ChatSidebar = ({
7949
8112
  };
7950
8113
 
7951
8114
  // src/react/components/ChatHeader.tsx
7952
- var import_react12 = require("react");
8115
+ var import_react14 = require("react");
7953
8116
  var import_jsx_runtime6 = require("react/jsx-runtime");
7954
8117
  var ChatHeader = ({
7955
8118
  title,
@@ -7963,7 +8126,7 @@ var ChatHeader = ({
7963
8126
  showSettings = true,
7964
8127
  renderHeaderExtra
7965
8128
  }) => {
7966
- const [modelDropdownOpen, setModelDropdownOpen] = (0, import_react12.useState)(false);
8129
+ const [modelDropdownOpen, setModelDropdownOpen] = (0, import_react14.useState)(false);
7967
8130
  const currentModel = models.find((m) => m.id === model);
7968
8131
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
7969
8132
  "header",
@@ -8218,7 +8381,7 @@ var ChatHeader = ({
8218
8381
  };
8219
8382
 
8220
8383
  // src/react/components/ChatInput.tsx
8221
- var import_react13 = __toESM(require("react"));
8384
+ var import_react15 = __toESM(require("react"));
8222
8385
  var import_jsx_runtime7 = require("react/jsx-runtime");
8223
8386
  var ChatInput = ({
8224
8387
  value,
@@ -8246,20 +8409,20 @@ var ChatInput = ({
8246
8409
  onDisclaimerClick,
8247
8410
  inline = false
8248
8411
  }) => {
8249
- const [mainMenuOpen, setMainMenuOpen] = import_react13.default.useState(false);
8250
- const textareaRef = (0, import_react13.useRef)(null);
8251
- const fileInputRef = (0, import_react13.useRef)(null);
8252
- const [actionMenuOpen, setActionMenuOpen] = (0, import_react13.useState)(false);
8253
- const [isDragOver, setIsDragOver] = (0, import_react13.useState)(false);
8254
- const mainMenuRef = (0, import_react13.useRef)(null);
8255
- const actionMenuRef = (0, import_react13.useRef)(null);
8256
- (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)(() => {
8257
8420
  if (textareaRef.current) {
8258
8421
  textareaRef.current.style.height = "auto";
8259
8422
  textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 200)}px`;
8260
8423
  }
8261
8424
  }, [value]);
8262
- (0, import_react13.useEffect)(() => {
8425
+ (0, import_react15.useEffect)(() => {
8263
8426
  const handleClickOutside = (event) => {
8264
8427
  if (mainMenuRef.current && !mainMenuRef.current.contains(event.target)) {
8265
8428
  setMainMenuOpen(false);
@@ -8981,13 +9144,13 @@ var iconButtonStyle = {
8981
9144
  };
8982
9145
 
8983
9146
  // src/react/components/MessageList.tsx
8984
- var import_react21 = __toESM(require("react"));
9147
+ var import_react23 = __toESM(require("react"));
8985
9148
 
8986
9149
  // src/react/components/MessageBubble.tsx
8987
- var import_react20 = require("react");
9150
+ var import_react22 = require("react");
8988
9151
 
8989
9152
  // src/react/components/DeepResearchProgressUI.tsx
8990
- var import_react14 = __toESM(require("react"));
9153
+ var import_react16 = __toESM(require("react"));
8991
9154
  var import_jsx_runtime8 = require("react/jsx-runtime");
8992
9155
  var StatusIcon = ({ status }) => {
8993
9156
  switch (status) {
@@ -9051,7 +9214,7 @@ var PhaseProgress = ({ phase }) => {
9051
9214
  gap: "8px",
9052
9215
  marginBottom: "16px"
9053
9216
  },
9054
- 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: [
9055
9218
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
9056
9219
  "div",
9057
9220
  {
@@ -9270,7 +9433,7 @@ var DeepResearchProgressUI = ({ progress }) => {
9270
9433
  };
9271
9434
 
9272
9435
  // src/react/components/PollCard.tsx
9273
- var import_react15 = require("react");
9436
+ var import_react17 = require("react");
9274
9437
  var import_jsx_runtime9 = require("react/jsx-runtime");
9275
9438
  var renderInlineMarkdown = (text) => {
9276
9439
  const parts = [];
@@ -9309,12 +9472,12 @@ var PollCard = ({
9309
9472
  onSubmit,
9310
9473
  onSkip
9311
9474
  }) => {
9312
- const [activeTab, setActiveTab] = (0, import_react15.useState)(0);
9313
- const [selections, setSelections] = (0, import_react15.useState)({});
9314
- const [otherTexts, setOtherTexts] = (0, import_react15.useState)({});
9315
- 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)({});
9316
9479
  const currentQuestion = questions[activeTab];
9317
- const handleOptionToggle = (0, import_react15.useCallback)(
9480
+ const handleOptionToggle = (0, import_react17.useCallback)(
9318
9481
  (questionId, optionId, multiSelect) => {
9319
9482
  setSelections((prev) => {
9320
9483
  const current = prev[questionId] || /* @__PURE__ */ new Set();
@@ -9336,7 +9499,7 @@ var PollCard = ({
9336
9499
  },
9337
9500
  []
9338
9501
  );
9339
- const handleOtherToggle = (0, import_react15.useCallback)((questionId, multiSelect) => {
9502
+ const handleOtherToggle = (0, import_react17.useCallback)((questionId, multiSelect) => {
9340
9503
  if (multiSelect) {
9341
9504
  setOtherSelected((prev) => ({ ...prev, [questionId]: !prev[questionId] }));
9342
9505
  } else {
@@ -9344,7 +9507,7 @@ var PollCard = ({
9344
9507
  setOtherSelected((prev) => ({ ...prev, [questionId]: true }));
9345
9508
  }
9346
9509
  }, []);
9347
- const handleSubmit = (0, import_react15.useCallback)(() => {
9510
+ const handleSubmit = (0, import_react17.useCallback)(() => {
9348
9511
  const responses = questions.map((q) => {
9349
9512
  const selected = selections[q.id] || /* @__PURE__ */ new Set();
9350
9513
  const other = otherSelected[q.id] && otherTexts[q.id]?.trim();
@@ -9357,7 +9520,7 @@ var PollCard = ({
9357
9520
  });
9358
9521
  onSubmit(responses);
9359
9522
  }, [questions, selections, otherSelected, otherTexts, onSubmit]);
9360
- const handleSkip = (0, import_react15.useCallback)(() => {
9523
+ const handleSkip = (0, import_react17.useCallback)(() => {
9361
9524
  const responses = questions.map((q) => ({
9362
9525
  questionId: q.id,
9363
9526
  selectedOptions: [],
@@ -9366,7 +9529,7 @@ var PollCard = ({
9366
9529
  onSubmit(responses);
9367
9530
  onSkip?.();
9368
9531
  }, [questions, onSubmit, onSkip]);
9369
- (0, import_react15.useEffect)(() => {
9532
+ (0, import_react17.useEffect)(() => {
9370
9533
  if (typeof window === "undefined") return;
9371
9534
  const handleKeyDown = (e) => {
9372
9535
  if (e.key !== "Escape") return;
@@ -9385,9 +9548,9 @@ var PollCard = ({
9385
9548
  const currentHasSelection = getSelectionCount(currentQuestion.id) > 0;
9386
9549
  const isLastTab = activeTab === questions.length - 1;
9387
9550
  const totalSelected = questions.reduce((sum, q) => sum + getSelectionCount(q.id), 0);
9388
- const [visibleCount, setVisibleCount] = (0, import_react15.useState)(0);
9389
- const [cardVisible, setCardVisible] = (0, import_react15.useState)(false);
9390
- (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)(() => {
9391
9554
  const fadeTimer = setTimeout(() => setCardVisible(true), 50);
9392
9555
  const totalItems = currentQuestion.options.length + 1;
9393
9556
  const timers = [fadeTimer];
@@ -9396,7 +9559,7 @@ var PollCard = ({
9396
9559
  }
9397
9560
  return () => timers.forEach(clearTimeout);
9398
9561
  }, [currentQuestion.options.length]);
9399
- (0, import_react15.useEffect)(() => {
9562
+ (0, import_react17.useEffect)(() => {
9400
9563
  setVisibleCount(0);
9401
9564
  const totalItems = currentQuestion.options.length + 1;
9402
9565
  const timers = [];
@@ -9405,7 +9568,7 @@ var PollCard = ({
9405
9568
  }
9406
9569
  return () => timers.forEach(clearTimeout);
9407
9570
  }, [activeTab, currentQuestion.options.length]);
9408
- const handleNext = (0, import_react15.useCallback)(() => {
9571
+ const handleNext = (0, import_react17.useCallback)(() => {
9409
9572
  if (!isLastTab) {
9410
9573
  setActiveTab((prev) => prev + 1);
9411
9574
  } else {
@@ -9823,15 +9986,15 @@ var SkillProgressUI = ({
9823
9986
  };
9824
9987
 
9825
9988
  // src/react/components/ImageContentCard.tsx
9826
- var import_react16 = require("react");
9989
+ var import_react18 = require("react");
9827
9990
  var import_jsx_runtime11 = require("react/jsx-runtime");
9828
9991
  var ImageContentCard = ({ part }) => {
9829
- const [isExpanded, setIsExpanded] = (0, import_react16.useState)(false);
9830
- const [isLoaded, setIsLoaded] = (0, import_react16.useState)(false);
9831
- const [hasError, setHasError] = (0, import_react16.useState)(false);
9832
- 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);
9833
9996
  const onImageError = useImageError();
9834
- const handleImageError = (0, import_react16.useCallback)(async () => {
9997
+ const handleImageError = (0, import_react18.useCallback)(async () => {
9835
9998
  if (onImageError) {
9836
9999
  const newUrl = await onImageError(imgSrc, part.fileName);
9837
10000
  if (newUrl) {
@@ -10044,7 +10207,7 @@ var FileContentCard = ({ part }) => {
10044
10207
  };
10045
10208
 
10046
10209
  // src/react/components/ToolStatusCard.tsx
10047
- var import_react17 = require("react");
10210
+ var import_react19 = require("react");
10048
10211
  var import_jsx_runtime13 = require("react/jsx-runtime");
10049
10212
  var mapIcon2 = (icon) => {
10050
10213
  const iconMap = {
@@ -10077,7 +10240,7 @@ var ToolStatusCard = ({
10077
10240
  sources,
10078
10241
  errorMessage
10079
10242
  }) => {
10080
- const [isExpanded, setIsExpanded] = (0, import_react17.useState)(false);
10243
+ const [isExpanded, setIsExpanded] = (0, import_react19.useState)(false);
10081
10244
  const displayLabel = label || getDefaultLabel(toolName);
10082
10245
  const statusText = getStatusSuffix(displayLabel, status);
10083
10246
  const hasSources = sources && sources.length > 0;
@@ -10208,7 +10371,7 @@ var ToolStatusCard = ({
10208
10371
  };
10209
10372
 
10210
10373
  // src/react/components/ArtifactCard.tsx
10211
- var import_react18 = require("react");
10374
+ var import_react20 = require("react");
10212
10375
  var import_jsx_runtime14 = require("react/jsx-runtime");
10213
10376
  var getThemeStyles = () => {
10214
10377
  const root = document.documentElement;
@@ -10249,10 +10412,10 @@ var getAutoResizeScript = (id) => `
10249
10412
  });
10250
10413
  </script>`;
10251
10414
  var HtmlArtifact = ({ code }) => {
10252
- const iframeRef = (0, import_react18.useRef)(null);
10253
- const [height, setHeight] = (0, import_react18.useState)(200);
10254
- const artifactId = (0, import_react18.useRef)(`artifact-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`);
10255
- (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)(() => {
10256
10419
  const id = artifactId.current;
10257
10420
  const handleMessage = (e) => {
10258
10421
  if (e.data?.type === "artifact-resize" && e.data.id === id && typeof e.data.height === "number") {
@@ -10298,10 +10461,10 @@ var SvgArtifact = ({ code }) => {
10298
10461
  );
10299
10462
  };
10300
10463
  var MermaidArtifact = ({ code }) => {
10301
- const containerRef = (0, import_react18.useRef)(null);
10302
- const [svgHtml, setSvgHtml] = (0, import_react18.useState)(null);
10303
- const [error, setError] = (0, import_react18.useState)(null);
10304
- 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 () => {
10305
10468
  try {
10306
10469
  let mermaid;
10307
10470
  try {
@@ -10317,7 +10480,7 @@ var MermaidArtifact = ({ code }) => {
10317
10480
  setError(e instanceof Error ? e.message : "Mermaid \uB80C\uB354\uB9C1 \uC2E4\uD328");
10318
10481
  }
10319
10482
  }, [code]);
10320
- (0, import_react18.useEffect)(() => {
10483
+ (0, import_react20.useEffect)(() => {
10321
10484
  renderMermaid();
10322
10485
  }, [renderMermaid]);
10323
10486
  if (error) {
@@ -10393,9 +10556,9 @@ var svgToPngBlob = (svgString, scale = 2) => {
10393
10556
  });
10394
10557
  };
10395
10558
  var ArtifactActions = ({ code, language, containerRef }) => {
10396
- const [showCode, setShowCode] = (0, import_react18.useState)(false);
10397
- const [copied, setCopied] = (0, import_react18.useState)(false);
10398
- 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);
10399
10562
  const handleCopy = async () => {
10400
10563
  try {
10401
10564
  await navigator.clipboard.writeText(code);
@@ -10560,7 +10723,7 @@ var ArtifactActions = ({ code, language, containerRef }) => {
10560
10723
  ] });
10561
10724
  };
10562
10725
  var ArtifactCard = ({ part, index = 0 }) => {
10563
- const contentRef = (0, import_react18.useRef)(null);
10726
+ const contentRef = (0, import_react20.useRef)(null);
10564
10727
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
10565
10728
  "div",
10566
10729
  {
@@ -10732,7 +10895,7 @@ var ContentPartRenderer = ({
10732
10895
  };
10733
10896
 
10734
10897
  // src/react/components/ChecklistCard.tsx
10735
- var import_react19 = require("react");
10898
+ var import_react21 = require("react");
10736
10899
  var import_jsx_runtime16 = require("react/jsx-runtime");
10737
10900
  var ChecklistCard = ({
10738
10901
  items,
@@ -10742,8 +10905,8 @@ var ChecklistCard = ({
10742
10905
  onSkipStep,
10743
10906
  onStart
10744
10907
  }) => {
10745
- const [expandedItems, setExpandedItems] = (0, import_react19.useState)(/* @__PURE__ */ new Set());
10746
- 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)(() => {
10747
10910
  const done = items.filter((it) => it.status === "done").length;
10748
10911
  const running = items.some((it) => it.status === "in_progress");
10749
10912
  const waiting = items.every((it) => it.status === "pending");
@@ -11240,9 +11403,9 @@ var MessageBubble = ({
11240
11403
  onToggleChecklistPanel,
11241
11404
  onChecklistStart
11242
11405
  }) => {
11243
- const [showActions, setShowActions] = (0, import_react20.useState)(false);
11244
- const [showModelMenu, setShowModelMenu] = (0, import_react20.useState)(false);
11245
- 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);
11246
11409
  const isUser = message.role === "user";
11247
11410
  const isAssistant = message.role === "assistant";
11248
11411
  const relevantAlternatives = isUser ? alternatives : message.alternatives;
@@ -11993,14 +12156,14 @@ var MessageList = ({
11993
12156
  onExport,
11994
12157
  onToggleChecklistPanel
11995
12158
  }) => {
11996
- const messagesEndRef = (0, import_react21.useRef)(null);
11997
- const containerRef = (0, import_react21.useRef)(null);
11998
- const [selectedText, setSelectedText] = (0, import_react21.useState)("");
11999
- const [selectionPosition, setSelectionPosition] = (0, import_react21.useState)(null);
12000
- const [showScrollButton, setShowScrollButton] = (0, import_react21.useState)(false);
12001
- 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);
12002
12165
  const SCROLL_THRESHOLD = 100;
12003
- (0, import_react21.useEffect)(() => {
12166
+ (0, import_react23.useEffect)(() => {
12004
12167
  const container = containerRef.current;
12005
12168
  if (!container) return;
12006
12169
  const handleWheel = (e) => {
@@ -12037,7 +12200,7 @@ var MessageList = ({
12037
12200
  container.removeEventListener("touchmove", handleTouchMove);
12038
12201
  };
12039
12202
  }, []);
12040
- const handleScroll = (0, import_react21.useCallback)(() => {
12203
+ const handleScroll = (0, import_react23.useCallback)(() => {
12041
12204
  if (!containerRef.current) return;
12042
12205
  const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
12043
12206
  const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
@@ -12046,14 +12209,14 @@ var MessageList = ({
12046
12209
  setShowScrollButton(false);
12047
12210
  }
12048
12211
  }, []);
12049
- (0, import_react21.useEffect)(() => {
12212
+ (0, import_react23.useEffect)(() => {
12050
12213
  if (userScrollLockRef.current) return;
12051
12214
  containerRef.current?.scrollTo({
12052
12215
  top: containerRef.current.scrollHeight,
12053
12216
  behavior: "smooth"
12054
12217
  });
12055
12218
  }, [messages]);
12056
- const scrollToBottom = (0, import_react21.useCallback)(() => {
12219
+ const scrollToBottom = (0, import_react23.useCallback)(() => {
12057
12220
  userScrollLockRef.current = false;
12058
12221
  setShowScrollButton(false);
12059
12222
  containerRef.current?.scrollTo({
@@ -12061,7 +12224,7 @@ var MessageList = ({
12061
12224
  behavior: "smooth"
12062
12225
  });
12063
12226
  }, []);
12064
- const handleMouseUp = (0, import_react21.useCallback)(() => {
12227
+ const handleMouseUp = (0, import_react23.useCallback)(() => {
12065
12228
  const selection = typeof window !== "undefined" ? window.getSelection() : null;
12066
12229
  const text = selection?.toString().trim();
12067
12230
  if (text && text.length > 0) {
@@ -12162,7 +12325,7 @@ var MessageList = ({
12162
12325
  },
12163
12326
  message.id
12164
12327
  );
12165
- 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;
12166
12329
  }),
12167
12330
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { ref: messagesEndRef })
12168
12331
  ]
@@ -12256,7 +12419,7 @@ var MessageList = ({
12256
12419
  };
12257
12420
 
12258
12421
  // src/react/components/SettingsModal.tsx
12259
- var import_react22 = require("react");
12422
+ var import_react24 = require("react");
12260
12423
  var import_jsx_runtime19 = require("react/jsx-runtime");
12261
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.
12262
12425
 
@@ -12321,9 +12484,9 @@ var SettingsModal = ({
12321
12484
  onImportMemory,
12322
12485
  importMemoryPrompt
12323
12486
  }) => {
12324
- const [activeTab, setActiveTab] = (0, import_react22.useState)("general");
12325
- const [localApiKey, setLocalApiKey] = (0, import_react22.useState)(apiKey);
12326
- (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)(() => {
12327
12490
  setLocalApiKey(apiKey);
12328
12491
  }, [apiKey]);
12329
12492
  if (!isOpen) return null;
@@ -12799,11 +12962,11 @@ var memoryCategoryColors = {
12799
12962
  preference: "#8b5cf6"
12800
12963
  };
12801
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 }) => {
12802
- const [activeFilter, setActiveFilter] = (0, import_react22.useState)("all");
12803
- const [expandedId, setExpandedId] = (0, import_react22.useState)(null);
12804
- const [importModalOpen, setImportModalOpen] = (0, import_react22.useState)(false);
12805
- const [importText, setImportText] = (0, import_react22.useState)("");
12806
- 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);
12807
12970
  const filteredItems = activeFilter === "all" ? items : items.filter((item) => item.category === activeFilter);
12808
12971
  const formatDate = (timestamp) => {
12809
12972
  const date = new Date(timestamp);
@@ -13260,7 +13423,7 @@ var MemoryTabContent = ({ items, contextSummary, onDelete, onClearAll, title = "
13260
13423
  };
13261
13424
 
13262
13425
  // src/react/components/ProjectSettingsModal.tsx
13263
- var import_react23 = require("react");
13426
+ var import_react25 = require("react");
13264
13427
  var import_jsx_runtime20 = require("react/jsx-runtime");
13265
13428
  var COLOR_PRESETS = [
13266
13429
  "#3584FA",
@@ -13281,12 +13444,12 @@ var ProjectSettingsModal = ({
13281
13444
  onDeleteFile,
13282
13445
  onDeleteProject
13283
13446
  }) => {
13284
- const [activeTab, setActiveTab] = (0, import_react23.useState)("general");
13285
- const [title, setTitle] = (0, import_react23.useState)("");
13286
- const [description, setDescription] = (0, import_react23.useState)("");
13287
- const [instructions, setInstructions] = (0, import_react23.useState)("");
13288
- const [color, setColor] = (0, import_react23.useState)("#3584FA");
13289
- (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)(() => {
13290
13453
  if (project) {
13291
13454
  setTitle(project.title);
13292
13455
  setDescription(project.description || "");
@@ -14751,28 +14914,28 @@ var ChatUIView = ({
14751
14914
  handleChecklistSkip,
14752
14915
  activeChecklistMessage
14753
14916
  } = state;
14754
- const [disclaimerOpen, setDisclaimerOpen] = import_react24.default.useState(false);
14755
- const [isMobile, setIsMobile] = import_react24.default.useState(false);
14756
- 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(() => {
14757
14920
  if (typeof window === "undefined") return;
14758
14921
  const check = () => setIsMobile(window.innerWidth < 768);
14759
14922
  check();
14760
14923
  window.addEventListener("resize", check);
14761
14924
  return () => window.removeEventListener("resize", check);
14762
14925
  }, []);
14763
- const [checklistPanelDismissed, setChecklistPanelDismissed] = import_react24.default.useState(false);
14926
+ const [checklistPanelDismissed, setChecklistPanelDismissed] = import_react26.default.useState(false);
14764
14927
  const isChecklistPanelOpen = !!activeChecklistMessage && !checklistPanelDismissed;
14765
- const prevChecklistIdRef = import_react24.default.useRef(void 0);
14766
- import_react24.default.useEffect(() => {
14928
+ const prevChecklistIdRef = import_react26.default.useRef(void 0);
14929
+ import_react26.default.useEffect(() => {
14767
14930
  const currentId = activeChecklistMessage?.checklistBlock?.id;
14768
14931
  if (currentId && currentId !== prevChecklistIdRef.current && !activeChecklistMessage?.checklistBlock?.completed) {
14769
14932
  setChecklistPanelDismissed(false);
14770
14933
  }
14771
14934
  prevChecklistIdRef.current = currentId;
14772
14935
  }, [activeChecklistMessage?.checklistBlock?.id, activeChecklistMessage?.checklistBlock?.completed]);
14773
- const [welcomeExiting, setWelcomeExiting] = import_react24.default.useState(false);
14774
- const prevMessageCountRef = import_react24.default.useRef(messages.length);
14775
- 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(() => {
14776
14939
  let timer;
14777
14940
  if (prevMessageCountRef.current === 0 && messages.length > 0) {
14778
14941
  setWelcomeExiting(true);
@@ -14796,7 +14959,7 @@ var ChatUIView = ({
14796
14959
  const handleChoiceClick = (choice) => {
14797
14960
  setInput(choice.text);
14798
14961
  };
14799
- const memoryItems = import_react24.default.useMemo(() => {
14962
+ const memoryItems = import_react26.default.useMemo(() => {
14800
14963
  if (!globalMemory?.state.entries) return [];
14801
14964
  const items = [];
14802
14965
  for (const [key, entry] of globalMemory.state.entries) {
@@ -14810,7 +14973,7 @@ var ChatUIView = ({
14810
14973
  }
14811
14974
  return items;
14812
14975
  }, [globalMemory?.state.entries]);
14813
- const projectMemoryItems = import_react24.default.useMemo(() => {
14976
+ const projectMemoryItems = import_react26.default.useMemo(() => {
14814
14977
  if (!projectMemory?.state.entries) return [];
14815
14978
  const items = [];
14816
14979
  for (const [key, entry] of projectMemory.state.entries) {
@@ -15332,13 +15495,13 @@ var ChatUI = (props) => {
15332
15495
  };
15333
15496
 
15334
15497
  // src/react/ChatFloatingWidget.tsx
15335
- var import_react32 = require("react");
15498
+ var import_react34 = require("react");
15336
15499
 
15337
15500
  // src/react/hooks/useFloatingWidget.ts
15338
- var import_react26 = require("react");
15501
+ var import_react28 = require("react");
15339
15502
 
15340
15503
  // src/react/hooks/useDragResize.ts
15341
- var import_react25 = require("react");
15504
+ var import_react27 = require("react");
15342
15505
  var DRAG_THRESHOLD = 5;
15343
15506
  var FAB_SIZE = 56;
15344
15507
  var EDGE_MARGIN = 24;
@@ -15406,8 +15569,8 @@ var useDragResize = (options) => {
15406
15569
  maxWidth = DEFAULT_MAX_WIDTH,
15407
15570
  minHeight = DEFAULT_MIN_HEIGHT
15408
15571
  } = options;
15409
- const [isMobile, setIsMobile] = (0, import_react25.useState)(false);
15410
- (0, import_react25.useEffect)(() => {
15572
+ const [isMobile, setIsMobile] = (0, import_react27.useState)(false);
15573
+ (0, import_react27.useEffect)(() => {
15411
15574
  if (typeof window === "undefined") return;
15412
15575
  const mql = window.matchMedia("(max-width: 767px)");
15413
15576
  setIsMobile(mql.matches);
@@ -15422,19 +15585,19 @@ var useDragResize = (options) => {
15422
15585
  panelWidth: initialWidth,
15423
15586
  panelHeight: initialHeight
15424
15587
  });
15425
- const [fabPos, setFabPos] = (0, import_react25.useState)({ x: persisted.fabX, y: persisted.fabY });
15426
- const [panelSize, setPanelSize] = (0, import_react25.useState)({ width: persisted.panelWidth, height: persisted.panelHeight });
15427
- const [isDragging, setIsDragging] = (0, import_react25.useState)(false);
15428
- const [isResizing, setIsResizing] = (0, import_react25.useState)(false);
15429
- const [isDizzy, setIsDizzy] = (0, import_react25.useState)(false);
15430
- 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)(() => ({
15431
15594
  width: typeof window !== "undefined" ? window.innerWidth : 1024,
15432
15595
  height: typeof window !== "undefined" ? window.innerHeight : 768
15433
15596
  }));
15434
15597
  const vw = viewport.width;
15435
15598
  const vh = viewport.height;
15436
- const initializedRef = (0, import_react25.useRef)(false);
15437
- (0, import_react25.useEffect)(() => {
15599
+ const initializedRef = (0, import_react27.useRef)(false);
15600
+ (0, import_react27.useEffect)(() => {
15438
15601
  if (initializedRef.current || typeof window === "undefined") return;
15439
15602
  initializedRef.current = true;
15440
15603
  const correctFab = getInitialFabPosition(initialPosition);
@@ -15447,40 +15610,40 @@ var useDragResize = (options) => {
15447
15610
  setFabPos({ x: loaded.fabX, y: loaded.fabY });
15448
15611
  setPanelSize({ width: loaded.panelWidth, height: loaded.panelHeight });
15449
15612
  }, [initialPosition, storageKey, initialWidth, initialHeight]);
15450
- const dragStartRef = (0, import_react25.useRef)(null);
15451
- const isDraggingRef = (0, import_react25.useRef)(false);
15452
- const wasDragRef = (0, import_react25.useRef)(false);
15453
- const userDraggedRef = (0, import_react25.useRef)(false);
15454
- const dragDeltaRef = (0, import_react25.useRef)({ dx: 0, dy: 0 });
15455
- const fabElRef = (0, import_react25.useRef)(null);
15456
- const dragCleanupRef = (0, import_react25.useRef)(null);
15457
- const snapTimerRef = (0, import_react25.useRef)(null);
15458
- const prevDragRef = (0, import_react25.useRef)(null);
15459
- const shakeScoreRef = (0, import_react25.useRef)(0);
15460
- const prevVelocityRef = (0, import_react25.useRef)(null);
15461
- const isDizzyRef = (0, import_react25.useRef)(false);
15462
- const dizzyTimerRef = (0, import_react25.useRef)(null);
15463
- const lastVelocityRef = (0, import_react25.useRef)({ vx: 0, vy: 0, speed: 0 });
15464
- const dragRafRef = (0, import_react25.useRef)(null);
15465
- const animCleanupRef = (0, import_react25.useRef)(null);
15466
- const snapEnabledRef = (0, import_react25.useRef)(snapEnabled);
15467
- const storageKeyRef = (0, import_react25.useRef)(storageKey);
15468
- const panelSizeRef = (0, import_react25.useRef)(panelSize);
15469
- const fabPosRef = (0, import_react25.useRef)(fabPos);
15470
- (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)(() => {
15471
15634
  snapEnabledRef.current = snapEnabled;
15472
15635
  }, [snapEnabled]);
15473
- (0, import_react25.useEffect)(() => {
15636
+ (0, import_react27.useEffect)(() => {
15474
15637
  storageKeyRef.current = storageKey;
15475
15638
  }, [storageKey]);
15476
- (0, import_react25.useEffect)(() => {
15639
+ (0, import_react27.useEffect)(() => {
15477
15640
  panelSizeRef.current = panelSize;
15478
15641
  }, [panelSize]);
15479
- (0, import_react25.useEffect)(() => {
15642
+ (0, import_react27.useEffect)(() => {
15480
15643
  fabPosRef.current = fabPos;
15481
15644
  }, [fabPos]);
15482
- const resizeStartRef = (0, import_react25.useRef)(null);
15483
- (0, import_react25.useEffect)(() => {
15645
+ const resizeStartRef = (0, import_react27.useRef)(null);
15646
+ (0, import_react27.useEffect)(() => {
15484
15647
  return () => {
15485
15648
  dragCleanupRef.current?.();
15486
15649
  if (snapTimerRef.current) clearTimeout(snapTimerRef.current);
@@ -15488,7 +15651,7 @@ var useDragResize = (options) => {
15488
15651
  if (dragRafRef.current) cancelAnimationFrame(dragRafRef.current);
15489
15652
  };
15490
15653
  }, []);
15491
- const handleFabPointerDown = (0, import_react25.useCallback)((e) => {
15654
+ const handleFabPointerDown = (0, import_react27.useCallback)((e) => {
15492
15655
  if (disabled || isMobile) return;
15493
15656
  dragCleanupRef.current?.();
15494
15657
  const el = e.currentTarget;
@@ -15670,7 +15833,7 @@ var useDragResize = (options) => {
15670
15833
  }, [disabled, isMobile]);
15671
15834
  const fabCenterX = fabPos.x + FAB_SIZE / 2;
15672
15835
  const panelDirection = fabCenterX > vw / 2 ? "left" : "right";
15673
- const panelPositionStyle = (0, import_react25.useMemo)(() => {
15836
+ const panelPositionStyle = (0, import_react27.useMemo)(() => {
15674
15837
  if (isMobile || disabled) return {};
15675
15838
  const fabIsTop = fabPos.y < vh / 2;
15676
15839
  let panelBottom;
@@ -15702,7 +15865,7 @@ var useDragResize = (options) => {
15702
15865
  borderRadius: "var(--floating-panel-radius, 16px)"
15703
15866
  };
15704
15867
  }, [isMobile, disabled, fabPos.x, fabPos.y, panelSize.width, panelSize.height, vw, vh, panelDirection, minHeight]);
15705
- const handleResizePointerDown = (0, import_react25.useCallback)((edge, e) => {
15868
+ const handleResizePointerDown = (0, import_react27.useCallback)((edge, e) => {
15706
15869
  if (disabled || isMobile) return;
15707
15870
  e.preventDefault();
15708
15871
  e.stopPropagation();
@@ -15718,7 +15881,7 @@ var useDragResize = (options) => {
15718
15881
  };
15719
15882
  setIsResizing(true);
15720
15883
  }, [disabled, isMobile]);
15721
- const handleResizePointerMove = (0, import_react25.useCallback)((e) => {
15884
+ const handleResizePointerMove = (0, import_react27.useCallback)((e) => {
15722
15885
  if (!resizeStartRef.current) return;
15723
15886
  const { startX, startY, width, height, edge, fabY } = resizeStartRef.current;
15724
15887
  const dx = e.clientX - startX;
@@ -15742,7 +15905,7 @@ var useDragResize = (options) => {
15742
15905
  newHeight = Math.max(minHeight, Math.min(maxH, newHeight));
15743
15906
  setPanelSize({ width: newWidth, height: newHeight });
15744
15907
  }, [minWidth, maxWidth, minHeight]);
15745
- const handleResizePointerUp = (0, import_react25.useCallback)((e) => {
15908
+ const handleResizePointerUp = (0, import_react27.useCallback)((e) => {
15746
15909
  if (!resizeStartRef.current) return;
15747
15910
  e.currentTarget.releasePointerCapture(e.pointerId);
15748
15911
  resizeStartRef.current = null;
@@ -15751,7 +15914,7 @@ var useDragResize = (options) => {
15751
15914
  const fp = fabPosRef.current;
15752
15915
  persistState(storageKeyRef.current, { fabX: fp.x, fabY: fp.y, panelWidth: ps.width, panelHeight: ps.height });
15753
15916
  }, []);
15754
- (0, import_react25.useEffect)(() => {
15917
+ (0, import_react27.useEffect)(() => {
15755
15918
  if (typeof window === "undefined") return;
15756
15919
  const handleResize = () => {
15757
15920
  const newVw = window.innerWidth;
@@ -15813,9 +15976,9 @@ var useFloatingWidget = (options) => {
15813
15976
  maxWidth,
15814
15977
  minHeight
15815
15978
  } = options || {};
15816
- const [isOpen, setIsOpen] = (0, import_react26.useState)(defaultOpen);
15817
- const [activeTab, setActiveTab] = (0, import_react26.useState)(defaultTab);
15818
- 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);
15819
15982
  const dragResize = useDragResize({
15820
15983
  initialPosition: position,
15821
15984
  initialWidth: width,
@@ -15827,27 +15990,27 @@ var useFloatingWidget = (options) => {
15827
15990
  maxWidth,
15828
15991
  minHeight
15829
15992
  });
15830
- const onOpenRef = (0, import_react26.useRef)(onOpen);
15831
- const onCloseRef = (0, import_react26.useRef)(onClose);
15832
- const onTabChangeRef = (0, import_react26.useRef)(onTabChange);
15833
- (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)(() => {
15834
15997
  onOpenRef.current = onOpen;
15835
15998
  }, [onOpen]);
15836
- (0, import_react26.useEffect)(() => {
15999
+ (0, import_react28.useEffect)(() => {
15837
16000
  onCloseRef.current = onClose;
15838
16001
  }, [onClose]);
15839
- (0, import_react26.useEffect)(() => {
16002
+ (0, import_react28.useEffect)(() => {
15840
16003
  onTabChangeRef.current = onTabChange;
15841
16004
  }, [onTabChange]);
15842
- const open = (0, import_react26.useCallback)(() => {
16005
+ const open = (0, import_react28.useCallback)(() => {
15843
16006
  setIsOpen(true);
15844
16007
  onOpenRef.current?.();
15845
16008
  }, []);
15846
- const close = (0, import_react26.useCallback)(() => {
16009
+ const close = (0, import_react28.useCallback)(() => {
15847
16010
  setIsOpen(false);
15848
16011
  onCloseRef.current?.();
15849
16012
  }, []);
15850
- const toggle = (0, import_react26.useCallback)(() => {
16013
+ const toggle = (0, import_react28.useCallback)(() => {
15851
16014
  setIsOpen((prev) => {
15852
16015
  const next = !prev;
15853
16016
  if (next) onOpenRef.current?.();
@@ -15855,15 +16018,15 @@ var useFloatingWidget = (options) => {
15855
16018
  return next;
15856
16019
  });
15857
16020
  }, []);
15858
- const setTab = (0, import_react26.useCallback)((tabKey) => {
16021
+ const setTab = (0, import_react28.useCallback)((tabKey) => {
15859
16022
  setActiveTab(tabKey);
15860
16023
  onTabChangeRef.current?.(tabKey);
15861
16024
  }, []);
15862
- const handleFabInteraction = (0, import_react26.useCallback)(() => {
16025
+ const handleFabInteraction = (0, import_react28.useCallback)(() => {
15863
16026
  if (dragResize.isDragging) return;
15864
16027
  toggle();
15865
16028
  }, [dragResize.isDragging, toggle]);
15866
- (0, import_react26.useEffect)(() => {
16029
+ (0, import_react28.useEffect)(() => {
15867
16030
  if (!isOpen) return;
15868
16031
  const handleKeyDown = (e) => {
15869
16032
  if (e.key === "Escape") {
@@ -15873,7 +16036,7 @@ var useFloatingWidget = (options) => {
15873
16036
  document.addEventListener("keydown", handleKeyDown);
15874
16037
  return () => document.removeEventListener("keydown", handleKeyDown);
15875
16038
  }, [isOpen, close]);
15876
- (0, import_react26.useEffect)(() => {
16039
+ (0, import_react28.useEffect)(() => {
15877
16040
  if (!isOpen) return;
15878
16041
  const handleClickOutside = (e) => {
15879
16042
  if (dragResize.isDragging || dragResize.isResizing) return;
@@ -15889,10 +16052,10 @@ var useFloatingWidget = (options) => {
15889
16052
  };
15890
16053
 
15891
16054
  // src/react/components/floating/FloatingFab.tsx
15892
- var import_react28 = require("react");
16055
+ var import_react30 = require("react");
15893
16056
 
15894
16057
  // src/react/components/floating/DevDiveCharacter.tsx
15895
- var import_react27 = require("react");
16058
+ var import_react29 = require("react");
15896
16059
  var import_jsx_runtime24 = require("react/jsx-runtime");
15897
16060
  var THEMES = {
15898
16061
  dark: { body: "#2ecc71", stroke: "#27ae60", highlight: "#3ddc84", face: "#1a1a2e", eyeLight: "#fff" },
@@ -15913,32 +16076,32 @@ var DevDiveFabCharacter = ({
15913
16076
  isComplete = false
15914
16077
  }) => {
15915
16078
  const c = THEMES[theme];
15916
- const [eyeOffset, setEyeOffset] = (0, import_react27.useState)({ x: 0, y: 0 });
15917
- const [isBlinking, setIsBlinking] = (0, import_react27.useState)(false);
15918
- const [isHappy, setIsHappy] = (0, import_react27.useState)(false);
15919
- const [isPressed, setIsPressed] = (0, import_react27.useState)(false);
15920
- const [mouthOpen, setMouthOpen] = (0, import_react27.useState)(false);
15921
- const [isSleepy, setIsSleepy] = (0, import_react27.useState)(false);
15922
- const [isSurprised, setIsSurprised] = (0, import_react27.useState)(false);
15923
- const [isWinking, setIsWinking] = (0, import_react27.useState)(false);
15924
- const [isCatMouth, setIsCatMouth] = (0, import_react27.useState)(false);
15925
- const [isEntering, setIsEntering] = (0, import_react27.useState)(true);
15926
- const svgRef = (0, import_react27.useRef)(null);
15927
- const cleanupRef = (0, import_react27.useRef)(null);
15928
- const lastActivityRef = (0, import_react27.useRef)(Date.now());
15929
- const isSleepyRef = (0, import_react27.useRef)(false);
15930
- 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)(() => {
15931
16094
  lastActivityRef.current = Date.now();
15932
16095
  if (isSleepyRef.current) {
15933
16096
  isSleepyRef.current = false;
15934
16097
  setIsSleepy(false);
15935
16098
  }
15936
16099
  }, []);
15937
- (0, import_react27.useEffect)(() => {
16100
+ (0, import_react29.useEffect)(() => {
15938
16101
  const t = setTimeout(() => setIsEntering(false), 600);
15939
16102
  return () => clearTimeout(t);
15940
16103
  }, []);
15941
- (0, import_react27.useEffect)(() => {
16104
+ (0, import_react29.useEffect)(() => {
15942
16105
  let timer;
15943
16106
  let blinkTimer;
15944
16107
  const scheduleBlink = () => {
@@ -15954,7 +16117,7 @@ var DevDiveFabCharacter = ({
15954
16117
  clearTimeout(blinkTimer);
15955
16118
  };
15956
16119
  }, []);
15957
- (0, import_react27.useEffect)(() => {
16120
+ (0, import_react29.useEffect)(() => {
15958
16121
  if (!isTalking) {
15959
16122
  setMouthOpen(false);
15960
16123
  return;
@@ -15967,7 +16130,7 @@ var DevDiveFabCharacter = ({
15967
16130
  toggle();
15968
16131
  return () => clearTimeout(timer);
15969
16132
  }, [isTalking]);
15970
- (0, import_react27.useEffect)(() => {
16133
+ (0, import_react29.useEffect)(() => {
15971
16134
  if (typeof window === "undefined") return;
15972
16135
  let rafId = 0;
15973
16136
  const onMouseMove = (e) => {
@@ -15991,7 +16154,7 @@ var DevDiveFabCharacter = ({
15991
16154
  cancelAnimationFrame(rafId);
15992
16155
  };
15993
16156
  }, []);
15994
- (0, import_react27.useEffect)(() => {
16157
+ (0, import_react29.useEffect)(() => {
15995
16158
  let upTimer;
15996
16159
  let surpriseTimer;
15997
16160
  const rafId = requestAnimationFrame(() => {
@@ -16034,10 +16197,10 @@ var DevDiveFabCharacter = ({
16034
16197
  cleanupRef.current?.();
16035
16198
  };
16036
16199
  }, [markActivity]);
16037
- (0, import_react27.useEffect)(() => {
16200
+ (0, import_react29.useEffect)(() => {
16038
16201
  markActivity();
16039
16202
  }, [isOpen, isTalking, markActivity]);
16040
- (0, import_react27.useEffect)(() => {
16203
+ (0, import_react29.useEffect)(() => {
16041
16204
  if (isOpen || isTalking || isDizzy) return;
16042
16205
  const check = setInterval(() => {
16043
16206
  if (Date.now() - lastActivityRef.current > IDLE_TIMEOUT_MS && !isSleepyRef.current) {
@@ -16047,8 +16210,8 @@ var DevDiveFabCharacter = ({
16047
16210
  }, 5e3);
16048
16211
  return () => clearInterval(check);
16049
16212
  }, [isOpen, isTalking, isDizzy]);
16050
- const prevCompleteRef = (0, import_react27.useRef)(false);
16051
- (0, import_react27.useEffect)(() => {
16213
+ const prevCompleteRef = (0, import_react29.useRef)(false);
16214
+ (0, import_react29.useEffect)(() => {
16052
16215
  const wasComplete = prevCompleteRef.current;
16053
16216
  prevCompleteRef.current = isComplete;
16054
16217
  if (wasComplete || !isComplete) return;
@@ -16056,7 +16219,7 @@ var DevDiveFabCharacter = ({
16056
16219
  const t = setTimeout(() => setIsWinking(false), WINK_DURATION_MS);
16057
16220
  return () => clearTimeout(t);
16058
16221
  }, [isComplete]);
16059
- (0, import_react27.useEffect)(() => {
16222
+ (0, import_react29.useEffect)(() => {
16060
16223
  if (isOpen || isTalking || isDizzy || isError || isSleepy) {
16061
16224
  setIsCatMouth(false);
16062
16225
  return;
@@ -16248,12 +16411,12 @@ var FloatingFab = ({
16248
16411
  }) => {
16249
16412
  const isRight = position.includes("right");
16250
16413
  const isBottom = position.includes("bottom");
16251
- const fabRef = (0, import_react28.useRef)(null);
16414
+ const fabRef = (0, import_react30.useRef)(null);
16252
16415
  const isCharacterMode = !icon;
16253
- const [bubbleOnRight, setBubbleOnRight] = (0, import_react28.useState)(!isRight);
16416
+ const [bubbleOnRight, setBubbleOnRight] = (0, import_react30.useState)(!isRight);
16254
16417
  const posLeft = positionStyle?.left;
16255
16418
  const posTop = positionStyle?.top;
16256
- (0, import_react28.useEffect)(() => {
16419
+ (0, import_react30.useEffect)(() => {
16257
16420
  if (!fabRef.current || typeof window === "undefined") {
16258
16421
  setBubbleOnRight(!isRight);
16259
16422
  return;
@@ -16261,14 +16424,14 @@ var FloatingFab = ({
16261
16424
  const rect = fabRef.current.getBoundingClientRect();
16262
16425
  setBubbleOnRight(rect.left + rect.width / 2 < window.innerWidth / 2);
16263
16426
  }, [isRight, posLeft, posTop]);
16264
- const [bubbleText, setBubbleText] = (0, import_react28.useState)(null);
16265
- const [bubbleExiting, setBubbleExiting] = (0, import_react28.useState)(false);
16266
- 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);
16267
16430
  bubbleTextRef.current = bubbleText;
16268
- const [displayText, setDisplayText] = (0, import_react28.useState)(null);
16269
- const [isTyping, setIsTyping] = (0, import_react28.useState)(false);
16270
- const typingTimerRef = (0, import_react28.useRef)(null);
16271
- (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)(() => {
16272
16435
  if (notification) {
16273
16436
  setBubbleText(notification);
16274
16437
  setBubbleExiting(false);
@@ -16304,9 +16467,9 @@ var FloatingFab = ({
16304
16467
  }, 300);
16305
16468
  return () => clearTimeout(timer);
16306
16469
  }, [notification]);
16307
- const notifContentRef = (0, import_react28.useRef)(null);
16308
- const [needsMarquee, setNeedsMarquee] = (0, import_react28.useState)(false);
16309
- (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)(() => {
16310
16473
  if (isTyping || !notification) {
16311
16474
  setNeedsMarquee(false);
16312
16475
  return;
@@ -16448,7 +16611,7 @@ var FloatingFab = ({
16448
16611
  };
16449
16612
 
16450
16613
  // src/react/components/floating/FloatingPanel.tsx
16451
- var import_react29 = require("react");
16614
+ var import_react31 = require("react");
16452
16615
  var import_jsx_runtime26 = require("react/jsx-runtime");
16453
16616
  var FloatingPanel = ({
16454
16617
  isOpen,
@@ -16466,8 +16629,8 @@ var FloatingPanel = ({
16466
16629
  }) => {
16467
16630
  const isRight = position.includes("right");
16468
16631
  const themeClass = theme?.mode === "dark" ? "chatllm-dark" : "";
16469
- const [isMobile, setIsMobile] = (0, import_react29.useState)(false);
16470
- (0, import_react29.useEffect)(() => {
16632
+ const [isMobile, setIsMobile] = (0, import_react31.useState)(false);
16633
+ (0, import_react31.useEffect)(() => {
16471
16634
  if (typeof window === "undefined") return;
16472
16635
  const mql = window.matchMedia("(max-width: 767px)");
16473
16636
  setIsMobile(mql.matches);
@@ -16475,10 +16638,10 @@ var FloatingPanel = ({
16475
16638
  mql.addEventListener("change", handler);
16476
16639
  return () => mql.removeEventListener("change", handler);
16477
16640
  }, []);
16478
- const [shouldRender, setShouldRender] = (0, import_react29.useState)(isOpen);
16479
- const [isVisible, setIsVisible] = (0, import_react29.useState)(isOpen);
16480
- const rafRef = (0, import_react29.useRef)(0);
16481
- (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)(() => {
16482
16645
  if (isOpen) {
16483
16646
  setShouldRender(true);
16484
16647
  rafRef.current = requestAnimationFrame(() => {
@@ -16493,7 +16656,7 @@ var FloatingPanel = ({
16493
16656
  }
16494
16657
  return () => cancelAnimationFrame(rafRef.current);
16495
16658
  }, [isOpen]);
16496
- (0, import_react29.useEffect)(() => {
16659
+ (0, import_react31.useEffect)(() => {
16497
16660
  if (!isOpen || !isMobile) return;
16498
16661
  const prev = document.body.style.overflow;
16499
16662
  document.body.style.overflow = "hidden";
@@ -16654,10 +16817,10 @@ var FloatingTabBar = ({
16654
16817
  };
16655
16818
 
16656
16819
  // src/react/components/floating/CompactChatView.tsx
16657
- var import_react31 = require("react");
16820
+ var import_react33 = require("react");
16658
16821
 
16659
16822
  // src/react/components/floating/CompactSessionMenu.tsx
16660
- var import_react30 = require("react");
16823
+ var import_react32 = require("react");
16661
16824
  var import_jsx_runtime28 = require("react/jsx-runtime");
16662
16825
  var CompactSessionMenu = ({
16663
16826
  sessions,
@@ -16669,13 +16832,13 @@ var CompactSessionMenu = ({
16669
16832
  onClose,
16670
16833
  isLoading = false
16671
16834
  }) => {
16672
- const menuRef = (0, import_react30.useRef)(null);
16673
- const inputRef = (0, import_react30.useRef)(null);
16674
- 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);
16675
16838
  onCloseRef.current = onClose;
16676
- const [editingId, setEditingId] = (0, import_react30.useState)(null);
16677
- const [editingTitle, setEditingTitle] = (0, import_react30.useState)("");
16678
- (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)(() => {
16679
16842
  const handleMouseDown = (e) => {
16680
16843
  if (menuRef.current && !menuRef.current.contains(e.target)) {
16681
16844
  onCloseRef.current();
@@ -16684,7 +16847,7 @@ var CompactSessionMenu = ({
16684
16847
  document.addEventListener("mousedown", handleMouseDown);
16685
16848
  return () => document.removeEventListener("mousedown", handleMouseDown);
16686
16849
  }, []);
16687
- (0, import_react30.useEffect)(() => {
16850
+ (0, import_react32.useEffect)(() => {
16688
16851
  if (editingId && inputRef.current) {
16689
16852
  inputRef.current.focus();
16690
16853
  inputRef.current.select();
@@ -16999,16 +17162,16 @@ var CompactChatView = ({
16999
17162
  const handleChoiceClick = (choice) => {
17000
17163
  setInput(choice.text);
17001
17164
  };
17002
- const [showSessionMenu, setShowSessionMenu] = (0, import_react31.useState)(false);
17003
- const handleSessionSelect = (0, import_react31.useCallback)((id) => {
17165
+ const [showSessionMenu, setShowSessionMenu] = (0, import_react33.useState)(false);
17166
+ const handleSessionSelect = (0, import_react33.useCallback)((id) => {
17004
17167
  selectSession(id);
17005
17168
  setShowSessionMenu(false);
17006
17169
  }, [selectSession]);
17007
- const handleNewSession = (0, import_react31.useCallback)(() => {
17170
+ const handleNewSession = (0, import_react33.useCallback)(() => {
17008
17171
  newSession();
17009
17172
  setShowSessionMenu(false);
17010
17173
  }, [newSession]);
17011
- const handleCloseMenu = (0, import_react31.useCallback)(() => {
17174
+ const handleCloseMenu = (0, import_react33.useCallback)(() => {
17012
17175
  setShowSessionMenu(false);
17013
17176
  }, []);
17014
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";
@@ -17328,11 +17491,11 @@ var ChatFloatingWidget = ({
17328
17491
  maxWidth,
17329
17492
  minHeight
17330
17493
  });
17331
- const notifObj = (0, import_react32.useMemo)(() => {
17494
+ const notifObj = (0, import_react34.useMemo)(() => {
17332
17495
  if (!notification) return null;
17333
17496
  return typeof notification === "string" ? { text: notification } : notification;
17334
17497
  }, [notification]);
17335
- const handleFabClick = (0, import_react32.useCallback)(() => {
17498
+ const handleFabClick = (0, import_react34.useCallback)(() => {
17336
17499
  if (notifObj?.onClick) {
17337
17500
  notifObj.onClick();
17338
17501
  if (!isOpen) handleFabInteraction();
@@ -17346,7 +17509,7 @@ var ChatFloatingWidget = ({
17346
17509
  }
17347
17510
  handleFabInteraction();
17348
17511
  }, [notifObj, isOpen, handleFabInteraction, setTab, tabs]);
17349
- const allTabs = (0, import_react32.useMemo)(() => {
17512
+ const allTabs = (0, import_react34.useMemo)(() => {
17350
17513
  const chatTab = {
17351
17514
  key: "chat",
17352
17515
  label: "\uCC44\uD305",
@@ -17360,16 +17523,16 @@ var ChatFloatingWidget = ({
17360
17523
  }));
17361
17524
  return [chatTab, ...customTabs];
17362
17525
  }, [tabs]);
17363
- const [unreadBadge, setUnreadBadge] = (0, import_react32.useState)(0);
17364
- const seenMessageIdsRef = (0, import_react32.useRef)(/* @__PURE__ */ new Set());
17365
- (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)(() => {
17366
17529
  if (seenMessageIdsRef.current.size === 0 && chatState.messages.length > 0) {
17367
17530
  for (const m of chatState.messages) {
17368
17531
  seenMessageIdsRef.current.add(m.id);
17369
17532
  }
17370
17533
  }
17371
17534
  }, [chatState.messages]);
17372
- (0, import_react32.useEffect)(() => {
17535
+ (0, import_react34.useEffect)(() => {
17373
17536
  if (isOpen) {
17374
17537
  for (const m of chatState.messages) {
17375
17538
  seenMessageIdsRef.current.add(m.id);
@@ -17386,7 +17549,7 @@ var ChatFloatingWidget = ({
17386
17549
  }
17387
17550
  }
17388
17551
  }, [chatState.messages, isOpen]);
17389
- (0, import_react32.useEffect)(() => {
17552
+ (0, import_react34.useEffect)(() => {
17390
17553
  if (isOpen) {
17391
17554
  setUnreadBadge(0);
17392
17555
  for (const m of chatState.messages) {
@@ -17394,13 +17557,13 @@ var ChatFloatingWidget = ({
17394
17557
  }
17395
17558
  }
17396
17559
  }, [isOpen, chatState.messages]);
17397
- const totalBadge = (0, import_react32.useMemo)(() => {
17560
+ const totalBadge = (0, import_react34.useMemo)(() => {
17398
17561
  return tabs.reduce((sum, t) => sum + (t.badge || 0), 0) + unreadBadge;
17399
17562
  }, [tabs, unreadBadge]);
17400
- const isTalking = (0, import_react32.useMemo)(() => !!notification || chatState.isLoading, [notification, chatState.isLoading]);
17401
- const prevLoadingRef = (0, import_react32.useRef)(chatState.isLoading);
17402
- const [isComplete, setIsComplete] = (0, import_react32.useState)(false);
17403
- (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)(() => {
17404
17567
  const wasLoading = prevLoadingRef.current;
17405
17568
  prevLoadingRef.current = chatState.isLoading;
17406
17569
  if (!wasLoading || chatState.isLoading || chatState.messages.length === 0) return;
@@ -17516,7 +17679,7 @@ var ChatFloatingWidget = ({
17516
17679
  };
17517
17680
 
17518
17681
  // src/react/hooks/useDeepResearch.ts
17519
- var import_react33 = require("react");
17682
+ var import_react35 = require("react");
17520
17683
  var REPORT_GENERATION_PROMPT2 = `\uB2F9\uC2E0\uC740 \uB9AC\uC11C\uCE58 \uBCF4\uACE0\uC11C \uC791\uC131 \uC804\uBB38\uAC00\uC785\uB2C8\uB2E4.
17521
17684
 
17522
17685
  <collected_sources>
@@ -17567,10 +17730,10 @@ var QUERY_ANALYSIS_PROMPT2 = `\uC0AC\uC6A9\uC790 \uC9C8\uBB38\uC744 \uBD84\uC11D
17567
17730
  - JSON \uC678 \uB2E4\uB978 \uD14D\uC2A4\uD2B8 \uCD9C\uB825 \uAE08\uC9C0`;
17568
17731
  var useDeepResearch = (options) => {
17569
17732
  const { onWebSearch, onExtractContent, apiEndpoint, apiKey, model, provider } = options;
17570
- const [isResearching, setIsResearching] = (0, import_react33.useState)(false);
17571
- const [progress, setProgress] = (0, import_react33.useState)(null);
17572
- const abortControllerRef = (0, import_react33.useRef)(null);
17573
- 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)(
17574
17737
  async (prompt, stream = false) => {
17575
17738
  const response = await fetch(apiEndpoint, {
17576
17739
  method: "POST",
@@ -17597,7 +17760,7 @@ var useDeepResearch = (options) => {
17597
17760
  },
17598
17761
  [apiEndpoint, apiKey, model, provider]
17599
17762
  );
17600
- const analyzeQuery2 = (0, import_react33.useCallback)(
17763
+ const analyzeQuery2 = (0, import_react35.useCallback)(
17601
17764
  async (query) => {
17602
17765
  const prompt = QUERY_ANALYSIS_PROMPT2.replace("{question}", query);
17603
17766
  const response = await callLLM2(prompt);
@@ -17616,7 +17779,7 @@ var useDeepResearch = (options) => {
17616
17779
  },
17617
17780
  [callLLM2]
17618
17781
  );
17619
- const runSubAgent2 = (0, import_react33.useCallback)(
17782
+ const runSubAgent2 = (0, import_react35.useCallback)(
17620
17783
  async (topic, queries, agentId, updateProgress) => {
17621
17784
  updateProgress({ status: "searching", searchCount: 0, resultsCount: 0 });
17622
17785
  const allResults = [];
@@ -17655,7 +17818,7 @@ var useDeepResearch = (options) => {
17655
17818
  },
17656
17819
  [onWebSearch, onExtractContent]
17657
17820
  );
17658
- const generateReport2 = (0, import_react33.useCallback)(
17821
+ const generateReport2 = (0, import_react35.useCallback)(
17659
17822
  async (query, results, onStreamContent) => {
17660
17823
  const allSources = [];
17661
17824
  const sourcesForPrompt = [];
@@ -17713,7 +17876,7 @@ var useDeepResearch = (options) => {
17713
17876
  },
17714
17877
  [callLLM2]
17715
17878
  );
17716
- const runDeepResearch = (0, import_react33.useCallback)(
17879
+ const runDeepResearch = (0, import_react35.useCallback)(
17717
17880
  async (query, onStreamContent) => {
17718
17881
  abortControllerRef.current = new AbortController();
17719
17882
  setIsResearching(true);
@@ -17816,7 +17979,7 @@ var useDeepResearch = (options) => {
17816
17979
  },
17817
17980
  [analyzeQuery2, runSubAgent2, generateReport2]
17818
17981
  );
17819
- const stopResearch = (0, import_react33.useCallback)(() => {
17982
+ const stopResearch = (0, import_react35.useCallback)(() => {
17820
17983
  abortControllerRef.current?.abort();
17821
17984
  setIsResearching(false);
17822
17985
  setProgress(null);
@@ -17829,6 +17992,122 @@ var useDeepResearch = (options) => {
17829
17992
  };
17830
17993
  };
17831
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
+
17832
18111
  // src/react/utils/conversationSearchAdapter.ts
17833
18112
  var formatSearchResults = (results) => {
17834
18113
  if (results.length === 0) {
@@ -18019,7 +18298,7 @@ var EmptyState = ({
18019
18298
  };
18020
18299
 
18021
18300
  // src/react/components/MemoryPanel.tsx
18022
- var import_react34 = require("react");
18301
+ var import_react37 = require("react");
18023
18302
  var import_jsx_runtime33 = require("react/jsx-runtime");
18024
18303
  var categoryLabels = {
18025
18304
  fact: "\uC0AC\uC6A9\uC790 \uC815\uBCF4",
@@ -18039,8 +18318,8 @@ var MemoryPanel = ({
18039
18318
  isOpen,
18040
18319
  onToggle
18041
18320
  }) => {
18042
- const [expandedId, setExpandedId] = (0, import_react34.useState)(null);
18043
- 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");
18044
18323
  const filteredItems = activeTab === "all" ? items : items.filter((item) => item.category === activeTab);
18045
18324
  const formatDate = (timestamp) => {
18046
18325
  const date = new Date(timestamp);
@@ -18356,9 +18635,53 @@ var MemoryPanel = ({
18356
18635
  }
18357
18636
  );
18358
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
+ };
18359
18681
  // Annotate the CommonJS export names for ESM import in node:
18360
18682
  0 && (module.exports = {
18361
18683
  ArtifactCard,
18684
+ ChatError,
18362
18685
  ChatFloatingWidget,
18363
18686
  ChatHeader,
18364
18687
  ChatInput,
@@ -18371,6 +18694,7 @@ var MemoryPanel = ({
18371
18694
  ContentPartRenderer,
18372
18695
  DEFAULT_PROJECT_ID,
18373
18696
  DEFAULT_PROJECT_TITLE,
18697
+ DEFAULT_RETRY_CONFIG,
18374
18698
  DeepResearchProgressUI,
18375
18699
  DevDiveAvatar,
18376
18700
  DevDiveFabCharacter,
@@ -18394,20 +18718,28 @@ var MemoryPanel = ({
18394
18718
  ResizeHandles,
18395
18719
  SettingsModal,
18396
18720
  SkillProgressUI,
18721
+ classifyFetchError,
18397
18722
  convertSkillsToOpenAITools,
18398
18723
  convertToolsToSkills,
18399
18724
  createAdvancedResearchSkill,
18400
18725
  createConversationSearchSkill,
18401
18726
  createDeepResearchSkill,
18727
+ createTimeoutError,
18402
18728
  migrateSessionsToProjects,
18729
+ parseSSELine,
18730
+ parseSSEResponse,
18403
18731
  useChatUI,
18732
+ useChecklist,
18733
+ useContentParsers,
18404
18734
  useDeepResearch,
18405
18735
  useDragResize,
18406
18736
  useFloatingWidget,
18407
18737
  useImageError,
18408
18738
  useObserver,
18409
18739
  useProject,
18410
- useSkills
18740
+ useSkills,
18741
+ useStreamingFetch,
18742
+ withRetry
18411
18743
  });
18412
18744
  /**
18413
18745
  * @description localStorage 기반 메모리 저장소 어댑터