@gendive/chatllm 0.21.9 → 0.22.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.
@@ -1060,6 +1060,12 @@ interface ChatUIProps {
1060
1060
  * @Todo vibecode - 새 URL 반환 시 img.src 갱신 + contentParts url 업데이트, null 반환 시 기본 에러 처리 (#13)
1061
1061
  */
1062
1062
  onImageError?: (url: string, fileName?: string) => Promise<string | null>;
1063
+ /**
1064
+ * @description 첨부 파일 업로드 콜백 (File → URL 변환)
1065
+ * @Todo vibecode - autoConvertBase64 대신 File 객체를 직접 업로드하여 URL로 전달
1066
+ * base64 이중 변환(File→base64→File→업로드) 제거, 메모리 ~33% 절감
1067
+ */
1068
+ fileUploader?: (file: File) => Promise<string>;
1063
1069
  /**
1064
1070
  * @description Thinking 블록 표시 여부
1065
1071
  * @Todo vibecode - AI 추론 과정 표시 (기본: true)
@@ -1953,6 +1959,11 @@ interface UseChatUIOptions {
1953
1959
  * @Todo vibecode - 새 URL 반환 시 img.src 갱신, null 반환 시 기본 에러 처리 (#13)
1954
1960
  */
1955
1961
  onImageError?: (url: string, fileName?: string) => Promise<string | null>;
1962
+ /**
1963
+ * @description 첨부 파일 업로드 콜백 (File → URL 변환)
1964
+ * @Todo vibecode - autoConvertBase64 대신 File 객체를 직접 업로드하여 URL로 전달
1965
+ */
1966
+ fileUploader?: (file: File) => Promise<string>;
1956
1967
  /**
1957
1968
  * @description 외부 스토리지 사용 여부
1958
1969
  * @Todo vibecode - true 시 localStorage 대신 콜백 사용
@@ -1060,6 +1060,12 @@ interface ChatUIProps {
1060
1060
  * @Todo vibecode - 새 URL 반환 시 img.src 갱신 + contentParts url 업데이트, null 반환 시 기본 에러 처리 (#13)
1061
1061
  */
1062
1062
  onImageError?: (url: string, fileName?: string) => Promise<string | null>;
1063
+ /**
1064
+ * @description 첨부 파일 업로드 콜백 (File → URL 변환)
1065
+ * @Todo vibecode - autoConvertBase64 대신 File 객체를 직접 업로드하여 URL로 전달
1066
+ * base64 이중 변환(File→base64→File→업로드) 제거, 메모리 ~33% 절감
1067
+ */
1068
+ fileUploader?: (file: File) => Promise<string>;
1063
1069
  /**
1064
1070
  * @description Thinking 블록 표시 여부
1065
1071
  * @Todo vibecode - AI 추론 과정 표시 (기본: true)
@@ -1953,6 +1959,11 @@ interface UseChatUIOptions {
1953
1959
  * @Todo vibecode - 새 URL 반환 시 img.src 갱신, null 반환 시 기본 에러 처리 (#13)
1954
1960
  */
1955
1961
  onImageError?: (url: string, fileName?: string) => Promise<string | null>;
1962
+ /**
1963
+ * @description 첨부 파일 업로드 콜백 (File → URL 변환)
1964
+ * @Todo vibecode - autoConvertBase64 대신 File 객체를 직접 업로드하여 URL로 전달
1965
+ */
1966
+ fileUploader?: (file: File) => Promise<string>;
1956
1967
  /**
1957
1968
  * @description 외부 스토리지 사용 여부
1958
1969
  * @Todo vibecode - true 시 localStorage 대신 콜백 사용
@@ -2269,6 +2269,23 @@ var convertAttachmentsToBase64 = async (attachments) => Promise.all(
2269
2269
  size: att.size
2270
2270
  }))
2271
2271
  );
2272
+ var convertAttachmentsWithUploaderCached = async (attachments, uploader, cache) => Promise.all(
2273
+ attachments.map(async (att) => {
2274
+ let url = cache.get(att.id);
2275
+ if (!url) {
2276
+ url = await uploader(att.file);
2277
+ cache.set(att.id, url);
2278
+ }
2279
+ return {
2280
+ name: att.name,
2281
+ mimeType: att.mimeType,
2282
+ base64: "",
2283
+ url,
2284
+ size: att.size,
2285
+ source: "uploader"
2286
+ };
2287
+ })
2288
+ );
2272
2289
  var findPreviousResultImage = (messages) => {
2273
2290
  for (let i = messages.length - 1; i >= 0; i--) {
2274
2291
  const msg = messages[i];
@@ -2330,6 +2347,8 @@ var useChatUI = (options) => {
2330
2347
  // Image upload
2331
2348
  onUploadImage,
2332
2349
  onImageError,
2350
+ // File upload
2351
+ fileUploader,
2333
2352
  // External storage options
2334
2353
  useExternalStorage = false,
2335
2354
  startWithNewSession = false,
@@ -2437,6 +2456,7 @@ var useChatUI = (options) => {
2437
2456
  const onLoadModelsRef = (0, import_react6.useRef)(onLoadModels);
2438
2457
  const onUploadImageRef = (0, import_react6.useRef)(onUploadImage);
2439
2458
  const onImageErrorRef = (0, import_react6.useRef)(onImageError);
2459
+ const fileUploaderRef = (0, import_react6.useRef)(fileUploader);
2440
2460
  const globalMemoryRef = (0, import_react6.useRef)(null);
2441
2461
  (0, import_react6.useEffect)(() => {
2442
2462
  onSendMessageRef.current = onSendMessage;
@@ -2458,6 +2478,7 @@ var useChatUI = (options) => {
2458
2478
  onSessionContextChangeRef.current = onSessionContextChange;
2459
2479
  onUploadImageRef.current = onUploadImage;
2460
2480
  onImageErrorRef.current = onImageError;
2481
+ fileUploaderRef.current = fileUploader;
2461
2482
  onLoadModelsRef.current = onLoadModels;
2462
2483
  });
2463
2484
  const abortControllersRef = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
@@ -3537,6 +3558,7 @@ ${finalContent}`;
3537
3558
  const actionPrompt = selectedAction?.systemPrompt;
3538
3559
  const isHidden = options2?.hiddenUserMessage ?? false;
3539
3560
  const currentAttachments = attachments;
3561
+ const uploadedUrlMap = /* @__PURE__ */ new Map();
3540
3562
  let userContentParts;
3541
3563
  if (currentAttachments.length > 0) {
3542
3564
  userContentParts = [];
@@ -3545,8 +3567,19 @@ ${finalContent}`;
3545
3567
  }
3546
3568
  for (const att of currentAttachments) {
3547
3569
  if (att.type === "image" && att.file) {
3548
- const dataUri = await fileToDataUri(att.file);
3549
- const imageUrl = onUploadImageRef.current ? await onUploadImageRef.current(dataUri, att.name) : dataUri;
3570
+ let imageUrl;
3571
+ try {
3572
+ if (fileUploaderRef.current) {
3573
+ imageUrl = await fileUploaderRef.current(att.file);
3574
+ } else {
3575
+ const dataUri = await fileToDataUri(att.file);
3576
+ imageUrl = onUploadImageRef.current ? await onUploadImageRef.current(dataUri, att.name) : dataUri;
3577
+ }
3578
+ } catch (err) {
3579
+ console.error("[chatllm] image upload failed, falling back to data URI:", err);
3580
+ imageUrl = await fileToDataUri(att.file);
3581
+ }
3582
+ uploadedUrlMap.set(att.id, imageUrl);
3550
3583
  userContentParts.push({ type: "image", url: imageUrl, alt: att.name, fileName: att.name });
3551
3584
  } else {
3552
3585
  userContentParts.push({ type: "file", name: att.name, url: att.previewUrl || "", mimeType: att.mimeType, size: att.size });
@@ -3645,7 +3678,7 @@ ${finalContent}`;
3645
3678
  })
3646
3679
  );
3647
3680
  try {
3648
- const filesToPass = skillConfig.autoConvertBase64 ? await convertAttachmentsToBase64(matchedFiles) : matchedFiles;
3681
+ const filesToPass = fileUploaderRef.current ? await convertAttachmentsWithUploaderCached(matchedFiles, fileUploaderRef.current, uploadedUrlMap) : skillConfig.autoConvertBase64 ? await convertAttachmentsToBase64(matchedFiles) : matchedFiles;
3649
3682
  const result = await skillConfig.execute({ files: filesToPass, userMessage: finalContent });
3650
3683
  const attachResultType = result.metadata?.type || result.metadata?.resultType || "text";
3651
3684
  const toolResultPart = {
@@ -3703,7 +3736,12 @@ ${finalContent}`;
3703
3736
  const hasUserText = finalContent.trim().length > 0;
3704
3737
  if (hasImageAttachments && hasUserText) {
3705
3738
  const imageAttachments = currentAttachments.filter((a) => a.type === "image");
3706
- pendingAttachmentDataRef.current = await convertAttachmentsToBase64(imageAttachments);
3739
+ try {
3740
+ pendingAttachmentDataRef.current = fileUploaderRef.current ? await convertAttachmentsWithUploaderCached(imageAttachments, fileUploaderRef.current, uploadedUrlMap) : await convertAttachmentsToBase64(imageAttachments);
3741
+ } catch (err) {
3742
+ console.error("[chatllm] pendingAttachment conversion failed:", err);
3743
+ pendingAttachmentDataRef.current = null;
3744
+ }
3707
3745
  } else {
3708
3746
  pendingAttachmentDataRef.current = null;
3709
3747
  }
@@ -3851,7 +3889,9 @@ ${attachmentContext}
3851
3889
  }
3852
3890
  }
3853
3891
  } else if (attachmentResults.length === 0 && pendingAttachmentDataRef.current !== null) {
3854
- const isReferenceImage = pendingAttachmentDataRef.current.some((d) => d.url && !d.base64);
3892
+ const isReferenceImage = pendingAttachmentDataRef.current.some(
3893
+ (d) => d.url && !d.base64 && d.source !== "uploader"
3894
+ );
3855
3895
  if (isReferenceImage) {
3856
3896
  chatMessages.push({
3857
3897
  role: "user",
@@ -15162,7 +15202,9 @@ var ChatUIWithHook = ({
15162
15202
  onAnalyzePatterns,
15163
15203
  // Image upload
15164
15204
  onUploadImage,
15165
- onImageError
15205
+ onImageError,
15206
+ // File upload
15207
+ fileUploader
15166
15208
  }) => {
15167
15209
  const hookOptions = {
15168
15210
  models,
@@ -15213,7 +15255,9 @@ var ChatUIWithHook = ({
15213
15255
  onAnalyzePatterns,
15214
15256
  // Image upload
15215
15257
  onUploadImage,
15216
- onImageError
15258
+ onImageError,
15259
+ // File upload
15260
+ fileUploader
15217
15261
  };
15218
15262
  const state = useChatUI(hookOptions);
15219
15263
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ImageErrorContext.Provider, { value: onImageError || null, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(