@gendive/chatllm 0.17.0 → 0.17.2

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.
@@ -1133,7 +1133,7 @@ var useProject = (options) => {
1133
1133
  const currentProject = projects.find((p) => p.id === currentProjectId) || null;
1134
1134
  useEffect2(() => {
1135
1135
  if (!enabled || useExternalStorage || !initializedRef.current) return;
1136
- if (projects.length > 0) {
1136
+ if (projects.length > 0 && typeof window !== "undefined") {
1137
1137
  localStorage.setItem(storageKey, JSON.stringify(projects));
1138
1138
  }
1139
1139
  }, [enabled, projects, storageKey, useExternalStorage]);
@@ -1167,7 +1167,7 @@ var useProject = (options) => {
1167
1167
  }
1168
1168
  } else {
1169
1169
  try {
1170
- const saved = localStorage.getItem(storageKey);
1170
+ const saved = typeof window !== "undefined" ? localStorage.getItem(storageKey) : null;
1171
1171
  if (saved) {
1172
1172
  const parsed = JSON.parse(saved);
1173
1173
  if (!parsed.find((p) => p.id === DEFAULT_PROJECT_ID)) {
@@ -1474,6 +1474,7 @@ var convertToolsToSkills = (tools, onToolCall) => {
1474
1474
  label: tool.label || tool.name,
1475
1475
  icon: tool.icon,
1476
1476
  acceptedTypes: tool.acceptedTypes,
1477
+ autoConvertBase64: tool.autoConvertBase64,
1477
1478
  parameters: {
1478
1479
  type: "object",
1479
1480
  properties: Object.fromEntries(
@@ -1547,6 +1548,20 @@ var DEFAULT_KEEP_RECENT = 6;
1547
1548
  var DEFAULT_RECOMPRESSION_THRESHOLD = 10;
1548
1549
  var DEFAULT_TOKEN_LIMIT = 8e3;
1549
1550
  var generateId3 = (prefix) => `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
1551
+ var fileToBase64 = (file) => new Promise((resolve, reject) => {
1552
+ const reader = new FileReader();
1553
+ reader.onload = () => resolve(reader.result.split(",")[1] || "");
1554
+ reader.onerror = reject;
1555
+ reader.readAsDataURL(file);
1556
+ });
1557
+ var convertAttachmentsToBase64 = async (attachments) => Promise.all(
1558
+ attachments.map(async (att) => ({
1559
+ name: att.name,
1560
+ mimeType: att.mimeType,
1561
+ base64: await fileToBase64(att.file),
1562
+ size: att.size
1563
+ }))
1564
+ );
1550
1565
  var generateTitle = (messages) => {
1551
1566
  const firstUserMessage = messages.find((m) => m.role === "user");
1552
1567
  if (!firstUserMessage) return "\uC0C8 \uB300\uD654";
@@ -1769,11 +1784,13 @@ var useChatUI = (options) => {
1769
1784
  }).finally(() => {
1770
1785
  setIsSessionsLoading(false);
1771
1786
  });
1772
- const savedPersonalization2 = localStorage.getItem(`${storageKey}_personalization`);
1773
- if (savedPersonalization2) {
1774
- try {
1775
- setPersonalization(JSON.parse(savedPersonalization2));
1776
- } catch {
1787
+ if (typeof window !== "undefined") {
1788
+ const savedPersonalization2 = localStorage.getItem(`${storageKey}_personalization`);
1789
+ if (savedPersonalization2) {
1790
+ try {
1791
+ setPersonalization(JSON.parse(savedPersonalization2));
1792
+ } catch {
1793
+ }
1777
1794
  }
1778
1795
  }
1779
1796
  return;
@@ -1781,6 +1798,7 @@ var useChatUI = (options) => {
1781
1798
  if (enableProjects) {
1782
1799
  migrateSessionsToProjects(storageKey);
1783
1800
  }
1801
+ if (typeof window === "undefined") return;
1784
1802
  const saved = localStorage.getItem(storageKey);
1785
1803
  if (saved) {
1786
1804
  try {
@@ -2269,7 +2287,8 @@ ${newConversation}
2269
2287
  }, []);
2270
2288
  const sendMessage = useCallback5(async (content, options2) => {
2271
2289
  const messageContent = content || input;
2272
- if (!messageContent.trim() || isLoading) return;
2290
+ if (!messageContent.trim() && attachments.length === 0 || isLoading) return;
2291
+ setIsLoading(true);
2273
2292
  let sessionId = currentSessionId;
2274
2293
  if (!sessionId) {
2275
2294
  if (useExternalStorage && onCreateSessionRef.current) {
@@ -2360,7 +2379,8 @@ ${finalContent}`;
2360
2379
  });
2361
2380
  if (matchedFiles.length === 0) continue;
2362
2381
  try {
2363
- const result = await skillConfig.execute({ files: matchedFiles, userMessage: finalContent });
2382
+ const filesToPass = skillConfig.autoConvertBase64 ? await convertAttachmentsToBase64(matchedFiles) : matchedFiles;
2383
+ const result = await skillConfig.execute({ files: filesToPass, userMessage: finalContent });
2364
2384
  attachmentResults.push({
2365
2385
  type: "tool_result",
2366
2386
  toolName: skillName,
@@ -2378,6 +2398,20 @@ ${finalContent}`;
2378
2398
  }
2379
2399
  }
2380
2400
  }
2401
+ let shouldContinueAfterAttachment = continueAfterToolResult;
2402
+ if (attachmentResults.length > 0) {
2403
+ for (const part of attachmentResults) {
2404
+ if (part.type === "tool_result" && onSkillCompleteRef.current) {
2405
+ const decision = onSkillCompleteRef.current(part.toolName, {
2406
+ content: part.result.content,
2407
+ metadata: part.result.metadata,
2408
+ sources: part.result.sources
2409
+ });
2410
+ shouldContinueAfterAttachment = decision === "continue";
2411
+ if (!shouldContinueAfterAttachment) break;
2412
+ }
2413
+ }
2414
+ }
2381
2415
  const assistantMessageId = generateId3("msg");
2382
2416
  const assistantMessage = {
2383
2417
  id: assistantMessageId,
@@ -2425,7 +2459,10 @@ ${finalContent}`;
2425
2459
  }).catch(() => {
2426
2460
  });
2427
2461
  }
2428
- setIsLoading(true);
2462
+ if (attachmentResults.length > 0 && !shouldContinueAfterAttachment) {
2463
+ setIsLoading(false);
2464
+ return;
2465
+ }
2429
2466
  abortControllerRef.current = new AbortController();
2430
2467
  try {
2431
2468
  let messagesToSend = [...existingMessages, userMessage];
@@ -2484,6 +2521,20 @@ ${currentContextSummary}` },
2484
2521
  } else {
2485
2522
  chatMessages = messagesToSend.map((m) => ({ role: m.role, content: m.content }));
2486
2523
  }
2524
+ if (attachmentResults.length > 0 && shouldContinueAfterAttachment) {
2525
+ const attachmentContext = attachmentResults.filter((part) => part.type === "tool_result").map((part) => `[${part.label || part.toolName} \uACB0\uACFC]
2526
+ ${part.result.content}`).join("\n\n");
2527
+ if (attachmentContext) {
2528
+ chatMessages.push({
2529
+ role: "user",
2530
+ content: `\uD30C\uC77C \uBD84\uC11D \uACB0\uACFC:
2531
+
2532
+ ${attachmentContext}
2533
+
2534
+ \uC704 \uACB0\uACFC\uB97C \uCC38\uACE0\uD558\uC5EC \uB2F5\uBCC0\uD574\uC8FC\uC138\uC694.`
2535
+ });
2536
+ }
2537
+ }
2487
2538
  console.log("[ChatUI] Messages to send:", chatMessages.length, chatMessages.map((m) => ({ role: m.role, content: m.content.slice(0, 50) })));
2488
2539
  const baseSystemPrompt = buildSystemPrompt();
2489
2540
  const combinedSystemPrompt = [baseSystemPrompt, actionPrompt].filter(Boolean).join("\n\n");
@@ -2491,8 +2542,8 @@ ${currentContextSummary}` },
2491
2542
  const modelConfig = models.find((m) => m.id === selectedModel);
2492
2543
  const provider = modelConfig?.provider || "ollama";
2493
2544
  let response;
2494
- if (onSendMessage) {
2495
- const result = await onSendMessage({
2545
+ if (onSendMessageRef.current) {
2546
+ const result = await onSendMessageRef.current({
2496
2547
  messages: messagesForApi,
2497
2548
  model: selectedModel,
2498
2549
  provider,
@@ -2917,14 +2968,12 @@ ${result.content}
2917
2968
  keepRecentMessages,
2918
2969
  buildSystemPrompt,
2919
2970
  compressContext,
2920
- onSendMessage,
2921
- onError,
2922
- generateTitleCallback,
2923
- onTitleChange,
2924
2971
  useExternalStorage,
2925
- onSaveMessages,
2926
2972
  handleSkillCall,
2927
- resolvedSkills
2973
+ resolvedSkills,
2974
+ /** @Todo vibecode - attachments, continueAfterToolResult를 deps에 추가하여 stale closure 방지 */
2975
+ attachments,
2976
+ continueAfterToolResult
2928
2977
  ]);
2929
2978
  const handlePollSubmit = useCallback5(
2930
2979
  (messageId, responses) => {
@@ -3192,8 +3241,8 @@ ${currentSession.contextSummary}` },
3192
3241
  const provider = modelConfig?.provider || "ollama";
3193
3242
  let responseContent = "";
3194
3243
  let responseSources;
3195
- if (onSendMessage) {
3196
- const result = await onSendMessage({
3244
+ if (onSendMessageRef.current) {
3245
+ const result = await onSendMessageRef.current({
3197
3246
  messages: messagesForApi,
3198
3247
  model: targetModel,
3199
3248
  provider,
@@ -3471,7 +3520,25 @@ ${currentSession.contextSummary}` },
3471
3520
  systemPrompt: `\uC0AC\uC6A9\uC790\uAC00 "${toolConfig.label || skillName}" \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD558\uB824\uACE0 \uD569\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uC758 \uBA54\uC2DC\uC9C0\uB97C \uBC14\uD0D5\uC73C\uB85C \uBC18\uB4DC\uC2DC <skill_use name="${skillName}"> \uD0DC\uADF8\uB97C \uC0AC\uC6A9\uD558\uC5EC \uC774 \uB3C4\uAD6C\uB97C \uD638\uCD9C\uD558\uC138\uC694. \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD558\uC9C0 \uC54A\uACE0 \uD14D\uC2A4\uD2B8\uB85C\uB9CC \uB2F5\uBCC0\uD558\uC9C0 \uB9C8\uC138\uC694.`
3472
3521
  });
3473
3522
  } else {
3474
- executeManualSkill(skillName, { query: input });
3523
+ const currentQuery = input;
3524
+ executeManualSkill(skillName, { query: currentQuery }).then((result) => {
3525
+ if (!result || !result.content) return;
3526
+ let shouldContinue = continueAfterToolResult;
3527
+ if (onSkillCompleteRef.current) {
3528
+ const decision = onSkillCompleteRef.current(skillName, result);
3529
+ shouldContinue = decision === "continue";
3530
+ }
3531
+ if (!shouldContinue) return;
3532
+ skipNextSkillParsingRef.current = true;
3533
+ const resultPrompt = `\uC2A4\uD0AC "${skillName}" \uC2E4\uD589 \uACB0\uACFC:
3534
+
3535
+ ${result.content}
3536
+
3537
+ \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.`;
3538
+ setTimeout(() => {
3539
+ sendMessage(resultPrompt, { hiddenUserMessage: true });
3540
+ }, 100);
3541
+ });
3475
3542
  }
3476
3543
  },
3477
3544
  // Project
@@ -3902,6 +3969,7 @@ var ChatSidebar = ({
3902
3969
  className: `chatllm-sidebar chatllm-sidebar-transition ${theme ? `chatllm-root ${themeClass}` : ""}`,
3903
3970
  style: {
3904
3971
  width: isOpen ? sidebarWidth : "0",
3972
+ height: "100%",
3905
3973
  flexShrink: 0,
3906
3974
  backgroundColor: "var(--chatllm-sidebar-bg)",
3907
3975
  borderRight: isOpen ? "1px solid var(--chatllm-border)" : "none",
@@ -10305,6 +10373,7 @@ var ChatUIView = ({
10305
10373
  currentProjectId,
10306
10374
  onSelectProject: projects.length > 0 ? selectProject : void 0,
10307
10375
  onNewProject: projects.length > 0 ? () => {
10376
+ if (typeof window === "undefined") return;
10308
10377
  const title = window.prompt("\uD504\uB85C\uC81D\uD2B8 \uC774\uB984\uC744 \uC785\uB825\uD558\uC138\uC694");
10309
10378
  if (title?.trim()) {
10310
10379
  createProject({ title: title.trim() });