@gendive/chatllm 0.7.0 → 0.8.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.
@@ -1,5 +1,5 @@
1
1
  // src/react/ChatUI.tsx
2
- import React9, { useState as useState11 } from "react";
2
+ import React10, { useState as useState12 } from "react";
3
3
 
4
4
  // src/react/hooks/useChatUI.ts
5
5
  import { useState as useState3, useRef as useRef2, useCallback as useCallback3, useEffect as useEffect2, useMemo } from "react";
@@ -514,6 +514,8 @@ var useChatUI = (options) => {
514
514
  onSendMessage,
515
515
  onSessionChange,
516
516
  onError,
517
+ onTitleChange,
518
+ generateTitle: generateTitleCallback,
517
519
  // Memory options
518
520
  useGlobalMemoryEnabled = true,
519
521
  globalMemoryConfig,
@@ -768,6 +770,15 @@ ${newConversation}
768
770
  return filtered;
769
771
  });
770
772
  }, [currentSessionId, storageKey]);
773
+ const renameSession = useCallback3((id, newTitle) => {
774
+ if (!newTitle.trim()) return;
775
+ setSessions(
776
+ (prev) => prev.map(
777
+ (s) => s.id === id ? { ...s, title: newTitle.trim(), updatedAt: Date.now() } : s
778
+ )
779
+ );
780
+ onTitleChange?.(id, newTitle.trim());
781
+ }, [onTitleChange]);
771
782
  const setModel = useCallback3((model) => {
772
783
  setSelectedModel(model);
773
784
  if (currentSessionId) {
@@ -847,6 +858,7 @@ ${finalContent}`;
847
858
  setQuotedText(null);
848
859
  setSelectedAction(null);
849
860
  const capturedSessionId = sessionId;
861
+ const isFirstMessage = !sessions.find((s) => s.id === capturedSessionId)?.messages.length;
850
862
  setSessions(
851
863
  (prev) => prev.map((s) => {
852
864
  if (s.id === capturedSessionId) {
@@ -861,6 +873,19 @@ ${finalContent}`;
861
873
  return s;
862
874
  })
863
875
  );
876
+ if (isFirstMessage && generateTitleCallback) {
877
+ Promise.resolve(generateTitleCallback(finalContent)).then((generatedTitle) => {
878
+ if (generatedTitle && generatedTitle.trim()) {
879
+ setSessions(
880
+ (prev) => prev.map(
881
+ (s) => s.id === capturedSessionId ? { ...s, title: generatedTitle.trim(), updatedAt: Date.now() } : s
882
+ )
883
+ );
884
+ onTitleChange?.(capturedSessionId, generatedTitle.trim());
885
+ }
886
+ }).catch(() => {
887
+ });
888
+ }
864
889
  setIsLoading(true);
865
890
  abortControllerRef.current = new AbortController();
866
891
  try {
@@ -1060,7 +1085,9 @@ ${contextSummary}` },
1060
1085
  buildSystemPrompt,
1061
1086
  compressContext,
1062
1087
  onSendMessage,
1063
- onError
1088
+ onError,
1089
+ generateTitleCallback,
1090
+ onTitleChange
1064
1091
  ]);
1065
1092
  const saveEdit = useCallback3(async (content) => {
1066
1093
  if (!editingMessageId || !currentSession || !currentSessionId) return;
@@ -1286,6 +1313,7 @@ ${currentSession.contextSummary}` },
1286
1313
  newSession,
1287
1314
  selectSession,
1288
1315
  deleteSession,
1316
+ renameSession,
1289
1317
  setModel,
1290
1318
  toggleSidebar,
1291
1319
  openSettings,
@@ -1316,6 +1344,9 @@ ${currentSession.contextSummary}` },
1316
1344
  };
1317
1345
  };
1318
1346
 
1347
+ // src/react/components/ChatSidebar.tsx
1348
+ import { useState as useState4, useRef as useRef3, useEffect as useEffect3 } from "react";
1349
+
1319
1350
  // src/react/components/Icon.tsx
1320
1351
  import { jsx } from "react/jsx-runtime";
1321
1352
  var Icon = ({
@@ -1426,9 +1457,44 @@ var ChatSidebar = ({
1426
1457
  onSelectSession,
1427
1458
  onNewSession,
1428
1459
  onDeleteSession,
1460
+ onRenameSession,
1429
1461
  isOpen,
1430
1462
  onToggle
1431
1463
  }) => {
1464
+ const [editingId, setEditingId] = useState4(null);
1465
+ const [editingTitle, setEditingTitle] = useState4("");
1466
+ const inputRef = useRef3(null);
1467
+ useEffect3(() => {
1468
+ if (editingId && inputRef.current) {
1469
+ inputRef.current.focus();
1470
+ inputRef.current.select();
1471
+ }
1472
+ }, [editingId]);
1473
+ const handleStartEdit = (session, e) => {
1474
+ e.stopPropagation();
1475
+ setEditingId(session.id);
1476
+ setEditingTitle(session.title);
1477
+ };
1478
+ const handleSaveEdit = () => {
1479
+ if (editingId && editingTitle.trim() && onRenameSession) {
1480
+ onRenameSession(editingId, editingTitle.trim());
1481
+ }
1482
+ setEditingId(null);
1483
+ setEditingTitle("");
1484
+ };
1485
+ const handleCancelEdit = () => {
1486
+ setEditingId(null);
1487
+ setEditingTitle("");
1488
+ };
1489
+ const handleKeyDown = (e) => {
1490
+ if (e.key === "Enter") {
1491
+ e.preventDefault();
1492
+ handleSaveEdit();
1493
+ } else if (e.key === "Escape") {
1494
+ e.preventDefault();
1495
+ handleCancelEdit();
1496
+ }
1497
+ };
1432
1498
  return /* @__PURE__ */ jsx2(
1433
1499
  "aside",
1434
1500
  {
@@ -1538,7 +1604,30 @@ var ChatSidebar = ({
1538
1604
  },
1539
1605
  children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "flex-start" }, children: [
1540
1606
  /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
1541
- /* @__PURE__ */ jsx2(
1607
+ editingId === session.id ? /* @__PURE__ */ jsx2(
1608
+ "input",
1609
+ {
1610
+ ref: inputRef,
1611
+ type: "text",
1612
+ value: editingTitle,
1613
+ onChange: (e) => setEditingTitle(e.target.value),
1614
+ onKeyDown: handleKeyDown,
1615
+ onBlur: handleSaveEdit,
1616
+ onClick: (e) => e.stopPropagation(),
1617
+ "aria-label": "\uC138\uC158 \uC81C\uBAA9 \uD3B8\uC9D1",
1618
+ style: {
1619
+ width: "100%",
1620
+ padding: "4px 8px",
1621
+ fontSize: "14px",
1622
+ fontWeight: session.id === currentSessionId ? 500 : 400,
1623
+ color: "var(--chatllm-text, #1f2937)",
1624
+ backgroundColor: "var(--chatllm-bg, #ffffff)",
1625
+ border: "1px solid var(--chatllm-primary, #3b82f6)",
1626
+ borderRadius: "4px",
1627
+ outline: "none"
1628
+ }
1629
+ }
1630
+ ) : /* @__PURE__ */ jsx2(
1542
1631
  "div",
1543
1632
  {
1544
1633
  style: {
@@ -1564,31 +1653,57 @@ var ChatSidebar = ({
1564
1653
  }
1565
1654
  )
1566
1655
  ] }),
1567
- /* @__PURE__ */ jsx2(
1568
- "button",
1569
- {
1570
- onClick: (e) => {
1571
- e.stopPropagation();
1572
- onDeleteSession(session.id);
1573
- },
1574
- style: {
1575
- padding: "4px",
1576
- backgroundColor: "transparent",
1577
- border: "none",
1578
- borderRadius: "4px",
1579
- cursor: "pointer",
1580
- opacity: 0.5,
1581
- transition: "opacity 0.2s"
1582
- },
1583
- onMouseOver: (e) => {
1584
- e.currentTarget.style.opacity = "1";
1585
- },
1586
- onMouseOut: (e) => {
1587
- e.currentTarget.style.opacity = "0.5";
1588
- },
1589
- children: /* @__PURE__ */ jsx2(IconSvg, { name: "delete-bin-line", size: 16, color: "var(--chatllm-text-muted, #9ca3af)" })
1590
- }
1591
- )
1656
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "2px" }, children: [
1657
+ onRenameSession && editingId !== session.id && /* @__PURE__ */ jsx2(
1658
+ "button",
1659
+ {
1660
+ onClick: (e) => handleStartEdit(session, e),
1661
+ "aria-label": "\uC81C\uBAA9 \uD3B8\uC9D1",
1662
+ style: {
1663
+ padding: "4px",
1664
+ backgroundColor: "transparent",
1665
+ border: "none",
1666
+ borderRadius: "4px",
1667
+ cursor: "pointer",
1668
+ opacity: 0.5,
1669
+ transition: "opacity 0.2s"
1670
+ },
1671
+ onMouseOver: (e) => {
1672
+ e.currentTarget.style.opacity = "1";
1673
+ },
1674
+ onMouseOut: (e) => {
1675
+ e.currentTarget.style.opacity = "0.5";
1676
+ },
1677
+ children: /* @__PURE__ */ jsx2(IconSvg, { name: "pencil-line", size: 16, color: "var(--chatllm-text-muted, #9ca3af)" })
1678
+ }
1679
+ ),
1680
+ /* @__PURE__ */ jsx2(
1681
+ "button",
1682
+ {
1683
+ onClick: (e) => {
1684
+ e.stopPropagation();
1685
+ onDeleteSession(session.id);
1686
+ },
1687
+ "aria-label": "\uC138\uC158 \uC0AD\uC81C",
1688
+ style: {
1689
+ padding: "4px",
1690
+ backgroundColor: "transparent",
1691
+ border: "none",
1692
+ borderRadius: "4px",
1693
+ cursor: "pointer",
1694
+ opacity: 0.5,
1695
+ transition: "opacity 0.2s"
1696
+ },
1697
+ onMouseOver: (e) => {
1698
+ e.currentTarget.style.opacity = "1";
1699
+ },
1700
+ onMouseOut: (e) => {
1701
+ e.currentTarget.style.opacity = "0.5";
1702
+ },
1703
+ children: /* @__PURE__ */ jsx2(IconSvg, { name: "delete-bin-line", size: 16, color: "var(--chatllm-text-muted, #9ca3af)" })
1704
+ }
1705
+ )
1706
+ ] })
1592
1707
  ] })
1593
1708
  },
1594
1709
  session.id
@@ -1620,7 +1735,7 @@ var ChatSidebar = ({
1620
1735
  };
1621
1736
 
1622
1737
  // src/react/components/ChatHeader.tsx
1623
- import { useState as useState4 } from "react";
1738
+ import { useState as useState5 } from "react";
1624
1739
  import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1625
1740
  var ChatHeader = ({
1626
1741
  title,
@@ -1633,7 +1748,7 @@ var ChatHeader = ({
1633
1748
  showModelSelector = true,
1634
1749
  showSettings = true
1635
1750
  }) => {
1636
- const [modelDropdownOpen, setModelDropdownOpen] = useState4(false);
1751
+ const [modelDropdownOpen, setModelDropdownOpen] = useState5(false);
1637
1752
  const currentModel = models.find((m) => m.id === model);
1638
1753
  return /* @__PURE__ */ jsxs2(
1639
1754
  "header",
@@ -1887,7 +2002,7 @@ var ChatHeader = ({
1887
2002
  };
1888
2003
 
1889
2004
  // src/react/components/ChatInput.tsx
1890
- import { useRef as useRef3, useEffect as useEffect3, useState as useState5 } from "react";
2005
+ import { useRef as useRef4, useEffect as useEffect4, useState as useState6 } from "react";
1891
2006
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1892
2007
  var ChatInput = ({
1893
2008
  value,
@@ -1903,9 +2018,9 @@ var ChatInput = ({
1903
2018
  onActionSelect,
1904
2019
  actions = []
1905
2020
  }) => {
1906
- const textareaRef = useRef3(null);
1907
- const [actionMenuOpen, setActionMenuOpen] = useState5(false);
1908
- useEffect3(() => {
2021
+ const textareaRef = useRef4(null);
2022
+ const [actionMenuOpen, setActionMenuOpen] = useState6(false);
2023
+ useEffect4(() => {
1909
2024
  if (textareaRef.current) {
1910
2025
  textareaRef.current.style.height = "auto";
1911
2026
  textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 200)}px`;
@@ -2203,16 +2318,16 @@ var ChatInput = ({
2203
2318
  };
2204
2319
 
2205
2320
  // src/react/components/MessageList.tsx
2206
- import { useRef as useRef4, useEffect as useEffect4, useCallback as useCallback4, useState as useState8 } from "react";
2321
+ import { useRef as useRef5, useEffect as useEffect5, useCallback as useCallback4, useState as useState9 } from "react";
2207
2322
 
2208
2323
  // src/react/components/MessageBubble.tsx
2209
- import { useState as useState7 } from "react";
2324
+ import { useState as useState8 } from "react";
2210
2325
 
2211
2326
  // src/react/components/MarkdownRenderer.tsx
2212
- import React4, { useMemo as useMemo2 } from "react";
2327
+ import React5, { useMemo as useMemo2 } from "react";
2213
2328
 
2214
2329
  // src/react/components/LinkChip.tsx
2215
- import { useState as useState6 } from "react";
2330
+ import { useState as useState7 } from "react";
2216
2331
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2217
2332
  var getDomain = (url) => {
2218
2333
  try {
@@ -2266,7 +2381,7 @@ var LinkChip = ({
2266
2381
  index,
2267
2382
  style
2268
2383
  }) => {
2269
- const [isHovered, setIsHovered] = useState6(false);
2384
+ const [isHovered, setIsHovered] = useState7(false);
2270
2385
  const domain = getDomain(url);
2271
2386
  const shortName = getShortName(domain);
2272
2387
  const domainColor = getDomainColor(domain);
@@ -2561,7 +2676,7 @@ var MarkdownTable = ({ data }) => {
2561
2676
  );
2562
2677
  };
2563
2678
  var CodeBlock = ({ language, code }) => {
2564
- const [copied, setCopied] = React4.useState(false);
2679
+ const [copied, setCopied] = React5.useState(false);
2565
2680
  const handleCopy = async () => {
2566
2681
  try {
2567
2682
  await navigator.clipboard.writeText(code);
@@ -2644,9 +2759,9 @@ var CodeBlock = ({ language, code }) => {
2644
2759
  );
2645
2760
  };
2646
2761
  var ImageWithCopyButton = ({ src, alt, imageKey }) => {
2647
- const [isHovered, setIsHovered] = React4.useState(false);
2648
- const [copyState, setCopyState] = React4.useState("idle");
2649
- const imgRef = React4.useRef(null);
2762
+ const [isHovered, setIsHovered] = React5.useState(false);
2763
+ const [copyState, setCopyState] = React5.useState("idle");
2764
+ const imgRef = React5.useRef(null);
2650
2765
  const getImageBlob = async () => {
2651
2766
  const img = imgRef.current;
2652
2767
  if (img && img.complete && img.naturalWidth > 0) {
@@ -2862,7 +2977,7 @@ var ImageWithCopyButton = ({ src, alt, imageKey }) => {
2862
2977
  );
2863
2978
  };
2864
2979
  var ChoiceButtons = ({ choices, title, onChoiceClick }) => {
2865
- const [hoveredIndex, setHoveredIndex] = React4.useState(null);
2980
+ const [hoveredIndex, setHoveredIndex] = React5.useState(null);
2866
2981
  return /* @__PURE__ */ jsxs5(
2867
2982
  "div",
2868
2983
  {
@@ -3083,7 +3198,7 @@ var MarkdownRenderer = ({ content, className, onChoiceClick }) => {
3083
3198
  borderRadius: "0 8px 8px 0",
3084
3199
  color: "var(--chatllm-text, #374151)"
3085
3200
  },
3086
- children: blockquoteLines.map((line, i) => /* @__PURE__ */ jsxs5(React4.Fragment, { children: [
3201
+ children: blockquoteLines.map((line, i) => /* @__PURE__ */ jsxs5(React5.Fragment, { children: [
3087
3202
  parseInlineElements(line, `bq-line-${i}`),
3088
3203
  i < blockquoteLines.length - 1 && /* @__PURE__ */ jsx6("br", {})
3089
3204
  ] }, i))
@@ -3236,9 +3351,16 @@ var MarkdownRenderer = ({ content, className, onChoiceClick }) => {
3236
3351
  return;
3237
3352
  }
3238
3353
  }
3239
- elements.push(
3240
- /* @__PURE__ */ jsx6("p", { style: { margin: "4px 0" }, children: parseInlineElements(line, `p-${lineIndex}`) }, `p-${lineIndex}`)
3241
- );
3354
+ const hasImage = IMAGE_REGEX.test(line);
3355
+ if (hasImage) {
3356
+ elements.push(
3357
+ /* @__PURE__ */ jsx6("div", { style: { margin: "4px 0" }, children: parseInlineElements(line, `p-${lineIndex}`) }, `p-${lineIndex}`)
3358
+ );
3359
+ } else {
3360
+ elements.push(
3361
+ /* @__PURE__ */ jsx6("p", { style: { margin: "4px 0" }, children: parseInlineElements(line, `p-${lineIndex}`) }, `p-${lineIndex}`)
3362
+ );
3363
+ }
3242
3364
  });
3243
3365
  flushList();
3244
3366
  flushBlockquote();
@@ -3278,8 +3400,8 @@ var MessageBubble = ({
3278
3400
  nextAssistantMessage,
3279
3401
  onChoiceClick
3280
3402
  }) => {
3281
- const [showActions, setShowActions] = useState7(false);
3282
- const [showModelMenu, setShowModelMenu] = useState7(false);
3403
+ const [showActions, setShowActions] = useState8(false);
3404
+ const [showModelMenu, setShowModelMenu] = useState8(false);
3283
3405
  const isUser = message.role === "user";
3284
3406
  const isAssistant = message.role === "assistant";
3285
3407
  const relevantAlternatives = isUser ? alternatives : message.alternatives;
@@ -3672,11 +3794,11 @@ var MessageList = ({
3672
3794
  editingId,
3673
3795
  onChoiceClick
3674
3796
  }) => {
3675
- const messagesEndRef = useRef4(null);
3676
- const containerRef = useRef4(null);
3677
- const [selectedText, setSelectedText] = useState8("");
3678
- const [selectionPosition, setSelectionPosition] = useState8(null);
3679
- useEffect4(() => {
3797
+ const messagesEndRef = useRef5(null);
3798
+ const containerRef = useRef5(null);
3799
+ const [selectedText, setSelectedText] = useState9("");
3800
+ const [selectionPosition, setSelectionPosition] = useState9(null);
3801
+ useEffect5(() => {
3680
3802
  messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
3681
3803
  }, [messages]);
3682
3804
  const handleMouseUp = useCallback4(() => {
@@ -4056,7 +4178,7 @@ var EmptyState = ({
4056
4178
  };
4057
4179
 
4058
4180
  // src/react/components/MemoryPanel.tsx
4059
- import { useState as useState9 } from "react";
4181
+ import { useState as useState10 } from "react";
4060
4182
  import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
4061
4183
  var categoryLabels = {
4062
4184
  context: "\uB300\uD654 \uCEE8\uD14D\uC2A4\uD2B8",
@@ -4078,8 +4200,8 @@ var MemoryPanel = ({
4078
4200
  isOpen,
4079
4201
  onToggle
4080
4202
  }) => {
4081
- const [expandedId, setExpandedId] = useState9(null);
4082
- const [activeTab, setActiveTab] = useState9("all");
4203
+ const [expandedId, setExpandedId] = useState10(null);
4204
+ const [activeTab, setActiveTab] = useState10("all");
4083
4205
  const filteredItems = activeTab === "all" ? items : items.filter((item) => item.category === activeTab);
4084
4206
  const formatDate2 = (timestamp) => {
4085
4207
  const date = new Date(timestamp);
@@ -4397,7 +4519,7 @@ var MemoryPanel = ({
4397
4519
  };
4398
4520
 
4399
4521
  // src/react/components/SettingsModal.tsx
4400
- import { useState as useState10 } from "react";
4522
+ import { useState as useState11 } from "react";
4401
4523
  import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
4402
4524
  var DEFAULT_PERSONALIZATION2 = {
4403
4525
  responseStyle: {
@@ -4422,8 +4544,8 @@ var SettingsModal = ({
4422
4544
  apiKeyLabel = "API Key",
4423
4545
  apiKeyDescription = "Cloud \uBAA8\uB378 \uC0AC\uC6A9\uC5D0 \uD544\uC694\uD569\uB2C8\uB2E4"
4424
4546
  }) => {
4425
- const [activeTab, setActiveTab] = useState10("general");
4426
- const [localApiKey, setLocalApiKey] = useState10(apiKey);
4547
+ const [activeTab, setActiveTab] = useState11("general");
4548
+ const [localApiKey, setLocalApiKey] = useState11(apiKey);
4427
4549
  if (!isOpen) return null;
4428
4550
  const updateResponseStyle = (key, value) => {
4429
4551
  onPersonalizationChange({
@@ -5029,9 +5151,11 @@ var ChatUI = ({
5029
5151
  className = "",
5030
5152
  onSendMessage,
5031
5153
  onSessionChange,
5032
- onError
5154
+ onError,
5155
+ onTitleChange,
5156
+ generateTitle: generateTitle2
5033
5157
  }) => {
5034
- React9.useEffect(() => {
5158
+ React10.useEffect(() => {
5035
5159
  injectStyles();
5036
5160
  }, []);
5037
5161
  const hookOptions = {
@@ -5046,7 +5170,9 @@ var ChatUI = ({
5046
5170
  keepRecentMessages,
5047
5171
  onSendMessage,
5048
5172
  onSessionChange,
5049
- onError
5173
+ onError,
5174
+ onTitleChange,
5175
+ generateTitle: generateTitle2
5050
5176
  };
5051
5177
  const {
5052
5178
  sessions,
@@ -5069,6 +5195,7 @@ var ChatUI = ({
5069
5195
  newSession,
5070
5196
  selectSession,
5071
5197
  deleteSession,
5198
+ renameSession,
5072
5199
  setModel,
5073
5200
  toggleSidebar,
5074
5201
  openSettings,
@@ -5099,8 +5226,8 @@ var ChatUI = ({
5099
5226
  const handleChoiceClick = (choice) => {
5100
5227
  setInput(choice.text);
5101
5228
  };
5102
- const [memoryPanelOpen, setMemoryPanelOpen] = useState11(false);
5103
- const memoryItems = React9.useMemo(() => {
5229
+ const [memoryPanelOpen, setMemoryPanelOpen] = useState12(false);
5230
+ const memoryItems = React10.useMemo(() => {
5104
5231
  const items = [];
5105
5232
  if (currentSession?.contextSummary) {
5106
5233
  items.push({
@@ -5160,6 +5287,7 @@ var ChatUI = ({
5160
5287
  onSelectSession: selectSession,
5161
5288
  onNewSession: newSession,
5162
5289
  onDeleteSession: deleteSession,
5290
+ onRenameSession: renameSession,
5163
5291
  isOpen: sidebarOpen,
5164
5292
  onToggle: toggleSidebar
5165
5293
  }