@gendive/chatllm 0.17.37 → 0.17.38

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.
@@ -819,11 +819,6 @@ interface ChatUIProps {
819
819
  * @Todo vibecode - 라이브러리가 도구 호출 판단 후 호스트에 실행 위임
820
820
  */
821
821
  onToolCall?: (name: string, params: Record<string, unknown>) => Promise<ToolCallResult>;
822
- /**
823
- * @description 이미지 첨부 시 처리 방식 (기본: 'auto-execute')
824
- * @Todo vibecode - 'auto-execute': attachment 스킬 자동 실행, 'ai-decide': 이미지를 AI에 전달하여 AI가 도구 선택
825
- */
826
- imageAttachmentMode?: 'auto-execute' | 'ai-decide';
827
822
  /**
828
823
  * @description 도구/스킬 실행 후 AI 후속 스트리밍 계속 여부 (기본: true)
829
824
  * @Todo vibecode - false 시 도구 결과만 표시하고 AI 후속 응답 생략
@@ -886,29 +881,10 @@ interface ChatUIProps {
886
881
  */
887
882
  onDeleteProjectFile?: (projectId: string, fileId: string) => Promise<void>;
888
883
  }
889
- /**
890
- * @description OpenAI/Anthropic 호환 멀티모달 콘텐츠 파트
891
- * @Todo vibecode - 이미지 첨부 시 AI에 직접 전달 (ai-decide 모드)
892
- */
893
- type MultimodalContentPart = {
894
- type: 'text';
895
- text: string;
896
- } | {
897
- type: 'image_url';
898
- image_url: {
899
- url: string;
900
- detail?: 'auto' | 'low' | 'high';
901
- };
902
- };
903
- /**
904
- * @description 메시지 콘텐츠 (단순 텍스트 또는 멀티모달)
905
- * @Todo vibecode - 하위 호환: string이면 기존 텍스트, 배열이면 멀티모달
906
- */
907
- type MessageContent = string | MultimodalContentPart[];
908
884
  interface SendMessageParams {
909
885
  messages: {
910
886
  role: 'user' | 'assistant' | 'system';
911
- content: MessageContent;
887
+ content: string;
912
888
  }[];
913
889
  model: string;
914
890
  provider: ProviderType;
@@ -1539,11 +1515,6 @@ interface UseChatUIOptions {
1539
1515
  onAddProjectFile?: (projectId: string, file: File) => Promise<ProjectFile>;
1540
1516
  /** @Todo vibecode - 프로젝트 파일 삭제 콜백 */
1541
1517
  onDeleteProjectFile?: (projectId: string, fileId: string) => Promise<void>;
1542
- /**
1543
- * @description 이미지 첨부 시 처리 방식 (기본: 'auto-execute')
1544
- * @Todo vibecode - 'auto-execute': attachment 스킬 자동 실행, 'ai-decide': 이미지를 AI에 전달하여 AI가 도구 선택
1545
- */
1546
- imageAttachmentMode?: 'auto-execute' | 'ai-decide';
1547
1518
  /**
1548
1519
  * @description 도구 실행 후 AI 스트리밍 계속 여부 (기본: true, 하위호환)
1549
1520
  * @Todo vibecode - false 시 도구 결과만 표시하고 AI 후속 응답 생략
@@ -1643,7 +1614,9 @@ interface UseSkillsReturn {
1643
1614
  * @Todo vibecode - callbacks로 진행 상태/스트리밍/중단 시그널 전달
1644
1615
  * @returns skillCall이 있으면 실행하고 결과 반환, 없으면 null
1645
1616
  */
1646
- handleSkillCall: (content: string, callbacks?: SkillExecuteCallbacks) => Promise<{
1617
+ handleSkillCall: (content: string, callbacks?: SkillExecuteCallbacks,
1618
+ /** @Todo vibecode - 스킬 params에 병합할 추가 데이터 (예: 첨부 이미지) */
1619
+ extraParams?: Record<string, unknown>) => Promise<{
1647
1620
  skillCall: ParsedSkillCall | null;
1648
1621
  cleanContent: string;
1649
1622
  result: SkillExecutionResult | null;
@@ -2148,4 +2121,4 @@ declare const DEFAULT_PROJECT_TITLE = "\uAE30\uBCF8 \uD504\uB85C\uC81D\uD2B8";
2148
2121
  */
2149
2122
  declare const migrateSessionsToProjects: (storageKey: string) => void;
2150
2123
 
2151
- export { type ActionItem, type ActionMenuProps, type AdvancedResearchOptions, type AlternativeResponse, type ChatAttachment, ChatHeader, ChatInput, type ChatMessage, type ChatProject, type ChatSession, ChatSidebar, type ChatToolDefinition, type ChatToolParameter, ChatUI, type ChatUIComponents, type ChatUIProps, type ChecklistBlock, ChecklistCard, type ChecklistCardProps, type ChecklistItem, ChecklistMiniIndicator, type ChecklistMiniIndicatorProps, ChecklistPanel, type ChecklistPanelProps, ContentPartRenderer, type ContentPartRendererProps, DEFAULT_PROJECT_ID, DEFAULT_PROJECT_TITLE, type DeepResearchCallbacks, type DeepResearchProgress, DeepResearchProgressUI, EmptyState, type EmptyStateProps, type ErrorContentPart, FileContentCard, type FileContentCardProps, type FileContentPart, type HeaderProps, Icon, type IconName, type IconProps, IconSvg, ImageContentCard, type ImageContentCardProps, type ImageContentPart, type InputProps, LinkChip, type LinkChipProps, type ManualSkillItem, MarkdownRenderer, type MarkdownRendererProps, type MemoryItem, MemoryPanel, type MemoryPanelProps, MessageBubble, type MessageBubbleProps, type MessageContent, type MessageContentPart, MessageList, type MessageListProps, type ModelConfig, type ModelSelectorProps, type MultimodalContentPart, type PersonalizationConfig, type PollBlock, PollCard, type PollCardProps, type PollOption, type PollQuestion, type PollResponse, type PollState, type ProjectFile, ProjectSelector, type ProjectSelectorProps, ProjectSettingsModal, type ProjectSettingsModalProps, type PromptTemplate, type ProviderType, type ResponseStyle, type SearchResult, type SearchResultContentPart, type SendMessageParams, type SendMessageResponse, SettingsModal, type SettingsModalProps, type SettingsTab, type SidebarProps, type SkillConfig, type SkillExecuteCallbacks, type SkillExecution, type SkillExecutionResult, type SkillProgress, SkillProgressUI, type SkillProgressUIProps, type SkillTrigger, type SourceItem, type SubAgentProgress, type SuggestedPrompt, type TextContentPart, type ThemeConfig, type ThemeMode, type ToolCallResult, type ToolLoadingContentPart, type ToolResultContentPart, type UseChatUIOptions, type UseChatUIReturn, type UseDeepResearchOptions, type UseProjectOptions, type UseProjectReturn, type UseSkillsOptions, type UseSkillsReturn, type UserProfile, convertToolsToSkills, createAdvancedResearchSkill, createDeepResearchSkill, migrateSessionsToProjects, useChatUI, useDeepResearch, useProject, useSkills };
2124
+ export { type ActionItem, type ActionMenuProps, type AdvancedResearchOptions, type AlternativeResponse, type ChatAttachment, ChatHeader, ChatInput, type ChatMessage, type ChatProject, type ChatSession, ChatSidebar, type ChatToolDefinition, type ChatToolParameter, ChatUI, type ChatUIComponents, type ChatUIProps, type ChecklistBlock, ChecklistCard, type ChecklistCardProps, type ChecklistItem, ChecklistMiniIndicator, type ChecklistMiniIndicatorProps, ChecklistPanel, type ChecklistPanelProps, ContentPartRenderer, type ContentPartRendererProps, DEFAULT_PROJECT_ID, DEFAULT_PROJECT_TITLE, type DeepResearchCallbacks, type DeepResearchProgress, DeepResearchProgressUI, EmptyState, type EmptyStateProps, type ErrorContentPart, FileContentCard, type FileContentCardProps, type FileContentPart, type HeaderProps, Icon, type IconName, type IconProps, IconSvg, ImageContentCard, type ImageContentCardProps, type ImageContentPart, type InputProps, LinkChip, type LinkChipProps, type ManualSkillItem, MarkdownRenderer, type MarkdownRendererProps, type MemoryItem, MemoryPanel, type MemoryPanelProps, MessageBubble, type MessageBubbleProps, type MessageContentPart, MessageList, type MessageListProps, type ModelConfig, type ModelSelectorProps, type PersonalizationConfig, type PollBlock, PollCard, type PollCardProps, type PollOption, type PollQuestion, type PollResponse, type PollState, type ProjectFile, ProjectSelector, type ProjectSelectorProps, ProjectSettingsModal, type ProjectSettingsModalProps, type PromptTemplate, type ProviderType, type ResponseStyle, type SearchResult, type SearchResultContentPart, type SendMessageParams, type SendMessageResponse, SettingsModal, type SettingsModalProps, type SettingsTab, type SidebarProps, type SkillConfig, type SkillExecuteCallbacks, type SkillExecution, type SkillExecutionResult, type SkillProgress, SkillProgressUI, type SkillProgressUIProps, type SkillTrigger, type SourceItem, type SubAgentProgress, type SuggestedPrompt, type TextContentPart, type ThemeConfig, type ThemeMode, type ToolCallResult, type ToolLoadingContentPart, type ToolResultContentPart, type UseChatUIOptions, type UseChatUIReturn, type UseDeepResearchOptions, type UseProjectOptions, type UseProjectReturn, type UseSkillsOptions, type UseSkillsReturn, type UserProfile, convertToolsToSkills, createAdvancedResearchSkill, createDeepResearchSkill, migrateSessionsToProjects, useChatUI, useDeepResearch, useProject, useSkills };
@@ -819,11 +819,6 @@ interface ChatUIProps {
819
819
  * @Todo vibecode - 라이브러리가 도구 호출 판단 후 호스트에 실행 위임
820
820
  */
821
821
  onToolCall?: (name: string, params: Record<string, unknown>) => Promise<ToolCallResult>;
822
- /**
823
- * @description 이미지 첨부 시 처리 방식 (기본: 'auto-execute')
824
- * @Todo vibecode - 'auto-execute': attachment 스킬 자동 실행, 'ai-decide': 이미지를 AI에 전달하여 AI가 도구 선택
825
- */
826
- imageAttachmentMode?: 'auto-execute' | 'ai-decide';
827
822
  /**
828
823
  * @description 도구/스킬 실행 후 AI 후속 스트리밍 계속 여부 (기본: true)
829
824
  * @Todo vibecode - false 시 도구 결과만 표시하고 AI 후속 응답 생략
@@ -886,29 +881,10 @@ interface ChatUIProps {
886
881
  */
887
882
  onDeleteProjectFile?: (projectId: string, fileId: string) => Promise<void>;
888
883
  }
889
- /**
890
- * @description OpenAI/Anthropic 호환 멀티모달 콘텐츠 파트
891
- * @Todo vibecode - 이미지 첨부 시 AI에 직접 전달 (ai-decide 모드)
892
- */
893
- type MultimodalContentPart = {
894
- type: 'text';
895
- text: string;
896
- } | {
897
- type: 'image_url';
898
- image_url: {
899
- url: string;
900
- detail?: 'auto' | 'low' | 'high';
901
- };
902
- };
903
- /**
904
- * @description 메시지 콘텐츠 (단순 텍스트 또는 멀티모달)
905
- * @Todo vibecode - 하위 호환: string이면 기존 텍스트, 배열이면 멀티모달
906
- */
907
- type MessageContent = string | MultimodalContentPart[];
908
884
  interface SendMessageParams {
909
885
  messages: {
910
886
  role: 'user' | 'assistant' | 'system';
911
- content: MessageContent;
887
+ content: string;
912
888
  }[];
913
889
  model: string;
914
890
  provider: ProviderType;
@@ -1539,11 +1515,6 @@ interface UseChatUIOptions {
1539
1515
  onAddProjectFile?: (projectId: string, file: File) => Promise<ProjectFile>;
1540
1516
  /** @Todo vibecode - 프로젝트 파일 삭제 콜백 */
1541
1517
  onDeleteProjectFile?: (projectId: string, fileId: string) => Promise<void>;
1542
- /**
1543
- * @description 이미지 첨부 시 처리 방식 (기본: 'auto-execute')
1544
- * @Todo vibecode - 'auto-execute': attachment 스킬 자동 실행, 'ai-decide': 이미지를 AI에 전달하여 AI가 도구 선택
1545
- */
1546
- imageAttachmentMode?: 'auto-execute' | 'ai-decide';
1547
1518
  /**
1548
1519
  * @description 도구 실행 후 AI 스트리밍 계속 여부 (기본: true, 하위호환)
1549
1520
  * @Todo vibecode - false 시 도구 결과만 표시하고 AI 후속 응답 생략
@@ -1643,7 +1614,9 @@ interface UseSkillsReturn {
1643
1614
  * @Todo vibecode - callbacks로 진행 상태/스트리밍/중단 시그널 전달
1644
1615
  * @returns skillCall이 있으면 실행하고 결과 반환, 없으면 null
1645
1616
  */
1646
- handleSkillCall: (content: string, callbacks?: SkillExecuteCallbacks) => Promise<{
1617
+ handleSkillCall: (content: string, callbacks?: SkillExecuteCallbacks,
1618
+ /** @Todo vibecode - 스킬 params에 병합할 추가 데이터 (예: 첨부 이미지) */
1619
+ extraParams?: Record<string, unknown>) => Promise<{
1647
1620
  skillCall: ParsedSkillCall | null;
1648
1621
  cleanContent: string;
1649
1622
  result: SkillExecutionResult | null;
@@ -2148,4 +2121,4 @@ declare const DEFAULT_PROJECT_TITLE = "\uAE30\uBCF8 \uD504\uB85C\uC81D\uD2B8";
2148
2121
  */
2149
2122
  declare const migrateSessionsToProjects: (storageKey: string) => void;
2150
2123
 
2151
- export { type ActionItem, type ActionMenuProps, type AdvancedResearchOptions, type AlternativeResponse, type ChatAttachment, ChatHeader, ChatInput, type ChatMessage, type ChatProject, type ChatSession, ChatSidebar, type ChatToolDefinition, type ChatToolParameter, ChatUI, type ChatUIComponents, type ChatUIProps, type ChecklistBlock, ChecklistCard, type ChecklistCardProps, type ChecklistItem, ChecklistMiniIndicator, type ChecklistMiniIndicatorProps, ChecklistPanel, type ChecklistPanelProps, ContentPartRenderer, type ContentPartRendererProps, DEFAULT_PROJECT_ID, DEFAULT_PROJECT_TITLE, type DeepResearchCallbacks, type DeepResearchProgress, DeepResearchProgressUI, EmptyState, type EmptyStateProps, type ErrorContentPart, FileContentCard, type FileContentCardProps, type FileContentPart, type HeaderProps, Icon, type IconName, type IconProps, IconSvg, ImageContentCard, type ImageContentCardProps, type ImageContentPart, type InputProps, LinkChip, type LinkChipProps, type ManualSkillItem, MarkdownRenderer, type MarkdownRendererProps, type MemoryItem, MemoryPanel, type MemoryPanelProps, MessageBubble, type MessageBubbleProps, type MessageContent, type MessageContentPart, MessageList, type MessageListProps, type ModelConfig, type ModelSelectorProps, type MultimodalContentPart, type PersonalizationConfig, type PollBlock, PollCard, type PollCardProps, type PollOption, type PollQuestion, type PollResponse, type PollState, type ProjectFile, ProjectSelector, type ProjectSelectorProps, ProjectSettingsModal, type ProjectSettingsModalProps, type PromptTemplate, type ProviderType, type ResponseStyle, type SearchResult, type SearchResultContentPart, type SendMessageParams, type SendMessageResponse, SettingsModal, type SettingsModalProps, type SettingsTab, type SidebarProps, type SkillConfig, type SkillExecuteCallbacks, type SkillExecution, type SkillExecutionResult, type SkillProgress, SkillProgressUI, type SkillProgressUIProps, type SkillTrigger, type SourceItem, type SubAgentProgress, type SuggestedPrompt, type TextContentPart, type ThemeConfig, type ThemeMode, type ToolCallResult, type ToolLoadingContentPart, type ToolResultContentPart, type UseChatUIOptions, type UseChatUIReturn, type UseDeepResearchOptions, type UseProjectOptions, type UseProjectReturn, type UseSkillsOptions, type UseSkillsReturn, type UserProfile, convertToolsToSkills, createAdvancedResearchSkill, createDeepResearchSkill, migrateSessionsToProjects, useChatUI, useDeepResearch, useProject, useSkills };
2124
+ export { type ActionItem, type ActionMenuProps, type AdvancedResearchOptions, type AlternativeResponse, type ChatAttachment, ChatHeader, ChatInput, type ChatMessage, type ChatProject, type ChatSession, ChatSidebar, type ChatToolDefinition, type ChatToolParameter, ChatUI, type ChatUIComponents, type ChatUIProps, type ChecklistBlock, ChecklistCard, type ChecklistCardProps, type ChecklistItem, ChecklistMiniIndicator, type ChecklistMiniIndicatorProps, ChecklistPanel, type ChecklistPanelProps, ContentPartRenderer, type ContentPartRendererProps, DEFAULT_PROJECT_ID, DEFAULT_PROJECT_TITLE, type DeepResearchCallbacks, type DeepResearchProgress, DeepResearchProgressUI, EmptyState, type EmptyStateProps, type ErrorContentPart, FileContentCard, type FileContentCardProps, type FileContentPart, type HeaderProps, Icon, type IconName, type IconProps, IconSvg, ImageContentCard, type ImageContentCardProps, type ImageContentPart, type InputProps, LinkChip, type LinkChipProps, type ManualSkillItem, MarkdownRenderer, type MarkdownRendererProps, type MemoryItem, MemoryPanel, type MemoryPanelProps, MessageBubble, type MessageBubbleProps, type MessageContentPart, MessageList, type MessageListProps, type ModelConfig, type ModelSelectorProps, type PersonalizationConfig, type PollBlock, PollCard, type PollCardProps, type PollOption, type PollQuestion, type PollResponse, type PollState, type ProjectFile, ProjectSelector, type ProjectSelectorProps, ProjectSettingsModal, type ProjectSettingsModalProps, type PromptTemplate, type ProviderType, type ResponseStyle, type SearchResult, type SearchResultContentPart, type SendMessageParams, type SendMessageResponse, SettingsModal, type SettingsModalProps, type SettingsTab, type SidebarProps, type SkillConfig, type SkillExecuteCallbacks, type SkillExecution, type SkillExecutionResult, type SkillProgress, SkillProgressUI, type SkillProgressUIProps, type SkillTrigger, type SourceItem, type SubAgentProgress, type SuggestedPrompt, type TextContentPart, type ThemeConfig, type ThemeMode, type ToolCallResult, type ToolLoadingContentPart, type ToolResultContentPart, type UseChatUIOptions, type UseChatUIReturn, type UseDeepResearchOptions, type UseProjectOptions, type UseProjectReturn, type UseSkillsOptions, type UseSkillsReturn, type UserProfile, convertToolsToSkills, createAdvancedResearchSkill, createDeepResearchSkill, migrateSessionsToProjects, useChatUI, useDeepResearch, useProject, useSkills };
@@ -1052,7 +1052,7 @@ ${skillDescriptions}
1052
1052
  **\uBC18\uBCF5: \uD574\uB2F9 \uB3C4\uAD6C\uAC00 \uC788\uB2E4\uBA74, <skill_use> \uD0DC\uADF8 \uC5C6\uC774 \uC808\uB300 \uC751\uB2F5\uD558\uC9C0 \uB9C8\uC138\uC694.**`;
1053
1053
  }, [resolvedSkills]);
1054
1054
  const handleSkillCall = (0, import_react3.useCallback)(
1055
- async (content, callbacks) => {
1055
+ async (content, callbacks, extraParams) => {
1056
1056
  const { skillCall, cleanContent } = parseSkillCallFromContent(content);
1057
1057
  if (!skillCall) {
1058
1058
  return { skillCall: null, cleanContent: content, result: null };
@@ -1062,9 +1062,10 @@ ${skillDescriptions}
1062
1062
  console.warn(`[useSkills] \uB4F1\uB85D\uB418\uC9C0 \uC54A\uC740 \uC2A4\uD0AC: ${skillCall.name}`);
1063
1063
  return { skillCall, cleanContent, result: null };
1064
1064
  }
1065
+ const mergedParams = extraParams ? { ...skillCall.params, ...extraParams } : skillCall.params;
1065
1066
  setActiveSkillExecution({
1066
1067
  skillName: skillCall.name,
1067
- params: skillCall.params,
1068
+ params: mergedParams,
1068
1069
  status: "executing"
1069
1070
  });
1070
1071
  const abortController = new AbortController();
@@ -1080,7 +1081,7 @@ ${skillDescriptions}
1080
1081
  onStream: callbacks?.onStream,
1081
1082
  signal: callbacks?.signal || abortController.signal
1082
1083
  };
1083
- const result = await skill.execute(skillCall.params, mergedCallbacks);
1084
+ const result = await skill.execute(mergedParams, mergedCallbacks);
1084
1085
  setActiveSkillExecution(
1085
1086
  (prev) => prev ? { ...prev, status: "done", result } : null
1086
1087
  );
@@ -1781,8 +1782,6 @@ var useChatUI = (options) => {
1781
1782
  onDeleteProject,
1782
1783
  onAddProjectFile,
1783
1784
  onDeleteProjectFile,
1784
- // Image attachment mode
1785
- imageAttachmentMode = "auto-execute",
1786
1785
  // Stream control
1787
1786
  continueAfterToolResult = true,
1788
1787
  onSkillComplete,
@@ -1859,6 +1858,7 @@ var useChatUI = (options) => {
1859
1858
  const skipNextSkillParsingRef = (0, import_react5.useRef)(false);
1860
1859
  const skipNextChecklistParsingRef = (0, import_react5.useRef)(false);
1861
1860
  const activeChecklistRef = (0, import_react5.useRef)(null);
1861
+ const pendingAttachmentDataRef = (0, import_react5.useRef)(null);
1862
1862
  const lastExtractionMsgCountRef = (0, import_react5.useRef)(0);
1863
1863
  const memoryOptions = (0, import_react5.useMemo)(
1864
1864
  () => ({
@@ -2603,20 +2603,12 @@ ${finalContent}`;
2603
2603
  })
2604
2604
  );
2605
2605
  let attachmentResults = [];
2606
- const aiDecideImageAttachments = [];
2607
2606
  if (currentAttachments.length > 0) {
2608
- const autoExecAttachments = imageAttachmentMode === "ai-decide" ? currentAttachments.filter((att) => {
2609
- if (att.type === "image") {
2610
- aiDecideImageAttachments.push(att);
2611
- return false;
2612
- }
2613
- return true;
2614
- }) : currentAttachments;
2615
2607
  const attachmentSkills = Object.entries(resolvedSkills).filter(
2616
2608
  ([, config]) => config.trigger === "attachment"
2617
2609
  );
2618
2610
  for (const [skillName, skillConfig] of attachmentSkills) {
2619
- const matchedFiles = autoExecAttachments.filter((att) => {
2611
+ const matchedFiles = currentAttachments.filter((att) => {
2620
2612
  if (!skillConfig.acceptedTypes || skillConfig.acceptedTypes.length === 0) return true;
2621
2613
  return skillConfig.acceptedTypes.some((type) => {
2622
2614
  if (type.startsWith(".")) return att.name.toLowerCase().endsWith(type.toLowerCase());
@@ -2701,6 +2693,14 @@ ${finalContent}`;
2701
2693
  );
2702
2694
  }
2703
2695
  }
2696
+ const hasImageAttachments = currentAttachments.some((a) => a.type === "image");
2697
+ const hasUserText = finalContent.trim().length > 0;
2698
+ if (hasImageAttachments && hasUserText) {
2699
+ const imageAttachments = currentAttachments.filter((a) => a.type === "image");
2700
+ pendingAttachmentDataRef.current = await convertAttachmentsToBase64(imageAttachments);
2701
+ } else {
2702
+ pendingAttachmentDataRef.current = null;
2703
+ }
2704
2704
  }
2705
2705
  let shouldContinueAfterAttachment = continueAfterToolResult;
2706
2706
  if (attachmentResults.length > 0) {
@@ -2794,48 +2794,37 @@ ${currentContextSummary}` },
2794
2794
  } else {
2795
2795
  chatMessages = messagesToSend.map((m) => ({ role: m.role, content: m.content }));
2796
2796
  }
2797
- if (aiDecideImageAttachments.length > 0) {
2798
- const imageBase64s = await Promise.all(
2799
- aiDecideImageAttachments.map(async (att) => ({
2800
- mimeType: att.mimeType,
2801
- base64: await fileToBase64(att.file)
2802
- }))
2803
- );
2804
- for (let i = chatMessages.length - 1; i >= 0; i--) {
2805
- if (chatMessages[i].role === "user") {
2806
- const textContent = typeof chatMessages[i].content === "string" ? chatMessages[i].content : "";
2807
- const multimodalParts = [];
2808
- if (textContent.trim()) {
2809
- multimodalParts.push({ type: "text", text: textContent });
2810
- }
2811
- for (const img of imageBase64s) {
2812
- multimodalParts.push({
2813
- type: "image_url",
2814
- image_url: { url: `data:${img.mimeType};base64,${img.base64}` }
2815
- });
2816
- }
2817
- chatMessages[i] = { ...chatMessages[i], content: multimodalParts };
2818
- break;
2819
- }
2820
- }
2821
- }
2822
2797
  if (attachmentResults.length > 0 && shouldContinueAfterAttachment) {
2823
- skipNextSkillParsingRef.current = true;
2824
2798
  const attachmentContext = attachmentResults.filter((part) => part.type === "tool_result").map((part) => `[${part.label || part.toolName} \uACB0\uACFC]
2825
2799
  ${part.result.content}`).join("\n\n");
2826
2800
  if (attachmentContext) {
2827
- chatMessages.push({
2828
- role: "user",
2829
- /** @Todo vibecode - 원본 파일 재요청 방지 + skill_use 태그 사용 금지 명시 */
2830
- content: `\uC0AC\uC6A9\uC790\uAC00 \uCCA8\uBD80\uD55C \uD30C\uC77C\uC758 \uBD84\uC11D \uACB0\uACFC\uC785\uB2C8\uB2E4. \uC6D0\uBCF8 \uD30C\uC77C \uB0B4\uC6A9\uC774 \uC774\uBBF8 \uC544\uB798\uC5D0 \uD3EC\uD568\uB418\uC5B4 \uC788\uC73C\uBBC0\uB85C \uC6D0\uBCF8 \uD30C\uC77C\uC744 \uB2E4\uC2DC \uC694\uCCAD\uD558\uC9C0 \uB9C8\uC138\uC694.
2801
+ const hasImageWithText = pendingAttachmentDataRef.current !== null;
2802
+ if (hasImageWithText) {
2803
+ chatMessages.push({
2804
+ role: "user",
2805
+ /** @Todo vibecode - 분석 결과 제공 + 도구 선택 허용 (편집/생성 등) */
2806
+ content: `\uC0AC\uC6A9\uC790\uAC00 \uC774\uBBF8\uC9C0\uB97C \uCCA8\uBD80\uD588\uC2B5\uB2C8\uB2E4. \uC544\uB798\uB294 \uC774\uBBF8\uC9C0 \uC790\uB3D9 \uBD84\uC11D \uACB0\uACFC\uC785\uB2C8\uB2E4.
2807
+
2808
+ ${attachmentContext}
2809
+
2810
+ \uC0AC\uC6A9\uC790\uC758 \uC694\uCCAD: "${finalContent}"
2811
+
2812
+ \uC704 \uBD84\uC11D \uACB0\uACFC\uB97C \uCC38\uACE0\uD558\uB418, \uC0AC\uC6A9\uC790\uC758 \uC694\uCCAD \uC758\uB3C4\uC5D0 \uB530\uB77C \uC801\uC808\uD55C \uB3C4\uAD6C\uB97C \uC120\uD0DD\uD558\uC138\uC694. \uC774\uBBF8\uC9C0 \uD3B8\uC9D1\uC774\uB098 \uC0DD\uC131\uC774 \uD544\uC694\uD558\uBA74 \uD574\uB2F9 \uB3C4\uAD6C\uB97C \uD638\uCD9C\uD558\uACE0, \uBD84\uC11D \uACB0\uACFC\uB9CC\uC73C\uB85C \uCDA9\uBD84\uD558\uBA74 \uBC14\uB85C \uB2F5\uBCC0\uD558\uC138\uC694.`
2813
+ });
2814
+ } else {
2815
+ skipNextSkillParsingRef.current = true;
2816
+ chatMessages.push({
2817
+ role: "user",
2818
+ content: `\uC0AC\uC6A9\uC790\uAC00 \uCCA8\uBD80\uD55C \uD30C\uC77C\uC758 \uBD84\uC11D \uACB0\uACFC\uC785\uB2C8\uB2E4. \uC6D0\uBCF8 \uD30C\uC77C \uB0B4\uC6A9\uC774 \uC774\uBBF8 \uC544\uB798\uC5D0 \uD3EC\uD568\uB418\uC5B4 \uC788\uC73C\uBBC0\uB85C \uC6D0\uBCF8 \uD30C\uC77C\uC744 \uB2E4\uC2DC \uC694\uCCAD\uD558\uC9C0 \uB9C8\uC138\uC694.
2831
2819
 
2832
2820
  ${attachmentContext}
2833
2821
 
2834
2822
  \uC704 \uBD84\uC11D \uACB0\uACFC\uB9CC\uC73C\uB85C \uC0AC\uC6A9\uC790\uC758 \uC9C8\uBB38\uC5D0 \uB2F5\uBCC0\uD574\uC8FC\uC138\uC694. skill_use \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \uB9C8\uC138\uC694.`
2835
- });
2823
+ });
2824
+ }
2836
2825
  }
2837
2826
  }
2838
- console.log("[ChatUI] Messages to send:", chatMessages.length, chatMessages.map((m) => ({ role: m.role, content: typeof m.content === "string" ? m.content.slice(0, 50) : "[multimodal]" })));
2827
+ console.log("[ChatUI] Messages to send:", chatMessages.length, chatMessages.map((m) => ({ role: m.role, content: m.content.slice(0, 50) })));
2839
2828
  const baseSystemPrompt = buildSystemPrompt();
2840
2829
  const combinedSystemPrompt = [baseSystemPrompt, actionPrompt].filter(Boolean).join("\n\n");
2841
2830
  const messagesForApi = combinedSystemPrompt ? [{ role: "system", content: combinedSystemPrompt }, ...chatMessages] : chatMessages;
@@ -3053,9 +3042,11 @@ ${attachmentContext}
3053
3042
  })
3054
3043
  );
3055
3044
  try {
3045
+ const attachExtra = pendingAttachmentDataRef.current ? { __attachedImages__: pendingAttachmentDataRef.current } : void 0;
3056
3046
  const { result: skillResult } = await handleSkillCall(assistantContent, {
3057
3047
  signal: abortControllerRef.current?.signal
3058
- });
3048
+ }, attachExtra);
3049
+ pendingAttachmentDataRef.current = null;
3059
3050
  if (skillResult?.content) {
3060
3051
  accumulatedContent = skillResult.content;
3061
3052
  }
@@ -3084,6 +3075,8 @@ ${attachmentContext}
3084
3075
  })
3085
3076
  );
3086
3077
  let streamedReport = "";
3078
+ const attachExtraParams = pendingAttachmentDataRef.current ? { __attachedImages__: pendingAttachmentDataRef.current } : void 0;
3079
+ pendingAttachmentDataRef.current = null;
3087
3080
  const { result } = await handleSkillCall(assistantContent, {
3088
3081
  onProgress: (progress) => {
3089
3082
  setSessions(
@@ -3118,7 +3111,7 @@ ${attachmentContext}
3118
3111
  );
3119
3112
  },
3120
3113
  signal: abortControllerRef.current?.signal
3121
- });
3114
+ }, attachExtraParams);
3122
3115
  if (result) {
3123
3116
  if (result.metadata?.__toolResult__) {
3124
3117
  const resultType = result.metadata.resultType;
@@ -12409,7 +12402,6 @@ var ChatUIWithHook = ({
12409
12402
  skills,
12410
12403
  tools,
12411
12404
  onToolCall,
12412
- imageAttachmentMode,
12413
12405
  continueAfterToolResult,
12414
12406
  onSkillComplete,
12415
12407
  onLoadModels,
@@ -12452,7 +12444,6 @@ var ChatUIWithHook = ({
12452
12444
  skills,
12453
12445
  tools,
12454
12446
  onToolCall,
12455
- imageAttachmentMode,
12456
12447
  continueAfterToolResult,
12457
12448
  onSkillComplete,
12458
12449
  onLoadModels,