@gendive/chatllm 0.6.2 → 0.6.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.
package/README.md CHANGED
@@ -931,12 +931,11 @@ const response = await fetch('http://localhost:11434/api/chat', {
931
931
 
932
932
  ```typescript
933
933
  // DevDive Gateway API
934
- const response = await fetch(
935
934
  `https://prd-gtw.devdive.ai/model/text-generation/v1/${model}`,
936
935
  {
937
- method: 'POST',
938
- headers: {
939
- 'Content-Type': 'application/json',
936
+ const response = await fetch(
937
+ headers: {
938
+ 'Content-Type': 'application/json',
940
939
  'X-API-KEY': apiKey,
941
940
  },
942
941
  body: JSON.stringify({
@@ -948,5 +947,5 @@ const response = await fetch(
948
947
  }
949
948
  )
950
949
  ```
951
-
950
+ method: 'POST',
952
951
  ---
@@ -48,6 +48,26 @@ interface PersonalizationConfig {
48
48
  language: string;
49
49
  }
50
50
 
51
+ /**
52
+ * @description 마크다운 렌더러 컴포넌트
53
+ * 외부 의존성 없이 기본 마크다운 파싱 지원
54
+ */
55
+
56
+ interface ChoiceOption {
57
+ number: number;
58
+ text: string;
59
+ fullText: string;
60
+ }
61
+ interface MarkdownRendererProps {
62
+ /** 마크다운 텍스트 */
63
+ content: string;
64
+ /** 커스텀 클래스 */
65
+ className?: string;
66
+ /** 선택지 클릭 핸들러 */
67
+ onChoiceClick?: (choice: ChoiceOption) => void;
68
+ }
69
+ declare const MarkdownRenderer: React$1.FC<MarkdownRendererProps>;
70
+
51
71
  /**
52
72
  * @description React UI 컴포넌트 타입 정의
53
73
  */
@@ -211,6 +231,8 @@ interface MessageListProps {
211
231
  models?: ModelConfig[];
212
232
  copiedId: string | null;
213
233
  editingId: string | null;
234
+ /** 선택지 클릭 핸들러 */
235
+ onChoiceClick?: (choice: ChoiceOption) => void;
214
236
  }
215
237
  interface MessageBubbleProps {
216
238
  message: ChatMessage;
@@ -227,6 +249,7 @@ interface MessageBubbleProps {
227
249
  activeAlternativeIndex?: number;
228
250
  models?: ModelConfig[];
229
251
  alternatives?: AlternativeResponse[];
252
+ onChoiceClick?: (choice: ChoiceOption) => void;
230
253
  }
231
254
  interface InputProps {
232
255
  value: string;
@@ -454,19 +477,6 @@ interface MemoryPanelProps {
454
477
  }
455
478
  declare const MemoryPanel: React$1.FC<MemoryPanelProps>;
456
479
 
457
- /**
458
- * @description 마크다운 렌더러 컴포넌트
459
- * 외부 의존성 없이 기본 마크다운 파싱 지원
460
- */
461
-
462
- interface MarkdownRendererProps {
463
- /** 마크다운 텍스트 */
464
- content: string;
465
- /** 커스텀 클래스 */
466
- className?: string;
467
- }
468
- declare const MarkdownRenderer: React$1.FC<MarkdownRendererProps>;
469
-
470
480
  /**
471
481
  * @description 링크 칩 컴포넌트
472
482
  * 출처 링크를 칩 형태로 표시
@@ -48,6 +48,26 @@ interface PersonalizationConfig {
48
48
  language: string;
49
49
  }
50
50
 
51
+ /**
52
+ * @description 마크다운 렌더러 컴포넌트
53
+ * 외부 의존성 없이 기본 마크다운 파싱 지원
54
+ */
55
+
56
+ interface ChoiceOption {
57
+ number: number;
58
+ text: string;
59
+ fullText: string;
60
+ }
61
+ interface MarkdownRendererProps {
62
+ /** 마크다운 텍스트 */
63
+ content: string;
64
+ /** 커스텀 클래스 */
65
+ className?: string;
66
+ /** 선택지 클릭 핸들러 */
67
+ onChoiceClick?: (choice: ChoiceOption) => void;
68
+ }
69
+ declare const MarkdownRenderer: React$1.FC<MarkdownRendererProps>;
70
+
51
71
  /**
52
72
  * @description React UI 컴포넌트 타입 정의
53
73
  */
@@ -211,6 +231,8 @@ interface MessageListProps {
211
231
  models?: ModelConfig[];
212
232
  copiedId: string | null;
213
233
  editingId: string | null;
234
+ /** 선택지 클릭 핸들러 */
235
+ onChoiceClick?: (choice: ChoiceOption) => void;
214
236
  }
215
237
  interface MessageBubbleProps {
216
238
  message: ChatMessage;
@@ -227,6 +249,7 @@ interface MessageBubbleProps {
227
249
  activeAlternativeIndex?: number;
228
250
  models?: ModelConfig[];
229
251
  alternatives?: AlternativeResponse[];
252
+ onChoiceClick?: (choice: ChoiceOption) => void;
230
253
  }
231
254
  interface InputProps {
232
255
  value: string;
@@ -454,19 +477,6 @@ interface MemoryPanelProps {
454
477
  }
455
478
  declare const MemoryPanel: React$1.FC<MemoryPanelProps>;
456
479
 
457
- /**
458
- * @description 마크다운 렌더러 컴포넌트
459
- * 외부 의존성 없이 기본 마크다운 파싱 지원
460
- */
461
-
462
- interface MarkdownRendererProps {
463
- /** 마크다운 텍스트 */
464
- content: string;
465
- /** 커스텀 클래스 */
466
- className?: string;
467
- }
468
- declare const MarkdownRenderer: React$1.FC<MarkdownRendererProps>;
469
-
470
480
  /**
471
481
  * @description 링크 칩 컴포넌트
472
482
  * 출처 링크를 칩 형태로 표시
@@ -1819,8 +1819,9 @@ var LinkChip = ({
1819
1819
 
1820
1820
  // src/react/components/MarkdownRenderer.tsx
1821
1821
  var import_jsx_runtime6 = require("react/jsx-runtime");
1822
- var LINK_REGEX = /\[([^\]]+)\]\(([^)]+)\)/g;
1823
- var SOURCE_LINKS_REGEX = /(\*{0,2}출처:?\*{0,2}\s*)?((?:\[`?[^\]]+`?\]\([^)]+\)\s*)+)/gi;
1822
+ var IMAGE_REGEX = /!\[([^\]]*)\]\(([^)]+)\)/g;
1823
+ var LINK_REGEX = /(?<!!)\[([^\]]+)\]\(([^)]+)\)/g;
1824
+ var SOURCE_LINKS_REGEX = /(\*{0,2}출처:?\*{0,2}\s*)?((?:(?<!!)\[`?[^\]]+`?\]\([^)]+\)\s*)+)/gi;
1824
1825
  var CODE_BLOCK_REGEX = /```(\w*)\n?([\s\S]*?)```/g;
1825
1826
  var INLINE_CODE_REGEX = /`([^`]+)`/g;
1826
1827
  var BOLD_REGEX = /\*\*([^*]+)\*\*/g;
@@ -1847,8 +1848,9 @@ var parseInlineElements = (text, key) => {
1847
1848
  currentText = currentText.replace(INLINE_CODE_REGEX, "\xA7CODE\xA7$1\xA7/CODE\xA7");
1848
1849
  currentText = currentText.replace(BOLD_REGEX, "\xA7BOLD\xA7$1\xA7/BOLD\xA7");
1849
1850
  currentText = currentText.replace(ITALIC_REGEX, "\xA7ITALIC\xA7$1\xA7/ITALIC\xA7");
1851
+ currentText = currentText.replace(IMAGE_REGEX, "\xA7IMAGE\xA7$1\xA7URL\xA7$2\xA7/IMAGE\xA7");
1850
1852
  currentText = currentText.replace(LINK_REGEX, "\xA7LINK\xA7$1\xA7URL\xA7$2\xA7/LINK\xA7");
1851
- const parts = currentText.split(/(§CODE§.*?§\/CODE§|§BOLD§.*?§\/BOLD§|§ITALIC§.*?§\/ITALIC§|§LINK§.*?§\/LINK§)/);
1853
+ const parts = currentText.split(/(§CODE§.*?§\/CODE§|§BOLD§.*?§\/BOLD§|§ITALIC§.*?§\/ITALIC§|§IMAGE§.*?§\/IMAGE§|§LINK§.*?§\/LINK§)/);
1852
1854
  parts.forEach((part, index) => {
1853
1855
  if (part.startsWith("\xA7CODE\xA7")) {
1854
1856
  const content = part.replace("\xA7CODE\xA7", "").replace("\xA7/CODE\xA7", "");
@@ -1875,6 +1877,28 @@ var parseInlineElements = (text, key) => {
1875
1877
  } else if (part.startsWith("\xA7ITALIC\xA7")) {
1876
1878
  const content = part.replace("\xA7ITALIC\xA7", "").replace("\xA7/ITALIC\xA7", "");
1877
1879
  elements.push(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("em", { children: content }, `${key}-italic-${index}`));
1880
+ } else if (part.startsWith("\xA7IMAGE\xA7")) {
1881
+ const match = part.match(/§IMAGE§(.*)§URL§(.+?)§\/IMAGE§/);
1882
+ if (match) {
1883
+ elements.push(
1884
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1885
+ "img",
1886
+ {
1887
+ src: match[2],
1888
+ alt: match[1] || "",
1889
+ className: "chatllm-image",
1890
+ style: {
1891
+ maxWidth: "100%",
1892
+ borderRadius: "8px",
1893
+ margin: "8px 0",
1894
+ display: "block"
1895
+ },
1896
+ loading: "lazy"
1897
+ },
1898
+ `${key}-image-${index}`
1899
+ )
1900
+ );
1901
+ }
1878
1902
  } else if (part.startsWith("\xA7LINK\xA7")) {
1879
1903
  const match = part.match(/§LINK§(.+?)§URL§(.+?)§\/LINK§/);
1880
1904
  if (match) {
@@ -2042,6 +2066,96 @@ var CodeBlock = ({ language, code }) => {
2042
2066
  }
2043
2067
  );
2044
2068
  };
2069
+ var ChoiceButtons = ({ choices, onChoiceClick }) => {
2070
+ const [hoveredIndex, setHoveredIndex] = import_react5.default.useState(null);
2071
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2072
+ "div",
2073
+ {
2074
+ className: "chatllm-choices",
2075
+ style: {
2076
+ display: "flex",
2077
+ flexDirection: "column",
2078
+ gap: "8px",
2079
+ margin: "16px 0",
2080
+ padding: "12px",
2081
+ backgroundColor: "var(--chatllm-bg-secondary, #f9fafb)",
2082
+ borderRadius: "12px",
2083
+ border: "1px solid var(--chatllm-border-light, #e5e7eb)"
2084
+ },
2085
+ children: [
2086
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2087
+ "div",
2088
+ {
2089
+ style: {
2090
+ fontSize: "12px",
2091
+ fontWeight: 600,
2092
+ color: "var(--chatllm-text-muted, #6b7280)",
2093
+ marginBottom: "4px",
2094
+ textTransform: "uppercase",
2095
+ letterSpacing: "0.5px"
2096
+ },
2097
+ children: "\uC120\uD0DD\uD558\uC138\uC694"
2098
+ }
2099
+ ),
2100
+ choices.map((choice, index) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2101
+ "button",
2102
+ {
2103
+ onClick: () => onChoiceClick?.(choice),
2104
+ onMouseEnter: () => setHoveredIndex(index),
2105
+ onMouseLeave: () => setHoveredIndex(null),
2106
+ style: {
2107
+ display: "flex",
2108
+ alignItems: "center",
2109
+ gap: "12px",
2110
+ padding: "12px 16px",
2111
+ backgroundColor: hoveredIndex === index ? "var(--chatllm-primary-light, #dbeafe)" : "var(--chatllm-bg, #ffffff)",
2112
+ border: "1px solid var(--chatllm-border, #e5e7eb)",
2113
+ borderRadius: "8px",
2114
+ cursor: "pointer",
2115
+ textAlign: "left",
2116
+ transition: "all 0.2s ease",
2117
+ transform: hoveredIndex === index ? "translateX(4px)" : "translateX(0)"
2118
+ },
2119
+ children: [
2120
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2121
+ "span",
2122
+ {
2123
+ style: {
2124
+ display: "flex",
2125
+ alignItems: "center",
2126
+ justifyContent: "center",
2127
+ width: "28px",
2128
+ height: "28px",
2129
+ borderRadius: "50%",
2130
+ backgroundColor: hoveredIndex === index ? "var(--chatllm-primary, #3b82f6)" : "var(--chatllm-bg-tertiary, #f3f4f6)",
2131
+ color: hoveredIndex === index ? "#ffffff" : "var(--chatllm-text, #374151)",
2132
+ fontSize: "14px",
2133
+ fontWeight: 600,
2134
+ flexShrink: 0,
2135
+ transition: "all 0.2s ease"
2136
+ },
2137
+ children: choice.number
2138
+ }
2139
+ ),
2140
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2141
+ "span",
2142
+ {
2143
+ style: {
2144
+ fontSize: "14px",
2145
+ color: "var(--chatllm-text, #374151)",
2146
+ lineHeight: "1.5"
2147
+ },
2148
+ children: choice.text
2149
+ }
2150
+ )
2151
+ ]
2152
+ },
2153
+ choice.number
2154
+ ))
2155
+ ]
2156
+ }
2157
+ );
2158
+ };
2045
2159
  var SourceLinksSection = ({ links, label }) => {
2046
2160
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2047
2161
  "div",
@@ -2075,7 +2189,7 @@ var SourceLinksSection = ({ links, label }) => {
2075
2189
  }
2076
2190
  );
2077
2191
  };
2078
- var MarkdownRenderer = ({ content, className }) => {
2192
+ var MarkdownRenderer = ({ content, className, onChoiceClick }) => {
2079
2193
  const rendered = (0, import_react5.useMemo)(() => {
2080
2194
  const elements = [];
2081
2195
  let processedContent = content;
@@ -2097,6 +2211,7 @@ var MarkdownRenderer = ({ content, className }) => {
2097
2211
  let currentList = null;
2098
2212
  let blockquoteLines = [];
2099
2213
  let tableLines = [];
2214
+ let choiceItems = [];
2100
2215
  const flushTable = () => {
2101
2216
  if (tableLines.length >= 2) {
2102
2217
  const headerLine = tableLines[0];
@@ -2125,6 +2240,24 @@ var MarkdownRenderer = ({ content, className }) => {
2125
2240
  }
2126
2241
  tableLines = [];
2127
2242
  };
2243
+ const flushChoices = () => {
2244
+ if (choiceItems.length >= 2 && onChoiceClick) {
2245
+ elements.push(
2246
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2247
+ ChoiceButtons,
2248
+ {
2249
+ choices: choiceItems,
2250
+ onChoiceClick
2251
+ },
2252
+ `choices-${elements.length}`
2253
+ )
2254
+ );
2255
+ choiceItems = [];
2256
+ return true;
2257
+ }
2258
+ choiceItems = [];
2259
+ return false;
2260
+ };
2128
2261
  const flushList = () => {
2129
2262
  if (currentList) {
2130
2263
  if (currentList.type === "ul") {
@@ -2132,9 +2265,11 @@ var MarkdownRenderer = ({ content, className }) => {
2132
2265
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { style: { margin: "8px 0", paddingLeft: "24px" }, children: currentList.items }, `ul-${elements.length}`)
2133
2266
  );
2134
2267
  } else {
2135
- elements.push(
2136
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ol", { style: { margin: "8px 0", paddingLeft: "24px" }, children: currentList.items }, `ol-${elements.length}`)
2137
- );
2268
+ if (!flushChoices()) {
2269
+ elements.push(
2270
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ol", { style: { margin: "8px 0", paddingLeft: "24px" }, children: currentList.items }, `ol-${elements.length}`)
2271
+ );
2272
+ }
2138
2273
  }
2139
2274
  currentList = null;
2140
2275
  }
@@ -2263,15 +2398,24 @@ var MarkdownRenderer = ({ content, className }) => {
2263
2398
  );
2264
2399
  return;
2265
2400
  }
2266
- const olMatch = line.match(/^(\d+)\.\s+(.+)$/);
2401
+ const olMatch = line.match(/^(\d+)[.)]\s+(.+)$/);
2267
2402
  if (olMatch) {
2268
2403
  flushBlockquote();
2269
2404
  if (!currentList || currentList.type !== "ol") {
2270
2405
  flushList();
2271
2406
  currentList = { type: "ol", items: [] };
2272
2407
  }
2408
+ const itemNumber = parseInt(olMatch[1]);
2409
+ const itemText = olMatch[2];
2410
+ if (itemText.length <= 100) {
2411
+ choiceItems.push({
2412
+ number: itemNumber,
2413
+ text: itemText,
2414
+ fullText: line
2415
+ });
2416
+ }
2273
2417
  currentList.items.push(
2274
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("li", { style: { margin: "4px 0" }, children: parseInlineElements(olMatch[2], `li-${lineIndex}`) }, `li-${lineIndex}`)
2418
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("li", { style: { margin: "4px 0" }, children: parseInlineElements(itemText, `li-${lineIndex}`) }, `li-${lineIndex}`)
2275
2419
  );
2276
2420
  return;
2277
2421
  }
@@ -2288,7 +2432,7 @@ var MarkdownRenderer = ({ content, className }) => {
2288
2432
  flushBlockquote();
2289
2433
  flushTable();
2290
2434
  return elements;
2291
- }, [content]);
2435
+ }, [content, onChoiceClick]);
2292
2436
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2293
2437
  "div",
2294
2438
  {
@@ -2319,7 +2463,8 @@ var MessageBubble = ({
2319
2463
  alternatives,
2320
2464
  activeAlternativeIndex = 0,
2321
2465
  onAlternativeChange,
2322
- nextAssistantMessage
2466
+ nextAssistantMessage,
2467
+ onChoiceClick
2323
2468
  }) => {
2324
2469
  const [showActions, setShowActions] = (0, import_react6.useState)(false);
2325
2470
  const [showModelMenu, setShowModelMenu] = (0, import_react6.useState)(false);
@@ -2423,7 +2568,7 @@ var MessageBubble = ({
2423
2568
  children: [
2424
2569
  isAssistant ? (
2425
2570
  // AI 메시지는 마크다운 렌더링
2426
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MarkdownRenderer, { content: displayContent })
2571
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MarkdownRenderer, { content: displayContent, onChoiceClick })
2427
2572
  ) : (
2428
2573
  // 사용자 메시지는 일반 텍스트
2429
2574
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
@@ -2712,7 +2857,8 @@ var MessageList = ({
2712
2857
  activeAlternatives = {},
2713
2858
  models,
2714
2859
  copiedId,
2715
- editingId
2860
+ editingId,
2861
+ onChoiceClick
2716
2862
  }) => {
2717
2863
  const messagesEndRef = (0, import_react7.useRef)(null);
2718
2864
  const containerRef = (0, import_react7.useRef)(null);
@@ -2784,7 +2930,8 @@ var MessageList = ({
2784
2930
  onAskOtherModel: message.role === "user" && assistantForAlts && onAskOtherModel ? (targetModel) => onAskOtherModel(message.id, assistantForAlts.id, targetModel) : void 0,
2785
2931
  onAlternativeChange: message.role === "user" && assistantForAlts && onSetActiveAlternative ? (idx) => onSetActiveAlternative(assistantForAlts.id, idx) : message.role === "assistant" && onSetActiveAlternative ? (idx) => onSetActiveAlternative(message.id, idx) : void 0,
2786
2932
  models,
2787
- alternatives: message.role === "assistant" ? message.alternatives : assistantForAlts?.alternatives
2933
+ alternatives: message.role === "assistant" ? message.alternatives : assistantForAlts?.alternatives,
2934
+ onChoiceClick: message.role === "assistant" ? onChoiceClick : void 0
2788
2935
  },
2789
2936
  message.id
2790
2937
  );
@@ -4036,6 +4183,18 @@ var injectStyles = () => {
4036
4183
  .chatllm-table tr:hover {
4037
4184
  background-color: var(--chatllm-bg-hover, #f3f4f6);
4038
4185
  }
4186
+
4187
+ .chatllm-image {
4188
+ max-width: 100%;
4189
+ border-radius: 8px;
4190
+ margin: 8px 0;
4191
+ display: block;
4192
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
4193
+ }
4194
+
4195
+ .chatllm-image:hover {
4196
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
4197
+ }
4039
4198
  `;
4040
4199
  document.head.appendChild(style);
4041
4200
  };
@@ -4125,6 +4284,9 @@ var ChatUI = ({
4125
4284
  const handleSubmit = () => {
4126
4285
  sendMessage();
4127
4286
  };
4287
+ const handleChoiceClick = (choice) => {
4288
+ setInput(choice.text);
4289
+ };
4128
4290
  const [memoryPanelOpen, setMemoryPanelOpen] = (0, import_react10.useState)(false);
4129
4291
  const memoryItems = import_react10.default.useMemo(() => {
4130
4292
  const items = [];
@@ -4236,7 +4398,8 @@ var ChatUI = ({
4236
4398
  activeAlternatives,
4237
4399
  models: hookModels,
4238
4400
  copiedId: copiedMessageId,
4239
- editingId: editingMessageId
4401
+ editingId: editingMessageId,
4402
+ onChoiceClick: handleChoiceClick
4240
4403
  }
4241
4404
  ),
4242
4405
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(