@gendive/chatllm 0.17.0 → 0.17.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.
@@ -396,6 +396,11 @@ interface ChatToolDefinition {
396
396
  * @Todo vibecode - MIME 와일드카드 ('image/*'), 확장자 ('.pdf') 지원
397
397
  */
398
398
  acceptedTypes?: string[];
399
+ /**
400
+ * @description 파일 첨부 시 File 객체를 base64 문자열로 자동 변환
401
+ * @Todo vibecode - true 시 execute()에 { name, mimeType, base64, size } 형태로 전달
402
+ */
403
+ autoConvertBase64?: boolean;
399
404
  }
400
405
  interface ChatMessage {
401
406
  id: string;
@@ -541,6 +546,11 @@ interface SkillConfig<TParams = Record<string, unknown>> {
541
546
  * @example ['image/*', 'application/pdf', '.docx']
542
547
  */
543
548
  acceptedTypes?: string[];
549
+ /**
550
+ * @description 파일 첨부 시 File 객체를 base64 문자열로 자동 변환
551
+ * @Todo vibecode - true 시 execute()에 { name, mimeType, base64, size } 형태로 전달
552
+ */
553
+ autoConvertBase64?: boolean;
544
554
  }
545
555
  /**
546
556
  * @description 스킬 실행 상태 (메시지에 첨부)
@@ -576,6 +586,14 @@ interface ModelConfig {
576
586
  name: string;
577
587
  provider: ProviderType;
578
588
  description?: string;
589
+ /** @Todo vibecode - UI 표시용 아이콘 (URL 또는 아이콘 이름) */
590
+ icon?: string;
591
+ /** @Todo vibecode - 최대 출력 토큰 수 */
592
+ maxTokens?: number;
593
+ /** @Todo vibecode - 컨텍스트 윈도우 크기 (토큰) */
594
+ contextWindow?: number;
595
+ /** @Todo vibecode - 호스트 커스텀 필드 허용 */
596
+ [key: string]: unknown;
579
597
  }
580
598
  interface PromptTemplate {
581
599
  id: string;
@@ -396,6 +396,11 @@ interface ChatToolDefinition {
396
396
  * @Todo vibecode - MIME 와일드카드 ('image/*'), 확장자 ('.pdf') 지원
397
397
  */
398
398
  acceptedTypes?: string[];
399
+ /**
400
+ * @description 파일 첨부 시 File 객체를 base64 문자열로 자동 변환
401
+ * @Todo vibecode - true 시 execute()에 { name, mimeType, base64, size } 형태로 전달
402
+ */
403
+ autoConvertBase64?: boolean;
399
404
  }
400
405
  interface ChatMessage {
401
406
  id: string;
@@ -541,6 +546,11 @@ interface SkillConfig<TParams = Record<string, unknown>> {
541
546
  * @example ['image/*', 'application/pdf', '.docx']
542
547
  */
543
548
  acceptedTypes?: string[];
549
+ /**
550
+ * @description 파일 첨부 시 File 객체를 base64 문자열로 자동 변환
551
+ * @Todo vibecode - true 시 execute()에 { name, mimeType, base64, size } 형태로 전달
552
+ */
553
+ autoConvertBase64?: boolean;
544
554
  }
545
555
  /**
546
556
  * @description 스킬 실행 상태 (메시지에 첨부)
@@ -576,6 +586,14 @@ interface ModelConfig {
576
586
  name: string;
577
587
  provider: ProviderType;
578
588
  description?: string;
589
+ /** @Todo vibecode - UI 표시용 아이콘 (URL 또는 아이콘 이름) */
590
+ icon?: string;
591
+ /** @Todo vibecode - 최대 출력 토큰 수 */
592
+ maxTokens?: number;
593
+ /** @Todo vibecode - 컨텍스트 윈도우 크기 (토큰) */
594
+ contextWindow?: number;
595
+ /** @Todo vibecode - 호스트 커스텀 필드 허용 */
596
+ [key: string]: unknown;
579
597
  }
580
598
  interface PromptTemplate {
581
599
  id: string;
@@ -1199,7 +1199,7 @@ var useProject = (options) => {
1199
1199
  const currentProject = projects.find((p) => p.id === currentProjectId) || null;
1200
1200
  (0, import_react4.useEffect)(() => {
1201
1201
  if (!enabled || useExternalStorage || !initializedRef.current) return;
1202
- if (projects.length > 0) {
1202
+ if (projects.length > 0 && typeof window !== "undefined") {
1203
1203
  localStorage.setItem(storageKey, JSON.stringify(projects));
1204
1204
  }
1205
1205
  }, [enabled, projects, storageKey, useExternalStorage]);
@@ -1233,7 +1233,7 @@ var useProject = (options) => {
1233
1233
  }
1234
1234
  } else {
1235
1235
  try {
1236
- const saved = localStorage.getItem(storageKey);
1236
+ const saved = typeof window !== "undefined" ? localStorage.getItem(storageKey) : null;
1237
1237
  if (saved) {
1238
1238
  const parsed = JSON.parse(saved);
1239
1239
  if (!parsed.find((p) => p.id === DEFAULT_PROJECT_ID)) {
@@ -1540,6 +1540,7 @@ var convertToolsToSkills = (tools, onToolCall) => {
1540
1540
  label: tool.label || tool.name,
1541
1541
  icon: tool.icon,
1542
1542
  acceptedTypes: tool.acceptedTypes,
1543
+ autoConvertBase64: tool.autoConvertBase64,
1543
1544
  parameters: {
1544
1545
  type: "object",
1545
1546
  properties: Object.fromEntries(
@@ -1613,6 +1614,20 @@ var DEFAULT_KEEP_RECENT = 6;
1613
1614
  var DEFAULT_RECOMPRESSION_THRESHOLD = 10;
1614
1615
  var DEFAULT_TOKEN_LIMIT = 8e3;
1615
1616
  var generateId3 = (prefix) => `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
1617
+ var fileToBase64 = (file) => new Promise((resolve, reject) => {
1618
+ const reader = new FileReader();
1619
+ reader.onload = () => resolve(reader.result.split(",")[1] || "");
1620
+ reader.onerror = reject;
1621
+ reader.readAsDataURL(file);
1622
+ });
1623
+ var convertAttachmentsToBase64 = async (attachments) => Promise.all(
1624
+ attachments.map(async (att) => ({
1625
+ name: att.name,
1626
+ mimeType: att.mimeType,
1627
+ base64: await fileToBase64(att.file),
1628
+ size: att.size
1629
+ }))
1630
+ );
1616
1631
  var generateTitle = (messages) => {
1617
1632
  const firstUserMessage = messages.find((m) => m.role === "user");
1618
1633
  if (!firstUserMessage) return "\uC0C8 \uB300\uD654";
@@ -1835,11 +1850,13 @@ var useChatUI = (options) => {
1835
1850
  }).finally(() => {
1836
1851
  setIsSessionsLoading(false);
1837
1852
  });
1838
- const savedPersonalization2 = localStorage.getItem(`${storageKey}_personalization`);
1839
- if (savedPersonalization2) {
1840
- try {
1841
- setPersonalization(JSON.parse(savedPersonalization2));
1842
- } catch {
1853
+ if (typeof window !== "undefined") {
1854
+ const savedPersonalization2 = localStorage.getItem(`${storageKey}_personalization`);
1855
+ if (savedPersonalization2) {
1856
+ try {
1857
+ setPersonalization(JSON.parse(savedPersonalization2));
1858
+ } catch {
1859
+ }
1843
1860
  }
1844
1861
  }
1845
1862
  return;
@@ -1847,6 +1864,7 @@ var useChatUI = (options) => {
1847
1864
  if (enableProjects) {
1848
1865
  migrateSessionsToProjects(storageKey);
1849
1866
  }
1867
+ if (typeof window === "undefined") return;
1850
1868
  const saved = localStorage.getItem(storageKey);
1851
1869
  if (saved) {
1852
1870
  try {
@@ -2426,7 +2444,8 @@ ${finalContent}`;
2426
2444
  });
2427
2445
  if (matchedFiles.length === 0) continue;
2428
2446
  try {
2429
- const result = await skillConfig.execute({ files: matchedFiles, userMessage: finalContent });
2447
+ const filesToPass = skillConfig.autoConvertBase64 ? await convertAttachmentsToBase64(matchedFiles) : matchedFiles;
2448
+ const result = await skillConfig.execute({ files: filesToPass, userMessage: finalContent });
2430
2449
  attachmentResults.push({
2431
2450
  type: "tool_result",
2432
2451
  toolName: skillName,
@@ -2444,6 +2463,20 @@ ${finalContent}`;
2444
2463
  }
2445
2464
  }
2446
2465
  }
2466
+ let shouldContinueAfterAttachment = continueAfterToolResult;
2467
+ if (attachmentResults.length > 0) {
2468
+ for (const part of attachmentResults) {
2469
+ if (part.type === "tool_result" && onSkillCompleteRef.current) {
2470
+ const decision = onSkillCompleteRef.current(part.toolName, {
2471
+ content: part.result.content,
2472
+ metadata: part.result.metadata,
2473
+ sources: part.result.sources
2474
+ });
2475
+ shouldContinueAfterAttachment = decision === "continue";
2476
+ if (!shouldContinueAfterAttachment) break;
2477
+ }
2478
+ }
2479
+ }
2447
2480
  const assistantMessageId = generateId3("msg");
2448
2481
  const assistantMessage = {
2449
2482
  id: assistantMessageId,
@@ -2491,6 +2524,10 @@ ${finalContent}`;
2491
2524
  }).catch(() => {
2492
2525
  });
2493
2526
  }
2527
+ if (attachmentResults.length > 0 && !shouldContinueAfterAttachment) {
2528
+ setIsLoading(false);
2529
+ return;
2530
+ }
2494
2531
  setIsLoading(true);
2495
2532
  abortControllerRef.current = new AbortController();
2496
2533
  try {
@@ -2550,6 +2587,20 @@ ${currentContextSummary}` },
2550
2587
  } else {
2551
2588
  chatMessages = messagesToSend.map((m) => ({ role: m.role, content: m.content }));
2552
2589
  }
2590
+ if (attachmentResults.length > 0 && shouldContinueAfterAttachment) {
2591
+ const attachmentContext = attachmentResults.filter((part) => part.type === "tool_result").map((part) => `[${part.label || part.toolName} \uACB0\uACFC]
2592
+ ${part.result.content}`).join("\n\n");
2593
+ if (attachmentContext) {
2594
+ chatMessages.push({
2595
+ role: "user",
2596
+ content: `\uD30C\uC77C \uBD84\uC11D \uACB0\uACFC:
2597
+
2598
+ ${attachmentContext}
2599
+
2600
+ \uC704 \uACB0\uACFC\uB97C \uCC38\uACE0\uD558\uC5EC \uB2F5\uBCC0\uD574\uC8FC\uC138\uC694.`
2601
+ });
2602
+ }
2603
+ }
2553
2604
  console.log("[ChatUI] Messages to send:", chatMessages.length, chatMessages.map((m) => ({ role: m.role, content: m.content.slice(0, 50) })));
2554
2605
  const baseSystemPrompt = buildSystemPrompt();
2555
2606
  const combinedSystemPrompt = [baseSystemPrompt, actionPrompt].filter(Boolean).join("\n\n");
@@ -3537,7 +3588,25 @@ ${currentSession.contextSummary}` },
3537
3588
  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.`
3538
3589
  });
3539
3590
  } else {
3540
- executeManualSkill(skillName, { query: input });
3591
+ const currentQuery = input;
3592
+ executeManualSkill(skillName, { query: currentQuery }).then((result) => {
3593
+ if (!result || !result.content) return;
3594
+ let shouldContinue = continueAfterToolResult;
3595
+ if (onSkillCompleteRef.current) {
3596
+ const decision = onSkillCompleteRef.current(skillName, result);
3597
+ shouldContinue = decision === "continue";
3598
+ }
3599
+ if (!shouldContinue) return;
3600
+ skipNextSkillParsingRef.current = true;
3601
+ const resultPrompt = `\uC2A4\uD0AC "${skillName}" \uC2E4\uD589 \uACB0\uACFC:
3602
+
3603
+ ${result.content}
3604
+
3605
+ \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.`;
3606
+ setTimeout(() => {
3607
+ sendMessage(resultPrompt, { hiddenUserMessage: true });
3608
+ }, 100);
3609
+ });
3541
3610
  }
3542
3611
  },
3543
3612
  // Project
@@ -3968,6 +4037,7 @@ var ChatSidebar = ({
3968
4037
  className: `chatllm-sidebar chatllm-sidebar-transition ${theme ? `chatllm-root ${themeClass}` : ""}`,
3969
4038
  style: {
3970
4039
  width: isOpen ? sidebarWidth : "0",
4040
+ height: "100%",
3971
4041
  flexShrink: 0,
3972
4042
  backgroundColor: "var(--chatllm-sidebar-bg)",
3973
4043
  borderRight: isOpen ? "1px solid var(--chatllm-border)" : "none",
@@ -10371,6 +10441,7 @@ var ChatUIView = ({
10371
10441
  currentProjectId,
10372
10442
  onSelectProject: projects.length > 0 ? selectProject : void 0,
10373
10443
  onNewProject: projects.length > 0 ? () => {
10444
+ if (typeof window === "undefined") return;
10374
10445
  const title = window.prompt("\uD504\uB85C\uC81D\uD2B8 \uC774\uB984\uC744 \uC785\uB825\uD558\uC138\uC694");
10375
10446
  if (title?.trim()) {
10376
10447
  createProject({ title: title.trim() });