@gendive/chatllm 0.21.5 → 0.21.7

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.
@@ -2761,6 +2761,11 @@ ${projectMemoryContext}`);
2761
2761
  - \uB370\uC774\uD130 \uBE44\uAD50/\uCD94\uC774 \u2192 html (Chart.js CDN \uC0AC\uC6A9, <artifact> \uD0DC\uADF8)
2762
2762
  - \uC544\uC774\uCF58/\uB85C\uACE0/\uC77C\uB7EC\uC2A4\uD2B8 \u2192 svg
2763
2763
 
2764
+ **mermaid \uC8FC\uC758\uC0AC\uD56D:**
2765
+ - \uB178\uB4DC \uB77C\uBCA8\uC5D0 \uAD04\uD638 () \uC0AC\uC6A9 \uAE08\uC9C0 \u2192 \uB300\uAD04\uD638 [] \uC0AC\uC6A9: B[\uC0C1\uD0DC \uAD00\uB9AC useState] \u2705 B[\uC0C1\uD0DC \uAD00\uB9AC (useState)] \u274C
2766
+ - \uB178\uB4DC \uB77C\uBCA8\uC5D0 \uD2B9\uC218\uBB38\uC790\uAC00 \uC788\uC73C\uBA74 \uD070\uB530\uC634\uD45C\uB85C \uAC10\uC2F8\uAE30: B["API \uD638\uCD9C \u2192 \uC751\uB2F5"]
2767
+ - \uC18C\uAD04\uD638 ()\uB294 mermaid \uBB38\uBC95\uC5D0\uC11C \uB465\uADFC \uB178\uB4DC\uB97C \uC758\uBBF8\uD558\uBBC0\uB85C \uB77C\uBCA8 \uD14D\uC2A4\uD2B8\uC5D0 \uC808\uB300 \uD3EC\uD568\uD558\uC9C0 \uB9C8\uC138\uC694
2768
+
2764
2769
  **mermaid \uC608\uC2DC:**
2765
2770
  \`\`\`mermaid
2766
2771
  graph TD
@@ -2785,7 +2790,7 @@ new Chart(document.getElementById('chart'), {
2785
2790
  - html \uC2DC\uAC01\uD654\uB294 \uBC18\uB4DC\uC2DC <artifact language="html"> \uD0DC\uADF8\uB85C \uAC10\uC2F8\uC57C \uD568 (\`\`\`html \uCF54\uB4DC\uBE14\uB85D \uC0AC\uC6A9 \uAE08\uC9C0)
2786
2791
  - \uC0AC\uC6A9\uC790\uAC00 \uC2DC\uAC01\uD654\uB97C \uC694\uCCAD\uD558\uC9C0 \uC54A\uC544\uB3C4 \uC801\uC808\uD558\uBA74 \uC790\uB3D9\uC73C\uB85C \uD3EC\uD568
2787
2792
  - \uC2DC\uAC01\uD654 \uC55E\uB4A4\uC5D0 \uAC04\uACB0\uD55C \uD14D\uC2A4\uD2B8 \uC124\uBA85 \uD3EC\uD568
2788
- - mermaid \uBB38\uBC95 \uC624\uB958 \uC8FC\uC758 (\uD2B9\uD788 \uAD04\uD638, \uB530\uC634\uD45C \uC774\uC2A4\uCF00\uC774\uD504)`);
2793
+ - mermaid \uB178\uB4DC \uB77C\uBCA8\uC5D0 \uC18C\uAD04\uD638 () \uC808\uB300 \uC0AC\uC6A9 \uAE08\uC9C0 \u2014 \uD30C\uC2F1 \uC5D0\uB7EC \uC6D0\uC778`);
2789
2794
  if (shouldIncludeSkills) {
2790
2795
  const skillsPrompt = buildSkillsPrompt();
2791
2796
  if (skillsPrompt) {
@@ -3118,18 +3123,29 @@ ${newConversation}
3118
3123
  setIsSessionLoading(true);
3119
3124
  try {
3120
3125
  const sessionDetail = await onLoadSessionRef.current(id);
3121
- let loadedMessages = sessionDetail.messages.map((m, idx) => ({
3122
- id: m.id || generateId5("msg"),
3123
- role: typeof m.role === "string" ? m.role.toLowerCase() : m.role,
3124
- // API는 message 필드, 내부는 content 필드 사용
3125
- content: m.content || m.message || "",
3126
- timestamp: m.created_at ? new Date(m.created_at).getTime() : Date.now() - (sessionDetail.messages.length - idx) * 1e3,
3127
- model: m.model,
3128
- alternatives: m.alternatives,
3129
- sources: m.sources,
3130
- /** @Todo vibecode - 백엔드에서 전달받은 contentParts 복원 (이미지/파일 등) */
3131
- ...m.contentParts && { contentParts: m.contentParts }
3132
- }));
3126
+ let loadedMessages = sessionDetail.messages.map((m, idx) => {
3127
+ const msg = {
3128
+ id: m.id || generateId5("msg"),
3129
+ role: typeof m.role === "string" ? m.role.toLowerCase() : m.role,
3130
+ // API는 message 필드, 내부는 content 필드 사용
3131
+ content: m.content || m.message || "",
3132
+ timestamp: m.created_at ? new Date(m.created_at).getTime() : Date.now() - (sessionDetail.messages.length - idx) * 1e3,
3133
+ model: m.model,
3134
+ alternatives: m.alternatives,
3135
+ sources: m.sources,
3136
+ /** @Todo vibecode - 백엔드에서 전달받은 contentParts 복원 (이미지/파일 등) */
3137
+ ...m.contentParts && { contentParts: m.contentParts }
3138
+ };
3139
+ if (!msg.contentParts && msg.role === "assistant" && hasArtifactTag(msg.content)) {
3140
+ const { artifacts, cleanContent } = parseArtifactsFromContent(msg.content);
3141
+ if (artifacts.length > 0) {
3142
+ const textPart = cleanContent.trim() ? [{ type: "text", content: cleanContent }] : [];
3143
+ msg.content = cleanContent;
3144
+ msg.contentParts = [...textPart, ...artifacts];
3145
+ }
3146
+ }
3147
+ return msg;
3148
+ });
3133
3149
  let resolvedTitle = sessionDetail.title;
3134
3150
  let resolvedSessionContext = sessionDetail.sessionContext;
3135
3151
  if (loadedMessages.length === 0) {
@@ -3504,13 +3520,17 @@ ${finalContent}`;
3504
3520
  for (const [skillName, skillConfig] of attachmentSkills) {
3505
3521
  let matchedFiles = currentAttachments.filter((att) => {
3506
3522
  if (!skillConfig.acceptedTypes || skillConfig.acceptedTypes.length === 0) return true;
3523
+ const fileName = (att.name || "").toLowerCase();
3524
+ const fileMime = (att.mimeType || "").toLowerCase();
3525
+ const fileExt = fileName.includes(".") ? fileName.slice(fileName.lastIndexOf(".")) : "";
3507
3526
  return skillConfig.acceptedTypes.some((type) => {
3508
- if (type.startsWith(".")) return att.name.toLowerCase().endsWith(type.toLowerCase());
3509
- if (type.includes("*")) {
3510
- const regex = new RegExp("^" + type.replace("*", ".*") + "$");
3511
- return regex.test(att.mimeType);
3527
+ const t = type.toLowerCase();
3528
+ if (t.startsWith(".")) return fileExt === t || fileName.endsWith(t);
3529
+ if (t.includes("*")) {
3530
+ const regex = new RegExp("^" + t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\*", ".*") + "$");
3531
+ return regex.test(fileMime);
3512
3532
  }
3513
- return att.mimeType === type;
3533
+ return fileMime === t;
3514
3534
  });
3515
3535
  });
3516
3536
  if (skipImageAttachmentSkill) {
@@ -3965,25 +3985,26 @@ ${attachmentContext}
3965
3985
  }
3966
3986
  }
3967
3987
  }
3968
- const saveMessagesOnEarlyReturn = () => {
3988
+ const saveMessagesOnEarlyReturn = (overrideContentParts) => {
3969
3989
  if (!useExternalStorage || !capturedSessionId) return;
3970
- queueMicrotask(() => {
3990
+ setTimeout(() => {
3971
3991
  const latestSession = sessionsRef.current.find((s) => s.id === capturedSessionId);
3972
3992
  if (!latestSession) return;
3973
3993
  const latestMessages = latestSession.messages;
3974
3994
  const assistantMsg = [...latestMessages].reverse().find((m) => m.role === "assistant");
3975
3995
  const userMsg = latestMessages.find((m) => m.role === "user" && m.content === finalContent);
3976
3996
  const assistantContent = assistantMsg?.content || "";
3977
- if (assistantContent && onSaveMessagesRef.current) {
3997
+ const finalContentParts = overrideContentParts || assistantMsg?.contentParts;
3998
+ if ((assistantContent || finalContentParts) && onSaveMessagesRef.current) {
3978
3999
  onSaveMessagesRef.current(capturedSessionId, [
3979
4000
  { role: "user", message: finalContent, ...userMsg?.contentParts && { contentParts: userMsg.contentParts } },
3980
- { role: "assistant", message: assistantContent, ...assistantMsg?.contentParts && { contentParts: assistantMsg.contentParts } }
4001
+ { role: "assistant", message: assistantContent, ...finalContentParts && { contentParts: finalContentParts } }
3981
4002
  ]).catch((e) => console.error("[useChatUI] Failed to save messages:", e));
3982
4003
  }
3983
4004
  if (latestSession.messages.length > 0) {
3984
4005
  writeSessionCache(storageKey, latestSession);
3985
4006
  }
3986
- });
4007
+ }, 0);
3987
4008
  };
3988
4009
  if (!shouldSkipSkillParsing) {
3989
4010
  const assistantContent = accumulatedContent;
@@ -4173,7 +4194,7 @@ ${attachmentContext}
4173
4194
  promoteToSessionContext(capturedSessionId, toolName, result.content, result.metadata, toolLabel || toolName);
4174
4195
  }
4175
4196
  if (resultType === "image" || resultType === "file") {
4176
- saveMessagesOnEarlyReturn();
4197
+ saveMessagesOnEarlyReturn(parts);
4177
4198
  removeLoadingSession(capturedSessionId);
4178
4199
  abortControllersRef.current.delete(capturedSessionId);
4179
4200
  return;
@@ -4184,7 +4205,7 @@ ${attachmentContext}
4184
4205
  shouldContinue = decision === "continue";
4185
4206
  }
4186
4207
  if (!shouldContinue) {
4187
- saveMessagesOnEarlyReturn();
4208
+ saveMessagesOnEarlyReturn(parts);
4188
4209
  removeLoadingSession(capturedSessionId);
4189
4210
  abortControllersRef.current.delete(capturedSessionId);
4190
4211
  return;
@@ -4259,13 +4280,13 @@ ${result.content}
4259
4280
  shouldContinueImgFile = decision === "continue";
4260
4281
  }
4261
4282
  if (!shouldContinueImgFile) {
4262
- saveMessagesOnEarlyReturn();
4283
+ saveMessagesOnEarlyReturn(imgFileParts);
4263
4284
  removeLoadingSession(capturedSessionId);
4264
4285
  abortControllersRef.current.delete(capturedSessionId);
4265
4286
  return;
4266
4287
  }
4267
4288
  skipNextSkillParsingRef.current = true;
4268
- saveMessagesOnEarlyReturn();
4289
+ saveMessagesOnEarlyReturn(imgFileParts);
4269
4290
  const imgFilePrompt = skillResultType === "image" ? `"${detectedSkill.name}" \uC2A4\uD0AC\uB85C \uC774\uBBF8\uC9C0\uAC00 \uC0DD\uC131\uB418\uC5B4 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uD45C\uC2DC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC774\uBBF8\uC9C0 URL\uC744 \uD14D\uC2A4\uD2B8\uC5D0 \uD3EC\uD568\uD558\uC9C0 \uB9D0\uACE0, \uC0DD\uC131\uB41C \uC774\uBBF8\uC9C0\uC5D0 \uB300\uD574 \uAC04\uB2E8\uD788 \uC548\uB0B4\uD574\uC8FC\uC138\uC694. skill_use \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \uB9C8\uC138\uC694.` : `"${detectedSkill.name}" \uC2A4\uD0AC\uB85C \uD30C\uC77C\uC774 \uC0DD\uC131\uB418\uC5B4 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uD45C\uC2DC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uD30C\uC77C URL\uC744 \uD14D\uC2A4\uD2B8\uC5D0 \uD3EC\uD568\uD558\uC9C0 \uB9D0\uACE0, \uACB0\uACFC\uC5D0 \uB300\uD574 \uAC04\uB2E8\uD788 \uC548\uB0B4\uD574\uC8FC\uC138\uC694. skill_use \uD0DC\uADF8\uB294 \uC0AC\uC6A9\uD558\uC9C0 \uB9C8\uC138\uC694.`;
4270
4291
  setTimeout(() => {
4271
4292
  sendMessage(imgFilePrompt, { hiddenUserMessage: true });
@@ -10203,9 +10224,39 @@ var MermaidArtifact = ({ code }) => {
10203
10224
  }
10204
10225
  );
10205
10226
  };
10206
- var ArtifactActions = ({ code, language }) => {
10227
+ var svgToPngBlob = (svgString, scale = 2) => {
10228
+ return new Promise((resolve) => {
10229
+ const parser = new DOMParser();
10230
+ const doc = parser.parseFromString(svgString, "image/svg+xml");
10231
+ const svgEl = doc.querySelector("svg");
10232
+ if (!svgEl) return resolve(null);
10233
+ const w = parseInt(svgEl.getAttribute("width") || "800", 10);
10234
+ const h = parseInt(svgEl.getAttribute("height") || "600", 10);
10235
+ const canvas = document.createElement("canvas");
10236
+ canvas.width = w * scale;
10237
+ canvas.height = h * scale;
10238
+ const ctx = canvas.getContext("2d");
10239
+ if (!ctx) return resolve(null);
10240
+ ctx.scale(scale, scale);
10241
+ const blob = new Blob([new XMLSerializer().serializeToString(svgEl)], { type: "image/svg+xml;charset=utf-8" });
10242
+ const url = URL.createObjectURL(blob);
10243
+ const img = new Image();
10244
+ img.onload = () => {
10245
+ ctx.drawImage(img, 0, 0, w, h);
10246
+ URL.revokeObjectURL(url);
10247
+ canvas.toBlob((pngBlob) => resolve(pngBlob), "image/png");
10248
+ };
10249
+ img.onerror = () => {
10250
+ URL.revokeObjectURL(url);
10251
+ resolve(null);
10252
+ };
10253
+ img.src = url;
10254
+ });
10255
+ };
10256
+ var ArtifactActions = ({ code, language, containerRef }) => {
10207
10257
  const [showCode, setShowCode] = useState15(false);
10208
10258
  const [copied, setCopied] = useState15(false);
10259
+ const [saving, setSaving] = useState15(false);
10209
10260
  const handleCopy = async () => {
10210
10261
  try {
10211
10262
  await navigator.clipboard.writeText(code);
@@ -10214,6 +10265,39 @@ var ArtifactActions = ({ code, language }) => {
10214
10265
  } catch {
10215
10266
  }
10216
10267
  };
10268
+ const handleSaveImage = async () => {
10269
+ setSaving(true);
10270
+ try {
10271
+ let blob = null;
10272
+ if (language === "svg" || language === "mermaid") {
10273
+ const svgEl = containerRef?.current?.querySelector("svg");
10274
+ if (svgEl) {
10275
+ blob = await svgToPngBlob(svgEl.outerHTML);
10276
+ }
10277
+ } else if (language === "html") {
10278
+ const htmlBlob = new Blob([code], { type: "text/html;charset=utf-8" });
10279
+ const url = URL.createObjectURL(htmlBlob);
10280
+ const a = document.createElement("a");
10281
+ a.href = url;
10282
+ a.download = `artifact.html`;
10283
+ a.click();
10284
+ URL.revokeObjectURL(url);
10285
+ setSaving(false);
10286
+ return;
10287
+ }
10288
+ if (blob) {
10289
+ const url = URL.createObjectURL(blob);
10290
+ const a = document.createElement("a");
10291
+ a.href = url;
10292
+ a.download = `artifact.png`;
10293
+ a.click();
10294
+ URL.revokeObjectURL(url);
10295
+ }
10296
+ } catch {
10297
+ } finally {
10298
+ setSaving(false);
10299
+ }
10300
+ };
10217
10301
  const langLabel = language === "mermaid" ? "Mermaid" : language === "svg" ? "SVG" : "HTML";
10218
10302
  return /* @__PURE__ */ jsxs13("div", { children: [
10219
10303
  /* @__PURE__ */ jsxs13(
@@ -10279,6 +10363,30 @@ var ArtifactActions = ({ code, language }) => {
10279
10363
  copied ? "\uBCF5\uC0AC\uB428" : "\uBCF5\uC0AC"
10280
10364
  ]
10281
10365
  }
10366
+ ),
10367
+ /* @__PURE__ */ jsxs13(
10368
+ "button",
10369
+ {
10370
+ onClick: handleSaveImage,
10371
+ disabled: saving,
10372
+ style: {
10373
+ display: "flex",
10374
+ alignItems: "center",
10375
+ gap: "4px",
10376
+ padding: "4px 8px",
10377
+ fontSize: "12px",
10378
+ color: "var(--chatllm-text-muted, #64748b)",
10379
+ backgroundColor: "transparent",
10380
+ border: "none",
10381
+ borderRadius: "4px",
10382
+ cursor: saving ? "wait" : "pointer",
10383
+ opacity: saving ? 0.5 : 1
10384
+ },
10385
+ children: [
10386
+ /* @__PURE__ */ jsx14(IconSvg, { name: "download-line", size: 12, color: "var(--chatllm-text-muted, #64748b)" }),
10387
+ language === "html" ? "HTML \uC800\uC7A5" : "\uC774\uBBF8\uC9C0 \uC800\uC7A5"
10388
+ ]
10389
+ }
10282
10390
  )
10283
10391
  ]
10284
10392
  }
@@ -10313,6 +10421,7 @@ var ArtifactActions = ({ code, language }) => {
10313
10421
  ] });
10314
10422
  };
10315
10423
  var ArtifactCard = ({ part, index = 0 }) => {
10424
+ const contentRef = useRef9(null);
10316
10425
  return /* @__PURE__ */ jsxs13(
10317
10426
  "div",
10318
10427
  {
@@ -10352,12 +10461,12 @@ var ArtifactCard = ({ part, index = 0 }) => {
10352
10461
  ]
10353
10462
  }
10354
10463
  ),
10355
- /* @__PURE__ */ jsxs13("div", { style: { padding: part.language === "html" ? 0 : "12px" }, children: [
10464
+ /* @__PURE__ */ jsxs13("div", { ref: contentRef, style: { padding: part.language === "html" ? 0 : "12px" }, children: [
10356
10465
  part.language === "html" && /* @__PURE__ */ jsx14(HtmlArtifact, { code: part.code }),
10357
10466
  part.language === "svg" && /* @__PURE__ */ jsx14(SvgArtifact, { code: part.code }),
10358
10467
  part.language === "mermaid" && /* @__PURE__ */ jsx14(MermaidArtifact, { code: part.code })
10359
10468
  ] }),
10360
- /* @__PURE__ */ jsx14(ArtifactActions, { code: part.code, language: part.language })
10469
+ /* @__PURE__ */ jsx14(ArtifactActions, { code: part.code, language: part.language, containerRef: contentRef })
10361
10470
  ]
10362
10471
  }
10363
10472
  );
@@ -15196,6 +15305,7 @@ var useDragResize = (options) => {
15196
15305
  const dragStartRef = useRef11(null);
15197
15306
  const isDraggingRef = useRef11(false);
15198
15307
  const wasDragRef = useRef11(false);
15308
+ const userDraggedRef = useRef11(false);
15199
15309
  const dragDeltaRef = useRef11({ dx: 0, dy: 0 });
15200
15310
  const fabElRef = useRef11(null);
15201
15311
  const dragCleanupRef = useRef11(null);
@@ -15262,6 +15372,7 @@ var useDragResize = (options) => {
15262
15372
  if (!isDraggingRef.current && distance > DRAG_THRESHOLD) {
15263
15373
  isDraggingRef.current = true;
15264
15374
  wasDragRef.current = true;
15375
+ userDraggedRef.current = true;
15265
15376
  setIsDragging(true);
15266
15377
  fabElRef.current?.classList.add("chatllm-fab-dragging");
15267
15378
  }
@@ -15501,7 +15612,11 @@ var useDragResize = (options) => {
15501
15612
  const newVw = window.innerWidth;
15502
15613
  const newVh = window.innerHeight;
15503
15614
  setViewport({ width: newVw, height: newVh });
15504
- setFabPos((prev) => clampToViewport(prev.x, prev.y, newVw, newVh));
15615
+ if (!userDraggedRef.current) {
15616
+ setFabPos(getInitialFabPosition(initialPosition));
15617
+ } else {
15618
+ setFabPos((prev) => clampToViewport(prev.x, prev.y, newVw, newVh));
15619
+ }
15505
15620
  };
15506
15621
  window.addEventListener("resize", handleResize);
15507
15622
  return () => window.removeEventListener("resize", handleResize);
@@ -17101,23 +17216,39 @@ var ChatFloatingWidget = ({
17101
17216
  return [chatTab, ...customTabs];
17102
17217
  }, [tabs]);
17103
17218
  const [unreadBadge, setUnreadBadge] = useState28(0);
17104
- const seenMessageIdsRef = useRef17(new Set(chatState.messages.map((m) => m.id)));
17219
+ const seenMessageIdsRef = useRef17(/* @__PURE__ */ new Set());
17220
+ useEffect19(() => {
17221
+ if (seenMessageIdsRef.current.size === 0 && chatState.messages.length > 0) {
17222
+ for (const m of chatState.messages) {
17223
+ seenMessageIdsRef.current.add(m.id);
17224
+ }
17225
+ }
17226
+ }, [chatState.messages]);
17105
17227
  useEffect19(() => {
17106
- if (!isOpen) {
17228
+ if (isOpen) {
17229
+ for (const m of chatState.messages) {
17230
+ seenMessageIdsRef.current.add(m.id);
17231
+ }
17232
+ } else {
17107
17233
  const newAssistant = chatState.messages.filter(
17108
17234
  (m) => m.role === "assistant" && !seenMessageIdsRef.current.has(m.id)
17109
17235
  );
17110
17236
  if (newAssistant.length > 0) {
17111
17237
  setUnreadBadge((prev) => prev + newAssistant.length);
17238
+ for (const m of newAssistant) {
17239
+ seenMessageIdsRef.current.add(m.id);
17240
+ }
17112
17241
  }
17113
17242
  }
17114
- for (const m of chatState.messages) {
17115
- seenMessageIdsRef.current.add(m.id);
17116
- }
17117
17243
  }, [chatState.messages, isOpen]);
17118
17244
  useEffect19(() => {
17119
- if (isOpen) setUnreadBadge(0);
17120
- }, [isOpen]);
17245
+ if (isOpen) {
17246
+ setUnreadBadge(0);
17247
+ for (const m of chatState.messages) {
17248
+ seenMessageIdsRef.current.add(m.id);
17249
+ }
17250
+ }
17251
+ }, [isOpen, chatState.messages]);
17121
17252
  const totalBadge = useMemo7(() => {
17122
17253
  return tabs.reduce((sum, t) => sum + (t.badge || 0), 0) + unreadBadge;
17123
17254
  }, [tabs, unreadBadge]);
@@ -17553,6 +17684,57 @@ var useDeepResearch = (options) => {
17553
17684
  };
17554
17685
  };
17555
17686
 
17687
+ // src/react/utils/conversationSearchAdapter.ts
17688
+ var formatSearchResults = (results) => {
17689
+ if (results.length === 0) {
17690
+ return "\uAD00\uB828 \uB300\uD654\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.";
17691
+ }
17692
+ const formatted = results.map((r, i) => {
17693
+ const header = r.sessionTitle ? `[${r.sessionTitle}]` : "";
17694
+ const time = r.timestamp ? ` (${new Date(r.timestamp).toLocaleDateString("ko-KR")})` : "";
17695
+ return `[${i + 1}] ${header}${time} (${r.role}): ${r.content}`;
17696
+ }).join("\n\n");
17697
+ return `\uAC80\uC0C9 \uACB0\uACFC (${results.length}\uAC74):
17698
+
17699
+ ${formatted}`;
17700
+ };
17701
+ var createConversationSearchSkill = (options) => {
17702
+ const { onSearch, label, disabled } = options;
17703
+ return {
17704
+ description: '\uC774\uC804 \uB300\uD654 \uB0B4\uC6A9\uC744 \uAC80\uC0C9\uD569\uB2C8\uB2E4. \uBC18\uB4DC\uC2DC \uD638\uCD9C\uD574\uC57C \uD558\uB294 \uC0C1\uD669: \uC0AC\uC6A9\uC790\uAC00 "\uC804\uC5D0", "\uC9C0\uB09C\uBC88\uC5D0", "\uC608\uC804\uC5D0", "\uC544\uAE4C", "\uC774\uC804\uC5D0", "\uB2E4\uC2DC \uC54C\uB824\uC918", "\uB610", "\uADF8\uB54C" \uB4F1 \uACFC\uAC70 \uB300\uD654\uB97C \uCC38\uC870\uD558\uB294 \uD45C\uD604\uC744 \uC0AC\uC6A9\uD560 \uB54C. \uACFC\uAC70\uC5D0 \uB17C\uC758\uD588\uB358 \uC8FC\uC81C\uB97C \uB2E4\uC2DC \uBB3C\uC5B4\uBCFC \uB54C\uB3C4 \uD638\uCD9C\uD558\uC138\uC694.',
17705
+ trigger: "auto",
17706
+ label: label || "\uB300\uD654 \uAC80\uC0C9",
17707
+ icon: "search-line",
17708
+ disabled,
17709
+ persist: false,
17710
+ parameters: {
17711
+ type: "object",
17712
+ properties: {
17713
+ query: {
17714
+ type: "string",
17715
+ description: "\uAC80\uC0C9 \uD0A4\uC6CC\uB4DC \uB610\uB294 \uBB38\uC7A5 (\uC0AC\uC6A9\uC790 \uC758\uB3C4\uB97C \uBC18\uC601\uD55C \uAC80\uC0C9\uC5B4)"
17716
+ },
17717
+ limit: {
17718
+ type: "number",
17719
+ description: "\uCD5C\uB300 \uACB0\uACFC \uC218 (\uAE30\uBCF8: 5)"
17720
+ }
17721
+ },
17722
+ required: ["query"]
17723
+ },
17724
+ execute: async (params) => {
17725
+ const { query, limit } = params;
17726
+ if (!query) {
17727
+ return { content: "\uAC80\uC0C9\uC5B4\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4." };
17728
+ }
17729
+ const results = await onSearch(query, { limit: limit || 5 });
17730
+ return {
17731
+ content: formatSearchResults(results),
17732
+ metadata: { resultCount: results.length }
17733
+ };
17734
+ }
17735
+ };
17736
+ };
17737
+
17556
17738
  // src/react/components/EmptyState.tsx
17557
17739
  import { jsx as jsx31, jsxs as jsxs30 } from "react/jsx-runtime";
17558
17740
  var EmptyState = ({
@@ -18068,6 +18250,7 @@ export {
18068
18250
  convertSkillsToOpenAITools,
18069
18251
  convertToolsToSkills,
18070
18252
  createAdvancedResearchSkill,
18253
+ createConversationSearchSkill,
18071
18254
  createDeepResearchSkill,
18072
18255
  migrateSessionsToProjects,
18073
18256
  useChatUI,