@gendive/chatllm 0.22.1 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +2 -815
- package/dist/index.d.ts +2 -815
- package/dist/index.js +2 -2158
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -2043
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.mts +309 -2
- package/dist/react/index.d.ts +309 -2
- package/dist/react/index.js +1254 -941
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +1053 -750
- package/dist/react/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/react/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import React17 from "react";
|
|
3
3
|
|
|
4
4
|
// src/react/hooks/useChatUI.ts
|
|
5
|
-
import { useState as useState6, useRef as
|
|
5
|
+
import { useState as useState6, useRef as useRef7, useCallback as useCallback8, useEffect as useEffect4, useMemo as useMemo4 } from "react";
|
|
6
6
|
|
|
7
7
|
// src/types.ts
|
|
8
8
|
var DEFAULT_PERSONALIZATION = {
|
|
@@ -2059,6 +2059,462 @@ var parseContextRefs = (content) => {
|
|
|
2059
2059
|
return { refs, cleanContent };
|
|
2060
2060
|
};
|
|
2061
2061
|
|
|
2062
|
+
// src/react/utils/errors.ts
|
|
2063
|
+
var ChatError = class extends Error {
|
|
2064
|
+
code;
|
|
2065
|
+
retryable;
|
|
2066
|
+
statusCode;
|
|
2067
|
+
originalError;
|
|
2068
|
+
constructor(code, message, options) {
|
|
2069
|
+
super(message);
|
|
2070
|
+
this.name = "ChatError";
|
|
2071
|
+
this.code = code;
|
|
2072
|
+
this.statusCode = options?.statusCode;
|
|
2073
|
+
this.originalError = options?.originalError;
|
|
2074
|
+
this.retryable = options?.retryable ?? isRetryableCode(code);
|
|
2075
|
+
}
|
|
2076
|
+
};
|
|
2077
|
+
var classifyStatusCode = (status) => {
|
|
2078
|
+
if (status === 401 || status === 403) return "AUTH";
|
|
2079
|
+
if (status === 429) return "RATE_LIMIT";
|
|
2080
|
+
if (status >= 500) return "API";
|
|
2081
|
+
if (status >= 400) return "API";
|
|
2082
|
+
return "UNKNOWN";
|
|
2083
|
+
};
|
|
2084
|
+
var isRetryableCode = (code) => code === "NETWORK" || code === "RATE_LIMIT" || code === "API";
|
|
2085
|
+
var classifyFetchError = (error, response) => {
|
|
2086
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
2087
|
+
return new ChatError("ABORT", "\uC694\uCCAD\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", {
|
|
2088
|
+
originalError: error,
|
|
2089
|
+
retryable: false
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2093
|
+
return new ChatError("ABORT", "\uC694\uCCAD\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", {
|
|
2094
|
+
originalError: error,
|
|
2095
|
+
retryable: false
|
|
2096
|
+
});
|
|
2097
|
+
}
|
|
2098
|
+
if (error instanceof ChatError) return error;
|
|
2099
|
+
if (response && !response.ok) {
|
|
2100
|
+
const code = classifyStatusCode(response.status);
|
|
2101
|
+
const messages = {
|
|
2102
|
+
AUTH: "\uC778\uC99D\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. API \uD0A4\uB97C \uD655\uC778\uD574\uC8FC\uC138\uC694.",
|
|
2103
|
+
RATE_LIMIT: "\uC694\uCCAD \uD55C\uB3C4\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4. \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.",
|
|
2104
|
+
API: `\uC11C\uBC84 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4. (${response.status})`,
|
|
2105
|
+
NETWORK: "\uB124\uD2B8\uC6CC\uD06C \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.",
|
|
2106
|
+
TIMEOUT: "\uC694\uCCAD \uC2DC\uAC04\uC774 \uCD08\uACFC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.",
|
|
2107
|
+
ABORT: "\uC694\uCCAD\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.",
|
|
2108
|
+
UNKNOWN: "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4."
|
|
2109
|
+
};
|
|
2110
|
+
return new ChatError(code, messages[code], {
|
|
2111
|
+
statusCode: response.status,
|
|
2112
|
+
originalError: error
|
|
2113
|
+
});
|
|
2114
|
+
}
|
|
2115
|
+
if (error instanceof TypeError) {
|
|
2116
|
+
return new ChatError("NETWORK", "\uB124\uD2B8\uC6CC\uD06C \uC5F0\uACB0\uC744 \uD655\uC778\uD574\uC8FC\uC138\uC694.", {
|
|
2117
|
+
originalError: error
|
|
2118
|
+
});
|
|
2119
|
+
}
|
|
2120
|
+
const message = error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
|
|
2121
|
+
return new ChatError("UNKNOWN", message, { originalError: error });
|
|
2122
|
+
};
|
|
2123
|
+
var createTimeoutError = (timeoutMs) => new ChatError("TIMEOUT", `\uC2A4\uD2B8\uB9AC\uBC0D \uC751\uB2F5\uC774 ${timeoutMs / 1e3}\uCD08 \uB3D9\uC548 \uC5C6\uC2B5\uB2C8\uB2E4.`, {
|
|
2124
|
+
retryable: false
|
|
2125
|
+
});
|
|
2126
|
+
|
|
2127
|
+
// src/react/hooks/useStreamingFetch.ts
|
|
2128
|
+
import { useCallback as useCallback6, useRef as useRef5 } from "react";
|
|
2129
|
+
var DEFAULT_CHUNK_TIMEOUT = 3e4;
|
|
2130
|
+
var parseSSELine = (line) => {
|
|
2131
|
+
if (!line.trim()) return null;
|
|
2132
|
+
let data = line;
|
|
2133
|
+
if (line.startsWith("data: ")) {
|
|
2134
|
+
data = line.slice(6);
|
|
2135
|
+
if (data === "[DONE]") return null;
|
|
2136
|
+
}
|
|
2137
|
+
try {
|
|
2138
|
+
const parsed = JSON.parse(data);
|
|
2139
|
+
let usage = null;
|
|
2140
|
+
if (parsed.usage) {
|
|
2141
|
+
usage = {
|
|
2142
|
+
promptTokens: parsed.usage.prompt_tokens ?? parsed.usage.promptTokens ?? 0,
|
|
2143
|
+
completionTokens: parsed.usage.completion_tokens ?? parsed.usage.completionTokens ?? 0,
|
|
2144
|
+
totalTokens: parsed.usage.total_tokens ?? parsed.usage.totalTokens ?? 0
|
|
2145
|
+
};
|
|
2146
|
+
} else if (parsed.prompt_eval_count != null || parsed.eval_count != null) {
|
|
2147
|
+
usage = {
|
|
2148
|
+
promptTokens: parsed.prompt_eval_count ?? 0,
|
|
2149
|
+
completionTokens: parsed.eval_count ?? 0,
|
|
2150
|
+
totalTokens: (parsed.prompt_eval_count ?? 0) + (parsed.eval_count ?? 0)
|
|
2151
|
+
};
|
|
2152
|
+
}
|
|
2153
|
+
const delta = parsed.choices?.[0]?.delta ?? null;
|
|
2154
|
+
const finishReason = parsed.choices?.[0]?.finish_reason ?? null;
|
|
2155
|
+
const content = delta?.content ?? parsed.message?.content ?? parsed.content ?? parsed.text ?? "";
|
|
2156
|
+
const thinking = parsed.message?.thinking ?? "";
|
|
2157
|
+
return { content, thinking, finishReason, delta, usage, raw: parsed };
|
|
2158
|
+
} catch {
|
|
2159
|
+
return null;
|
|
2160
|
+
}
|
|
2161
|
+
};
|
|
2162
|
+
var parseSSEResponse = async (response) => {
|
|
2163
|
+
const reader = response.body?.getReader();
|
|
2164
|
+
if (!reader) return "";
|
|
2165
|
+
const decoder = new TextDecoder();
|
|
2166
|
+
let buffer = "";
|
|
2167
|
+
let result = "";
|
|
2168
|
+
try {
|
|
2169
|
+
while (true) {
|
|
2170
|
+
const { done, value } = await reader.read();
|
|
2171
|
+
if (done) break;
|
|
2172
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2173
|
+
const lines = buffer.split("\n");
|
|
2174
|
+
buffer = lines.pop() || "";
|
|
2175
|
+
for (const line of lines) {
|
|
2176
|
+
const chunk = parseSSELine(line);
|
|
2177
|
+
if (chunk?.content) result += chunk.content;
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
if (buffer.trim()) {
|
|
2181
|
+
const chunk = parseSSELine(buffer);
|
|
2182
|
+
if (chunk?.content) result += chunk.content;
|
|
2183
|
+
}
|
|
2184
|
+
} finally {
|
|
2185
|
+
reader.releaseLock();
|
|
2186
|
+
}
|
|
2187
|
+
return result;
|
|
2188
|
+
};
|
|
2189
|
+
var useStreamingFetch = (options = {}) => {
|
|
2190
|
+
const { chunkTimeout = DEFAULT_CHUNK_TIMEOUT } = options;
|
|
2191
|
+
const abortControllersRef = useRef5(/* @__PURE__ */ new Map());
|
|
2192
|
+
const createAbortController = useCallback6((sessionId) => {
|
|
2193
|
+
const controller = new AbortController();
|
|
2194
|
+
abortControllersRef.current.set(sessionId, controller);
|
|
2195
|
+
return controller;
|
|
2196
|
+
}, []);
|
|
2197
|
+
const abort = useCallback6((sessionId) => {
|
|
2198
|
+
abortControllersRef.current.get(sessionId)?.abort();
|
|
2199
|
+
}, []);
|
|
2200
|
+
const cleanup = useCallback6((sessionId) => {
|
|
2201
|
+
abortControllersRef.current.delete(sessionId);
|
|
2202
|
+
}, []);
|
|
2203
|
+
const getSignal = useCallback6((sessionId) => {
|
|
2204
|
+
return abortControllersRef.current.get(sessionId)?.signal;
|
|
2205
|
+
}, []);
|
|
2206
|
+
const readWithTimeout = useCallback6(async (reader) => {
|
|
2207
|
+
let timerId;
|
|
2208
|
+
try {
|
|
2209
|
+
return await Promise.race([
|
|
2210
|
+
reader.read(),
|
|
2211
|
+
new Promise((_, reject) => {
|
|
2212
|
+
timerId = setTimeout(() => reject(createTimeoutError(chunkTimeout)), chunkTimeout);
|
|
2213
|
+
})
|
|
2214
|
+
]);
|
|
2215
|
+
} finally {
|
|
2216
|
+
clearTimeout(timerId);
|
|
2217
|
+
}
|
|
2218
|
+
}, [chunkTimeout]);
|
|
2219
|
+
const streamResponse = useCallback6(async (response, onChunk, streamOptions) => {
|
|
2220
|
+
if (!response.ok) {
|
|
2221
|
+
throw classifyFetchError(new Error("API error"), response);
|
|
2222
|
+
}
|
|
2223
|
+
const reader = response.body?.getReader();
|
|
2224
|
+
if (!reader) throw new Error("No reader");
|
|
2225
|
+
const decoder = new TextDecoder();
|
|
2226
|
+
let buffer = "";
|
|
2227
|
+
let lastUsage = null;
|
|
2228
|
+
try {
|
|
2229
|
+
while (true) {
|
|
2230
|
+
const { done, value } = streamOptions?.noTimeout ? await reader.read() : await readWithTimeout(reader);
|
|
2231
|
+
if (done) break;
|
|
2232
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2233
|
+
const lines = buffer.split("\n");
|
|
2234
|
+
buffer = lines.pop() || "";
|
|
2235
|
+
let shouldBreak = false;
|
|
2236
|
+
for (const line of lines) {
|
|
2237
|
+
const chunk = parseSSELine(line);
|
|
2238
|
+
if (!chunk) continue;
|
|
2239
|
+
if (chunk.usage) lastUsage = chunk.usage;
|
|
2240
|
+
const result = onChunk(chunk);
|
|
2241
|
+
if (result === "break") {
|
|
2242
|
+
shouldBreak = true;
|
|
2243
|
+
break;
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
if (shouldBreak) break;
|
|
2247
|
+
}
|
|
2248
|
+
if (buffer.trim()) {
|
|
2249
|
+
const chunk = parseSSELine(buffer);
|
|
2250
|
+
if (chunk) {
|
|
2251
|
+
if (chunk.usage) lastUsage = chunk.usage;
|
|
2252
|
+
onChunk(chunk);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
} finally {
|
|
2256
|
+
reader.releaseLock();
|
|
2257
|
+
}
|
|
2258
|
+
return { usage: lastUsage };
|
|
2259
|
+
}, [readWithTimeout]);
|
|
2260
|
+
const fetchAndStream = useCallback6(async (fetchOptions, onChunk) => {
|
|
2261
|
+
const response = await fetch(fetchOptions.url, {
|
|
2262
|
+
method: "POST",
|
|
2263
|
+
headers: { "Content-Type": "application/json" },
|
|
2264
|
+
body: JSON.stringify(fetchOptions.body),
|
|
2265
|
+
signal: fetchOptions.signal
|
|
2266
|
+
});
|
|
2267
|
+
fetchOptions.onHeaders?.(response);
|
|
2268
|
+
return streamResponse(response, onChunk);
|
|
2269
|
+
}, [streamResponse]);
|
|
2270
|
+
return {
|
|
2271
|
+
abortControllers: abortControllersRef,
|
|
2272
|
+
createAbortController,
|
|
2273
|
+
abort,
|
|
2274
|
+
cleanup,
|
|
2275
|
+
getSignal,
|
|
2276
|
+
readWithTimeout,
|
|
2277
|
+
streamResponse,
|
|
2278
|
+
fetchAndStream,
|
|
2279
|
+
parseSSELine
|
|
2280
|
+
};
|
|
2281
|
+
};
|
|
2282
|
+
|
|
2283
|
+
// src/react/hooks/useChecklist.ts
|
|
2284
|
+
import { useRef as useRef6, useCallback as useCallback7, useMemo as useMemo3 } from "react";
|
|
2285
|
+
var CHECKLIST_STEP_DELAY_MS = 100;
|
|
2286
|
+
var toActiveItems = (items) => items.map((it) => ({
|
|
2287
|
+
id: it.id,
|
|
2288
|
+
title: it.title,
|
|
2289
|
+
skill: it.skill,
|
|
2290
|
+
fileIndex: it.fileIndex,
|
|
2291
|
+
fileType: it.fileType,
|
|
2292
|
+
refImage: it.refImage,
|
|
2293
|
+
refStep: it.refStep
|
|
2294
|
+
}));
|
|
2295
|
+
var useChecklist = ({
|
|
2296
|
+
sessionsRef,
|
|
2297
|
+
sessions,
|
|
2298
|
+
setSessions,
|
|
2299
|
+
sendMessage,
|
|
2300
|
+
abortControllers,
|
|
2301
|
+
removeLoadingSession,
|
|
2302
|
+
pendingAttachmentDataRef,
|
|
2303
|
+
trackChecklistSkip,
|
|
2304
|
+
resolveChecklistRefImage,
|
|
2305
|
+
buildChecklistStepPrompt
|
|
2306
|
+
}) => {
|
|
2307
|
+
const skipNextChecklistParsingRef = useRef6(false);
|
|
2308
|
+
const activeChecklistRef = useRef6(null);
|
|
2309
|
+
const pendingChecklistRef = useRef6(null);
|
|
2310
|
+
const sendMessageRef = useRef6(sendMessage);
|
|
2311
|
+
sendMessageRef.current = sendMessage;
|
|
2312
|
+
const findSessionAndMessage = useCallback7(
|
|
2313
|
+
(messageId) => {
|
|
2314
|
+
const session = sessionsRef.current?.find(
|
|
2315
|
+
(s) => s.messages.some((m) => m.id === messageId)
|
|
2316
|
+
);
|
|
2317
|
+
if (!session) return null;
|
|
2318
|
+
const message = session.messages.find((m) => m.id === messageId);
|
|
2319
|
+
if (!message?.checklistBlock) return null;
|
|
2320
|
+
return { session, message };
|
|
2321
|
+
},
|
|
2322
|
+
[sessionsRef]
|
|
2323
|
+
);
|
|
2324
|
+
const executeStep = useCallback7(
|
|
2325
|
+
(items, stepIndex, messageId, sessionId, isFirst) => {
|
|
2326
|
+
skipNextChecklistParsingRef.current = true;
|
|
2327
|
+
setTimeout(() => {
|
|
2328
|
+
const item = items[stepIndex];
|
|
2329
|
+
const refUrl = resolveChecklistRefImage(item, messageId, sessionId);
|
|
2330
|
+
if (refUrl) {
|
|
2331
|
+
pendingAttachmentDataRef.current = [
|
|
2332
|
+
{ name: "ref_image", mimeType: "image/png", base64: "", size: 0, url: refUrl }
|
|
2333
|
+
];
|
|
2334
|
+
}
|
|
2335
|
+
sendMessageRef.current(
|
|
2336
|
+
buildChecklistStepPrompt(stepIndex, items.length, item, isFirst, refUrl),
|
|
2337
|
+
{ hiddenUserMessage: true, isChecklistExecution: true, checklistStep: { index: stepIndex, title: item.title } }
|
|
2338
|
+
);
|
|
2339
|
+
}, CHECKLIST_STEP_DELAY_MS);
|
|
2340
|
+
},
|
|
2341
|
+
[resolveChecklistRefImage, buildChecklistStepPrompt, pendingAttachmentDataRef]
|
|
2342
|
+
);
|
|
2343
|
+
const updateChecklistItems = useCallback7(
|
|
2344
|
+
(sessionId, messageId, updater) => {
|
|
2345
|
+
setSessions(
|
|
2346
|
+
(prev) => prev.map((s) => {
|
|
2347
|
+
if (s.id !== sessionId) return s;
|
|
2348
|
+
return {
|
|
2349
|
+
...s,
|
|
2350
|
+
messages: s.messages.map((m) => {
|
|
2351
|
+
if (m.id !== messageId || !m.checklistBlock) return m;
|
|
2352
|
+
const result = updater(m.checklistBlock.items, m.checklistBlock);
|
|
2353
|
+
return {
|
|
2354
|
+
...m,
|
|
2355
|
+
checklistBlock: {
|
|
2356
|
+
...m.checklistBlock,
|
|
2357
|
+
items: result.items,
|
|
2358
|
+
...result.currentStep !== void 0 ? { currentStep: result.currentStep } : {},
|
|
2359
|
+
...result.completed !== void 0 ? { completed: result.completed } : {}
|
|
2360
|
+
}
|
|
2361
|
+
};
|
|
2362
|
+
})
|
|
2363
|
+
};
|
|
2364
|
+
})
|
|
2365
|
+
);
|
|
2366
|
+
},
|
|
2367
|
+
[setSessions]
|
|
2368
|
+
);
|
|
2369
|
+
const handleChecklistStart = useCallback7(
|
|
2370
|
+
(messageId) => {
|
|
2371
|
+
const found = findSessionAndMessage(messageId);
|
|
2372
|
+
if (!found) return;
|
|
2373
|
+
const { session, message } = found;
|
|
2374
|
+
pendingChecklistRef.current = null;
|
|
2375
|
+
activeChecklistRef.current = {
|
|
2376
|
+
messageId,
|
|
2377
|
+
sessionId: session.id,
|
|
2378
|
+
items: toActiveItems(message.checklistBlock.items),
|
|
2379
|
+
currentStep: 0,
|
|
2380
|
+
stepResults: []
|
|
2381
|
+
};
|
|
2382
|
+
updateChecklistItems(session.id, messageId, (items) => ({
|
|
2383
|
+
items: items.map((it, idx) => ({
|
|
2384
|
+
...it,
|
|
2385
|
+
status: idx === 0 ? "in_progress" : it.status
|
|
2386
|
+
})),
|
|
2387
|
+
currentStep: 0
|
|
2388
|
+
}));
|
|
2389
|
+
executeStep(message.checklistBlock.items, 0, messageId, session.id, true);
|
|
2390
|
+
},
|
|
2391
|
+
[findSessionAndMessage, updateChecklistItems, executeStep]
|
|
2392
|
+
);
|
|
2393
|
+
const handleChecklistAbort = useCallback7(() => {
|
|
2394
|
+
if (!activeChecklistRef.current) return;
|
|
2395
|
+
const checklist = activeChecklistRef.current;
|
|
2396
|
+
const stepIdx = checklist.currentStep;
|
|
2397
|
+
abortControllers.current?.get(checklist.sessionId)?.abort();
|
|
2398
|
+
updateChecklistItems(checklist.sessionId, checklist.messageId, (items) => ({
|
|
2399
|
+
items: items.map((it, idx) => ({
|
|
2400
|
+
...it,
|
|
2401
|
+
status: idx === stepIdx ? "error" : it.status
|
|
2402
|
+
}))
|
|
2403
|
+
}));
|
|
2404
|
+
activeChecklistRef.current = null;
|
|
2405
|
+
removeLoadingSession(checklist.sessionId);
|
|
2406
|
+
}, [abortControllers, updateChecklistItems, removeLoadingSession]);
|
|
2407
|
+
const handleChecklistRetry = useCallback7(
|
|
2408
|
+
(messageId, stepIndex) => {
|
|
2409
|
+
const found = findSessionAndMessage(messageId);
|
|
2410
|
+
if (!found) return;
|
|
2411
|
+
const { session, message } = found;
|
|
2412
|
+
activeChecklistRef.current = {
|
|
2413
|
+
messageId,
|
|
2414
|
+
sessionId: session.id,
|
|
2415
|
+
items: toActiveItems(message.checklistBlock.items),
|
|
2416
|
+
currentStep: stepIndex,
|
|
2417
|
+
stepResults: message.checklistBlock.items.slice(0, stepIndex).map((it) => it.result || "")
|
|
2418
|
+
};
|
|
2419
|
+
updateChecklistItems(session.id, messageId, (items) => ({
|
|
2420
|
+
items: items.map((it, idx) => ({
|
|
2421
|
+
...it,
|
|
2422
|
+
status: idx === stepIndex ? "in_progress" : idx > stepIndex ? "pending" : it.status
|
|
2423
|
+
})),
|
|
2424
|
+
currentStep: stepIndex
|
|
2425
|
+
}));
|
|
2426
|
+
executeStep(message.checklistBlock.items, stepIndex, messageId, session.id, stepIndex === 0);
|
|
2427
|
+
},
|
|
2428
|
+
[findSessionAndMessage, updateChecklistItems, executeStep]
|
|
2429
|
+
);
|
|
2430
|
+
const handleChecklistSkip = useCallback7(
|
|
2431
|
+
(messageId, stepIndex) => {
|
|
2432
|
+
const found = findSessionAndMessage(messageId);
|
|
2433
|
+
if (!found) return;
|
|
2434
|
+
const { session, message } = found;
|
|
2435
|
+
trackChecklistSkip();
|
|
2436
|
+
updateChecklistItems(session.id, messageId, (items) => ({
|
|
2437
|
+
items: items.map((it, idx) => ({
|
|
2438
|
+
...it,
|
|
2439
|
+
status: idx === stepIndex ? "done" : it.status,
|
|
2440
|
+
result: idx === stepIndex ? "(\uAC74\uB108\uB700)" : it.result
|
|
2441
|
+
}))
|
|
2442
|
+
}));
|
|
2443
|
+
const nextPending = message.checklistBlock.items.findIndex(
|
|
2444
|
+
(it, idx) => idx > stepIndex && (it.status === "pending" || it.status === "error")
|
|
2445
|
+
);
|
|
2446
|
+
if (nextPending >= 0) {
|
|
2447
|
+
activeChecklistRef.current = {
|
|
2448
|
+
messageId,
|
|
2449
|
+
sessionId: session.id,
|
|
2450
|
+
items: toActiveItems(message.checklistBlock.items),
|
|
2451
|
+
currentStep: nextPending,
|
|
2452
|
+
stepResults: message.checklistBlock.items.slice(0, nextPending).map((it) => it.result || "(\uAC74\uB108\uB700)")
|
|
2453
|
+
};
|
|
2454
|
+
updateChecklistItems(session.id, messageId, (items) => ({
|
|
2455
|
+
items: items.map((it, idx) => ({
|
|
2456
|
+
...it,
|
|
2457
|
+
status: idx === nextPending ? "in_progress" : it.status
|
|
2458
|
+
})),
|
|
2459
|
+
currentStep: nextPending
|
|
2460
|
+
}));
|
|
2461
|
+
executeStep(message.checklistBlock.items, nextPending, messageId, session.id, false);
|
|
2462
|
+
} else {
|
|
2463
|
+
const allResults = message.checklistBlock.items.map((it, i) => {
|
|
2464
|
+
const result = i === stepIndex ? "(\uAC74\uB108\uB700)" : it.result || "(\uAC74\uB108\uB700)";
|
|
2465
|
+
return `### ${i + 1}. ${it.title}
|
|
2466
|
+
${result}`;
|
|
2467
|
+
}).join("\n\n");
|
|
2468
|
+
updateChecklistItems(session.id, messageId, () => ({
|
|
2469
|
+
items: message.checklistBlock.items.map((it, idx) => ({
|
|
2470
|
+
...it,
|
|
2471
|
+
status: idx === stepIndex ? "done" : it.status,
|
|
2472
|
+
result: idx === stepIndex ? "(\uAC74\uB108\uB700)" : it.result
|
|
2473
|
+
})),
|
|
2474
|
+
completed: true
|
|
2475
|
+
}));
|
|
2476
|
+
skipNextChecklistParsingRef.current = true;
|
|
2477
|
+
setTimeout(() => {
|
|
2478
|
+
sendMessageRef.current(
|
|
2479
|
+
`\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:
|
|
2480
|
+
|
|
2481
|
+
${allResults}
|
|
2482
|
+
|
|
2483
|
+
\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.`,
|
|
2484
|
+
{ hiddenUserMessage: true, isChecklistExecution: true }
|
|
2485
|
+
);
|
|
2486
|
+
}, CHECKLIST_STEP_DELAY_MS);
|
|
2487
|
+
}
|
|
2488
|
+
},
|
|
2489
|
+
[findSessionAndMessage, updateChecklistItems, executeStep, trackChecklistSkip]
|
|
2490
|
+
);
|
|
2491
|
+
const activeChecklistMessage = useMemo3(() => {
|
|
2492
|
+
const active = activeChecklistRef.current;
|
|
2493
|
+
if (!active) {
|
|
2494
|
+
for (const session2 of sessions) {
|
|
2495
|
+
const msg = session2.messages.find(
|
|
2496
|
+
(m) => m.checklistBlock && !m.checklistBlock.completed
|
|
2497
|
+
);
|
|
2498
|
+
if (msg) return msg;
|
|
2499
|
+
}
|
|
2500
|
+
return null;
|
|
2501
|
+
}
|
|
2502
|
+
const session = sessions.find((s) => s.id === active.sessionId);
|
|
2503
|
+
if (!session) return null;
|
|
2504
|
+
return session.messages.find((m) => m.id === active.messageId) || null;
|
|
2505
|
+
}, [sessions]);
|
|
2506
|
+
return {
|
|
2507
|
+
activeChecklistRef,
|
|
2508
|
+
pendingChecklistRef,
|
|
2509
|
+
skipNextChecklistParsingRef,
|
|
2510
|
+
handleChecklistStart,
|
|
2511
|
+
handleChecklistAbort,
|
|
2512
|
+
handleChecklistRetry,
|
|
2513
|
+
handleChecklistSkip,
|
|
2514
|
+
activeChecklistMessage
|
|
2515
|
+
};
|
|
2516
|
+
};
|
|
2517
|
+
|
|
2062
2518
|
// src/react/utils/sessionCache.ts
|
|
2063
2519
|
var buildCacheKey = (storageKey, sessionId) => `${storageKey}_cache_${sessionId}`;
|
|
2064
2520
|
var writeSessionCache = (storageKey, session) => {
|
|
@@ -2091,38 +2547,14 @@ var removeSessionCache = (storageKey, sessionId) => {
|
|
|
2091
2547
|
};
|
|
2092
2548
|
|
|
2093
2549
|
// src/react/hooks/useChatUI.ts
|
|
2094
|
-
var parseSSEResponse = async (response) => {
|
|
2095
|
-
const reader = response.body?.getReader();
|
|
2096
|
-
if (!reader) return "";
|
|
2097
|
-
const decoder = new TextDecoder();
|
|
2098
|
-
let buffer = "";
|
|
2099
|
-
let result = "";
|
|
2100
|
-
while (true) {
|
|
2101
|
-
const { done, value } = await reader.read();
|
|
2102
|
-
if (done) break;
|
|
2103
|
-
buffer += decoder.decode(value, { stream: true });
|
|
2104
|
-
const lines = buffer.split("\n");
|
|
2105
|
-
buffer = lines.pop() || "";
|
|
2106
|
-
for (const line of lines) {
|
|
2107
|
-
if (line.startsWith("data: ")) {
|
|
2108
|
-
const data = line.slice(6);
|
|
2109
|
-
if (data === "[DONE]") continue;
|
|
2110
|
-
try {
|
|
2111
|
-
const parsed = JSON.parse(data);
|
|
2112
|
-
const chunk = parsed.content ?? parsed.text ?? "";
|
|
2113
|
-
if (chunk) result += chunk;
|
|
2114
|
-
} catch {
|
|
2115
|
-
}
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
}
|
|
2119
|
-
return result;
|
|
2120
|
-
};
|
|
2121
2550
|
var DEFAULT_STORAGE_KEY2 = "chatllm_sessions";
|
|
2122
2551
|
var DEFAULT_COMPRESSION_THRESHOLD = 20;
|
|
2123
2552
|
var DEFAULT_KEEP_RECENT = 6;
|
|
2124
2553
|
var DEFAULT_RECOMPRESSION_THRESHOLD = 10;
|
|
2125
2554
|
var DEFAULT_TOKEN_LIMIT = 8e3;
|
|
2555
|
+
var DEFAULT_STREAM_CHUNK_TIMEOUT = 3e4;
|
|
2556
|
+
var DEFAULT_MAX_TOOL_CALL_DEPTH = 5;
|
|
2557
|
+
var DEFAULT_MAX_TOOL_RESULT_SIZE = 1e4;
|
|
2126
2558
|
var DEFAULT_SESSION_CONTEXT_MAX_CHARS = 4e3;
|
|
2127
2559
|
var DEFAULT_SESSION_CONTEXT_MAX_ITEMS = 10;
|
|
2128
2560
|
var SESSION_CONTEXT_ITEM_MAX_CHARS = 500;
|
|
@@ -2249,6 +2681,12 @@ var useChatUI = (options) => {
|
|
|
2249
2681
|
onSendMessage,
|
|
2250
2682
|
onSessionChange,
|
|
2251
2683
|
onError,
|
|
2684
|
+
streamChunkTimeout = DEFAULT_STREAM_CHUNK_TIMEOUT,
|
|
2685
|
+
retry: retryConfig,
|
|
2686
|
+
onAbort,
|
|
2687
|
+
onTokenUsage,
|
|
2688
|
+
maxToolCallDepth = DEFAULT_MAX_TOOL_CALL_DEPTH,
|
|
2689
|
+
maxToolResultSize = DEFAULT_MAX_TOOL_RESULT_SIZE,
|
|
2252
2690
|
onTitleChange,
|
|
2253
2691
|
generateTitle: generateTitleCallback,
|
|
2254
2692
|
// Memory options
|
|
@@ -2315,8 +2753,8 @@ var useChatUI = (options) => {
|
|
|
2315
2753
|
const [input, setInput] = useState6("");
|
|
2316
2754
|
const [loadingSessionIds, setLoadingSessionIds] = useState6(/* @__PURE__ */ new Set());
|
|
2317
2755
|
const isLoading = currentSessionId !== null && loadingSessionIds.has(currentSessionId);
|
|
2318
|
-
const addLoadingSession =
|
|
2319
|
-
const removeLoadingSession =
|
|
2756
|
+
const addLoadingSession = useCallback8((id) => setLoadingSessionIds((prev) => new Set(prev).add(id)), []);
|
|
2757
|
+
const removeLoadingSession = useCallback8((id) => setLoadingSessionIds((prev) => {
|
|
2320
2758
|
const next = new Set(prev);
|
|
2321
2759
|
next.delete(id);
|
|
2322
2760
|
return next;
|
|
@@ -2343,41 +2781,45 @@ var useChatUI = (options) => {
|
|
|
2343
2781
|
const [deepResearchProgress, setDeepResearchProgress] = useState6(
|
|
2344
2782
|
null
|
|
2345
2783
|
);
|
|
2346
|
-
const sessionsRef =
|
|
2784
|
+
const sessionsRef = useRef7(sessions);
|
|
2347
2785
|
useEffect4(() => {
|
|
2348
2786
|
sessionsRef.current = sessions;
|
|
2349
2787
|
}, [sessions]);
|
|
2350
|
-
const modelsRef =
|
|
2788
|
+
const modelsRef = useRef7(models);
|
|
2351
2789
|
useEffect4(() => {
|
|
2352
2790
|
modelsRef.current = models;
|
|
2353
2791
|
}, [models]);
|
|
2354
|
-
const onSendMessageRef =
|
|
2355
|
-
const onResponseHeadersRef =
|
|
2356
|
-
const onSessionChangeRef =
|
|
2357
|
-
const onErrorRef =
|
|
2358
|
-
const
|
|
2359
|
-
const
|
|
2360
|
-
const
|
|
2361
|
-
const
|
|
2362
|
-
const
|
|
2363
|
-
const
|
|
2364
|
-
const
|
|
2365
|
-
const
|
|
2366
|
-
const
|
|
2367
|
-
const
|
|
2368
|
-
const
|
|
2369
|
-
const
|
|
2370
|
-
const
|
|
2371
|
-
const
|
|
2372
|
-
const
|
|
2373
|
-
const
|
|
2374
|
-
const
|
|
2375
|
-
const
|
|
2792
|
+
const onSendMessageRef = useRef7(onSendMessage);
|
|
2793
|
+
const onResponseHeadersRef = useRef7(options.onResponseHeaders);
|
|
2794
|
+
const onSessionChangeRef = useRef7(onSessionChange);
|
|
2795
|
+
const onErrorRef = useRef7(onError);
|
|
2796
|
+
const onAbortRef = useRef7(onAbort);
|
|
2797
|
+
const onTokenUsageRef = useRef7(onTokenUsage);
|
|
2798
|
+
const onTitleChangeRef = useRef7(onTitleChange);
|
|
2799
|
+
const generateTitleRef = useRef7(generateTitleCallback);
|
|
2800
|
+
const onPersonalizationChangeRef = useRef7(options.onPersonalizationChange);
|
|
2801
|
+
const onPersonalizationSaveRef = useRef7(options.onPersonalizationSave);
|
|
2802
|
+
const onLoadSessionsRef = useRef7(onLoadSessions);
|
|
2803
|
+
const onCreateSessionRef = useRef7(onCreateSession);
|
|
2804
|
+
const onLoadSessionRef = useRef7(onLoadSession);
|
|
2805
|
+
const onDeleteSessionCallbackRef = useRef7(onDeleteSessionCallback);
|
|
2806
|
+
const onUpdateSessionTitleRef = useRef7(onUpdateSessionTitle);
|
|
2807
|
+
const onSaveMessagesRef = useRef7(onSaveMessages);
|
|
2808
|
+
const onToolCallRef = useRef7(onToolCall);
|
|
2809
|
+
const onSkillCompleteRef = useRef7(onSkillComplete);
|
|
2810
|
+
const onSessionContextChangeRef = useRef7(onSessionContextChange);
|
|
2811
|
+
const onLoadModelsRef = useRef7(onLoadModels);
|
|
2812
|
+
const onUploadImageRef = useRef7(onUploadImage);
|
|
2813
|
+
const onImageErrorRef = useRef7(onImageError);
|
|
2814
|
+
const fileUploaderRef = useRef7(fileUploader);
|
|
2815
|
+
const globalMemoryRef = useRef7(null);
|
|
2376
2816
|
useEffect4(() => {
|
|
2377
2817
|
onSendMessageRef.current = onSendMessage;
|
|
2378
2818
|
onResponseHeadersRef.current = options.onResponseHeaders;
|
|
2379
2819
|
onSessionChangeRef.current = onSessionChange;
|
|
2380
2820
|
onErrorRef.current = onError;
|
|
2821
|
+
onAbortRef.current = onAbort;
|
|
2822
|
+
onTokenUsageRef.current = onTokenUsage;
|
|
2381
2823
|
onTitleChangeRef.current = onTitleChange;
|
|
2382
2824
|
generateTitleRef.current = generateTitleCallback;
|
|
2383
2825
|
onPersonalizationChangeRef.current = options.onPersonalizationChange;
|
|
@@ -2396,15 +2838,19 @@ var useChatUI = (options) => {
|
|
|
2396
2838
|
fileUploaderRef.current = fileUploader;
|
|
2397
2839
|
onLoadModelsRef.current = onLoadModels;
|
|
2398
2840
|
});
|
|
2399
|
-
const abortControllersRef =
|
|
2400
|
-
const
|
|
2401
|
-
const
|
|
2402
|
-
const
|
|
2403
|
-
const
|
|
2404
|
-
const
|
|
2405
|
-
const
|
|
2406
|
-
|
|
2407
|
-
|
|
2841
|
+
const abortControllersRef = useRef7(/* @__PURE__ */ new Map());
|
|
2842
|
+
const { streamResponse } = useStreamingFetch({ chunkTimeout: streamChunkTimeout });
|
|
2843
|
+
const pendingInitialLoadRef = useRef7(null);
|
|
2844
|
+
const skipNextPollParsingRef = useRef7(false);
|
|
2845
|
+
const toolCallDepthRef = useRef7(0);
|
|
2846
|
+
const skipNextSkillParsingRef = useRef7(false);
|
|
2847
|
+
const sendMessageForChecklistRef = useRef7(
|
|
2848
|
+
async () => {
|
|
2849
|
+
console.warn("[ChatUI] sendMessageForChecklistRef called before sendMessage initialized");
|
|
2850
|
+
}
|
|
2851
|
+
);
|
|
2852
|
+
const pendingAttachmentDataRef = useRef7(null);
|
|
2853
|
+
const lastExtractionMsgCountRef = useRef7(0);
|
|
2408
2854
|
const resolveChecklistRefImage = (item, checklistMessageId, sessionId) => {
|
|
2409
2855
|
const session = sessionsRef.current.find((s) => s.id === sessionId);
|
|
2410
2856
|
if (!session) return null;
|
|
@@ -2471,7 +2917,7 @@ ${hints.join(" ")}` : "";
|
|
|
2471
2917
|
return `${stepLabel}
|
|
2472
2918
|
${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \uB9C8\uC138\uC694.`;
|
|
2473
2919
|
};
|
|
2474
|
-
const memoryOptions =
|
|
2920
|
+
const memoryOptions = useMemo4(
|
|
2475
2921
|
() => ({
|
|
2476
2922
|
storageType: globalMemoryConfig?.storageType || "localStorage",
|
|
2477
2923
|
storageKey: globalMemoryConfig?.localStorage?.key || `${storageKey}_memory`,
|
|
@@ -2485,11 +2931,11 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
|
|
|
2485
2931
|
const globalMemoryRaw = useGlobalMemory(memoryOptions);
|
|
2486
2932
|
const globalMemory = useGlobalMemoryEnabled ? globalMemoryRaw : null;
|
|
2487
2933
|
globalMemoryRef.current = globalMemory;
|
|
2488
|
-
const stableToolCall =
|
|
2934
|
+
const stableToolCall = useCallback8(
|
|
2489
2935
|
(name, params) => onToolCallRef.current(name, params),
|
|
2490
2936
|
[]
|
|
2491
2937
|
);
|
|
2492
|
-
const mergedSkills =
|
|
2938
|
+
const mergedSkills = useMemo4(() => {
|
|
2493
2939
|
if (!tools || !onToolCall) return skills || {};
|
|
2494
2940
|
const toolSkills = convertToolsToSkills(tools, stableToolCall);
|
|
2495
2941
|
return { ...skills || {}, ...toolSkills };
|
|
@@ -2527,7 +2973,7 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
|
|
|
2527
2973
|
});
|
|
2528
2974
|
const [projectSettingsOpen, setProjectSettingsOpen] = useState6(false);
|
|
2529
2975
|
const projectMemoryKey = enableProjects && projectHook.currentProjectId ? `${storageKey}_project_memory_${projectHook.currentProjectId}` : `${storageKey}_project_memory_none`;
|
|
2530
|
-
const projectMemoryOptions =
|
|
2976
|
+
const projectMemoryOptions = useMemo4(
|
|
2531
2977
|
() => ({
|
|
2532
2978
|
storageType: globalMemoryConfig?.storageType || "localStorage",
|
|
2533
2979
|
storageKey: projectMemoryKey,
|
|
@@ -2539,7 +2985,7 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
|
|
|
2539
2985
|
);
|
|
2540
2986
|
const projectMemoryRaw = useGlobalMemory(projectMemoryOptions);
|
|
2541
2987
|
const projectMemory = enableProjects ? projectMemoryRaw : null;
|
|
2542
|
-
const unwrapResponseHeaders =
|
|
2988
|
+
const unwrapResponseHeaders = useCallback8((result) => {
|
|
2543
2989
|
if (typeof result === "object" && result !== null && "response" in result && "headers" in result) {
|
|
2544
2990
|
const wrapped = result;
|
|
2545
2991
|
onResponseHeadersRef.current?.(wrapped.headers);
|
|
@@ -2547,7 +2993,7 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
|
|
|
2547
2993
|
}
|
|
2548
2994
|
return result;
|
|
2549
2995
|
}, []);
|
|
2550
|
-
const emitFetchHeaders =
|
|
2996
|
+
const emitFetchHeaders = useCallback8((response) => {
|
|
2551
2997
|
if (onResponseHeadersRef.current && response.headers) {
|
|
2552
2998
|
const headerMap = {};
|
|
2553
2999
|
response.headers.forEach((v, k) => {
|
|
@@ -2556,7 +3002,7 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
|
|
|
2556
3002
|
onResponseHeadersRef.current(headerMap);
|
|
2557
3003
|
}
|
|
2558
3004
|
}, []);
|
|
2559
|
-
const callInternalLLM =
|
|
3005
|
+
const callInternalLLM = useCallback8(async (prompt, model) => {
|
|
2560
3006
|
if (onSendMessageRef.current) {
|
|
2561
3007
|
const modelConfig = modelsRef.current.find((m) => m.id === model);
|
|
2562
3008
|
const provider = modelConfig?.provider || "ollama";
|
|
@@ -2594,7 +3040,7 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
|
|
|
2594
3040
|
const currentSession = sessions.find((s) => s.id === currentSessionId) || null;
|
|
2595
3041
|
const messages = currentSession?.messages.filter((m) => !m.hidden) || [];
|
|
2596
3042
|
const compressionState = currentSession?.compressionState || null;
|
|
2597
|
-
const visibleSessions =
|
|
3043
|
+
const visibleSessions = useMemo4(() => {
|
|
2598
3044
|
if (!enableProjects || !projectHook.currentProjectId) return sessions;
|
|
2599
3045
|
return sessions.filter((s) => s.projectId === projectHook.currentProjectId);
|
|
2600
3046
|
}, [sessions, enableProjects, projectHook.currentProjectId]);
|
|
@@ -2689,7 +3135,7 @@ ${focusHint}${hintLine} checklist \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \u
|
|
|
2689
3135
|
useEffect4(() => {
|
|
2690
3136
|
onSessionChangeRef.current?.(currentSession);
|
|
2691
3137
|
}, [currentSession]);
|
|
2692
|
-
const buildSystemPrompt =
|
|
3138
|
+
const buildSystemPrompt = useCallback8((session) => {
|
|
2693
3139
|
const parts = [];
|
|
2694
3140
|
const { userProfile, responseStyle, language } = personalization;
|
|
2695
3141
|
const identityName = assistantName || "AI \uC5B4\uC2DC\uC2A4\uD134\uD2B8";
|
|
@@ -3048,7 +3494,7 @@ AI (\uD655\uC815):
|
|
|
3048
3494
|
}
|
|
3049
3495
|
return parts.length > 0 ? parts.join("\n") : "";
|
|
3050
3496
|
}, [personalization, globalMemory, useGlobalMemoryEnabled, enablePoll, enableChecklist, buildSkillsPrompt, enableProjects, projectHook.currentProject, projectMemory, resolvedSkills, assistantName, onBuildSystemPrompt, compactSystemPrompt, selectedModel]);
|
|
3051
|
-
const promoteToSessionContext =
|
|
3497
|
+
const promoteToSessionContext = useCallback8((sessionId, skillName, content, metadata, label) => {
|
|
3052
3498
|
const item = createSessionContextItem(skillName, content, metadata, label);
|
|
3053
3499
|
setSessions(
|
|
3054
3500
|
(prev) => prev.map((s) => {
|
|
@@ -3059,7 +3505,7 @@ AI (\uD655\uC815):
|
|
|
3059
3505
|
})
|
|
3060
3506
|
);
|
|
3061
3507
|
}, []);
|
|
3062
|
-
const compressContext =
|
|
3508
|
+
const compressContext = useCallback8(async (messagesToCompress, model) => {
|
|
3063
3509
|
const conversationText = messagesToCompress.map((m) => `${m.role === "user" ? "\uC0AC\uC6A9\uC790" : "AI"}: ${m.content}`).join("\n\n");
|
|
3064
3510
|
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.
|
|
3065
3511
|
\uC911\uC694\uD55C \uACB0\uC815\uC0AC\uD56D, \uC0AC\uC6A9\uC790 \uC694\uAD6C\uC0AC\uD56D, \uB9E5\uB77D \uC815\uBCF4\uB97C \uBCF4\uC874\uD558\uC138\uC694.
|
|
@@ -3074,7 +3520,7 @@ ${conversationText}
|
|
|
3074
3520
|
return "";
|
|
3075
3521
|
}
|
|
3076
3522
|
}, [callInternalLLM]);
|
|
3077
|
-
const incrementalCompressContext =
|
|
3523
|
+
const incrementalCompressContext = useCallback8(
|
|
3078
3524
|
async (existingSummary, newMessages, model) => {
|
|
3079
3525
|
const newConversation = newMessages.map((m) => `${m.role === "user" ? "\uC0AC\uC6A9\uC790" : "AI"}: ${m.content}`).join("\n\n");
|
|
3080
3526
|
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.
|
|
@@ -3101,10 +3547,10 @@ ${newConversation}
|
|
|
3101
3547
|
},
|
|
3102
3548
|
[callInternalLLM]
|
|
3103
3549
|
);
|
|
3104
|
-
const estimateTokens =
|
|
3550
|
+
const estimateTokens = useCallback8((messages2) => {
|
|
3105
3551
|
return messages2.reduce((sum, m) => sum + Math.ceil(m.content.length / 4), 0);
|
|
3106
3552
|
}, []);
|
|
3107
|
-
const newSession =
|
|
3553
|
+
const newSession = useCallback8(async () => {
|
|
3108
3554
|
const projectId = enableProjects ? projectHook.currentProjectId || DEFAULT_PROJECT_ID : void 0;
|
|
3109
3555
|
if (useExternalStorage && onCreateSessionRef.current) {
|
|
3110
3556
|
setIsSessionLoading(true);
|
|
@@ -3142,7 +3588,7 @@ ${newConversation}
|
|
|
3142
3588
|
setSessions((prev) => [newSess, ...prev]);
|
|
3143
3589
|
setCurrentSessionId(newSess.id);
|
|
3144
3590
|
}, [selectedModel, useExternalStorage, enableProjects, projectHook.currentProjectId]);
|
|
3145
|
-
const selectSession =
|
|
3591
|
+
const selectSession = useCallback8(async (id) => {
|
|
3146
3592
|
if (useExternalStorage && onLoadSessionRef.current) {
|
|
3147
3593
|
setIsSessionLoading(true);
|
|
3148
3594
|
try {
|
|
@@ -3252,7 +3698,7 @@ ${newConversation}
|
|
|
3252
3698
|
pendingInitialLoadRef.current = null;
|
|
3253
3699
|
selectSession(id);
|
|
3254
3700
|
}, [currentSessionId, selectSession, useExternalStorage]);
|
|
3255
|
-
const deleteSession =
|
|
3701
|
+
const deleteSession = useCallback8(async (id) => {
|
|
3256
3702
|
if (useExternalStorage && onDeleteSessionCallbackRef.current) {
|
|
3257
3703
|
try {
|
|
3258
3704
|
await onDeleteSessionCallbackRef.current(id);
|
|
@@ -3280,7 +3726,7 @@ ${newConversation}
|
|
|
3280
3726
|
return filtered;
|
|
3281
3727
|
});
|
|
3282
3728
|
}, [currentSessionId, storageKey, useExternalStorage]);
|
|
3283
|
-
const renameSession =
|
|
3729
|
+
const renameSession = useCallback8(async (id, newTitle) => {
|
|
3284
3730
|
if (!newTitle.trim()) return;
|
|
3285
3731
|
if (useExternalStorage && onUpdateSessionTitleRef.current) {
|
|
3286
3732
|
try {
|
|
@@ -3303,7 +3749,7 @@ ${newConversation}
|
|
|
3303
3749
|
);
|
|
3304
3750
|
onTitleChangeRef.current?.(id, newTitle.trim());
|
|
3305
3751
|
}, [useExternalStorage]);
|
|
3306
|
-
const setModel =
|
|
3752
|
+
const setModel = useCallback8((model) => {
|
|
3307
3753
|
setSelectedModel(model);
|
|
3308
3754
|
if (currentSessionId) {
|
|
3309
3755
|
setSessions(
|
|
@@ -3311,10 +3757,10 @@ ${newConversation}
|
|
|
3311
3757
|
);
|
|
3312
3758
|
}
|
|
3313
3759
|
}, [currentSessionId]);
|
|
3314
|
-
const toggleSidebar =
|
|
3315
|
-
const openSettings =
|
|
3316
|
-
const closeSettings =
|
|
3317
|
-
const copyMessage =
|
|
3760
|
+
const toggleSidebar = useCallback8(() => setSidebarOpen((prev) => !prev), []);
|
|
3761
|
+
const openSettings = useCallback8(() => setSettingsOpen(true), []);
|
|
3762
|
+
const closeSettings = useCallback8(() => setSettingsOpen(false), []);
|
|
3763
|
+
const copyMessage = useCallback8((content, id) => {
|
|
3318
3764
|
if (typeof navigator !== "undefined") {
|
|
3319
3765
|
navigator.clipboard.writeText(content).then(() => {
|
|
3320
3766
|
setCopiedMessageId(id);
|
|
@@ -3322,30 +3768,30 @@ ${newConversation}
|
|
|
3322
3768
|
});
|
|
3323
3769
|
}
|
|
3324
3770
|
}, []);
|
|
3325
|
-
const startEdit =
|
|
3771
|
+
const startEdit = useCallback8((message) => {
|
|
3326
3772
|
if (message.role === "user") {
|
|
3327
3773
|
setEditingMessageId(message.id);
|
|
3328
3774
|
}
|
|
3329
3775
|
}, []);
|
|
3330
|
-
const cancelEdit =
|
|
3776
|
+
const cancelEdit = useCallback8(() => {
|
|
3331
3777
|
setEditingMessageId(null);
|
|
3332
3778
|
}, []);
|
|
3333
|
-
const stopGeneration =
|
|
3779
|
+
const stopGeneration = useCallback8(() => {
|
|
3334
3780
|
if (currentSessionId) {
|
|
3335
3781
|
abortControllersRef.current.get(currentSessionId)?.abort();
|
|
3336
3782
|
}
|
|
3337
3783
|
}, [currentSessionId]);
|
|
3338
|
-
const updatePersonalization =
|
|
3784
|
+
const updatePersonalization = useCallback8((config) => {
|
|
3339
3785
|
setPersonalization((prev) => {
|
|
3340
3786
|
const next = { ...prev, ...config };
|
|
3341
3787
|
onPersonalizationChangeRef.current?.(next);
|
|
3342
3788
|
return next;
|
|
3343
3789
|
});
|
|
3344
3790
|
}, []);
|
|
3345
|
-
const savePersonalization =
|
|
3791
|
+
const savePersonalization = useCallback8(() => {
|
|
3346
3792
|
onPersonalizationSaveRef.current?.(personalization);
|
|
3347
3793
|
}, [personalization]);
|
|
3348
|
-
const addAttachments =
|
|
3794
|
+
const addAttachments = useCallback8((files) => {
|
|
3349
3795
|
const newAttachments = files.map((file) => {
|
|
3350
3796
|
const isImage = file.type.startsWith("image/");
|
|
3351
3797
|
return {
|
|
@@ -3360,7 +3806,7 @@ ${newConversation}
|
|
|
3360
3806
|
});
|
|
3361
3807
|
setAttachments((prev) => [...prev, ...newAttachments]);
|
|
3362
3808
|
}, []);
|
|
3363
|
-
const removeAttachment =
|
|
3809
|
+
const removeAttachment = useCallback8((id) => {
|
|
3364
3810
|
setAttachments((prev) => {
|
|
3365
3811
|
const target = prev.find((a) => a.id === id);
|
|
3366
3812
|
if (target?.previewUrl) {
|
|
@@ -3369,17 +3815,17 @@ ${newConversation}
|
|
|
3369
3815
|
return prev.filter((a) => a.id !== id);
|
|
3370
3816
|
});
|
|
3371
3817
|
}, []);
|
|
3372
|
-
const toggleDeepResearchMode =
|
|
3818
|
+
const toggleDeepResearchMode = useCallback8(() => {
|
|
3373
3819
|
setIsDeepResearchMode((prev) => !prev);
|
|
3374
3820
|
}, []);
|
|
3375
|
-
const trackBehavior =
|
|
3821
|
+
const trackBehavior = useCallback8(async (key, updater) => {
|
|
3376
3822
|
if (!globalMemory) return;
|
|
3377
3823
|
const fullKey = `behavior.${key}`;
|
|
3378
3824
|
const prev = globalMemory.get(fullKey) ?? {};
|
|
3379
3825
|
const next = updater(prev);
|
|
3380
3826
|
await globalMemory.set(fullKey, next, { category: "behavior" });
|
|
3381
3827
|
}, [globalMemory]);
|
|
3382
|
-
const trackMessageLength =
|
|
3828
|
+
const trackMessageLength = useCallback8((length) => {
|
|
3383
3829
|
trackBehavior("messageLength", (prev) => {
|
|
3384
3830
|
const samples = (prev.messageLengthSamples || []).slice(-19);
|
|
3385
3831
|
samples.push(length);
|
|
@@ -3387,39 +3833,69 @@ ${newConversation}
|
|
|
3387
3833
|
return { ...prev, messageLengthSamples: samples, avgMessageLength: avg };
|
|
3388
3834
|
});
|
|
3389
3835
|
}, [trackBehavior]);
|
|
3390
|
-
const trackSkillUsage =
|
|
3836
|
+
const trackSkillUsage = useCallback8((skillName) => {
|
|
3391
3837
|
trackBehavior("skillUsage", (prev) => ({
|
|
3392
3838
|
...prev,
|
|
3393
3839
|
[skillName]: (prev[skillName] || 0) + 1
|
|
3394
3840
|
}));
|
|
3395
3841
|
}, [trackBehavior]);
|
|
3396
|
-
const trackChecklistSkip =
|
|
3842
|
+
const trackChecklistSkip = useCallback8(() => {
|
|
3397
3843
|
trackBehavior("checklistSkipRate", (prev) => ({
|
|
3398
3844
|
total: (prev.total || 0) + 1,
|
|
3399
3845
|
skipped: (prev.skipped || 0) + 1
|
|
3400
3846
|
}));
|
|
3401
3847
|
}, [trackBehavior]);
|
|
3402
|
-
const trackChecklistComplete =
|
|
3848
|
+
const trackChecklistComplete = useCallback8(() => {
|
|
3403
3849
|
trackBehavior("checklistSkipRate", (prev) => ({
|
|
3404
3850
|
total: (prev.total || 0) + 1,
|
|
3405
3851
|
skipped: prev.skipped || 0
|
|
3406
3852
|
}));
|
|
3407
3853
|
}, [trackBehavior]);
|
|
3408
|
-
const
|
|
3854
|
+
const {
|
|
3855
|
+
activeChecklistRef,
|
|
3856
|
+
pendingChecklistRef,
|
|
3857
|
+
skipNextChecklistParsingRef,
|
|
3858
|
+
handleChecklistStart,
|
|
3859
|
+
handleChecklistAbort,
|
|
3860
|
+
handleChecklistRetry,
|
|
3861
|
+
handleChecklistSkip
|
|
3862
|
+
} = useChecklist({
|
|
3863
|
+
sessionsRef,
|
|
3864
|
+
sessions,
|
|
3865
|
+
setSessions,
|
|
3866
|
+
sendMessage: (...args) => sendMessageForChecklistRef.current(...args),
|
|
3867
|
+
abortControllers: abortControllersRef,
|
|
3868
|
+
removeLoadingSession,
|
|
3869
|
+
pendingAttachmentDataRef,
|
|
3870
|
+
trackChecklistSkip,
|
|
3871
|
+
resolveChecklistRefImage,
|
|
3872
|
+
buildChecklistStepPrompt
|
|
3873
|
+
});
|
|
3874
|
+
const trackRegenerate = useCallback8(() => {
|
|
3409
3875
|
trackBehavior("regenerateRate", (prev) => ({
|
|
3410
3876
|
...prev,
|
|
3411
3877
|
regenerated: (prev.regenerated || 0) + 1
|
|
3412
3878
|
}));
|
|
3413
3879
|
}, [trackBehavior]);
|
|
3414
|
-
const trackMessageSent =
|
|
3880
|
+
const trackMessageSent = useCallback8(() => {
|
|
3415
3881
|
trackBehavior("regenerateRate", (prev) => ({
|
|
3416
3882
|
total: (prev.total || 0) + 1,
|
|
3417
3883
|
regenerated: prev.regenerated || 0
|
|
3418
3884
|
}));
|
|
3419
3885
|
}, [trackBehavior]);
|
|
3420
|
-
const sendMessage =
|
|
3886
|
+
const sendMessage = useCallback8(async (content, options2) => {
|
|
3421
3887
|
const messageContent = content || input;
|
|
3422
3888
|
if (!messageContent.trim() && attachments.length === 0 || isLoading) return;
|
|
3889
|
+
if (options2?.hiddenUserMessage) {
|
|
3890
|
+
toolCallDepthRef.current += 1;
|
|
3891
|
+
if (toolCallDepthRef.current > maxToolCallDepth) {
|
|
3892
|
+
toolCallDepthRef.current = 0;
|
|
3893
|
+
onErrorRef.current?.(new Error(`\uB3C4\uAD6C \uD638\uCD9C \uAE4A\uC774 \uC81C\uD55C \uCD08\uACFC (\uCD5C\uB300 ${maxToolCallDepth}\uD68C)`));
|
|
3894
|
+
return;
|
|
3895
|
+
}
|
|
3896
|
+
} else {
|
|
3897
|
+
toolCallDepthRef.current = 0;
|
|
3898
|
+
}
|
|
3423
3899
|
let sessionId = currentSessionId;
|
|
3424
3900
|
if (!sessionId) {
|
|
3425
3901
|
if (useExternalStorage && onCreateSessionRef.current) {
|
|
@@ -3705,10 +4181,11 @@ ${finalContent}`;
|
|
|
3705
4181
|
return;
|
|
3706
4182
|
}
|
|
3707
4183
|
abortControllersRef.current.set(capturedSessionId, new AbortController());
|
|
4184
|
+
let accumulatedContent = "";
|
|
4185
|
+
let lastUsage = null;
|
|
3708
4186
|
try {
|
|
3709
4187
|
const shouldSkipSkillParsing = skipNextSkillParsingRef.current;
|
|
3710
4188
|
skipNextSkillParsingRef.current = false;
|
|
3711
|
-
let accumulatedContent = "";
|
|
3712
4189
|
let checklistStepImageUrl = null;
|
|
3713
4190
|
let messagesToSend = [...existingMessages, userMessage];
|
|
3714
4191
|
const recompressionThreshold = DEFAULT_RECOMPRESSION_THRESHOLD;
|
|
@@ -3914,119 +4391,71 @@ ${attachmentContext}
|
|
|
3914
4391
|
emitFetchHeaders(response);
|
|
3915
4392
|
}
|
|
3916
4393
|
if (response) {
|
|
3917
|
-
if (!response.ok) throw new Error("API error");
|
|
3918
|
-
const reader = response.body?.getReader();
|
|
3919
|
-
if (!reader) throw new Error("No reader");
|
|
3920
|
-
const decoder = new TextDecoder();
|
|
3921
|
-
let buffer = "";
|
|
3922
4394
|
let skillTagDetected = false;
|
|
3923
4395
|
let checklistTagDetected = false;
|
|
3924
4396
|
let toolCallAcc = null;
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
4397
|
+
const streamResult = await streamResponse(response, (chunk) => {
|
|
4398
|
+
if (useNativeTools && chunk.delta?.tool_calls) {
|
|
4399
|
+
toolCallAcc = accumulateToolCallDelta(toolCallAcc, chunk.delta);
|
|
4400
|
+
}
|
|
4401
|
+
if (useNativeTools && chunk.finishReason === "tool_calls" && toolCallAcc) {
|
|
4402
|
+
accumulatedContent += toolCallToSkillUseXml(toolCallAcc);
|
|
4403
|
+
skillTagDetected = true;
|
|
4404
|
+
return "break";
|
|
4405
|
+
}
|
|
4406
|
+
const { content: content2, thinking } = chunk;
|
|
4407
|
+
if (content2 || thinking) {
|
|
4408
|
+
if (content2) accumulatedContent += content2;
|
|
4409
|
+
if (!shouldSkipSkillParsing && accumulatedContent.includes("</skill_use>")) {
|
|
4410
|
+
const endIdx = accumulatedContent.indexOf("</skill_use>");
|
|
4411
|
+
accumulatedContent = accumulatedContent.substring(0, endIdx + "</skill_use>".length);
|
|
4412
|
+
skillTagDetected = true;
|
|
3937
4413
|
}
|
|
3938
|
-
|
|
3939
|
-
const
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
if (useNativeTools && delta?.tool_calls) {
|
|
3943
|
-
toolCallAcc = accumulateToolCallDelta(toolCallAcc, delta);
|
|
3944
|
-
}
|
|
3945
|
-
if (useNativeTools && finishReason === "tool_calls" && toolCallAcc) {
|
|
3946
|
-
accumulatedContent += toolCallToSkillUseXml(toolCallAcc);
|
|
3947
|
-
skillTagDetected = true;
|
|
3948
|
-
break;
|
|
3949
|
-
}
|
|
3950
|
-
const content2 = delta?.content || parsed.message?.content || parsed.content || parsed.text || "";
|
|
3951
|
-
const thinking = parsed.message?.thinking || "";
|
|
3952
|
-
if (content2 || thinking) {
|
|
3953
|
-
if (content2) accumulatedContent += content2;
|
|
3954
|
-
if (!shouldSkipSkillParsing && accumulatedContent.includes("</skill_use>")) {
|
|
3955
|
-
const endIdx = accumulatedContent.indexOf("</skill_use>");
|
|
3956
|
-
accumulatedContent = accumulatedContent.substring(0, endIdx + "</skill_use>".length);
|
|
3957
|
-
skillTagDetected = true;
|
|
3958
|
-
}
|
|
3959
|
-
if (!skipNextChecklistParsingRef.current && accumulatedContent.includes("</checklist>")) {
|
|
3960
|
-
const endIdx = accumulatedContent.indexOf("</checklist>");
|
|
3961
|
-
accumulatedContent = accumulatedContent.substring(0, endIdx + "</checklist>".length);
|
|
3962
|
-
checklistTagDetected = true;
|
|
3963
|
-
}
|
|
3964
|
-
const displayContent = skillTagDetected ? accumulatedContent : null;
|
|
3965
|
-
setSessions(
|
|
3966
|
-
(prev) => prev.map((s) => {
|
|
3967
|
-
if (s.id === capturedSessionId) {
|
|
3968
|
-
return {
|
|
3969
|
-
...s,
|
|
3970
|
-
messages: s.messages.map((m) => {
|
|
3971
|
-
if (m.id !== assistantMessageId) return m;
|
|
3972
|
-
if (displayContent) {
|
|
3973
|
-
return { ...m, content: displayContent };
|
|
3974
|
-
}
|
|
3975
|
-
let newContent = m.content;
|
|
3976
|
-
if (thinking) {
|
|
3977
|
-
if (!newContent.includes("<thinking>")) {
|
|
3978
|
-
newContent = "<thinking>" + thinking;
|
|
3979
|
-
} else if (!newContent.includes("</thinking>")) {
|
|
3980
|
-
newContent += thinking;
|
|
3981
|
-
}
|
|
3982
|
-
}
|
|
3983
|
-
if (content2) {
|
|
3984
|
-
if (newContent.includes("<thinking>") && !newContent.includes("</thinking>")) {
|
|
3985
|
-
newContent += "</thinking>\n\n";
|
|
3986
|
-
}
|
|
3987
|
-
newContent += content2;
|
|
3988
|
-
}
|
|
3989
|
-
return { ...m, content: newContent };
|
|
3990
|
-
})
|
|
3991
|
-
};
|
|
3992
|
-
}
|
|
3993
|
-
return s;
|
|
3994
|
-
})
|
|
3995
|
-
);
|
|
3996
|
-
if (skillTagDetected || checklistTagDetected) break;
|
|
3997
|
-
}
|
|
3998
|
-
} catch {
|
|
4414
|
+
if (!skipNextChecklistParsingRef.current && accumulatedContent.includes("</checklist>")) {
|
|
4415
|
+
const endIdx = accumulatedContent.indexOf("</checklist>");
|
|
4416
|
+
accumulatedContent = accumulatedContent.substring(0, endIdx + "</checklist>".length);
|
|
4417
|
+
checklistTagDetected = true;
|
|
3999
4418
|
}
|
|
4419
|
+
const displayContent = skillTagDetected ? accumulatedContent : null;
|
|
4420
|
+
setSessions(
|
|
4421
|
+
(prev) => prev.map((s) => {
|
|
4422
|
+
if (s.id === capturedSessionId) {
|
|
4423
|
+
return {
|
|
4424
|
+
...s,
|
|
4425
|
+
messages: s.messages.map((m) => {
|
|
4426
|
+
if (m.id !== assistantMessageId) return m;
|
|
4427
|
+
if (displayContent) {
|
|
4428
|
+
return { ...m, content: displayContent };
|
|
4429
|
+
}
|
|
4430
|
+
let newContent = m.content;
|
|
4431
|
+
if (thinking) {
|
|
4432
|
+
if (!newContent.includes("<thinking>")) {
|
|
4433
|
+
newContent = "<thinking>" + thinking;
|
|
4434
|
+
} else if (!newContent.includes("</thinking>")) {
|
|
4435
|
+
newContent += thinking;
|
|
4436
|
+
}
|
|
4437
|
+
}
|
|
4438
|
+
if (content2) {
|
|
4439
|
+
if (newContent.includes("<thinking>") && !newContent.includes("</thinking>")) {
|
|
4440
|
+
newContent += "</thinking>\n\n";
|
|
4441
|
+
}
|
|
4442
|
+
newContent += content2;
|
|
4443
|
+
}
|
|
4444
|
+
return { ...m, content: newContent };
|
|
4445
|
+
})
|
|
4446
|
+
};
|
|
4447
|
+
}
|
|
4448
|
+
return s;
|
|
4449
|
+
})
|
|
4450
|
+
);
|
|
4451
|
+
if (skillTagDetected || checklistTagDetected) return "break";
|
|
4000
4452
|
}
|
|
4001
|
-
|
|
4002
|
-
|
|
4453
|
+
});
|
|
4454
|
+
lastUsage = streamResult.usage;
|
|
4003
4455
|
if (useNativeTools && toolCallAcc && !skillTagDetected) {
|
|
4004
4456
|
accumulatedContent += toolCallToSkillUseXml(toolCallAcc);
|
|
4005
4457
|
skillTagDetected = true;
|
|
4006
4458
|
}
|
|
4007
|
-
if (buffer.trim()) {
|
|
4008
|
-
try {
|
|
4009
|
-
const parsed = JSON.parse(buffer);
|
|
4010
|
-
const content2 = parsed.message?.content || parsed.content || parsed.text;
|
|
4011
|
-
if (content2) {
|
|
4012
|
-
accumulatedContent += content2;
|
|
4013
|
-
setSessions(
|
|
4014
|
-
(prev) => prev.map((s) => {
|
|
4015
|
-
if (s.id === capturedSessionId) {
|
|
4016
|
-
return {
|
|
4017
|
-
...s,
|
|
4018
|
-
messages: s.messages.map(
|
|
4019
|
-
(m) => m.id === assistantMessageId ? { ...m, content: m.content + content2 } : m
|
|
4020
|
-
)
|
|
4021
|
-
};
|
|
4022
|
-
}
|
|
4023
|
-
return s;
|
|
4024
|
-
})
|
|
4025
|
-
);
|
|
4026
|
-
}
|
|
4027
|
-
} catch {
|
|
4028
|
-
}
|
|
4029
|
-
}
|
|
4030
4459
|
}
|
|
4031
4460
|
const saveMessagesOnEarlyReturn = (overrideContentParts) => {
|
|
4032
4461
|
if (!useExternalStorage || !capturedSessionId) return;
|
|
@@ -4387,9 +4816,10 @@ ${result.content}
|
|
|
4387
4816
|
}
|
|
4388
4817
|
skipNextSkillParsingRef.current = true;
|
|
4389
4818
|
saveMessagesOnEarlyReturn();
|
|
4819
|
+
const truncatedResult = result.content.length > maxToolResultSize ? result.content.substring(0, maxToolResultSize) + "\n\n...(truncated)" : result.content;
|
|
4390
4820
|
const resultPrompt = `\uC2A4\uD0AC "${detectedSkill.name}" \uC2E4\uD589 \uACB0\uACFC:
|
|
4391
4821
|
|
|
4392
|
-
${
|
|
4822
|
+
${truncatedResult}
|
|
4393
4823
|
|
|
4394
4824
|
\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.`;
|
|
4395
4825
|
setTimeout(() => {
|
|
@@ -4715,6 +5145,32 @@ ${stepSummary}
|
|
|
4715
5145
|
};
|
|
4716
5146
|
})
|
|
4717
5147
|
);
|
|
5148
|
+
if (lastUsage && capturedSessionId) {
|
|
5149
|
+
const usage = lastUsage;
|
|
5150
|
+
setSessions(
|
|
5151
|
+
(prev) => prev.map((s) => {
|
|
5152
|
+
if (s.id !== capturedSessionId) return s;
|
|
5153
|
+
const prevStats = s.tokenStats ?? {
|
|
5154
|
+
totalPromptTokens: 0,
|
|
5155
|
+
totalCompletionTokens: 0,
|
|
5156
|
+
totalTokens: 0,
|
|
5157
|
+
messageCount: 0,
|
|
5158
|
+
lastUpdated: 0
|
|
5159
|
+
};
|
|
5160
|
+
return {
|
|
5161
|
+
...s,
|
|
5162
|
+
tokenStats: {
|
|
5163
|
+
totalPromptTokens: prevStats.totalPromptTokens + usage.promptTokens,
|
|
5164
|
+
totalCompletionTokens: prevStats.totalCompletionTokens + usage.completionTokens,
|
|
5165
|
+
totalTokens: prevStats.totalTokens + usage.totalTokens,
|
|
5166
|
+
messageCount: prevStats.messageCount + 1,
|
|
5167
|
+
lastUpdated: Date.now()
|
|
5168
|
+
}
|
|
5169
|
+
};
|
|
5170
|
+
})
|
|
5171
|
+
);
|
|
5172
|
+
onTokenUsageRef.current?.(capturedSessionId, usage);
|
|
5173
|
+
}
|
|
4718
5174
|
if (useExternalStorage && capturedSessionId) {
|
|
4719
5175
|
const assistantContentForSave = accumulatedContent;
|
|
4720
5176
|
if (assistantContentForSave && onSaveMessagesRef.current) {
|
|
@@ -4743,296 +5199,77 @@ ${stepSummary}
|
|
|
4743
5199
|
if (sinceLastExtraction >= 4) {
|
|
4744
5200
|
lastExtractionMsgCountRef.current = currentMsgCount;
|
|
4745
5201
|
const recentForExtraction = [...existingMessages.slice(-6), userMessage].map(
|
|
4746
|
-
(m) => ({ role: m.role, content: m.content })
|
|
4747
|
-
);
|
|
4748
|
-
infoExtraction.extractInfo(recentForExtraction).catch(() => {
|
|
4749
|
-
});
|
|
4750
|
-
}
|
|
4751
|
-
}
|
|
4752
|
-
if (observerConfig) {
|
|
4753
|
-
const newMessages = [userMessage, { id: assistantMessageId, role: "assistant", content: accumulatedContent, timestamp: Date.now() }];
|
|
4754
|
-
observer.processMessages(capturedSessionId, newMessages);
|
|
4755
|
-
}
|
|
4756
|
-
} catch (error) {
|
|
4757
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
4758
|
-
return;
|
|
4759
|
-
}
|
|
4760
|
-
const err = error instanceof Error ? error : new Error("Unknown error");
|
|
4761
|
-
onErrorRef.current?.(err);
|
|
4762
|
-
setSessions(
|
|
4763
|
-
(prev) => prev.map((s) => {
|
|
4764
|
-
if (s.id === capturedSessionId) {
|
|
4765
|
-
return {
|
|
4766
|
-
...s,
|
|
4767
|
-
messages: s.messages.map(
|
|
4768
|
-
(m) => m.id === assistantMessageId ? { ...m, content: "\uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694." } : m
|
|
4769
|
-
)
|
|
4770
|
-
};
|
|
4771
|
-
}
|
|
4772
|
-
return s;
|
|
4773
|
-
})
|
|
4774
|
-
);
|
|
4775
|
-
} finally {
|
|
4776
|
-
removeLoadingSession(capturedSessionId);
|
|
4777
|
-
abortControllersRef.current.delete(capturedSessionId);
|
|
4778
|
-
}
|
|
4779
|
-
}, [
|
|
4780
|
-
input,
|
|
4781
|
-
loadingSessionIds,
|
|
4782
|
-
currentSessionId,
|
|
4783
|
-
sessions,
|
|
4784
|
-
selectedModel,
|
|
4785
|
-
quotedText,
|
|
4786
|
-
selectedAction,
|
|
4787
|
-
apiEndpoint,
|
|
4788
|
-
apiKey,
|
|
4789
|
-
models,
|
|
4790
|
-
contextCompressionThreshold,
|
|
4791
|
-
keepRecentMessages,
|
|
4792
|
-
buildSystemPrompt,
|
|
4793
|
-
compressContext,
|
|
4794
|
-
useExternalStorage,
|
|
4795
|
-
handleSkillCall,
|
|
4796
|
-
resolvedSkills,
|
|
4797
|
-
/** @Todo vibecode - attachments, continueAfterToolResult를 deps에 추가하여 stale closure 방지 */
|
|
4798
|
-
attachments,
|
|
4799
|
-
continueAfterToolResult
|
|
4800
|
-
]);
|
|
4801
|
-
const handleChecklistStart = useCallback6(
|
|
4802
|
-
(messageId) => {
|
|
4803
|
-
const session = sessionsRef.current.find(
|
|
4804
|
-
(s) => s.messages.some((m) => m.id === messageId)
|
|
4805
|
-
);
|
|
4806
|
-
if (!session) return;
|
|
4807
|
-
const message = session.messages.find((m) => m.id === messageId);
|
|
4808
|
-
if (!message?.checklistBlock) return;
|
|
4809
|
-
pendingChecklistRef.current = null;
|
|
4810
|
-
activeChecklistRef.current = {
|
|
4811
|
-
messageId,
|
|
4812
|
-
sessionId: session.id,
|
|
4813
|
-
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 })),
|
|
4814
|
-
currentStep: 0,
|
|
4815
|
-
stepResults: []
|
|
4816
|
-
};
|
|
4817
|
-
setSessions(
|
|
4818
|
-
(prev) => prev.map((s) => {
|
|
4819
|
-
if (s.id !== session.id) return s;
|
|
4820
|
-
return {
|
|
4821
|
-
...s,
|
|
4822
|
-
messages: s.messages.map((m) => {
|
|
4823
|
-
if (m.id !== messageId || !m.checklistBlock) return m;
|
|
4824
|
-
const updatedItems = m.checklistBlock.items.map((it, idx) => ({
|
|
4825
|
-
...it,
|
|
4826
|
-
status: idx === 0 ? "in_progress" : it.status
|
|
4827
|
-
}));
|
|
4828
|
-
return {
|
|
4829
|
-
...m,
|
|
4830
|
-
checklistBlock: { ...m.checklistBlock, items: updatedItems, currentStep: 0 }
|
|
4831
|
-
};
|
|
4832
|
-
})
|
|
4833
|
-
};
|
|
4834
|
-
})
|
|
4835
|
-
);
|
|
4836
|
-
skipNextChecklistParsingRef.current = true;
|
|
4837
|
-
setTimeout(() => {
|
|
4838
|
-
const items = message.checklistBlock.items;
|
|
4839
|
-
const refUrl = resolveChecklistRefImage(items[0], messageId, session.id);
|
|
4840
|
-
if (refUrl) pendingAttachmentDataRef.current = [{ name: "ref_image", mimeType: "image/png", base64: "", size: 0, url: refUrl }];
|
|
4841
|
-
sendMessage(
|
|
4842
|
-
buildChecklistStepPrompt(0, items.length, items[0], true, refUrl),
|
|
4843
|
-
{ hiddenUserMessage: true, isChecklistExecution: true, checklistStep: { index: 0, title: items[0].title } }
|
|
4844
|
-
);
|
|
4845
|
-
}, 100);
|
|
4846
|
-
},
|
|
4847
|
-
[sendMessage]
|
|
4848
|
-
);
|
|
4849
|
-
const handleChecklistAbort = useCallback6(() => {
|
|
4850
|
-
if (!activeChecklistRef.current) return;
|
|
4851
|
-
const checklist = activeChecklistRef.current;
|
|
4852
|
-
const stepIdx = checklist.currentStep;
|
|
4853
|
-
abortControllersRef.current.get(checklist.sessionId)?.abort();
|
|
4854
|
-
setSessions(
|
|
4855
|
-
(prev) => prev.map((s) => {
|
|
4856
|
-
if (s.id !== checklist.sessionId) return s;
|
|
4857
|
-
return {
|
|
4858
|
-
...s,
|
|
4859
|
-
messages: s.messages.map((m) => {
|
|
4860
|
-
if (m.id !== checklist.messageId || !m.checklistBlock) return m;
|
|
4861
|
-
return {
|
|
4862
|
-
...m,
|
|
4863
|
-
checklistBlock: {
|
|
4864
|
-
...m.checklistBlock,
|
|
4865
|
-
items: m.checklistBlock.items.map((it, idx) => ({
|
|
4866
|
-
...it,
|
|
4867
|
-
status: idx === stepIdx ? "error" : it.status
|
|
4868
|
-
}))
|
|
4869
|
-
}
|
|
4870
|
-
};
|
|
4871
|
-
})
|
|
4872
|
-
};
|
|
4873
|
-
})
|
|
4874
|
-
);
|
|
4875
|
-
activeChecklistRef.current = null;
|
|
4876
|
-
removeLoadingSession(checklist.sessionId);
|
|
4877
|
-
}, [removeLoadingSession]);
|
|
4878
|
-
const handleChecklistRetry = useCallback6(
|
|
4879
|
-
(messageId, stepIndex) => {
|
|
4880
|
-
const session = sessionsRef.current.find(
|
|
4881
|
-
(s) => s.messages.some((m) => m.id === messageId)
|
|
4882
|
-
);
|
|
4883
|
-
if (!session) return;
|
|
4884
|
-
const message = session.messages.find((m) => m.id === messageId);
|
|
4885
|
-
if (!message?.checklistBlock) return;
|
|
4886
|
-
activeChecklistRef.current = {
|
|
4887
|
-
messageId,
|
|
4888
|
-
sessionId: session.id,
|
|
4889
|
-
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 })),
|
|
4890
|
-
currentStep: stepIndex,
|
|
4891
|
-
stepResults: message.checklistBlock.items.slice(0, stepIndex).map((it) => it.result || "")
|
|
4892
|
-
};
|
|
4893
|
-
setSessions(
|
|
4894
|
-
(prev) => prev.map((s) => {
|
|
4895
|
-
if (s.id !== session.id) return s;
|
|
4896
|
-
return {
|
|
4897
|
-
...s,
|
|
4898
|
-
messages: s.messages.map((m) => {
|
|
4899
|
-
if (m.id !== messageId || !m.checklistBlock) return m;
|
|
4900
|
-
return {
|
|
4901
|
-
...m,
|
|
4902
|
-
checklistBlock: {
|
|
4903
|
-
...m.checklistBlock,
|
|
4904
|
-
items: m.checklistBlock.items.map((it, idx) => ({
|
|
4905
|
-
...it,
|
|
4906
|
-
status: idx === stepIndex ? "in_progress" : idx > stepIndex ? "pending" : it.status
|
|
4907
|
-
})),
|
|
4908
|
-
currentStep: stepIndex
|
|
4909
|
-
}
|
|
4910
|
-
};
|
|
4911
|
-
})
|
|
4912
|
-
};
|
|
4913
|
-
})
|
|
4914
|
-
);
|
|
4915
|
-
skipNextChecklistParsingRef.current = true;
|
|
4916
|
-
setTimeout(() => {
|
|
4917
|
-
const items = message.checklistBlock.items;
|
|
4918
|
-
const refUrl = resolveChecklistRefImage(items[stepIndex], messageId, session.id);
|
|
4919
|
-
if (refUrl) pendingAttachmentDataRef.current = [{ name: "ref_image", mimeType: "image/png", base64: "", size: 0, url: refUrl }];
|
|
4920
|
-
sendMessage(
|
|
4921
|
-
buildChecklistStepPrompt(stepIndex, items.length, items[stepIndex], stepIndex === 0, refUrl),
|
|
4922
|
-
{ hiddenUserMessage: true, isChecklistExecution: true, checklistStep: { index: stepIndex, title: items[stepIndex].title } }
|
|
4923
|
-
);
|
|
4924
|
-
}, 100);
|
|
4925
|
-
},
|
|
4926
|
-
[sendMessage]
|
|
4927
|
-
);
|
|
4928
|
-
const handleChecklistSkip = useCallback6(
|
|
4929
|
-
(messageId, stepIndex) => {
|
|
4930
|
-
const session = sessionsRef.current.find(
|
|
4931
|
-
(s) => s.messages.some((m) => m.id === messageId)
|
|
4932
|
-
);
|
|
4933
|
-
if (!session) return;
|
|
4934
|
-
const message = session.messages.find((m) => m.id === messageId);
|
|
4935
|
-
if (!message?.checklistBlock) return;
|
|
4936
|
-
trackChecklistSkip();
|
|
4937
|
-
setSessions(
|
|
4938
|
-
(prev) => prev.map((s) => {
|
|
4939
|
-
if (s.id !== session.id) return s;
|
|
4940
|
-
return {
|
|
4941
|
-
...s,
|
|
4942
|
-
messages: s.messages.map((m) => {
|
|
4943
|
-
if (m.id !== messageId || !m.checklistBlock) return m;
|
|
4944
|
-
return {
|
|
4945
|
-
...m,
|
|
4946
|
-
checklistBlock: {
|
|
4947
|
-
...m.checklistBlock,
|
|
4948
|
-
items: m.checklistBlock.items.map((it, idx) => ({
|
|
4949
|
-
...it,
|
|
4950
|
-
status: idx === stepIndex ? "done" : it.status,
|
|
4951
|
-
result: idx === stepIndex ? "(\uAC74\uB108\uB700)" : it.result
|
|
4952
|
-
}))
|
|
4953
|
-
}
|
|
4954
|
-
};
|
|
4955
|
-
})
|
|
4956
|
-
};
|
|
4957
|
-
})
|
|
4958
|
-
);
|
|
4959
|
-
const nextPending = message.checklistBlock.items.findIndex(
|
|
4960
|
-
(it, idx) => idx > stepIndex && (it.status === "pending" || it.status === "error")
|
|
4961
|
-
);
|
|
4962
|
-
if (nextPending >= 0) {
|
|
4963
|
-
activeChecklistRef.current = {
|
|
4964
|
-
messageId,
|
|
4965
|
-
sessionId: session.id,
|
|
4966
|
-
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 })),
|
|
4967
|
-
currentStep: nextPending,
|
|
4968
|
-
stepResults: message.checklistBlock.items.slice(0, nextPending).map((it) => it.result || "(\uAC74\uB108\uB700)")
|
|
4969
|
-
};
|
|
4970
|
-
setSessions(
|
|
4971
|
-
(prev) => prev.map((s) => {
|
|
4972
|
-
if (s.id !== session.id) return s;
|
|
4973
|
-
return {
|
|
4974
|
-
...s,
|
|
4975
|
-
messages: s.messages.map((m) => {
|
|
4976
|
-
if (m.id !== messageId || !m.checklistBlock) return m;
|
|
4977
|
-
return {
|
|
4978
|
-
...m,
|
|
4979
|
-
checklistBlock: {
|
|
4980
|
-
...m.checklistBlock,
|
|
4981
|
-
items: m.checklistBlock.items.map((it, idx) => ({
|
|
4982
|
-
...it,
|
|
4983
|
-
status: idx === nextPending ? "in_progress" : it.status
|
|
4984
|
-
})),
|
|
4985
|
-
currentStep: nextPending
|
|
4986
|
-
}
|
|
4987
|
-
};
|
|
4988
|
-
})
|
|
4989
|
-
};
|
|
4990
|
-
})
|
|
4991
|
-
);
|
|
4992
|
-
skipNextChecklistParsingRef.current = true;
|
|
4993
|
-
setTimeout(() => {
|
|
4994
|
-
const items = message.checklistBlock.items;
|
|
4995
|
-
const refUrl = resolveChecklistRefImage(items[nextPending], messageId, session.id);
|
|
4996
|
-
if (refUrl) pendingAttachmentDataRef.current = [{ name: "ref_image", mimeType: "image/png", base64: "", size: 0, url: refUrl }];
|
|
4997
|
-
sendMessage(
|
|
4998
|
-
buildChecklistStepPrompt(nextPending, items.length, items[nextPending], false, refUrl),
|
|
4999
|
-
{ hiddenUserMessage: true, isChecklistExecution: true, checklistStep: { index: nextPending, title: items[nextPending].title } }
|
|
5202
|
+
(m) => ({ role: m.role, content: m.content })
|
|
5000
5203
|
);
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5204
|
+
infoExtraction.extractInfo(recentForExtraction).catch(() => {
|
|
5205
|
+
});
|
|
5206
|
+
}
|
|
5207
|
+
}
|
|
5208
|
+
if (observerConfig) {
|
|
5209
|
+
const newMessages = [userMessage, { id: assistantMessageId, role: "assistant", content: accumulatedContent, timestamp: Date.now() }];
|
|
5210
|
+
observer.processMessages(capturedSessionId, newMessages);
|
|
5211
|
+
}
|
|
5212
|
+
} catch (error) {
|
|
5213
|
+
const classified = classifyFetchError(error);
|
|
5214
|
+
if (classified.code === "ABORT") {
|
|
5215
|
+
if (accumulatedContent) {
|
|
5216
|
+
setSessions(
|
|
5217
|
+
(prev) => prev.map((s) => {
|
|
5218
|
+
if (s.id !== capturedSessionId) return s;
|
|
5219
|
+
return {
|
|
5220
|
+
...s,
|
|
5221
|
+
messages: s.messages.map(
|
|
5222
|
+
(m) => m.id === assistantMessageId ? { ...m, content: accumulatedContent } : m
|
|
5223
|
+
)
|
|
5224
|
+
};
|
|
5225
|
+
})
|
|
5226
|
+
);
|
|
5227
|
+
onAbortRef.current?.(capturedSessionId, accumulatedContent);
|
|
5228
|
+
}
|
|
5229
|
+
return;
|
|
5230
|
+
}
|
|
5231
|
+
onErrorRef.current?.(classified);
|
|
5232
|
+
setSessions(
|
|
5233
|
+
(prev) => prev.map((s) => {
|
|
5234
|
+
if (s.id === capturedSessionId) {
|
|
5011
5235
|
return {
|
|
5012
5236
|
...s,
|
|
5013
|
-
messages: s.messages.map(
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
})
|
|
5237
|
+
messages: s.messages.map(
|
|
5238
|
+
(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
|
|
5239
|
+
)
|
|
5017
5240
|
};
|
|
5018
|
-
}
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5241
|
+
}
|
|
5242
|
+
return s;
|
|
5243
|
+
})
|
|
5244
|
+
);
|
|
5245
|
+
} finally {
|
|
5246
|
+
removeLoadingSession(capturedSessionId);
|
|
5247
|
+
abortControllersRef.current.delete(capturedSessionId);
|
|
5248
|
+
}
|
|
5249
|
+
}, [
|
|
5250
|
+
input,
|
|
5251
|
+
loadingSessionIds,
|
|
5252
|
+
currentSessionId,
|
|
5253
|
+
sessions,
|
|
5254
|
+
selectedModel,
|
|
5255
|
+
quotedText,
|
|
5256
|
+
selectedAction,
|
|
5257
|
+
apiEndpoint,
|
|
5258
|
+
apiKey,
|
|
5259
|
+
models,
|
|
5260
|
+
contextCompressionThreshold,
|
|
5261
|
+
keepRecentMessages,
|
|
5262
|
+
buildSystemPrompt,
|
|
5263
|
+
compressContext,
|
|
5264
|
+
useExternalStorage,
|
|
5265
|
+
handleSkillCall,
|
|
5266
|
+
resolvedSkills,
|
|
5267
|
+
/** @Todo vibecode - attachments, continueAfterToolResult를 deps에 추가하여 stale closure 방지 */
|
|
5268
|
+
attachments,
|
|
5269
|
+
continueAfterToolResult
|
|
5270
|
+
]);
|
|
5271
|
+
sendMessageForChecklistRef.current = sendMessage;
|
|
5272
|
+
const handlePollSubmit = useCallback8(
|
|
5036
5273
|
(messageId, responses) => {
|
|
5037
5274
|
const currentSess = sessions.find((s) => s.id === currentSessionId);
|
|
5038
5275
|
const targetMessage = currentSess?.messages.find((m) => m.id === messageId);
|
|
@@ -5102,7 +5339,7 @@ ${formattedParts.join("\n")}
|
|
|
5102
5339
|
},
|
|
5103
5340
|
[sessions, currentSessionId, selectedModel, sendMessage]
|
|
5104
5341
|
);
|
|
5105
|
-
const saveEdit =
|
|
5342
|
+
const saveEdit = useCallback8(async (content) => {
|
|
5106
5343
|
if (!editingMessageId || !currentSession || !currentSessionId) return;
|
|
5107
5344
|
const messageIndex = currentSession.messages.findIndex((m) => m.id === editingMessageId);
|
|
5108
5345
|
if (messageIndex === -1) return;
|
|
@@ -5119,7 +5356,7 @@ ${formattedParts.join("\n")}
|
|
|
5119
5356
|
setEditingMessageId(null);
|
|
5120
5357
|
await sendMessage(content);
|
|
5121
5358
|
}, [editingMessageId, currentSession, currentSessionId, sendMessage]);
|
|
5122
|
-
const regenerate =
|
|
5359
|
+
const regenerate = useCallback8(async (messageId) => {
|
|
5123
5360
|
console.log("[ChatUI] Regenerate called:", { messageId, currentSessionId, isLoading });
|
|
5124
5361
|
if (!currentSession || !currentSessionId || isLoading) {
|
|
5125
5362
|
console.log("[ChatUI] Regenerate early return - missing session or loading");
|
|
@@ -5195,100 +5432,39 @@ ${formattedParts.join("\n")}
|
|
|
5195
5432
|
signal: abortControllersRef.current.get(capturedSessionId).signal
|
|
5196
5433
|
});
|
|
5197
5434
|
console.log("[ChatUI] Regenerate response status:", response.status);
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
if (data === "[DONE]") continue;
|
|
5215
|
-
}
|
|
5216
|
-
try {
|
|
5217
|
-
const parsed = JSON.parse(data);
|
|
5218
|
-
const content = parsed.message?.content || parsed.content || parsed.text || "";
|
|
5219
|
-
const thinking = parsed.message?.thinking || "";
|
|
5220
|
-
if (content || thinking) {
|
|
5221
|
-
setSessions(
|
|
5222
|
-
(prev) => prev.map((s) => {
|
|
5223
|
-
if (s.id === capturedSessionId) {
|
|
5224
|
-
return {
|
|
5225
|
-
...s,
|
|
5226
|
-
messages: s.messages.map((m) => {
|
|
5227
|
-
if (m.id !== assistantMessageId) return m;
|
|
5228
|
-
let newContent = m.content;
|
|
5229
|
-
if (thinking) {
|
|
5230
|
-
if (!newContent.includes("<thinking>")) {
|
|
5231
|
-
newContent = "<thinking>" + thinking;
|
|
5232
|
-
} else if (!newContent.includes("</thinking>")) {
|
|
5233
|
-
newContent += thinking;
|
|
5234
|
-
}
|
|
5235
|
-
}
|
|
5236
|
-
if (content) {
|
|
5237
|
-
if (newContent.includes("<thinking>") && !newContent.includes("</thinking>")) {
|
|
5238
|
-
newContent += "</thinking>\n\n";
|
|
5239
|
-
}
|
|
5240
|
-
newContent += content;
|
|
5241
|
-
}
|
|
5242
|
-
return { ...m, content: newContent };
|
|
5243
|
-
})
|
|
5244
|
-
};
|
|
5245
|
-
}
|
|
5246
|
-
return s;
|
|
5247
|
-
})
|
|
5248
|
-
);
|
|
5249
|
-
}
|
|
5250
|
-
} catch {
|
|
5251
|
-
}
|
|
5252
|
-
}
|
|
5253
|
-
}
|
|
5254
|
-
if (buffer.trim()) {
|
|
5255
|
-
try {
|
|
5256
|
-
const parsed = JSON.parse(buffer);
|
|
5257
|
-
const content = parsed.message?.content || parsed.content || parsed.text || "";
|
|
5258
|
-
const thinking = parsed.message?.thinking || "";
|
|
5259
|
-
if (content || thinking) {
|
|
5260
|
-
setSessions(
|
|
5261
|
-
(prev) => prev.map((s) => {
|
|
5262
|
-
if (s.id === capturedSessionId) {
|
|
5263
|
-
return {
|
|
5264
|
-
...s,
|
|
5265
|
-
messages: s.messages.map((m) => {
|
|
5266
|
-
if (m.id !== assistantMessageId) return m;
|
|
5267
|
-
let newContent = m.content;
|
|
5268
|
-
if (thinking) {
|
|
5269
|
-
if (!newContent.includes("<thinking>")) {
|
|
5270
|
-
newContent = "<thinking>" + thinking;
|
|
5271
|
-
} else if (!newContent.includes("</thinking>")) {
|
|
5272
|
-
newContent += thinking;
|
|
5273
|
-
}
|
|
5435
|
+
await streamResponse(response, (chunk) => {
|
|
5436
|
+
const { content, thinking } = chunk;
|
|
5437
|
+
if (content || thinking) {
|
|
5438
|
+
setSessions(
|
|
5439
|
+
(prev) => prev.map((s) => {
|
|
5440
|
+
if (s.id === capturedSessionId) {
|
|
5441
|
+
return {
|
|
5442
|
+
...s,
|
|
5443
|
+
messages: s.messages.map((m) => {
|
|
5444
|
+
if (m.id !== assistantMessageId) return m;
|
|
5445
|
+
let newContent = m.content;
|
|
5446
|
+
if (thinking) {
|
|
5447
|
+
if (!newContent.includes("<thinking>")) {
|
|
5448
|
+
newContent = "<thinking>" + thinking;
|
|
5449
|
+
} else if (!newContent.includes("</thinking>")) {
|
|
5450
|
+
newContent += thinking;
|
|
5274
5451
|
}
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
newContent += content;
|
|
5452
|
+
}
|
|
5453
|
+
if (content) {
|
|
5454
|
+
if (newContent.includes("<thinking>") && !newContent.includes("</thinking>")) {
|
|
5455
|
+
newContent += "</thinking>\n\n";
|
|
5280
5456
|
}
|
|
5281
|
-
|
|
5282
|
-
}
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
}
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5457
|
+
newContent += content;
|
|
5458
|
+
}
|
|
5459
|
+
return { ...m, content: newContent };
|
|
5460
|
+
})
|
|
5461
|
+
};
|
|
5462
|
+
}
|
|
5463
|
+
return s;
|
|
5464
|
+
})
|
|
5465
|
+
);
|
|
5290
5466
|
}
|
|
5291
|
-
}
|
|
5467
|
+
}, { noTimeout: true });
|
|
5292
5468
|
} catch (error) {
|
|
5293
5469
|
if (error instanceof Error && error.name === "AbortError") {
|
|
5294
5470
|
return;
|
|
@@ -5299,7 +5475,7 @@ ${formattedParts.join("\n")}
|
|
|
5299
5475
|
removeLoadingSession(capturedSessionId);
|
|
5300
5476
|
}
|
|
5301
5477
|
}, [currentSession, currentSessionId, loadingSessionIds, selectedModel, models, apiEndpoint, apiKey, buildSystemPrompt]);
|
|
5302
|
-
const askOtherModel =
|
|
5478
|
+
const askOtherModel = useCallback8(async (messageId, targetModel) => {
|
|
5303
5479
|
if (!currentSession || !currentSessionId || isLoading) return;
|
|
5304
5480
|
const assistantIndex = currentSession.messages.findIndex((m) => m.id === messageId);
|
|
5305
5481
|
if (assistantIndex === -1) return;
|
|
@@ -5354,30 +5530,7 @@ ${currentSession.contextSummary}` },
|
|
|
5354
5530
|
responseContent = result.content;
|
|
5355
5531
|
responseSources = result.sources;
|
|
5356
5532
|
} else {
|
|
5357
|
-
|
|
5358
|
-
const decoder = new TextDecoder();
|
|
5359
|
-
let buffer = "";
|
|
5360
|
-
while (true) {
|
|
5361
|
-
const { done, value } = await reader.read();
|
|
5362
|
-
if (done) break;
|
|
5363
|
-
buffer += decoder.decode(value, { stream: true });
|
|
5364
|
-
const lines = buffer.split("\n");
|
|
5365
|
-
buffer = lines.pop() || "";
|
|
5366
|
-
for (const line of lines) {
|
|
5367
|
-
if (line.startsWith("data: ")) {
|
|
5368
|
-
const data = line.slice(6);
|
|
5369
|
-
if (data === "[DONE]") continue;
|
|
5370
|
-
try {
|
|
5371
|
-
const parsed = JSON.parse(data);
|
|
5372
|
-
{
|
|
5373
|
-
const chunk = parsed.content ?? parsed.text ?? "";
|
|
5374
|
-
if (chunk) responseContent += chunk;
|
|
5375
|
-
}
|
|
5376
|
-
} catch {
|
|
5377
|
-
}
|
|
5378
|
-
}
|
|
5379
|
-
}
|
|
5380
|
-
}
|
|
5533
|
+
responseContent = await parseSSEResponse(new Response(result));
|
|
5381
5534
|
}
|
|
5382
5535
|
} else {
|
|
5383
5536
|
const isOllama = provider === "ollama" || apiEndpoint.includes("ollama") || apiEndpoint.includes("11434");
|
|
@@ -5395,32 +5548,9 @@ ${currentSession.contextSummary}` },
|
|
|
5395
5548
|
signal: abortControllersRef.current.get(currentSessionId).signal
|
|
5396
5549
|
});
|
|
5397
5550
|
emitFetchHeaders(response);
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
const decoder = new TextDecoder();
|
|
5402
|
-
let buffer = "";
|
|
5403
|
-
while (true) {
|
|
5404
|
-
const { done, value } = await reader.read();
|
|
5405
|
-
if (done) break;
|
|
5406
|
-
buffer += decoder.decode(value, { stream: true });
|
|
5407
|
-
const lines = buffer.split("\n");
|
|
5408
|
-
buffer = lines.pop() || "";
|
|
5409
|
-
for (const line of lines) {
|
|
5410
|
-
if (line.startsWith("data: ")) {
|
|
5411
|
-
const data = line.slice(6);
|
|
5412
|
-
if (data === "[DONE]") continue;
|
|
5413
|
-
try {
|
|
5414
|
-
const parsed = JSON.parse(data);
|
|
5415
|
-
{
|
|
5416
|
-
const chunk = parsed.content ?? parsed.text ?? "";
|
|
5417
|
-
if (chunk) responseContent += chunk;
|
|
5418
|
-
}
|
|
5419
|
-
} catch {
|
|
5420
|
-
}
|
|
5421
|
-
}
|
|
5422
|
-
}
|
|
5423
|
-
}
|
|
5551
|
+
await streamResponse(response, (chunk) => {
|
|
5552
|
+
if (chunk.content) responseContent += chunk.content;
|
|
5553
|
+
}, { noTimeout: true });
|
|
5424
5554
|
}
|
|
5425
5555
|
const alternative = {
|
|
5426
5556
|
id: generateId5("alt"),
|
|
@@ -5480,13 +5610,13 @@ ${currentSession.contextSummary}` },
|
|
|
5480
5610
|
onSendMessage,
|
|
5481
5611
|
onError
|
|
5482
5612
|
]);
|
|
5483
|
-
const setActiveAlternative =
|
|
5613
|
+
const setActiveAlternative = useCallback8((assistantMessageId, index) => {
|
|
5484
5614
|
setActiveAlternatives((prev) => ({
|
|
5485
5615
|
...prev,
|
|
5486
5616
|
[assistantMessageId]: index
|
|
5487
5617
|
}));
|
|
5488
5618
|
}, []);
|
|
5489
|
-
const getActiveAlternative =
|
|
5619
|
+
const getActiveAlternative = useCallback8((assistantMessageId) => {
|
|
5490
5620
|
return activeAlternatives[assistantMessageId] ?? 0;
|
|
5491
5621
|
}, [activeAlternatives]);
|
|
5492
5622
|
return {
|
|
@@ -5544,6 +5674,10 @@ ${currentSession.contextSummary}` },
|
|
|
5544
5674
|
}));
|
|
5545
5675
|
await infoExtraction.extractInfo(recentMessages);
|
|
5546
5676
|
},
|
|
5677
|
+
getTokenStats: useCallback8((sessionId) => {
|
|
5678
|
+
const session = sessions.find((s) => s.id === sessionId);
|
|
5679
|
+
return session?.tokenStats ?? null;
|
|
5680
|
+
}, [sessions]),
|
|
5547
5681
|
// External Storage Loading States
|
|
5548
5682
|
/**
|
|
5549
5683
|
* @description 세션 목록 로딩 상태
|
|
@@ -5704,7 +5838,7 @@ var ImageErrorContext = createContext(null);
|
|
|
5704
5838
|
var useImageError = () => useContext(ImageErrorContext);
|
|
5705
5839
|
|
|
5706
5840
|
// src/react/components/ChatSidebar.tsx
|
|
5707
|
-
import { useState as useState9, useRef as
|
|
5841
|
+
import { useState as useState9, useRef as useRef9, useEffect as useEffect6 } from "react";
|
|
5708
5842
|
|
|
5709
5843
|
// src/react/components/Icon.tsx
|
|
5710
5844
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -5804,7 +5938,7 @@ var IconSvg = ({
|
|
|
5804
5938
|
};
|
|
5805
5939
|
|
|
5806
5940
|
// src/react/components/MarkdownRenderer.tsx
|
|
5807
|
-
import React2, { useMemo as
|
|
5941
|
+
import React2, { useMemo as useMemo5 } from "react";
|
|
5808
5942
|
|
|
5809
5943
|
// src/react/components/LinkChip.tsx
|
|
5810
5944
|
import { useState as useState7 } from "react";
|
|
@@ -6861,11 +6995,11 @@ var MarkdownRenderer = ({
|
|
|
6861
6995
|
sources,
|
|
6862
6996
|
inline = false
|
|
6863
6997
|
}) => {
|
|
6864
|
-
const inlineRendered =
|
|
6998
|
+
const inlineRendered = useMemo5(() => {
|
|
6865
6999
|
if (!inline) return null;
|
|
6866
7000
|
return parseInlineElements(content, "inline-md", sources);
|
|
6867
7001
|
}, [inline, content, sources]);
|
|
6868
|
-
const rendered =
|
|
7002
|
+
const rendered = useMemo5(() => {
|
|
6869
7003
|
if (inline) return null;
|
|
6870
7004
|
const elements = [];
|
|
6871
7005
|
let processedContent = content;
|
|
@@ -7262,7 +7396,7 @@ var MarkdownRenderer = ({
|
|
|
7262
7396
|
};
|
|
7263
7397
|
|
|
7264
7398
|
// src/react/components/ProjectSelector.tsx
|
|
7265
|
-
import { useState as useState8, useRef as
|
|
7399
|
+
import { useState as useState8, useRef as useRef8, useEffect as useEffect5 } from "react";
|
|
7266
7400
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
7267
7401
|
var ProjectSelector = ({
|
|
7268
7402
|
projects,
|
|
@@ -7272,7 +7406,7 @@ var ProjectSelector = ({
|
|
|
7272
7406
|
onProjectSettings
|
|
7273
7407
|
}) => {
|
|
7274
7408
|
const [isOpen, setIsOpen] = useState8(false);
|
|
7275
|
-
const dropdownRef =
|
|
7409
|
+
const dropdownRef = useRef8(null);
|
|
7276
7410
|
const currentProject = projects.find((p) => p.id === currentProjectId);
|
|
7277
7411
|
useEffect5(() => {
|
|
7278
7412
|
const handleClickOutside = (e) => {
|
|
@@ -7510,7 +7644,7 @@ var ChatSidebar = ({
|
|
|
7510
7644
|
const sidebarWidth = typeof widthProp === "number" ? `${widthProp}px` : widthProp || "288px";
|
|
7511
7645
|
const [editingId, setEditingId] = useState9(null);
|
|
7512
7646
|
const [editingTitle, setEditingTitle] = useState9("");
|
|
7513
|
-
const inputRef =
|
|
7647
|
+
const inputRef = useRef9(null);
|
|
7514
7648
|
useEffect6(() => {
|
|
7515
7649
|
if (editingId && inputRef.current) {
|
|
7516
7650
|
inputRef.current.focus();
|
|
@@ -8152,7 +8286,7 @@ var ChatHeader = ({
|
|
|
8152
8286
|
};
|
|
8153
8287
|
|
|
8154
8288
|
// src/react/components/ChatInput.tsx
|
|
8155
|
-
import React6, { useRef as
|
|
8289
|
+
import React6, { useRef as useRef10, useEffect as useEffect7, useState as useState11 } from "react";
|
|
8156
8290
|
import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
8157
8291
|
var ChatInput = ({
|
|
8158
8292
|
value,
|
|
@@ -8181,12 +8315,12 @@ var ChatInput = ({
|
|
|
8181
8315
|
inline = false
|
|
8182
8316
|
}) => {
|
|
8183
8317
|
const [mainMenuOpen, setMainMenuOpen] = React6.useState(false);
|
|
8184
|
-
const textareaRef =
|
|
8185
|
-
const fileInputRef =
|
|
8318
|
+
const textareaRef = useRef10(null);
|
|
8319
|
+
const fileInputRef = useRef10(null);
|
|
8186
8320
|
const [actionMenuOpen, setActionMenuOpen] = useState11(false);
|
|
8187
8321
|
const [isDragOver, setIsDragOver] = useState11(false);
|
|
8188
|
-
const mainMenuRef =
|
|
8189
|
-
const actionMenuRef =
|
|
8322
|
+
const mainMenuRef = useRef10(null);
|
|
8323
|
+
const actionMenuRef = useRef10(null);
|
|
8190
8324
|
useEffect7(() => {
|
|
8191
8325
|
if (textareaRef.current) {
|
|
8192
8326
|
textareaRef.current.style.height = "auto";
|
|
@@ -8915,7 +9049,7 @@ var iconButtonStyle = {
|
|
|
8915
9049
|
};
|
|
8916
9050
|
|
|
8917
9051
|
// src/react/components/MessageList.tsx
|
|
8918
|
-
import React14, { useRef as
|
|
9052
|
+
import React14, { useRef as useRef12, useEffect as useEffect10, useCallback as useCallback13, useState as useState18 } from "react";
|
|
8919
9053
|
|
|
8920
9054
|
// src/react/components/MessageBubble.tsx
|
|
8921
9055
|
import { useState as useState17 } from "react";
|
|
@@ -9204,7 +9338,7 @@ var DeepResearchProgressUI = ({ progress }) => {
|
|
|
9204
9338
|
};
|
|
9205
9339
|
|
|
9206
9340
|
// src/react/components/PollCard.tsx
|
|
9207
|
-
import { useState as useState12, useCallback as
|
|
9341
|
+
import { useState as useState12, useCallback as useCallback9, useEffect as useEffect8 } from "react";
|
|
9208
9342
|
import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
9209
9343
|
var renderInlineMarkdown = (text) => {
|
|
9210
9344
|
const parts = [];
|
|
@@ -9248,7 +9382,7 @@ var PollCard = ({
|
|
|
9248
9382
|
const [otherTexts, setOtherTexts] = useState12({});
|
|
9249
9383
|
const [otherSelected, setOtherSelected] = useState12({});
|
|
9250
9384
|
const currentQuestion = questions[activeTab];
|
|
9251
|
-
const handleOptionToggle =
|
|
9385
|
+
const handleOptionToggle = useCallback9(
|
|
9252
9386
|
(questionId, optionId, multiSelect) => {
|
|
9253
9387
|
setSelections((prev) => {
|
|
9254
9388
|
const current = prev[questionId] || /* @__PURE__ */ new Set();
|
|
@@ -9270,7 +9404,7 @@ var PollCard = ({
|
|
|
9270
9404
|
},
|
|
9271
9405
|
[]
|
|
9272
9406
|
);
|
|
9273
|
-
const handleOtherToggle =
|
|
9407
|
+
const handleOtherToggle = useCallback9((questionId, multiSelect) => {
|
|
9274
9408
|
if (multiSelect) {
|
|
9275
9409
|
setOtherSelected((prev) => ({ ...prev, [questionId]: !prev[questionId] }));
|
|
9276
9410
|
} else {
|
|
@@ -9278,7 +9412,7 @@ var PollCard = ({
|
|
|
9278
9412
|
setOtherSelected((prev) => ({ ...prev, [questionId]: true }));
|
|
9279
9413
|
}
|
|
9280
9414
|
}, []);
|
|
9281
|
-
const handleSubmit =
|
|
9415
|
+
const handleSubmit = useCallback9(() => {
|
|
9282
9416
|
const responses = questions.map((q) => {
|
|
9283
9417
|
const selected = selections[q.id] || /* @__PURE__ */ new Set();
|
|
9284
9418
|
const other = otherSelected[q.id] && otherTexts[q.id]?.trim();
|
|
@@ -9291,7 +9425,7 @@ var PollCard = ({
|
|
|
9291
9425
|
});
|
|
9292
9426
|
onSubmit(responses);
|
|
9293
9427
|
}, [questions, selections, otherSelected, otherTexts, onSubmit]);
|
|
9294
|
-
const handleSkip =
|
|
9428
|
+
const handleSkip = useCallback9(() => {
|
|
9295
9429
|
const responses = questions.map((q) => ({
|
|
9296
9430
|
questionId: q.id,
|
|
9297
9431
|
selectedOptions: [],
|
|
@@ -9339,7 +9473,7 @@ var PollCard = ({
|
|
|
9339
9473
|
}
|
|
9340
9474
|
return () => timers.forEach(clearTimeout);
|
|
9341
9475
|
}, [activeTab, currentQuestion.options.length]);
|
|
9342
|
-
const handleNext =
|
|
9476
|
+
const handleNext = useCallback9(() => {
|
|
9343
9477
|
if (!isLastTab) {
|
|
9344
9478
|
setActiveTab((prev) => prev + 1);
|
|
9345
9479
|
} else {
|
|
@@ -9757,7 +9891,7 @@ var SkillProgressUI = ({
|
|
|
9757
9891
|
};
|
|
9758
9892
|
|
|
9759
9893
|
// src/react/components/ImageContentCard.tsx
|
|
9760
|
-
import { useState as useState13, useCallback as
|
|
9894
|
+
import { useState as useState13, useCallback as useCallback10 } from "react";
|
|
9761
9895
|
import { Fragment as Fragment6, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
9762
9896
|
var ImageContentCard = ({ part }) => {
|
|
9763
9897
|
const [isExpanded, setIsExpanded] = useState13(false);
|
|
@@ -9765,7 +9899,7 @@ var ImageContentCard = ({ part }) => {
|
|
|
9765
9899
|
const [hasError, setHasError] = useState13(false);
|
|
9766
9900
|
const [imgSrc, setImgSrc] = useState13(part.url);
|
|
9767
9901
|
const onImageError = useImageError();
|
|
9768
|
-
const handleImageError =
|
|
9902
|
+
const handleImageError = useCallback10(async () => {
|
|
9769
9903
|
if (onImageError) {
|
|
9770
9904
|
const newUrl = await onImageError(imgSrc, part.fileName);
|
|
9771
9905
|
if (newUrl) {
|
|
@@ -10142,7 +10276,7 @@ var ToolStatusCard = ({
|
|
|
10142
10276
|
};
|
|
10143
10277
|
|
|
10144
10278
|
// src/react/components/ArtifactCard.tsx
|
|
10145
|
-
import { useRef as
|
|
10279
|
+
import { useRef as useRef11, useEffect as useEffect9, useState as useState15, useCallback as useCallback11 } from "react";
|
|
10146
10280
|
import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
10147
10281
|
var getThemeStyles = () => {
|
|
10148
10282
|
const root = document.documentElement;
|
|
@@ -10183,9 +10317,9 @@ var getAutoResizeScript = (id) => `
|
|
|
10183
10317
|
});
|
|
10184
10318
|
</script>`;
|
|
10185
10319
|
var HtmlArtifact = ({ code }) => {
|
|
10186
|
-
const iframeRef =
|
|
10320
|
+
const iframeRef = useRef11(null);
|
|
10187
10321
|
const [height, setHeight] = useState15(200);
|
|
10188
|
-
const artifactId =
|
|
10322
|
+
const artifactId = useRef11(`artifact-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`);
|
|
10189
10323
|
useEffect9(() => {
|
|
10190
10324
|
const id = artifactId.current;
|
|
10191
10325
|
const handleMessage = (e) => {
|
|
@@ -10232,10 +10366,10 @@ var SvgArtifact = ({ code }) => {
|
|
|
10232
10366
|
);
|
|
10233
10367
|
};
|
|
10234
10368
|
var MermaidArtifact = ({ code }) => {
|
|
10235
|
-
const containerRef =
|
|
10369
|
+
const containerRef = useRef11(null);
|
|
10236
10370
|
const [svgHtml, setSvgHtml] = useState15(null);
|
|
10237
10371
|
const [error, setError] = useState15(null);
|
|
10238
|
-
const renderMermaid =
|
|
10372
|
+
const renderMermaid = useCallback11(async () => {
|
|
10239
10373
|
try {
|
|
10240
10374
|
let mermaid;
|
|
10241
10375
|
try {
|
|
@@ -10494,7 +10628,7 @@ var ArtifactActions = ({ code, language, containerRef }) => {
|
|
|
10494
10628
|
] });
|
|
10495
10629
|
};
|
|
10496
10630
|
var ArtifactCard = ({ part, index = 0 }) => {
|
|
10497
|
-
const contentRef =
|
|
10631
|
+
const contentRef = useRef11(null);
|
|
10498
10632
|
return /* @__PURE__ */ jsxs13(
|
|
10499
10633
|
"div",
|
|
10500
10634
|
{
|
|
@@ -10666,7 +10800,7 @@ var ContentPartRenderer = ({
|
|
|
10666
10800
|
};
|
|
10667
10801
|
|
|
10668
10802
|
// src/react/components/ChecklistCard.tsx
|
|
10669
|
-
import { useState as useState16, useMemo as
|
|
10803
|
+
import { useState as useState16, useMemo as useMemo6 } from "react";
|
|
10670
10804
|
import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
10671
10805
|
var ChecklistCard = ({
|
|
10672
10806
|
items,
|
|
@@ -10677,7 +10811,7 @@ var ChecklistCard = ({
|
|
|
10677
10811
|
onStart
|
|
10678
10812
|
}) => {
|
|
10679
10813
|
const [expandedItems, setExpandedItems] = useState16(/* @__PURE__ */ new Set());
|
|
10680
|
-
const { doneCount, isRunning, hasError, isWaiting } =
|
|
10814
|
+
const { doneCount, isRunning, hasError, isWaiting } = useMemo6(() => {
|
|
10681
10815
|
const done = items.filter((it) => it.status === "done").length;
|
|
10682
10816
|
const running = items.some((it) => it.status === "in_progress");
|
|
10683
10817
|
const waiting = items.every((it) => it.status === "pending");
|
|
@@ -11927,12 +12061,12 @@ var MessageList = ({
|
|
|
11927
12061
|
onExport,
|
|
11928
12062
|
onToggleChecklistPanel
|
|
11929
12063
|
}) => {
|
|
11930
|
-
const messagesEndRef =
|
|
11931
|
-
const containerRef =
|
|
12064
|
+
const messagesEndRef = useRef12(null);
|
|
12065
|
+
const containerRef = useRef12(null);
|
|
11932
12066
|
const [selectedText, setSelectedText] = useState18("");
|
|
11933
12067
|
const [selectionPosition, setSelectionPosition] = useState18(null);
|
|
11934
12068
|
const [showScrollButton, setShowScrollButton] = useState18(false);
|
|
11935
|
-
const userScrollLockRef =
|
|
12069
|
+
const userScrollLockRef = useRef12(false);
|
|
11936
12070
|
const SCROLL_THRESHOLD = 100;
|
|
11937
12071
|
useEffect10(() => {
|
|
11938
12072
|
const container = containerRef.current;
|
|
@@ -11971,7 +12105,7 @@ var MessageList = ({
|
|
|
11971
12105
|
container.removeEventListener("touchmove", handleTouchMove);
|
|
11972
12106
|
};
|
|
11973
12107
|
}, []);
|
|
11974
|
-
const handleScroll =
|
|
12108
|
+
const handleScroll = useCallback13(() => {
|
|
11975
12109
|
if (!containerRef.current) return;
|
|
11976
12110
|
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
|
|
11977
12111
|
const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
|
|
@@ -11987,7 +12121,7 @@ var MessageList = ({
|
|
|
11987
12121
|
behavior: "smooth"
|
|
11988
12122
|
});
|
|
11989
12123
|
}, [messages]);
|
|
11990
|
-
const scrollToBottom =
|
|
12124
|
+
const scrollToBottom = useCallback13(() => {
|
|
11991
12125
|
userScrollLockRef.current = false;
|
|
11992
12126
|
setShowScrollButton(false);
|
|
11993
12127
|
containerRef.current?.scrollTo({
|
|
@@ -11995,7 +12129,7 @@ var MessageList = ({
|
|
|
11995
12129
|
behavior: "smooth"
|
|
11996
12130
|
});
|
|
11997
12131
|
}, []);
|
|
11998
|
-
const handleMouseUp =
|
|
12132
|
+
const handleMouseUp = useCallback13(() => {
|
|
11999
12133
|
const selection = typeof window !== "undefined" ? window.getSelection() : null;
|
|
12000
12134
|
const text = selection?.toString().trim();
|
|
12001
12135
|
if (text && text.length > 0) {
|
|
@@ -15266,13 +15400,13 @@ var ChatUI = (props) => {
|
|
|
15266
15400
|
};
|
|
15267
15401
|
|
|
15268
15402
|
// src/react/ChatFloatingWidget.tsx
|
|
15269
|
-
import { useState as useState28, useRef as
|
|
15403
|
+
import { useState as useState28, useRef as useRef19, useEffect as useEffect19, useMemo as useMemo8, useCallback as useCallback18 } from "react";
|
|
15270
15404
|
|
|
15271
15405
|
// src/react/hooks/useFloatingWidget.ts
|
|
15272
|
-
import { useState as useState22, useCallback as
|
|
15406
|
+
import { useState as useState22, useCallback as useCallback15, useEffect as useEffect14, useRef as useRef14 } from "react";
|
|
15273
15407
|
|
|
15274
15408
|
// src/react/hooks/useDragResize.ts
|
|
15275
|
-
import { useState as useState21, useRef as
|
|
15409
|
+
import { useState as useState21, useRef as useRef13, useCallback as useCallback14, useEffect as useEffect13, useMemo as useMemo7 } from "react";
|
|
15276
15410
|
var DRAG_THRESHOLD = 5;
|
|
15277
15411
|
var FAB_SIZE = 56;
|
|
15278
15412
|
var EDGE_MARGIN = 24;
|
|
@@ -15367,7 +15501,7 @@ var useDragResize = (options) => {
|
|
|
15367
15501
|
}));
|
|
15368
15502
|
const vw = viewport.width;
|
|
15369
15503
|
const vh = viewport.height;
|
|
15370
|
-
const initializedRef =
|
|
15504
|
+
const initializedRef = useRef13(false);
|
|
15371
15505
|
useEffect13(() => {
|
|
15372
15506
|
if (initializedRef.current || typeof window === "undefined") return;
|
|
15373
15507
|
initializedRef.current = true;
|
|
@@ -15381,26 +15515,26 @@ var useDragResize = (options) => {
|
|
|
15381
15515
|
setFabPos({ x: loaded.fabX, y: loaded.fabY });
|
|
15382
15516
|
setPanelSize({ width: loaded.panelWidth, height: loaded.panelHeight });
|
|
15383
15517
|
}, [initialPosition, storageKey, initialWidth, initialHeight]);
|
|
15384
|
-
const dragStartRef =
|
|
15385
|
-
const isDraggingRef =
|
|
15386
|
-
const wasDragRef =
|
|
15387
|
-
const userDraggedRef =
|
|
15388
|
-
const dragDeltaRef =
|
|
15389
|
-
const fabElRef =
|
|
15390
|
-
const dragCleanupRef =
|
|
15391
|
-
const snapTimerRef =
|
|
15392
|
-
const prevDragRef =
|
|
15393
|
-
const shakeScoreRef =
|
|
15394
|
-
const prevVelocityRef =
|
|
15395
|
-
const isDizzyRef =
|
|
15396
|
-
const dizzyTimerRef =
|
|
15397
|
-
const lastVelocityRef =
|
|
15398
|
-
const dragRafRef =
|
|
15399
|
-
const animCleanupRef =
|
|
15400
|
-
const snapEnabledRef =
|
|
15401
|
-
const storageKeyRef =
|
|
15402
|
-
const panelSizeRef =
|
|
15403
|
-
const fabPosRef =
|
|
15518
|
+
const dragStartRef = useRef13(null);
|
|
15519
|
+
const isDraggingRef = useRef13(false);
|
|
15520
|
+
const wasDragRef = useRef13(false);
|
|
15521
|
+
const userDraggedRef = useRef13(false);
|
|
15522
|
+
const dragDeltaRef = useRef13({ dx: 0, dy: 0 });
|
|
15523
|
+
const fabElRef = useRef13(null);
|
|
15524
|
+
const dragCleanupRef = useRef13(null);
|
|
15525
|
+
const snapTimerRef = useRef13(null);
|
|
15526
|
+
const prevDragRef = useRef13(null);
|
|
15527
|
+
const shakeScoreRef = useRef13(0);
|
|
15528
|
+
const prevVelocityRef = useRef13(null);
|
|
15529
|
+
const isDizzyRef = useRef13(false);
|
|
15530
|
+
const dizzyTimerRef = useRef13(null);
|
|
15531
|
+
const lastVelocityRef = useRef13({ vx: 0, vy: 0, speed: 0 });
|
|
15532
|
+
const dragRafRef = useRef13(null);
|
|
15533
|
+
const animCleanupRef = useRef13(null);
|
|
15534
|
+
const snapEnabledRef = useRef13(snapEnabled);
|
|
15535
|
+
const storageKeyRef = useRef13(storageKey);
|
|
15536
|
+
const panelSizeRef = useRef13(panelSize);
|
|
15537
|
+
const fabPosRef = useRef13(fabPos);
|
|
15404
15538
|
useEffect13(() => {
|
|
15405
15539
|
snapEnabledRef.current = snapEnabled;
|
|
15406
15540
|
}, [snapEnabled]);
|
|
@@ -15413,7 +15547,7 @@ var useDragResize = (options) => {
|
|
|
15413
15547
|
useEffect13(() => {
|
|
15414
15548
|
fabPosRef.current = fabPos;
|
|
15415
15549
|
}, [fabPos]);
|
|
15416
|
-
const resizeStartRef =
|
|
15550
|
+
const resizeStartRef = useRef13(null);
|
|
15417
15551
|
useEffect13(() => {
|
|
15418
15552
|
return () => {
|
|
15419
15553
|
dragCleanupRef.current?.();
|
|
@@ -15422,7 +15556,7 @@ var useDragResize = (options) => {
|
|
|
15422
15556
|
if (dragRafRef.current) cancelAnimationFrame(dragRafRef.current);
|
|
15423
15557
|
};
|
|
15424
15558
|
}, []);
|
|
15425
|
-
const handleFabPointerDown =
|
|
15559
|
+
const handleFabPointerDown = useCallback14((e) => {
|
|
15426
15560
|
if (disabled || isMobile) return;
|
|
15427
15561
|
dragCleanupRef.current?.();
|
|
15428
15562
|
const el = e.currentTarget;
|
|
@@ -15604,7 +15738,7 @@ var useDragResize = (options) => {
|
|
|
15604
15738
|
}, [disabled, isMobile]);
|
|
15605
15739
|
const fabCenterX = fabPos.x + FAB_SIZE / 2;
|
|
15606
15740
|
const panelDirection = fabCenterX > vw / 2 ? "left" : "right";
|
|
15607
|
-
const panelPositionStyle =
|
|
15741
|
+
const panelPositionStyle = useMemo7(() => {
|
|
15608
15742
|
if (isMobile || disabled) return {};
|
|
15609
15743
|
const fabIsTop = fabPos.y < vh / 2;
|
|
15610
15744
|
let panelBottom;
|
|
@@ -15636,7 +15770,7 @@ var useDragResize = (options) => {
|
|
|
15636
15770
|
borderRadius: "var(--floating-panel-radius, 16px)"
|
|
15637
15771
|
};
|
|
15638
15772
|
}, [isMobile, disabled, fabPos.x, fabPos.y, panelSize.width, panelSize.height, vw, vh, panelDirection, minHeight]);
|
|
15639
|
-
const handleResizePointerDown =
|
|
15773
|
+
const handleResizePointerDown = useCallback14((edge, e) => {
|
|
15640
15774
|
if (disabled || isMobile) return;
|
|
15641
15775
|
e.preventDefault();
|
|
15642
15776
|
e.stopPropagation();
|
|
@@ -15652,7 +15786,7 @@ var useDragResize = (options) => {
|
|
|
15652
15786
|
};
|
|
15653
15787
|
setIsResizing(true);
|
|
15654
15788
|
}, [disabled, isMobile]);
|
|
15655
|
-
const handleResizePointerMove =
|
|
15789
|
+
const handleResizePointerMove = useCallback14((e) => {
|
|
15656
15790
|
if (!resizeStartRef.current) return;
|
|
15657
15791
|
const { startX, startY, width, height, edge, fabY } = resizeStartRef.current;
|
|
15658
15792
|
const dx = e.clientX - startX;
|
|
@@ -15676,7 +15810,7 @@ var useDragResize = (options) => {
|
|
|
15676
15810
|
newHeight = Math.max(minHeight, Math.min(maxH, newHeight));
|
|
15677
15811
|
setPanelSize({ width: newWidth, height: newHeight });
|
|
15678
15812
|
}, [minWidth, maxWidth, minHeight]);
|
|
15679
|
-
const handleResizePointerUp =
|
|
15813
|
+
const handleResizePointerUp = useCallback14((e) => {
|
|
15680
15814
|
if (!resizeStartRef.current) return;
|
|
15681
15815
|
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
15682
15816
|
resizeStartRef.current = null;
|
|
@@ -15749,7 +15883,7 @@ var useFloatingWidget = (options) => {
|
|
|
15749
15883
|
} = options || {};
|
|
15750
15884
|
const [isOpen, setIsOpen] = useState22(defaultOpen);
|
|
15751
15885
|
const [activeTab, setActiveTab] = useState22(defaultTab);
|
|
15752
|
-
const panelRef =
|
|
15886
|
+
const panelRef = useRef14(null);
|
|
15753
15887
|
const dragResize = useDragResize({
|
|
15754
15888
|
initialPosition: position,
|
|
15755
15889
|
initialWidth: width,
|
|
@@ -15761,9 +15895,9 @@ var useFloatingWidget = (options) => {
|
|
|
15761
15895
|
maxWidth,
|
|
15762
15896
|
minHeight
|
|
15763
15897
|
});
|
|
15764
|
-
const onOpenRef =
|
|
15765
|
-
const onCloseRef =
|
|
15766
|
-
const onTabChangeRef =
|
|
15898
|
+
const onOpenRef = useRef14(onOpen);
|
|
15899
|
+
const onCloseRef = useRef14(onClose);
|
|
15900
|
+
const onTabChangeRef = useRef14(onTabChange);
|
|
15767
15901
|
useEffect14(() => {
|
|
15768
15902
|
onOpenRef.current = onOpen;
|
|
15769
15903
|
}, [onOpen]);
|
|
@@ -15773,15 +15907,15 @@ var useFloatingWidget = (options) => {
|
|
|
15773
15907
|
useEffect14(() => {
|
|
15774
15908
|
onTabChangeRef.current = onTabChange;
|
|
15775
15909
|
}, [onTabChange]);
|
|
15776
|
-
const open =
|
|
15910
|
+
const open = useCallback15(() => {
|
|
15777
15911
|
setIsOpen(true);
|
|
15778
15912
|
onOpenRef.current?.();
|
|
15779
15913
|
}, []);
|
|
15780
|
-
const close =
|
|
15914
|
+
const close = useCallback15(() => {
|
|
15781
15915
|
setIsOpen(false);
|
|
15782
15916
|
onCloseRef.current?.();
|
|
15783
15917
|
}, []);
|
|
15784
|
-
const toggle =
|
|
15918
|
+
const toggle = useCallback15(() => {
|
|
15785
15919
|
setIsOpen((prev) => {
|
|
15786
15920
|
const next = !prev;
|
|
15787
15921
|
if (next) onOpenRef.current?.();
|
|
@@ -15789,11 +15923,11 @@ var useFloatingWidget = (options) => {
|
|
|
15789
15923
|
return next;
|
|
15790
15924
|
});
|
|
15791
15925
|
}, []);
|
|
15792
|
-
const setTab =
|
|
15926
|
+
const setTab = useCallback15((tabKey) => {
|
|
15793
15927
|
setActiveTab(tabKey);
|
|
15794
15928
|
onTabChangeRef.current?.(tabKey);
|
|
15795
15929
|
}, []);
|
|
15796
|
-
const handleFabInteraction =
|
|
15930
|
+
const handleFabInteraction = useCallback15(() => {
|
|
15797
15931
|
if (dragResize.isDragging) return;
|
|
15798
15932
|
toggle();
|
|
15799
15933
|
}, [dragResize.isDragging, toggle]);
|
|
@@ -15823,10 +15957,10 @@ var useFloatingWidget = (options) => {
|
|
|
15823
15957
|
};
|
|
15824
15958
|
|
|
15825
15959
|
// src/react/components/floating/FloatingFab.tsx
|
|
15826
|
-
import { useRef as
|
|
15960
|
+
import { useRef as useRef16, useState as useState24, useEffect as useEffect16 } from "react";
|
|
15827
15961
|
|
|
15828
15962
|
// src/react/components/floating/DevDiveCharacter.tsx
|
|
15829
|
-
import { useState as useState23, useEffect as useEffect15, useRef as
|
|
15963
|
+
import { useState as useState23, useEffect as useEffect15, useRef as useRef15, useCallback as useCallback16 } from "react";
|
|
15830
15964
|
import { Fragment as Fragment10, jsx as jsx24, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
15831
15965
|
var THEMES = {
|
|
15832
15966
|
dark: { body: "#2ecc71", stroke: "#27ae60", highlight: "#3ddc84", face: "#1a1a2e", eyeLight: "#fff" },
|
|
@@ -15857,11 +15991,11 @@ var DevDiveFabCharacter = ({
|
|
|
15857
15991
|
const [isWinking, setIsWinking] = useState23(false);
|
|
15858
15992
|
const [isCatMouth, setIsCatMouth] = useState23(false);
|
|
15859
15993
|
const [isEntering, setIsEntering] = useState23(true);
|
|
15860
|
-
const svgRef =
|
|
15861
|
-
const cleanupRef =
|
|
15862
|
-
const lastActivityRef =
|
|
15863
|
-
const isSleepyRef =
|
|
15864
|
-
const markActivity =
|
|
15994
|
+
const svgRef = useRef15(null);
|
|
15995
|
+
const cleanupRef = useRef15(null);
|
|
15996
|
+
const lastActivityRef = useRef15(Date.now());
|
|
15997
|
+
const isSleepyRef = useRef15(false);
|
|
15998
|
+
const markActivity = useCallback16(() => {
|
|
15865
15999
|
lastActivityRef.current = Date.now();
|
|
15866
16000
|
if (isSleepyRef.current) {
|
|
15867
16001
|
isSleepyRef.current = false;
|
|
@@ -15981,7 +16115,7 @@ var DevDiveFabCharacter = ({
|
|
|
15981
16115
|
}, 5e3);
|
|
15982
16116
|
return () => clearInterval(check);
|
|
15983
16117
|
}, [isOpen, isTalking, isDizzy]);
|
|
15984
|
-
const prevCompleteRef =
|
|
16118
|
+
const prevCompleteRef = useRef15(false);
|
|
15985
16119
|
useEffect15(() => {
|
|
15986
16120
|
const wasComplete = prevCompleteRef.current;
|
|
15987
16121
|
prevCompleteRef.current = isComplete;
|
|
@@ -16182,7 +16316,7 @@ var FloatingFab = ({
|
|
|
16182
16316
|
}) => {
|
|
16183
16317
|
const isRight = position.includes("right");
|
|
16184
16318
|
const isBottom = position.includes("bottom");
|
|
16185
|
-
const fabRef =
|
|
16319
|
+
const fabRef = useRef16(null);
|
|
16186
16320
|
const isCharacterMode = !icon;
|
|
16187
16321
|
const [bubbleOnRight, setBubbleOnRight] = useState24(!isRight);
|
|
16188
16322
|
const posLeft = positionStyle?.left;
|
|
@@ -16197,11 +16331,11 @@ var FloatingFab = ({
|
|
|
16197
16331
|
}, [isRight, posLeft, posTop]);
|
|
16198
16332
|
const [bubbleText, setBubbleText] = useState24(null);
|
|
16199
16333
|
const [bubbleExiting, setBubbleExiting] = useState24(false);
|
|
16200
|
-
const bubbleTextRef =
|
|
16334
|
+
const bubbleTextRef = useRef16(bubbleText);
|
|
16201
16335
|
bubbleTextRef.current = bubbleText;
|
|
16202
16336
|
const [displayText, setDisplayText] = useState24(null);
|
|
16203
16337
|
const [isTyping, setIsTyping] = useState24(false);
|
|
16204
|
-
const typingTimerRef =
|
|
16338
|
+
const typingTimerRef = useRef16(null);
|
|
16205
16339
|
useEffect16(() => {
|
|
16206
16340
|
if (notification) {
|
|
16207
16341
|
setBubbleText(notification);
|
|
@@ -16238,7 +16372,7 @@ var FloatingFab = ({
|
|
|
16238
16372
|
}, 300);
|
|
16239
16373
|
return () => clearTimeout(timer);
|
|
16240
16374
|
}, [notification]);
|
|
16241
|
-
const notifContentRef =
|
|
16375
|
+
const notifContentRef = useRef16(null);
|
|
16242
16376
|
const [needsMarquee, setNeedsMarquee] = useState24(false);
|
|
16243
16377
|
useEffect16(() => {
|
|
16244
16378
|
if (isTyping || !notification) {
|
|
@@ -16382,7 +16516,7 @@ var FloatingFab = ({
|
|
|
16382
16516
|
};
|
|
16383
16517
|
|
|
16384
16518
|
// src/react/components/floating/FloatingPanel.tsx
|
|
16385
|
-
import { useState as useState25, useEffect as useEffect17, useRef as
|
|
16519
|
+
import { useState as useState25, useEffect as useEffect17, useRef as useRef17 } from "react";
|
|
16386
16520
|
import { jsxs as jsxs25 } from "react/jsx-runtime";
|
|
16387
16521
|
var FloatingPanel = ({
|
|
16388
16522
|
isOpen,
|
|
@@ -16411,7 +16545,7 @@ var FloatingPanel = ({
|
|
|
16411
16545
|
}, []);
|
|
16412
16546
|
const [shouldRender, setShouldRender] = useState25(isOpen);
|
|
16413
16547
|
const [isVisible, setIsVisible] = useState25(isOpen);
|
|
16414
|
-
const rafRef =
|
|
16548
|
+
const rafRef = useRef17(0);
|
|
16415
16549
|
useEffect17(() => {
|
|
16416
16550
|
if (isOpen) {
|
|
16417
16551
|
setShouldRender(true);
|
|
@@ -16588,10 +16722,10 @@ var FloatingTabBar = ({
|
|
|
16588
16722
|
};
|
|
16589
16723
|
|
|
16590
16724
|
// src/react/components/floating/CompactChatView.tsx
|
|
16591
|
-
import { useState as useState27, useCallback as
|
|
16725
|
+
import { useState as useState27, useCallback as useCallback17 } from "react";
|
|
16592
16726
|
|
|
16593
16727
|
// src/react/components/floating/CompactSessionMenu.tsx
|
|
16594
|
-
import { useState as useState26, useRef as
|
|
16728
|
+
import { useState as useState26, useRef as useRef18, useEffect as useEffect18 } from "react";
|
|
16595
16729
|
import { jsx as jsx27, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
16596
16730
|
var CompactSessionMenu = ({
|
|
16597
16731
|
sessions,
|
|
@@ -16603,9 +16737,9 @@ var CompactSessionMenu = ({
|
|
|
16603
16737
|
onClose,
|
|
16604
16738
|
isLoading = false
|
|
16605
16739
|
}) => {
|
|
16606
|
-
const menuRef =
|
|
16607
|
-
const inputRef =
|
|
16608
|
-
const onCloseRef =
|
|
16740
|
+
const menuRef = useRef18(null);
|
|
16741
|
+
const inputRef = useRef18(null);
|
|
16742
|
+
const onCloseRef = useRef18(onClose);
|
|
16609
16743
|
onCloseRef.current = onClose;
|
|
16610
16744
|
const [editingId, setEditingId] = useState26(null);
|
|
16611
16745
|
const [editingTitle, setEditingTitle] = useState26("");
|
|
@@ -16934,15 +17068,15 @@ var CompactChatView = ({
|
|
|
16934
17068
|
setInput(choice.text);
|
|
16935
17069
|
};
|
|
16936
17070
|
const [showSessionMenu, setShowSessionMenu] = useState27(false);
|
|
16937
|
-
const handleSessionSelect =
|
|
17071
|
+
const handleSessionSelect = useCallback17((id) => {
|
|
16938
17072
|
selectSession(id);
|
|
16939
17073
|
setShowSessionMenu(false);
|
|
16940
17074
|
}, [selectSession]);
|
|
16941
|
-
const handleNewSession =
|
|
17075
|
+
const handleNewSession = useCallback17(() => {
|
|
16942
17076
|
newSession();
|
|
16943
17077
|
setShowSessionMenu(false);
|
|
16944
17078
|
}, [newSession]);
|
|
16945
|
-
const handleCloseMenu =
|
|
17079
|
+
const handleCloseMenu = useCallback17(() => {
|
|
16946
17080
|
setShowSessionMenu(false);
|
|
16947
17081
|
}, []);
|
|
16948
17082
|
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";
|
|
@@ -17262,11 +17396,11 @@ var ChatFloatingWidget = ({
|
|
|
17262
17396
|
maxWidth,
|
|
17263
17397
|
minHeight
|
|
17264
17398
|
});
|
|
17265
|
-
const notifObj =
|
|
17399
|
+
const notifObj = useMemo8(() => {
|
|
17266
17400
|
if (!notification) return null;
|
|
17267
17401
|
return typeof notification === "string" ? { text: notification } : notification;
|
|
17268
17402
|
}, [notification]);
|
|
17269
|
-
const handleFabClick =
|
|
17403
|
+
const handleFabClick = useCallback18(() => {
|
|
17270
17404
|
if (notifObj?.onClick) {
|
|
17271
17405
|
notifObj.onClick();
|
|
17272
17406
|
if (!isOpen) handleFabInteraction();
|
|
@@ -17280,7 +17414,7 @@ var ChatFloatingWidget = ({
|
|
|
17280
17414
|
}
|
|
17281
17415
|
handleFabInteraction();
|
|
17282
17416
|
}, [notifObj, isOpen, handleFabInteraction, setTab, tabs]);
|
|
17283
|
-
const allTabs =
|
|
17417
|
+
const allTabs = useMemo8(() => {
|
|
17284
17418
|
const chatTab = {
|
|
17285
17419
|
key: "chat",
|
|
17286
17420
|
label: "\uCC44\uD305",
|
|
@@ -17295,7 +17429,7 @@ var ChatFloatingWidget = ({
|
|
|
17295
17429
|
return [chatTab, ...customTabs];
|
|
17296
17430
|
}, [tabs]);
|
|
17297
17431
|
const [unreadBadge, setUnreadBadge] = useState28(0);
|
|
17298
|
-
const seenMessageIdsRef =
|
|
17432
|
+
const seenMessageIdsRef = useRef19(/* @__PURE__ */ new Set());
|
|
17299
17433
|
useEffect19(() => {
|
|
17300
17434
|
if (seenMessageIdsRef.current.size === 0 && chatState.messages.length > 0) {
|
|
17301
17435
|
for (const m of chatState.messages) {
|
|
@@ -17328,11 +17462,11 @@ var ChatFloatingWidget = ({
|
|
|
17328
17462
|
}
|
|
17329
17463
|
}
|
|
17330
17464
|
}, [isOpen, chatState.messages]);
|
|
17331
|
-
const totalBadge =
|
|
17465
|
+
const totalBadge = useMemo8(() => {
|
|
17332
17466
|
return tabs.reduce((sum, t) => sum + (t.badge || 0), 0) + unreadBadge;
|
|
17333
17467
|
}, [tabs, unreadBadge]);
|
|
17334
|
-
const isTalking =
|
|
17335
|
-
const prevLoadingRef =
|
|
17468
|
+
const isTalking = useMemo8(() => !!notification || chatState.isLoading, [notification, chatState.isLoading]);
|
|
17469
|
+
const prevLoadingRef = useRef19(chatState.isLoading);
|
|
17336
17470
|
const [isComplete, setIsComplete] = useState28(false);
|
|
17337
17471
|
useEffect19(() => {
|
|
17338
17472
|
const wasLoading = prevLoadingRef.current;
|
|
@@ -17450,7 +17584,7 @@ var ChatFloatingWidget = ({
|
|
|
17450
17584
|
};
|
|
17451
17585
|
|
|
17452
17586
|
// src/react/hooks/useDeepResearch.ts
|
|
17453
|
-
import { useState as useState29, useCallback as
|
|
17587
|
+
import { useState as useState29, useCallback as useCallback19, useRef as useRef20 } from "react";
|
|
17454
17588
|
var REPORT_GENERATION_PROMPT2 = `\uB2F9\uC2E0\uC740 \uB9AC\uC11C\uCE58 \uBCF4\uACE0\uC11C \uC791\uC131 \uC804\uBB38\uAC00\uC785\uB2C8\uB2E4.
|
|
17455
17589
|
|
|
17456
17590
|
<collected_sources>
|
|
@@ -17503,8 +17637,8 @@ var useDeepResearch = (options) => {
|
|
|
17503
17637
|
const { onWebSearch, onExtractContent, apiEndpoint, apiKey, model, provider } = options;
|
|
17504
17638
|
const [isResearching, setIsResearching] = useState29(false);
|
|
17505
17639
|
const [progress, setProgress] = useState29(null);
|
|
17506
|
-
const abortControllerRef =
|
|
17507
|
-
const callLLM2 =
|
|
17640
|
+
const abortControllerRef = useRef20(null);
|
|
17641
|
+
const callLLM2 = useCallback19(
|
|
17508
17642
|
async (prompt, stream = false) => {
|
|
17509
17643
|
const response = await fetch(apiEndpoint, {
|
|
17510
17644
|
method: "POST",
|
|
@@ -17531,7 +17665,7 @@ var useDeepResearch = (options) => {
|
|
|
17531
17665
|
},
|
|
17532
17666
|
[apiEndpoint, apiKey, model, provider]
|
|
17533
17667
|
);
|
|
17534
|
-
const analyzeQuery2 =
|
|
17668
|
+
const analyzeQuery2 = useCallback19(
|
|
17535
17669
|
async (query) => {
|
|
17536
17670
|
const prompt = QUERY_ANALYSIS_PROMPT2.replace("{question}", query);
|
|
17537
17671
|
const response = await callLLM2(prompt);
|
|
@@ -17550,7 +17684,7 @@ var useDeepResearch = (options) => {
|
|
|
17550
17684
|
},
|
|
17551
17685
|
[callLLM2]
|
|
17552
17686
|
);
|
|
17553
|
-
const runSubAgent2 =
|
|
17687
|
+
const runSubAgent2 = useCallback19(
|
|
17554
17688
|
async (topic, queries, agentId, updateProgress) => {
|
|
17555
17689
|
updateProgress({ status: "searching", searchCount: 0, resultsCount: 0 });
|
|
17556
17690
|
const allResults = [];
|
|
@@ -17589,7 +17723,7 @@ var useDeepResearch = (options) => {
|
|
|
17589
17723
|
},
|
|
17590
17724
|
[onWebSearch, onExtractContent]
|
|
17591
17725
|
);
|
|
17592
|
-
const generateReport2 =
|
|
17726
|
+
const generateReport2 = useCallback19(
|
|
17593
17727
|
async (query, results, onStreamContent) => {
|
|
17594
17728
|
const allSources = [];
|
|
17595
17729
|
const sourcesForPrompt = [];
|
|
@@ -17647,7 +17781,7 @@ var useDeepResearch = (options) => {
|
|
|
17647
17781
|
},
|
|
17648
17782
|
[callLLM2]
|
|
17649
17783
|
);
|
|
17650
|
-
const runDeepResearch =
|
|
17784
|
+
const runDeepResearch = useCallback19(
|
|
17651
17785
|
async (query, onStreamContent) => {
|
|
17652
17786
|
abortControllerRef.current = new AbortController();
|
|
17653
17787
|
setIsResearching(true);
|
|
@@ -17750,7 +17884,7 @@ var useDeepResearch = (options) => {
|
|
|
17750
17884
|
},
|
|
17751
17885
|
[analyzeQuery2, runSubAgent2, generateReport2]
|
|
17752
17886
|
);
|
|
17753
|
-
const stopResearch =
|
|
17887
|
+
const stopResearch = useCallback19(() => {
|
|
17754
17888
|
abortControllerRef.current?.abort();
|
|
17755
17889
|
setIsResearching(false);
|
|
17756
17890
|
setProgress(null);
|
|
@@ -17763,6 +17897,122 @@ var useDeepResearch = (options) => {
|
|
|
17763
17897
|
};
|
|
17764
17898
|
};
|
|
17765
17899
|
|
|
17900
|
+
// src/react/hooks/useContentParsers.ts
|
|
17901
|
+
import { useCallback as useCallback20 } from "react";
|
|
17902
|
+
var useContentParsers = ({
|
|
17903
|
+
sessionsRef,
|
|
17904
|
+
setSessions
|
|
17905
|
+
}) => {
|
|
17906
|
+
const applyPollParsing = useCallback20(
|
|
17907
|
+
(sessionId, messageId, content, skipParsing) => {
|
|
17908
|
+
const { pollBlock, cleanContent } = parsePollFromContent(content);
|
|
17909
|
+
setSessions(
|
|
17910
|
+
(prev) => prev.map((s) => {
|
|
17911
|
+
if (s.id !== sessionId) return s;
|
|
17912
|
+
return {
|
|
17913
|
+
...s,
|
|
17914
|
+
messages: s.messages.map((m) => {
|
|
17915
|
+
if (m.id !== messageId) return m;
|
|
17916
|
+
if (skipParsing) {
|
|
17917
|
+
return { ...m, content: cleanContent };
|
|
17918
|
+
}
|
|
17919
|
+
if (pollBlock) {
|
|
17920
|
+
return { ...m, content: cleanContent, pollBlock };
|
|
17921
|
+
}
|
|
17922
|
+
return m;
|
|
17923
|
+
})
|
|
17924
|
+
};
|
|
17925
|
+
})
|
|
17926
|
+
);
|
|
17927
|
+
return skipParsing ? null : pollBlock;
|
|
17928
|
+
},
|
|
17929
|
+
[setSessions]
|
|
17930
|
+
);
|
|
17931
|
+
const applyChecklistParsing = useCallback20(
|
|
17932
|
+
(sessionId, messageId, content, skipParsing) => {
|
|
17933
|
+
if (skipParsing) return null;
|
|
17934
|
+
const { checklistBlock, cleanContent } = parseChecklistFromContent(content);
|
|
17935
|
+
if (!checklistBlock) return null;
|
|
17936
|
+
setSessions(
|
|
17937
|
+
(prev) => prev.map((s) => {
|
|
17938
|
+
if (s.id !== sessionId) return s;
|
|
17939
|
+
return {
|
|
17940
|
+
...s,
|
|
17941
|
+
messages: s.messages.map((m) => {
|
|
17942
|
+
if (m.id !== messageId) return m;
|
|
17943
|
+
return { ...m, content: cleanContent, checklistBlock };
|
|
17944
|
+
})
|
|
17945
|
+
};
|
|
17946
|
+
})
|
|
17947
|
+
);
|
|
17948
|
+
return checklistBlock;
|
|
17949
|
+
},
|
|
17950
|
+
[setSessions]
|
|
17951
|
+
);
|
|
17952
|
+
const applyArtifactParsing = useCallback20(
|
|
17953
|
+
(sessionId, messageId, content) => {
|
|
17954
|
+
if (!hasArtifactTag(content)) return false;
|
|
17955
|
+
let found = false;
|
|
17956
|
+
setSessions(
|
|
17957
|
+
(prev) => prev.map((s) => {
|
|
17958
|
+
if (s.id !== sessionId) return s;
|
|
17959
|
+
return {
|
|
17960
|
+
...s,
|
|
17961
|
+
messages: s.messages.map((m) => {
|
|
17962
|
+
if (m.id !== messageId) return m;
|
|
17963
|
+
const { artifacts, cleanContent } = parseArtifactsFromContent(m.content);
|
|
17964
|
+
if (artifacts.length === 0) return m;
|
|
17965
|
+
found = true;
|
|
17966
|
+
const existingParts = m.contentParts || [];
|
|
17967
|
+
const textPart = cleanContent.trim() ? [{ type: "text", content: cleanContent }] : [];
|
|
17968
|
+
return {
|
|
17969
|
+
...m,
|
|
17970
|
+
content: cleanContent,
|
|
17971
|
+
contentParts: [
|
|
17972
|
+
...textPart,
|
|
17973
|
+
...existingParts.filter(
|
|
17974
|
+
(p) => p.type !== "text" && p.type !== "artifact"
|
|
17975
|
+
),
|
|
17976
|
+
...artifacts
|
|
17977
|
+
]
|
|
17978
|
+
};
|
|
17979
|
+
})
|
|
17980
|
+
};
|
|
17981
|
+
})
|
|
17982
|
+
);
|
|
17983
|
+
return found;
|
|
17984
|
+
},
|
|
17985
|
+
[setSessions]
|
|
17986
|
+
);
|
|
17987
|
+
const parseContextReferences = useCallback20(
|
|
17988
|
+
(content, sessionContext) => {
|
|
17989
|
+
const { refs, cleanContent } = parseContextRefs(content);
|
|
17990
|
+
if (refs.length === 0 || !sessionContext) {
|
|
17991
|
+
return { refs, cleanContent, refContents: null };
|
|
17992
|
+
}
|
|
17993
|
+
const assembled = refs.map((refId) => {
|
|
17994
|
+
const item = sessionContext.references.find(
|
|
17995
|
+
(r) => r.id === refId
|
|
17996
|
+
);
|
|
17997
|
+
return item ? `[${item.label || item.skillName}]
|
|
17998
|
+
${item.summary}` : null;
|
|
17999
|
+
}).filter(Boolean).join("\n\n");
|
|
18000
|
+
return {
|
|
18001
|
+
refs,
|
|
18002
|
+
cleanContent,
|
|
18003
|
+
refContents: assembled || null
|
|
18004
|
+
};
|
|
18005
|
+
},
|
|
18006
|
+
[]
|
|
18007
|
+
);
|
|
18008
|
+
return {
|
|
18009
|
+
applyPollParsing,
|
|
18010
|
+
applyChecklistParsing,
|
|
18011
|
+
applyArtifactParsing,
|
|
18012
|
+
parseContextReferences
|
|
18013
|
+
};
|
|
18014
|
+
};
|
|
18015
|
+
|
|
17766
18016
|
// src/react/utils/conversationSearchAdapter.ts
|
|
17767
18017
|
var formatSearchResults = (results) => {
|
|
17768
18018
|
if (results.length === 0) {
|
|
@@ -18290,8 +18540,52 @@ var MemoryPanel = ({
|
|
|
18290
18540
|
}
|
|
18291
18541
|
);
|
|
18292
18542
|
};
|
|
18543
|
+
|
|
18544
|
+
// src/react/utils/retry.ts
|
|
18545
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
18546
|
+
maxRetries: 3,
|
|
18547
|
+
initialDelayMs: 1e3,
|
|
18548
|
+
maxDelayMs: 3e4,
|
|
18549
|
+
backoffMultiplier: 2
|
|
18550
|
+
};
|
|
18551
|
+
var calculateDelay = (attempt, config) => {
|
|
18552
|
+
const exponential = config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt);
|
|
18553
|
+
const capped = Math.min(exponential, config.maxDelayMs);
|
|
18554
|
+
const jitter = 0.5 + Math.random() * 0.5;
|
|
18555
|
+
return Math.floor(capped * jitter);
|
|
18556
|
+
};
|
|
18557
|
+
var sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
18558
|
+
if (signal?.aborted) {
|
|
18559
|
+
reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
18560
|
+
return;
|
|
18561
|
+
}
|
|
18562
|
+
const timer = setTimeout(resolve, ms);
|
|
18563
|
+
signal?.addEventListener("abort", () => {
|
|
18564
|
+
clearTimeout(timer);
|
|
18565
|
+
reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
18566
|
+
}, { once: true });
|
|
18567
|
+
});
|
|
18568
|
+
var withRetry = async (fn, config, signal) => {
|
|
18569
|
+
const merged = { ...DEFAULT_RETRY_CONFIG, ...config };
|
|
18570
|
+
let lastError;
|
|
18571
|
+
for (let attempt = 0; attempt <= merged.maxRetries; attempt++) {
|
|
18572
|
+
try {
|
|
18573
|
+
return await fn();
|
|
18574
|
+
} catch (error) {
|
|
18575
|
+
lastError = error;
|
|
18576
|
+
if (error instanceof DOMException && error.name === "AbortError") throw error;
|
|
18577
|
+
if (error instanceof Error && error.name === "AbortError") throw error;
|
|
18578
|
+
if (signal?.aborted) throw error;
|
|
18579
|
+
if (attempt >= merged.maxRetries) throw error;
|
|
18580
|
+
if (error instanceof ChatError && !error.retryable) throw error;
|
|
18581
|
+
await sleep(calculateDelay(attempt, merged), signal);
|
|
18582
|
+
}
|
|
18583
|
+
}
|
|
18584
|
+
throw lastError;
|
|
18585
|
+
};
|
|
18293
18586
|
export {
|
|
18294
18587
|
ArtifactCard,
|
|
18588
|
+
ChatError,
|
|
18295
18589
|
ChatFloatingWidget,
|
|
18296
18590
|
ChatHeader,
|
|
18297
18591
|
ChatInput,
|
|
@@ -18304,6 +18598,7 @@ export {
|
|
|
18304
18598
|
ContentPartRenderer,
|
|
18305
18599
|
DEFAULT_PROJECT_ID,
|
|
18306
18600
|
DEFAULT_PROJECT_TITLE,
|
|
18601
|
+
DEFAULT_RETRY_CONFIG,
|
|
18307
18602
|
DeepResearchProgressUI,
|
|
18308
18603
|
DevDiveAvatar,
|
|
18309
18604
|
DevDiveFabCharacter,
|
|
@@ -18327,20 +18622,28 @@ export {
|
|
|
18327
18622
|
ResizeHandles,
|
|
18328
18623
|
SettingsModal,
|
|
18329
18624
|
SkillProgressUI,
|
|
18625
|
+
classifyFetchError,
|
|
18330
18626
|
convertSkillsToOpenAITools,
|
|
18331
18627
|
convertToolsToSkills,
|
|
18332
18628
|
createAdvancedResearchSkill,
|
|
18333
18629
|
createConversationSearchSkill,
|
|
18334
18630
|
createDeepResearchSkill,
|
|
18631
|
+
createTimeoutError,
|
|
18335
18632
|
migrateSessionsToProjects,
|
|
18633
|
+
parseSSELine,
|
|
18634
|
+
parseSSEResponse,
|
|
18336
18635
|
useChatUI,
|
|
18636
|
+
useChecklist,
|
|
18637
|
+
useContentParsers,
|
|
18337
18638
|
useDeepResearch,
|
|
18338
18639
|
useDragResize,
|
|
18339
18640
|
useFloatingWidget,
|
|
18340
18641
|
useImageError,
|
|
18341
18642
|
useObserver,
|
|
18342
18643
|
useProject,
|
|
18343
|
-
useSkills
|
|
18644
|
+
useSkills,
|
|
18645
|
+
useStreamingFetch,
|
|
18646
|
+
withRetry
|
|
18344
18647
|
};
|
|
18345
18648
|
/**
|
|
18346
18649
|
* @description localStorage 기반 메모리 저장소 어댑터
|