@gendive/chatllm 0.17.2 → 0.17.4

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.
@@ -2426,6 +2426,39 @@ ${finalContent}`;
2426
2426
  ...isHidden && { hidden: true },
2427
2427
  ...userContentParts && { contentParts: userContentParts }
2428
2428
  };
2429
+ const capturedSessionId = sessionId;
2430
+ const currentSession2 = sessions.find((s) => s.id === capturedSessionId);
2431
+ const existingMessages = currentSession2?.messages || [];
2432
+ const isFirstMessage = !existingMessages.length;
2433
+ const contextSummary = currentSession2?.compressionState?.contextSummary || currentSession2?.contextSummary;
2434
+ const summaryAfterIndex = currentSession2?.compressionState?.summaryAfterIndex || currentSession2?.summaryAfterIndex || 0;
2435
+ let compressionCount = currentSession2?.compressionState?.compressionCount || 0;
2436
+ const assistantMessageId = generateId3("msg");
2437
+ const assistantMessage = {
2438
+ id: assistantMessageId,
2439
+ role: "assistant",
2440
+ content: "",
2441
+ model: selectedModel,
2442
+ timestamp: Date.now()
2443
+ };
2444
+ setInput("");
2445
+ setQuotedText(null);
2446
+ setSelectedAction(null);
2447
+ setAttachments([]);
2448
+ setSessions(
2449
+ (prev) => prev.map((s) => {
2450
+ if (s.id === capturedSessionId) {
2451
+ const newMessages = [...s.messages, userMessage, assistantMessage];
2452
+ return {
2453
+ ...s,
2454
+ messages: newMessages,
2455
+ title: s.messages.length === 0 ? generateTitle([userMessage]) : s.title,
2456
+ updatedAt: Date.now()
2457
+ };
2458
+ }
2459
+ return s;
2460
+ })
2461
+ );
2429
2462
  let attachmentResults = [];
2430
2463
  if (currentAttachments.length > 0) {
2431
2464
  const attachmentSkills = Object.entries(resolvedSkills).filter(
@@ -2444,10 +2477,29 @@ ${finalContent}`;
2444
2477
  });
2445
2478
  });
2446
2479
  if (matchedFiles.length === 0) continue;
2480
+ setSessions(
2481
+ (prev) => prev.map((s) => {
2482
+ if (s.id !== capturedSessionId) return s;
2483
+ return {
2484
+ ...s,
2485
+ messages: s.messages.map((m) => {
2486
+ if (m.id !== assistantMessageId) return m;
2487
+ return {
2488
+ ...m,
2489
+ contentParts: [...m.contentParts || [], {
2490
+ type: "tool_loading",
2491
+ toolName: skillName,
2492
+ label: skillConfig.label
2493
+ }]
2494
+ };
2495
+ })
2496
+ };
2497
+ })
2498
+ );
2447
2499
  try {
2448
2500
  const filesToPass = skillConfig.autoConvertBase64 ? await convertAttachmentsToBase64(matchedFiles) : matchedFiles;
2449
2501
  const result = await skillConfig.execute({ files: filesToPass, userMessage: finalContent });
2450
- attachmentResults.push({
2502
+ const toolResultPart = {
2451
2503
  type: "tool_result",
2452
2504
  toolName: skillName,
2453
2505
  label: skillConfig.label,
@@ -2458,9 +2510,44 @@ ${finalContent}`;
2458
2510
  metadata: result.metadata,
2459
2511
  sources: result.sources
2460
2512
  }
2461
- });
2513
+ };
2514
+ attachmentResults.push(toolResultPart);
2515
+ setSessions(
2516
+ (prev) => prev.map((s) => {
2517
+ if (s.id !== capturedSessionId) return s;
2518
+ return {
2519
+ ...s,
2520
+ messages: s.messages.map((m) => {
2521
+ if (m.id !== assistantMessageId) return m;
2522
+ return {
2523
+ ...m,
2524
+ contentParts: (m.contentParts || []).map(
2525
+ (p) => p.type === "tool_loading" && p.toolName === skillName ? toolResultPart : p
2526
+ )
2527
+ };
2528
+ })
2529
+ };
2530
+ })
2531
+ );
2462
2532
  } catch (error) {
2463
2533
  console.error(`[useChatUI] attachment skill ${skillName} failed:`, error);
2534
+ setSessions(
2535
+ (prev) => prev.map((s) => {
2536
+ if (s.id !== capturedSessionId) return s;
2537
+ return {
2538
+ ...s,
2539
+ messages: s.messages.map((m) => {
2540
+ if (m.id !== assistantMessageId) return m;
2541
+ return {
2542
+ ...m,
2543
+ contentParts: (m.contentParts || []).filter(
2544
+ (p) => !(p.type === "tool_loading" && p.toolName === skillName)
2545
+ )
2546
+ };
2547
+ })
2548
+ };
2549
+ })
2550
+ );
2464
2551
  }
2465
2552
  }
2466
2553
  }
@@ -2478,40 +2565,6 @@ ${finalContent}`;
2478
2565
  }
2479
2566
  }
2480
2567
  }
2481
- const assistantMessageId = generateId3("msg");
2482
- const assistantMessage = {
2483
- id: assistantMessageId,
2484
- role: "assistant",
2485
- content: "",
2486
- model: selectedModel,
2487
- timestamp: Date.now(),
2488
- ...attachmentResults.length > 0 && { contentParts: attachmentResults }
2489
- };
2490
- setInput("");
2491
- setQuotedText(null);
2492
- setSelectedAction(null);
2493
- setAttachments([]);
2494
- const capturedSessionId = sessionId;
2495
- const currentSession2 = sessions.find((s) => s.id === capturedSessionId);
2496
- const existingMessages = currentSession2?.messages || [];
2497
- const isFirstMessage = !existingMessages.length;
2498
- const contextSummary = currentSession2?.compressionState?.contextSummary || currentSession2?.contextSummary;
2499
- const summaryAfterIndex = currentSession2?.compressionState?.summaryAfterIndex || currentSession2?.summaryAfterIndex || 0;
2500
- let compressionCount = currentSession2?.compressionState?.compressionCount || 0;
2501
- setSessions(
2502
- (prev) => prev.map((s) => {
2503
- if (s.id === capturedSessionId) {
2504
- const newMessages = [...s.messages, userMessage, assistantMessage];
2505
- return {
2506
- ...s,
2507
- messages: newMessages,
2508
- title: s.messages.length === 0 ? generateTitle([userMessage]) : s.title,
2509
- updatedAt: Date.now()
2510
- };
2511
- }
2512
- return s;
2513
- })
2514
- );
2515
2568
  if (isFirstMessage && generateTitleRef.current) {
2516
2569
  Promise.resolve(generateTitleRef.current(finalContent)).then((generatedTitle) => {
2517
2570
  if (generatedTitle && generatedTitle.trim()) {
@@ -2588,6 +2641,7 @@ ${currentContextSummary}` },
2588
2641
  chatMessages = messagesToSend.map((m) => ({ role: m.role, content: m.content }));
2589
2642
  }
2590
2643
  if (attachmentResults.length > 0 && shouldContinueAfterAttachment) {
2644
+ skipNextSkillParsingRef.current = true;
2591
2645
  const attachmentContext = attachmentResults.filter((part) => part.type === "tool_result").map((part) => `[${part.label || part.toolName} \uACB0\uACFC]
2592
2646
  ${part.result.content}`).join("\n\n");
2593
2647
  if (attachmentContext) {
@@ -8309,7 +8363,7 @@ var MessageBubble = ({
8309
8363
  ] })
8310
8364
  ] })
8311
8365
  ] }),
8312
- message.contentParts?.length ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: { wordBreak: "break-word" }, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8366
+ message.contentParts?.length && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: { wordBreak: "break-word" }, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8313
8367
  ContentPartRenderer,
8314
8368
  {
8315
8369
  parts: message.contentParts,
@@ -8317,7 +8371,8 @@ var MessageBubble = ({
8317
8371
  showThinking,
8318
8372
  thinkingDefaultOpen
8319
8373
  }
8320
- ) }) : displayContent ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: { wordBreak: "break-word" }, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8374
+ ) }),
8375
+ displayContent ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: { wordBreak: "break-word" }, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8321
8376
  MarkdownRenderer,
8322
8377
  {
8323
8378
  content: displayContent,
@@ -8649,9 +8704,26 @@ var MessageList = ({
8649
8704
  const containerRef = (0, import_react17.useRef)(null);
8650
8705
  const [selectedText, setSelectedText] = (0, import_react17.useState)("");
8651
8706
  const [selectionPosition, setSelectionPosition] = (0, import_react17.useState)(null);
8707
+ const [showScrollButton, setShowScrollButton] = (0, import_react17.useState)(false);
8708
+ const isUserScrolledUpRef = (0, import_react17.useRef)(false);
8709
+ const SCROLL_THRESHOLD = 100;
8710
+ const handleScroll = (0, import_react17.useCallback)(() => {
8711
+ if (!containerRef.current) return;
8712
+ const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
8713
+ const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
8714
+ const isNearBottom = distanceFromBottom < SCROLL_THRESHOLD;
8715
+ isUserScrolledUpRef.current = !isNearBottom;
8716
+ setShowScrollButton(!isNearBottom);
8717
+ }, []);
8652
8718
  (0, import_react17.useEffect)(() => {
8719
+ if (isUserScrolledUpRef.current) return;
8653
8720
  messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
8654
8721
  }, [messages]);
8722
+ const scrollToBottom = (0, import_react17.useCallback)(() => {
8723
+ isUserScrolledUpRef.current = false;
8724
+ setShowScrollButton(false);
8725
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
8726
+ }, []);
8655
8727
  const handleMouseUp = (0, import_react17.useCallback)(() => {
8656
8728
  const selection = typeof window !== "undefined" ? window.getSelection() : null;
8657
8729
  const text = selection?.toString().trim();
@@ -8696,6 +8768,7 @@ var MessageList = ({
8696
8768
  overflow: "auto",
8697
8769
  position: "relative"
8698
8770
  },
8771
+ onScroll: handleScroll,
8699
8772
  onMouseUp: handleMouseUp,
8700
8773
  children: [
8701
8774
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
@@ -8748,6 +8821,32 @@ var MessageList = ({
8748
8821
  ]
8749
8822
  }
8750
8823
  ),
8824
+ showScrollButton && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
8825
+ "button",
8826
+ {
8827
+ onClick: scrollToBottom,
8828
+ "aria-label": "\uB9E8 \uC544\uB798\uB85C \uC2A4\uD06C\uB864",
8829
+ style: {
8830
+ position: "sticky",
8831
+ bottom: "260px",
8832
+ left: "50%",
8833
+ transform: "translateX(-50%)",
8834
+ width: "40px",
8835
+ height: "40px",
8836
+ borderRadius: "50%",
8837
+ backgroundColor: "var(--chatllm-content-bg)",
8838
+ border: "1px solid var(--chatllm-border)",
8839
+ boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
8840
+ cursor: "pointer",
8841
+ display: "flex",
8842
+ alignItems: "center",
8843
+ justifyContent: "center",
8844
+ zIndex: 10,
8845
+ transition: "opacity 0.2s"
8846
+ },
8847
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IconSvg, { name: "arrow-down-s-line", size: 24, color: "var(--chatllm-text-secondary)" })
8848
+ }
8849
+ ),
8751
8850
  selectionPosition && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
8752
8851
  "div",
8753
8852
  {