@agentaily/design-system 0.5.0 → 0.7.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Markdown.js","sources":["../../../src/components/chat/Markdown.jsx"],"sourcesContent":["import React from \"react\";\nimport { CodeBlock } from \"./CodeBlock.jsx\";\n\n/* ============================================================\n Agentaily — Markdown primitive\n Renders a model's markdown STRING into typeset React nodes.\n\n Safety model: the source string is parsed into a block/inline\n tree and rendered as React elements only. All literal text\n becomes React text nodes (auto-escaped) — we NEVER touch\n dangerouslySetInnerHTML, so there is no HTML-injection surface.\n Link hrefs are scheme-sanitized; images render as inert\n placeholders (no remote fetch).\n\n Streaming model: inline delimiters match only when their\n closing delimiter is present, so an unterminated ** ` _ ~~ or\n [..]( degrades to literal text. A half table, a half blockquote,\n and an unclosed ``` fence all render their partial content\n instead of throwing — nothing disappears mid-stream.\n\n Coverage: paragraphs + soft breaks, bold, italic, strikethrough,\n inline code, code blocks, ordered/unordered/nested/task lists,\n blockquotes (nested), tables (GFM, column alignment), horizontal\n rules, headings, links, bare-URL autolinks, image placeholders.\n ============================================================ */\n\nconst AX_MD_CSS = `\n.ax-md { font-size: var(--text-md); line-height: var(--leading-body); color: var(--text-body); min-width: 0; }\n.ax-md > :first-child { margin-top: 0; }\n.ax-md > :last-child { margin-bottom: 0; }\n\n.ax-md__p { margin: 0 0 0.7em; text-wrap: pretty; }\n\n.ax-md__ul, .ax-md__ol { margin: 0 0 0.7em; padding-left: 1.5em; }\n.ax-md__li { margin: 0.2em 0; }\n.ax-md__li > .ax-md__ul, .ax-md__li > .ax-md__ol { margin: 0.25em 0 0; padding-left: 1.3em; }\n\n.ax-md__ul { list-style: none; padding-left: 1.35em; }\n.ax-md__ul > .ax-md__li { position: relative; }\n.ax-md__ul > .ax-md__li::before {\n content: \"\"; position: absolute; left: -1.05em; top: 0.62em;\n width: 4px; height: 4px; background: var(--text-faint);\n}\n\n.ax-md__ol { list-style: decimal; padding-left: 1.7em; }\n.ax-md__ol > .ax-md__li::marker {\n font-family: var(--font-mono); font-size: 0.9em; color: var(--text-faint);\n}\n\n.ax-md__li--task { list-style: none; display: flex; align-items: flex-start; gap: 8px; }\n.ax-md__ul > .ax-md__li--task::before { display: none; }\n.ax-md__check {\n appearance: none; -webkit-appearance: none; margin: 3px 0 0; flex: none;\n width: 15px; height: 15px; border: 1px solid var(--border-strong);\n border-radius: var(--radius-1); background: var(--bg-0); position: relative; cursor: default;\n}\n.ax-md__check:checked { background: var(--accent); border-color: var(--accent); }\n.ax-md__check:checked::after {\n content: \"\"; position: absolute; left: 4px; top: 1px; width: 4px; height: 8px;\n border: solid var(--accent-fg); border-width: 0 1.6px 1.6px 0; transform: rotate(45deg);\n}\n.ax-md__li--task > .ax-md__taskbody { flex: 1; min-width: 0; }\n\n.ax-md__strong { font-weight: var(--weight-bold); color: var(--text-body); }\n.ax-md__em { font-style: italic; }\n.ax-md__del { text-decoration: line-through; text-decoration-thickness: 1px; color: var(--text-muted); }\n\n.ax-md__code {\n font-family: var(--font-mono); font-size: 0.86em;\n background: var(--bg-3); color: var(--text-body);\n border: 1px solid var(--border-default);\n border-radius: var(--radius-1); padding: 0.12em 0.36em;\n white-space: break-spaces; word-break: break-word;\n}\n[data-theme=\"dark\"] .ax-md__code { background: var(--bg-2); }\n\n.ax-md > .ax-code, .ax-md__li > .ax-code, .ax-md__quote > .ax-code { margin: 0 0 0.7em; }\n\n.ax-md__link {\n color: var(--text-body); text-decoration: underline;\n text-underline-offset: 2px; text-decoration-color: var(--border-strong);\n transition: text-decoration-color var(--dur-1) var(--ease-out);\n word-break: break-word;\n}\n.ax-md__link:hover { text-decoration-color: var(--text-body); }\n\n.ax-md__img {\n display: inline-flex; align-items: center; gap: 6px; vertical-align: baseline;\n padding: 1px 8px; border: 1px dashed var(--border-strong); border-radius: var(--radius-1);\n font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-faint);\n}\n.ax-md__img b { font-weight: var(--weight-medium); letter-spacing: var(--tracking-label); }\n\n.ax-md__quote {\n margin: 0 0 0.7em; padding: 2px 0 2px 14px;\n border-left: 2px solid var(--border-strong); color: var(--text-muted);\n}\n.ax-md__quote > :last-child { margin-bottom: 0; }\n.ax-md__quote .ax-md__quote { margin-top: 0.5em; }\n\n.ax-md__hr { border: none; border-top: 1px solid var(--border-default); margin: 1.2em 0; }\n\n.ax-md__tablewrap {\n margin: 0 0 0.7em; overflow-x: auto;\n border: 1px solid var(--border-default); border-radius: var(--radius-2);\n}\n.ax-md__table { border-collapse: collapse; width: 100%; font-size: var(--text-sm); }\n.ax-md__table th, .ax-md__table td {\n padding: 7px 12px; text-align: left; vertical-align: top;\n border-bottom: 1px solid var(--border-default); border-right: 1px solid var(--border-default);\n}\n.ax-md__table th:last-child, .ax-md__table td:last-child { border-right: none; }\n.ax-md__table tbody tr:last-child td { border-bottom: none; }\n.ax-md__table thead th {\n background: var(--surface-card); color: var(--text-body);\n font-weight: var(--weight-medium); white-space: nowrap;\n}\n\n.ax-md__h { font-family: var(--font-display); font-weight: var(--weight-medium);\n line-height: var(--leading-snug); letter-spacing: var(--tracking-tight);\n margin: 1.1em 0 0.5em; color: var(--text-body); }\n.ax-md__h--1 { font-size: var(--text-xl); }\n.ax-md__h--2 { font-size: var(--text-lg); }\n.ax-md__h--3 { font-size: var(--text-md); }\n`;\n\nif (typeof document !== \"undefined\" && !document.getElementById(\"ax-md-css\")) {\n const s = document.createElement(\"style\");\n s.id = \"ax-md-css\";\n s.textContent = AX_MD_CSS;\n document.head.appendChild(s);\n}\n\n/* ---- url sanitizer: only allow safe schemes / relative ---- */\nfunction sanitizeUrl(raw) {\n // eslint-disable-next-line no-control-regex\n const url = String(raw || \"\")\n .replace(/[\\u0000-\\u001F\\u007F]/g, \"\")\n .trim();\n if (!url) return null;\n if (/^[a-z][a-z0-9+.-]*:/i.test(url)) {\n if (/^(https?|mailto|tel):/i.test(url)) return url;\n return null; // blocks javascript:, data:, vbscript:, file:, etc.\n }\n return url; // relative, anchor, query, protocol-relative\n}\n\n/* ==================== inline parser ==================== */\n/* string -> React node array. At each position it tries inline\n rules in priority order; a rule fires only when its CLOSING\n delimiter exists, so streamed/partial delimiters fall through\n to literal text. */\nfunction parseInline(text, keyBase) {\n const out = [];\n let buf = \"\";\n let i = 0;\n let k = 0;\n const len = text.length;\n const flush = () => {\n if (buf) {\n out.push(buf);\n buf = \"\";\n }\n };\n\n while (i < len) {\n const ch = text[i];\n const rest = text.slice(i);\n\n if (ch === \"\\n\") {\n flush();\n out.push(<br key={keyBase + \"-br\" + k++} />);\n i++;\n continue;\n }\n\n if (ch === \"\\\\\" && i + 1 < len && /[\\\\`*_{}\\[\\]()#+\\-.!~|>]/.test(text[i + 1])) {\n buf += text[i + 1];\n i += 2;\n continue;\n }\n\n let m;\n // inline code\n if (ch === \"`\" && (m = /^`([^`\\n]+?)`/.exec(rest))) {\n flush();\n out.push(\n <code key={keyBase + \"-c\" + k++} className=\"ax-md__code\">\n {m[1]}\n </code>,\n );\n i += m[0].length;\n continue;\n }\n // image placeholder ![alt](url) — inert, never fetched\n if (ch === \"!\" && text[i + 1] === \"[\" && (m = /^!\\[([^\\]]*)\\]\\(([^)\\s]*)\\)/.exec(rest))) {\n flush();\n out.push(\n <span key={keyBase + \"-img\" + k++} className=\"ax-md__img\" title={m[2]}>\n <b>IMG</b>\n {m[1] ? \" · \" + m[1] : \"\"}\n </span>,\n );\n i += m[0].length;\n continue;\n }\n // link [text](url)\n if (ch === \"[\" && (m = /^\\[([^\\]]*)\\]\\(([^)\\s]*)\\)/.exec(rest))) {\n flush();\n const href = sanitizeUrl(m[2]);\n const inner = parseInline(m[1], keyBase + \"-lt\" + k);\n out.push(\n href ? (\n <a\n key={keyBase + \"-l\" + k++}\n className=\"ax-md__link\"\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer nofollow\"\n >\n {inner}\n </a>\n ) : (\n <span key={keyBase + \"-l\" + k++}>{inner}</span>\n ),\n );\n i += m[0].length;\n continue;\n }\n // bare-URL autolink (word boundary on the left)\n if (\n (ch === \"h\" || ch === \"w\") &&\n (i === 0 || /[\\s(]/.test(text[i - 1])) &&\n (m = /^((?:https?:\\/\\/|www\\.)[^\\s<>]+)/i.exec(rest))\n ) {\n let urlText = m[1];\n const trail = /[.,;:!?'\")\\]]+$/.exec(urlText);\n if (trail) urlText = urlText.slice(0, urlText.length - trail[0].length);\n const href = sanitizeUrl(/^www\\./i.test(urlText) ? \"http://\" + urlText : urlText);\n if (href) {\n flush();\n out.push(\n <a\n key={keyBase + \"-a\" + k++}\n className=\"ax-md__link\"\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer nofollow\"\n >\n {urlText}\n </a>,\n );\n i += urlText.length;\n continue;\n }\n }\n // bold ** ** or __ __\n if (ch === \"*\" && (m = /^\\*\\*([\\s\\S]+?)\\*\\*/.exec(rest))) {\n flush();\n out.push(\n <strong key={keyBase + \"-b\" + k++} className=\"ax-md__strong\">\n {parseInline(m[1], keyBase + \"-bt\" + k)}\n </strong>,\n );\n i += m[0].length;\n continue;\n }\n if (ch === \"_\" && (m = /^__([\\s\\S]+?)__/.exec(rest))) {\n flush();\n out.push(\n <strong key={keyBase + \"-b\" + k++} className=\"ax-md__strong\">\n {parseInline(m[1], keyBase + \"-bt\" + k)}\n </strong>,\n );\n i += m[0].length;\n continue;\n }\n // strikethrough ~~ ~~\n if (ch === \"~\" && (m = /^~~([\\s\\S]+?)~~/.exec(rest))) {\n flush();\n out.push(\n <del key={keyBase + \"-s\" + k++} className=\"ax-md__del\">\n {parseInline(m[1], keyBase + \"-st\" + k)}\n </del>,\n );\n i += m[0].length;\n continue;\n }\n // italic * * or _ _ (no adjacent whitespace)\n if (ch === \"*\" && (m = /^\\*(?!\\s)([^*]+?)(?<!\\s)\\*/.exec(rest))) {\n flush();\n out.push(\n <em key={keyBase + \"-i\" + k++} className=\"ax-md__em\">\n {parseInline(m[1], keyBase + \"-it\" + k)}\n </em>,\n );\n i += m[0].length;\n continue;\n }\n if (ch === \"_\" && (m = /^_(?!\\s)([^_]+?)(?<!\\s)_/.exec(rest))) {\n flush();\n out.push(\n <em key={keyBase + \"-i\" + k++} className=\"ax-md__em\">\n {parseInline(m[1], keyBase + \"-it\" + k)}\n </em>,\n );\n i += m[0].length;\n continue;\n }\n\n buf += ch;\n i++;\n }\n flush();\n return out;\n}\n\n/* ==================== block parser ==================== */\nconst RE_FENCE = /^(\\s{0,3})(`{3,}|~{3,})\\s*([\\w+#.-]*)\\s*$/;\nconst RE_HEADING = /^(#{1,6})\\s+(.*)$/;\nconst RE_HR = /^\\s{0,3}([-*_])(?:\\s*\\1){2,}\\s*$/;\nconst RE_BLANK = /^\\s*$/;\nconst RE_QUOTE = /^\\s*>/;\nconst RE_LIST_ITEM = /^([ \\t]*)([-*+]|\\d{1,9}[.)])\\s+(.*)$/;\n\nfunction leadingSpaces(s) {\n const m = /^[ \\t]*/.exec(s);\n return m[0].length;\n}\n\nfunction listMarkerInfo(line) {\n const m = RE_LIST_ITEM.exec(line);\n if (!m) return null;\n const ordered = /\\d/.test(m[2]);\n return {\n indent: m[1].length,\n bullet: ordered ? \"ol\" : \"ul\",\n start: ordered ? parseInt(m[2], 10) : null,\n text: m[3],\n raw: m[0],\n };\n}\n\nfunction splitTableRow(row) {\n let s = row.trim();\n if (s.startsWith(\"|\")) s = s.slice(1);\n if (s.endsWith(\"|\")) s = s.slice(0, -1);\n return s.split(/(?<!\\\\)\\|/).map((c) => c.trim().replace(/\\\\\\|/g, \"|\"));\n}\n\nfunction isTableSep(line) {\n if (!line.includes(\"|\")) return false;\n const cells = splitTableRow(line);\n return cells.length > 0 && cells.every((c) => /^:?-{1,}:?$/.test(c.trim()));\n}\n\nfunction alignOf(cell) {\n const c = cell.trim();\n const l = c.startsWith(\":\"),\n r = c.endsWith(\":\");\n if (l && r) return \"center\";\n if (r) return \"right\";\n if (l) return \"left\";\n return null;\n}\n\nfunction startsBlock(lines, i) {\n const line = lines[i];\n if (RE_FENCE.test(line)) return true;\n if (RE_HR.test(line)) return true;\n if (RE_HEADING.test(line)) return true;\n if (RE_QUOTE.test(line)) return true;\n if (listMarkerInfo(line)) return true;\n if (line.includes(\"|\") && i + 1 < lines.length && isTableSep(lines[i + 1])) return true;\n return false;\n}\n\nfunction parseList(lines, i) {\n const base = leadingSpaces(lines[i]);\n const baseInfo = listMarkerInfo(lines[i]);\n const type = baseInfo.bullet;\n const start = baseInfo.start;\n const items = [];\n\n while (i < lines.length) {\n if (RE_BLANK.test(lines[i])) {\n let j = i;\n while (j < lines.length && RE_BLANK.test(lines[j])) j++;\n const info = j < lines.length ? listMarkerInfo(lines[j]) : null;\n if (\n j < lines.length &&\n ((info && leadingSpaces(lines[j]) === base) || leadingSpaces(lines[j]) > base)\n ) {\n i = j;\n continue;\n }\n break;\n }\n const ind = leadingSpaces(lines[i]);\n const info = listMarkerInfo(lines[i]);\n if (info && ind === base && info.bullet === type) {\n const markerWidth = info.raw.length - info.text.length;\n const itemLines = [info.text];\n i++;\n while (i < lines.length) {\n if (RE_BLANK.test(lines[i])) {\n let j = i;\n while (j < lines.length && RE_BLANK.test(lines[j])) j++;\n if (j < lines.length && leadingSpaces(lines[j]) > base) {\n itemLines.push(\"\");\n i++;\n continue;\n }\n break;\n }\n if (leadingSpaces(lines[i]) > base) {\n itemLines.push(lines[i].slice(Math.min(markerWidth, leadingSpaces(lines[i]))));\n i++;\n } else break;\n }\n while (itemLines.length && itemLines[itemLines.length - 1] === \"\") itemLines.pop();\n\n let checked = null;\n const tm = /^\\[([ xX])\\]\\s+([\\s\\S]*)$/.exec(itemLines[0] || \"\");\n if (tm) {\n checked = tm[1].toLowerCase() === \"x\";\n itemLines[0] = tm[2];\n }\n\n items.push({ checked, blocks: parseBlocks(itemLines.join(\"\\n\")) });\n } else {\n break; // sibling of different type, dedent, or non-list line ends the list\n }\n }\n return { type, start, items, next: i };\n}\n\nfunction parseBlocks(src) {\n const lines = String(src || \"\")\n .replace(/\\r\\n?/g, \"\\n\")\n .split(\"\\n\");\n const blocks = [];\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n\n // fenced code (tolerates a missing closing fence)\n const fence = RE_FENCE.exec(line);\n if (fence) {\n const mark = fence[2][0];\n const len = fence[2].length;\n const close = new RegExp(\"^\\\\s{0,3}\" + mark + \"{\" + len + \",}\\\\s*$\");\n i++;\n const code = [];\n while (i < lines.length && !close.test(lines[i])) {\n code.push(lines[i]);\n i++;\n }\n if (i < lines.length) i++;\n blocks.push({ type: \"code\", lang: fence[3] || \"\", code: code.join(\"\\n\") });\n continue;\n }\n\n if (RE_BLANK.test(line)) {\n i++;\n continue;\n }\n\n if (RE_HR.test(line)) {\n blocks.push({ type: \"hr\" });\n i++;\n continue;\n }\n\n const h = RE_HEADING.exec(line);\n if (h) {\n blocks.push({ type: \"heading\", level: h[1].length, text: h[2] });\n i++;\n continue;\n }\n\n // blockquote — strip one '>' level, recurse (enables nesting + inner blocks)\n if (RE_QUOTE.test(line)) {\n const inner = [];\n while (i < lines.length && RE_QUOTE.test(lines[i])) {\n inner.push(lines[i].replace(/^\\s*>\\s?/, \"\"));\n i++;\n }\n blocks.push({ type: \"quote\", blocks: parseBlocks(inner.join(\"\\n\")) });\n continue;\n }\n\n // GFM table (header + separator). Half tables render their partial rows.\n if (line.includes(\"|\") && i + 1 < lines.length && isTableSep(lines[i + 1])) {\n const header = splitTableRow(line);\n const aligns = splitTableRow(lines[i + 1]).map(alignOf);\n i += 2;\n const rows = [];\n while (\n i < lines.length &&\n !RE_BLANK.test(lines[i]) &&\n lines[i].includes(\"|\") &&\n !RE_FENCE.test(lines[i]) &&\n !RE_QUOTE.test(lines[i])\n ) {\n rows.push(splitTableRow(lines[i]));\n i++;\n }\n blocks.push({ type: \"table\", header, aligns, rows });\n continue;\n }\n\n // lists (nested + mixed handled recursively)\n if (listMarkerInfo(line)) {\n const res = parseList(lines, i);\n blocks.push({ type: \"list\", listType: res.type, start: res.start, items: res.items });\n i = res.next;\n continue;\n }\n\n // paragraph\n const para = [line];\n i++;\n while (i < lines.length && !RE_BLANK.test(lines[i]) && !startsBlock(lines, i)) {\n para.push(lines[i]);\n i++;\n }\n blocks.push({ type: \"p\", text: para.join(\"\\n\") });\n }\n\n return blocks;\n}\n\n/* ==================== render ==================== */\nfunction renderItemContent(blocks, key) {\n if (blocks.length === 1 && blocks[0].type === \"p\") return parseInline(blocks[0].text, key);\n return blocks.map((b, j) =>\n b.type === \"p\" ? (\n <React.Fragment key={j}>{parseInline(b.text, key + \"-\" + j)}</React.Fragment>\n ) : (\n renderBlock(b, key + \"-\" + j)\n ),\n );\n}\n\nfunction renderList(b, key) {\n const Tag = b.listType === \"ol\" ? \"ol\" : \"ul\";\n const cls = b.listType === \"ol\" ? \"ax-md__ol\" : \"ax-md__ul\";\n const startAttr = b.listType === \"ol\" && b.start && b.start !== 1 ? b.start : undefined;\n return (\n <Tag key={key} className={cls} start={startAttr}>\n {b.items.map((it, j) => {\n const content = renderItemContent(it.blocks, key + \"-\" + j);\n if (it.checked !== null) {\n return (\n <li key={j} className=\"ax-md__li ax-md__li--task\">\n <input\n type=\"checkbox\"\n className=\"ax-md__check\"\n checked={it.checked}\n disabled\n readOnly\n />\n <span className=\"ax-md__taskbody\">{content}</span>\n </li>\n );\n }\n return (\n <li key={j} className=\"ax-md__li\">\n {content}\n </li>\n );\n })}\n </Tag>\n );\n}\n\nfunction renderTable(b, key) {\n const cols = b.header.length;\n return (\n <div key={key} className=\"ax-md__tablewrap\">\n <table className=\"ax-md__table\">\n <thead>\n <tr>\n {b.header.map((c, ci) => (\n <th key={ci} style={{ textAlign: b.aligns[ci] || \"left\" }}>\n {parseInline(c, key + \"-h\" + ci)}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {b.rows.map((r, ri) => (\n <tr key={ri}>\n {Array.from({ length: cols }).map((_, ci) => (\n <td key={ci} style={{ textAlign: b.aligns[ci] || \"left\" }}>\n {parseInline(r[ci] || \"\", key + \"-r\" + ri + \"c\" + ci)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\nfunction renderBlock(b, key) {\n switch (b.type) {\n case \"code\":\n return <CodeBlock key={key} code={b.code} lang={b.lang || \"text\"} />;\n case \"hr\":\n return <hr key={key} className=\"ax-md__hr\" />;\n case \"quote\":\n return (\n <blockquote key={key} className=\"ax-md__quote\">\n {renderBlocks(b.blocks, key)}\n </blockquote>\n );\n case \"table\":\n return renderTable(b, key);\n case \"list\":\n return renderList(b, key);\n case \"heading\": {\n const lvl = Math.min(b.level, 3);\n const Tag = \"h\" + Math.min(b.level + 1, 6);\n return (\n <Tag key={key} className={\"ax-md__h ax-md__h--\" + lvl}>\n {parseInline(b.text, key)}\n </Tag>\n );\n }\n case \"p\":\n default:\n return (\n <p key={key} className=\"ax-md__p\">\n {parseInline(b.text, key)}\n </p>\n );\n }\n}\n\nfunction renderBlocks(blocks, keyBase) {\n return blocks.map((b, idx) => renderBlock(b, keyBase + \"-blk\" + idx));\n}\n\nexport function Markdown({ content, children, className = \"\", ...rest }) {\n const src = typeof content === \"string\" ? content : typeof children === \"string\" ? children : \"\";\n const blocks = React.useMemo(() => parseBlocks(src), [src]);\n const cls = [\"ax-md\", className].filter(Boolean).join(\" \");\n return (\n <div className={cls} {...rest}>\n {renderBlocks(blocks, \"md\")}\n </div>\n );\n}\n"],"names":["info"],"mappings":"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"}
@@ -11,6 +11,12 @@ export interface MessageProps {
11
11
  time?: string;
12
12
  /** Append a blinking block cursor (response in progress). @default false */
13
13
  streaming?: boolean;
14
+ /**
15
+ * Markdown source for the body, rendered through <Markdown>. Overrides
16
+ * `children`. (A plain string passed as `children` is also auto-rendered
17
+ * as markdown; React-node children render unchanged for back-compat.)
18
+ */
19
+ markdown?: string;
14
20
  children?: React.ReactNode;
15
21
  }
16
22
  export declare function Message(props: MessageProps): JSX.Element;
@@ -1,5 +1,6 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import "react";
3
+ import { Markdown } from "./Markdown.js";
3
4
  const AX_MSG_CSS = `
4
5
  .ax-msg { display: flex; flex-direction: column; }
5
6
  .ax-msg--user { align-items: flex-end; }
@@ -40,19 +41,28 @@ function Message({
40
41
  name,
41
42
  time,
42
43
  streaming = false,
44
+ markdown,
43
45
  children,
44
46
  className = "",
45
47
  ...rest
46
48
  }) {
47
49
  const isUser = role === "user";
48
50
  const cls = ["ax-msg", isUser ? "ax-msg--user" : "ax-msg--assistant", className].filter(Boolean).join(" ");
51
+ let body;
52
+ if (typeof markdown === "string") {
53
+ body = /* @__PURE__ */ jsx(Markdown, { content: markdown });
54
+ } else if (typeof children === "string") {
55
+ body = /* @__PURE__ */ jsx(Markdown, { content: children });
56
+ } else {
57
+ body = children;
58
+ }
49
59
  return /* @__PURE__ */ jsxs("div", { className: cls, ...rest, children: [
50
60
  !isUser ? /* @__PURE__ */ jsxs("div", { className: "ax-msg__head", children: [
51
61
  /* @__PURE__ */ jsx("span", { className: "ax-msg__name", children: name || "Agentaily" }),
52
62
  time ? /* @__PURE__ */ jsx("span", { className: "ax-msg__time", children: time }) : null
53
63
  ] }) : null,
54
64
  /* @__PURE__ */ jsxs("div", { className: "ax-msg__body", children: [
55
- children,
65
+ body,
56
66
  streaming ? /* @__PURE__ */ jsx("span", { className: "ax-msg__cursor" }) : null
57
67
  ] })
58
68
  ] });
@@ -1 +1 @@
1
- {"version":3,"file":"Message.js","sources":["../../../src/components/chat/Message.jsx"],"sourcesContent":["import React from \"react\";\n\nconst AX_MSG_CSS = `\n.ax-msg { display: flex; flex-direction: column; }\n.ax-msg--user { align-items: flex-end; }\n.ax-msg--user .ax-msg__body {\n max-width: 78%;\n background: var(--surface-card);\n border: 1px solid var(--border-default);\n border-radius: var(--radius-3);\n padding: 10px 14px;\n}\n.ax-msg--assistant { align-items: stretch; }\n.ax-msg__head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 6px; }\n.ax-msg__name {\n font-family: var(--font-mono); font-size: var(--text-xs);\n font-weight: var(--weight-medium); letter-spacing: var(--tracking-label);\n text-transform: uppercase; color: var(--text-faint);\n}\n.ax-msg__time { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-faint); opacity: 0.7; }\n.ax-msg__body { font-size: var(--text-md); line-height: var(--leading-body); color: var(--text-body); min-width: 0; }\n.ax-msg__body > p { margin: 0 0 0.7em; }\n.ax-msg__body > p:last-child { margin-bottom: 0; }\n.ax-msg__cursor {\n display: inline-block; width: 0.55em; height: 1.05em;\n background: currentColor; vertical-align: text-bottom; margin-left: 2px;\n animation: ax-msg-blink 1.1s steps(1) infinite;\n}\n@keyframes ax-msg-blink { 50% { opacity: 0; } }\n@media (prefers-reduced-motion: reduce) { .ax-msg__cursor { animation: none; } }\n`;\n\nif (typeof document !== \"undefined\" && !document.getElementById(\"ax-msg-css\")) {\n const s = document.createElement(\"style\");\n s.id = \"ax-msg-css\";\n s.textContent = AX_MSG_CSS;\n document.head.appendChild(s);\n}\n\nexport function Message({\n role = \"assistant\",\n name,\n time,\n streaming = false,\n children,\n className = \"\",\n ...rest\n}) {\n const isUser = role === \"user\";\n const cls = [\"ax-msg\", isUser ? \"ax-msg--user\" : \"ax-msg--assistant\", className].filter(Boolean).join(\" \");\n return (\n <div className={cls} {...rest}>\n {!isUser ? (\n <div className=\"ax-msg__head\">\n <span className=\"ax-msg__name\">{name || \"Agentaily\"}</span>\n {time ? <span className=\"ax-msg__time\">{time}</span> : null}\n </div>\n ) : null}\n <div className=\"ax-msg__body\">\n {children}\n {streaming ? <span className=\"ax-msg__cursor\"></span> : null}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":";;AAEA,MAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8BnB,IAAI,OAAO,aAAa,eAAe,CAAC,SAAS,eAAe,YAAY,GAAG;AAC7E,QAAM,IAAI,SAAS,cAAc,OAAO;AACxC,IAAE,KAAK;AACP,IAAE,cAAc;AAChB,WAAS,KAAK,YAAY,CAAC;AAC7B;AAEO,SAAS,QAAQ;AAAA,EACtB,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA,YAAY;AAAA,EACZ,GAAG;AACL,GAAG;AACD,QAAM,SAAS,SAAS;AACxB,QAAM,MAAM,CAAC,UAAU,SAAS,iBAAiB,qBAAqB,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AACzG,SACE,qBAAC,OAAA,EAAI,WAAW,KAAM,GAAG,MACtB,UAAA;AAAA,IAAA,CAAC,SACA,qBAAC,OAAA,EAAI,WAAU,gBACb,UAAA;AAAA,MAAA,oBAAC,QAAA,EAAK,WAAU,gBAAgB,UAAA,QAAQ,aAAY;AAAA,MACnD,OAAO,oBAAC,QAAA,EAAK,WAAU,gBAAgB,gBAAK,IAAU;AAAA,IAAA,EAAA,CACzD,IACE;AAAA,IACJ,qBAAC,OAAA,EAAI,WAAU,gBACZ,UAAA;AAAA,MAAA;AAAA,MACA,YAAY,oBAAC,QAAA,EAAK,WAAU,kBAAiB,IAAU;AAAA,IAAA,EAAA,CAC1D;AAAA,EAAA,GACF;AAEJ;"}
1
+ {"version":3,"file":"Message.js","sources":["../../../src/components/chat/Message.jsx"],"sourcesContent":["import React from \"react\";\nimport { Markdown } from \"./Markdown.jsx\";\n\nconst AX_MSG_CSS = `\n.ax-msg { display: flex; flex-direction: column; }\n.ax-msg--user { align-items: flex-end; }\n.ax-msg--user .ax-msg__body {\n max-width: 78%;\n background: var(--surface-card);\n border: 1px solid var(--border-default);\n border-radius: var(--radius-3);\n padding: 10px 14px;\n}\n.ax-msg--assistant { align-items: stretch; }\n.ax-msg__head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 6px; }\n.ax-msg__name {\n font-family: var(--font-mono); font-size: var(--text-xs);\n font-weight: var(--weight-medium); letter-spacing: var(--tracking-label);\n text-transform: uppercase; color: var(--text-faint);\n}\n.ax-msg__time { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-faint); opacity: 0.7; }\n.ax-msg__body { font-size: var(--text-md); line-height: var(--leading-body); color: var(--text-body); min-width: 0; }\n.ax-msg__body > p { margin: 0 0 0.7em; }\n.ax-msg__body > p:last-child { margin-bottom: 0; }\n.ax-msg__cursor {\n display: inline-block; width: 0.55em; height: 1.05em;\n background: currentColor; vertical-align: text-bottom; margin-left: 2px;\n animation: ax-msg-blink 1.1s steps(1) infinite;\n}\n@keyframes ax-msg-blink { 50% { opacity: 0; } }\n@media (prefers-reduced-motion: reduce) { .ax-msg__cursor { animation: none; } }\n`;\n\nif (typeof document !== \"undefined\" && !document.getElementById(\"ax-msg-css\")) {\n const s = document.createElement(\"style\");\n s.id = \"ax-msg-css\";\n s.textContent = AX_MSG_CSS;\n document.head.appendChild(s);\n}\n\nexport function Message({\n role = \"assistant\",\n name,\n time,\n streaming = false,\n markdown,\n children,\n className = \"\",\n ...rest\n}) {\n const isUser = role === \"user\";\n const cls = [\"ax-msg\", isUser ? \"ax-msg--user\" : \"ax-msg--assistant\", className]\n .filter(Boolean)\n .join(\" \");\n\n // Back-compat: React-node children render as-is. A markdown string (via the\n // `markdown` prop, or a plain string passed as children) routes through\n // <Markdown> so model output is typeset without breaking existing usage.\n let body;\n if (typeof markdown === \"string\") {\n body = <Markdown content={markdown} />;\n } else if (typeof children === \"string\") {\n body = <Markdown content={children} />;\n } else {\n body = children;\n }\n\n return (\n <div className={cls} {...rest}>\n {!isUser ? (\n <div className=\"ax-msg__head\">\n <span className=\"ax-msg__name\">{name || \"Agentaily\"}</span>\n {time ? <span className=\"ax-msg__time\">{time}</span> : null}\n </div>\n ) : null}\n <div className=\"ax-msg__body\">\n {body}\n {streaming ? <span className=\"ax-msg__cursor\"></span> : null}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":";;;AAGA,MAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8BnB,IAAI,OAAO,aAAa,eAAe,CAAC,SAAS,eAAe,YAAY,GAAG;AAC7E,QAAM,IAAI,SAAS,cAAc,OAAO;AACxC,IAAE,KAAK;AACP,IAAE,cAAc;AAChB,WAAS,KAAK,YAAY,CAAC;AAC7B;AAEO,SAAS,QAAQ;AAAA,EACtB,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,GAAG;AACL,GAAG;AACD,QAAM,SAAS,SAAS;AACxB,QAAM,MAAM,CAAC,UAAU,SAAS,iBAAiB,qBAAqB,SAAS,EAC5E,OAAO,OAAO,EACd,KAAK,GAAG;AAKX,MAAI;AACJ,MAAI,OAAO,aAAa,UAAU;AAChC,WAAO,oBAAC,UAAA,EAAS,SAAS,SAAA,CAAU;AAAA,EACtC,WAAW,OAAO,aAAa,UAAU;AACvC,WAAO,oBAAC,UAAA,EAAS,SAAS,SAAA,CAAU;AAAA,EACtC,OAAO;AACL,WAAO;AAAA,EACT;AAEA,SACE,qBAAC,OAAA,EAAI,WAAW,KAAM,GAAG,MACtB,UAAA;AAAA,IAAA,CAAC,SACA,qBAAC,OAAA,EAAI,WAAU,gBACb,UAAA;AAAA,MAAA,oBAAC,QAAA,EAAK,WAAU,gBAAgB,UAAA,QAAQ,aAAY;AAAA,MACnD,OAAO,oBAAC,QAAA,EAAK,WAAU,gBAAgB,gBAAK,IAAU;AAAA,IAAA,EAAA,CACzD,IACE;AAAA,IACJ,qBAAC,OAAA,EAAI,WAAU,gBACZ,UAAA;AAAA,MAAA;AAAA,MACA,YAAY,oBAAC,QAAA,EAAK,WAAU,kBAAiB,IAAU;AAAA,IAAA,EAAA,CAC1D;AAAA,EAAA,GACF;AAEJ;"}
@@ -2,7 +2,7 @@
2
2
  * Two-pane AI-designer frame: a chat column + a live preview, separated by a
3
3
  * draggable divider, collapsing to one pane with a segmented switcher on phones.
4
4
  * Pure frame — pass `chat` / `preview` / top-bar slots and mount your own
5
- * overlays (AuthDialog, IntegrationSettings, MarkupLayer) as siblings. The
5
+ * overlays (AuthDialog, integration cards, MarkupLayer) as siblings. The
6
6
  * preview pane is position:relative so a MarkupLayer can fill it.
7
7
  */
8
8
  export interface DesignerShellProps {
@@ -1 +1 @@
1
- {"version":3,"file":"DesignerShell.js","sources":["../../../src/components/layout/DesignerShell.jsx"],"sourcesContent":["import React, { useState, useRef, useEffect } from \"react\";\nimport { BrandMark } from \"../utilities/BrandMark.jsx\";\n\n// DesignerShell — the two-pane \"AI designer\" frame: a chat column on the left\n// and a live preview on the right, separated by a draggable divider, collapsing\n// to a single pane with a segmented switcher on phones. Pure frame: pass `chat`\n// and `preview` nodes plus top-bar slots; mount your own overlays (AuthDialog,\n// IntegrationSettings, MarkupLayer) as siblings.\nconst AX_DESIGNERSHELL_CSS = `\n.ax-dshell { display: flex; flex-direction: column; height: 100%; background: var(--surface-page); }\n.ax-dshell__top { display: flex; align-items: center; gap: 16px; height: var(--topbar-h); flex: none; padding: 0 16px; border-bottom: 1px solid var(--border-default); background: var(--surface-panel); }\n/* helper: use on a pane's sub-header (e.g. the preview tabs bar) so both panes' bars share --bar-h and line up */\n.ax-dshell__panebar { display: flex; align-items: center; gap: 12px; height: var(--bar-h); flex: none; padding: 0 16px; border-bottom: 1px solid var(--border-default); background: var(--surface-panel); }\n.ax-dshell__div { color: var(--text-faint); }\n.ax-dshell__crumb { font-family: var(--font-mono); font-size: 13px; color: var(--text-muted); }\n.ax-dshell__title { flex: 1; display: flex; align-items: center; gap: 10px; justify-content: center; }\n.ax-dshell__actions { display: flex; align-items: center; gap: 8px; }\n.ax-dshell__sep { width: 1px; height: 20px; background: var(--border-default); margin: 0 2px; flex: none; }\n.ax-dshell__split { flex: 1; display: flex; min-height: 0; }\n.ax-dshell__pane { display: flex; flex-direction: column; min-width: 0; min-height: 0; }\n.ax-dshell__pane--preview { flex: 1; position: relative; }\n.ax-dshell__divider { width: 9px; flex: none; cursor: col-resize; display: flex; align-items: center; justify-content: center;\n background: var(--surface-page); border-left: 1px solid var(--border-default); border-right: 1px solid var(--border-default); }\n.ax-dshell__grip { width: 2px; height: 26px; border-radius: 2px; background: var(--border-strong); transition: background var(--dur-1) var(--ease-out); }\n.ax-dshell__divider:hover .ax-dshell__grip { background: var(--text-faint); }\n.ax-dshell__mbar { display: none; }\n@media (max-width: 720px) {\n .ax-dshell__split { flex-direction: column; }\n .ax-dshell__divider { display: none; }\n .ax-dshell__pane { width: 100% !important; flex: 1; }\n .ax-dshell__split[data-mview=\"chat\"] .ax-dshell__pane--preview { display: none; }\n .ax-dshell__split[data-mview=\"preview\"] .ax-dshell__pane--chat { display: none; }\n .ax-dshell__div, .ax-dshell__crumb { display: none; }\n .ax-dshell__mbar { display: flex; flex: none; gap: 8px; padding: 8px 12px; border-bottom: 1px solid var(--border-default); background: var(--surface-panel); }\n .ax-dshell__mseg { flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 7px; height: 38px; border: 1px solid var(--border-default);\n background: var(--surface-page); border-radius: var(--radius-2); font-family: var(--font-body); font-size: var(--text-sm); color: var(--text-muted); cursor: pointer; }\n .ax-dshell__mseg.is-on { background: var(--surface-card); border-color: var(--border-strong); color: var(--text-body); }\n .ax-dshell__mcount { font-family: var(--font-mono); font-size: 11px; line-height: 16px; color: var(--text-faint); border: 1px solid var(--border-default); border-radius: var(--radius-1); padding: 0 5px; }\n .ax-dshell__mseg.is-on .ax-dshell__mcount { color: var(--text-muted); }\n}\n`;\n\nif (typeof document !== \"undefined\" && !document.getElementById(\"ax-dshell-css\")) {\n const s = document.createElement(\"style\");\n s.id = \"ax-dshell-css\";\n s.textContent = AX_DESIGNERSHELL_CSS;\n document.head.appendChild(s);\n}\n\nexport function DesignerShell({\n brand,\n crumb = \"设计器\",\n title,\n actions,\n account,\n chat,\n preview,\n split,\n onSplitChange,\n defaultSplit = 0.42,\n minSplit = 0.28,\n maxSplit = 0.7,\n mobileLabels = { chat: \"对话\", preview: \"预览\" },\n mobileView,\n onMobileViewChange,\n}) {\n const controlled = split !== undefined;\n const [internalW, setInternalW] = useState(defaultSplit);\n const leftW = controlled ? split : internalW;\n const [internalMV, setInternalMV] = useState(\"chat\");\n const mview = mobileView !== undefined ? mobileView : internalMV;\n const setView = (v) => {\n if (onMobileViewChange) onMobileViewChange(v);\n if (mobileView === undefined) setInternalMV(v);\n };\n const dragging = useRef(false);\n const splitRef = useRef(null);\n\n // latest applier (clamp + write back, controlled or internal), kept in a ref so\n // the window drag listeners always see current props without re-binding.\n const applyRef = useRef(null);\n applyRef.current = (f) => {\n const v = Math.min(maxSplit, Math.max(minSplit, f));\n if (onSplitChange) onSplitChange(v); // drag writes back to a controlled value (e.g. a slider)\n if (!controlled) setInternalW(v);\n };\n\n useEffect(() => {\n const move = (e) => {\n if (!dragging.current || !splitRef.current) return;\n const r = splitRef.current.getBoundingClientRect();\n applyRef.current((e.clientX - r.left) / r.width);\n };\n const up = () => {\n dragging.current = false;\n document.body.style.cursor = \"\";\n };\n window.addEventListener(\"mousemove\", move);\n window.addEventListener(\"mouseup\", up);\n return () => {\n window.removeEventListener(\"mousemove\", move);\n window.removeEventListener(\"mouseup\", up);\n };\n }, []);\n\n return (\n <div className=\"ax-dshell\">\n <div className=\"ax-dshell__top\">\n {brand || <BrandMark size={18} wordmark blink={false} />}\n {crumb ? (\n <React.Fragment>\n <span className=\"ax-dshell__div\">/</span>\n <span className=\"ax-dshell__crumb\">{crumb}</span>\n </React.Fragment>\n ) : null}\n <div className=\"ax-dshell__title\">{title}</div>\n <div className=\"ax-dshell__actions\">\n {actions}\n {actions && account ? <span className=\"ax-dshell__sep\"></span> : null}\n {account}\n </div>\n </div>\n\n <div className=\"ax-dshell__mbar\">\n <button\n className={\"ax-dshell__mseg\" + (mview === \"chat\" ? \" is-on\" : \"\")}\n onClick={() => setView(\"chat\")}\n >\n {mobileLabels.chat}\n </button>\n <button\n className={\"ax-dshell__mseg\" + (mview === \"preview\" ? \" is-on\" : \"\")}\n onClick={() => setView(\"preview\")}\n >\n {mobileLabels.preview}\n </button>\n </div>\n\n <div className=\"ax-dshell__split\" ref={splitRef} data-mview={mview}>\n <div className=\"ax-dshell__pane ax-dshell__pane--chat\" style={{ width: leftW * 100 + \"%\" }}>\n {chat}\n </div>\n <div\n className=\"ax-dshell__divider\"\n onMouseDown={() => {\n dragging.current = true;\n document.body.style.cursor = \"col-resize\";\n }}\n >\n <span className=\"ax-dshell__grip\"></span>\n </div>\n <div className=\"ax-dshell__pane ax-dshell__pane--preview\">{preview}</div>\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DesignerShell.js","sources":["../../../src/components/layout/DesignerShell.jsx"],"sourcesContent":["import React, { useState, useRef, useEffect } from \"react\";\nimport { BrandMark } from \"../utilities/BrandMark.jsx\";\n\n// DesignerShell — the two-pane \"AI designer\" frame: a chat column on the left\n// and a live preview on the right, separated by a draggable divider, collapsing\n// to a single pane with a segmented switcher on phones. Pure frame: pass `chat`\n// and `preview` nodes plus top-bar slots; mount your own overlays (AuthDialog,\n// integration cards, MarkupLayer) as siblings.\nconst AX_DESIGNERSHELL_CSS = `\n.ax-dshell { display: flex; flex-direction: column; height: 100%; background: var(--surface-page); }\n.ax-dshell__top { display: flex; align-items: center; gap: 16px; height: var(--topbar-h); flex: none; padding: 0 16px; border-bottom: 1px solid var(--border-default); background: var(--surface-panel); }\n/* helper: use on a pane's sub-header (e.g. the preview tabs bar) so both panes' bars share --bar-h and line up */\n.ax-dshell__panebar { display: flex; align-items: center; gap: 12px; height: var(--bar-h); flex: none; padding: 0 16px; border-bottom: 1px solid var(--border-default); background: var(--surface-panel); }\n.ax-dshell__div { color: var(--text-faint); }\n.ax-dshell__crumb { font-family: var(--font-mono); font-size: 13px; color: var(--text-muted); }\n.ax-dshell__title { flex: 1; display: flex; align-items: center; gap: 10px; justify-content: center; }\n.ax-dshell__actions { display: flex; align-items: center; gap: 8px; }\n.ax-dshell__sep { width: 1px; height: 20px; background: var(--border-default); margin: 0 2px; flex: none; }\n.ax-dshell__split { flex: 1; display: flex; min-height: 0; }\n.ax-dshell__pane { display: flex; flex-direction: column; min-width: 0; min-height: 0; }\n.ax-dshell__pane--preview { flex: 1; position: relative; }\n.ax-dshell__divider { width: 9px; flex: none; cursor: col-resize; display: flex; align-items: center; justify-content: center;\n background: var(--surface-page); border-left: 1px solid var(--border-default); border-right: 1px solid var(--border-default); }\n.ax-dshell__grip { width: 2px; height: 26px; border-radius: 2px; background: var(--border-strong); transition: background var(--dur-1) var(--ease-out); }\n.ax-dshell__divider:hover .ax-dshell__grip { background: var(--text-faint); }\n.ax-dshell__mbar { display: none; }\n@media (max-width: 720px) {\n .ax-dshell__split { flex-direction: column; }\n .ax-dshell__divider { display: none; }\n .ax-dshell__pane { width: 100% !important; flex: 1; }\n .ax-dshell__split[data-mview=\"chat\"] .ax-dshell__pane--preview { display: none; }\n .ax-dshell__split[data-mview=\"preview\"] .ax-dshell__pane--chat { display: none; }\n .ax-dshell__div, .ax-dshell__crumb { display: none; }\n .ax-dshell__mbar { display: flex; flex: none; gap: 8px; padding: 8px 12px; border-bottom: 1px solid var(--border-default); background: var(--surface-panel); }\n .ax-dshell__mseg { flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 7px; height: 38px; border: 1px solid var(--border-default);\n background: var(--surface-page); border-radius: var(--radius-2); font-family: var(--font-body); font-size: var(--text-sm); color: var(--text-muted); cursor: pointer; }\n .ax-dshell__mseg.is-on { background: var(--surface-card); border-color: var(--border-strong); color: var(--text-body); }\n .ax-dshell__mcount { font-family: var(--font-mono); font-size: 11px; line-height: 16px; color: var(--text-faint); border: 1px solid var(--border-default); border-radius: var(--radius-1); padding: 0 5px; }\n .ax-dshell__mseg.is-on .ax-dshell__mcount { color: var(--text-muted); }\n}\n`;\n\nif (typeof document !== \"undefined\" && !document.getElementById(\"ax-dshell-css\")) {\n const s = document.createElement(\"style\");\n s.id = \"ax-dshell-css\";\n s.textContent = AX_DESIGNERSHELL_CSS;\n document.head.appendChild(s);\n}\n\nexport function DesignerShell({\n brand,\n crumb = \"设计器\",\n title,\n actions,\n account,\n chat,\n preview,\n split,\n onSplitChange,\n defaultSplit = 0.42,\n minSplit = 0.28,\n maxSplit = 0.7,\n mobileLabels = { chat: \"对话\", preview: \"预览\" },\n mobileView,\n onMobileViewChange,\n}) {\n const controlled = split !== undefined;\n const [internalW, setInternalW] = useState(defaultSplit);\n const leftW = controlled ? split : internalW;\n const [internalMV, setInternalMV] = useState(\"chat\");\n const mview = mobileView !== undefined ? mobileView : internalMV;\n const setView = (v) => {\n if (onMobileViewChange) onMobileViewChange(v);\n if (mobileView === undefined) setInternalMV(v);\n };\n const dragging = useRef(false);\n const splitRef = useRef(null);\n\n // latest applier (clamp + write back, controlled or internal), kept in a ref so\n // the window drag listeners always see current props without re-binding.\n const applyRef = useRef(null);\n applyRef.current = (f) => {\n const v = Math.min(maxSplit, Math.max(minSplit, f));\n if (onSplitChange) onSplitChange(v); // drag writes back to a controlled value (e.g. a slider)\n if (!controlled) setInternalW(v);\n };\n\n useEffect(() => {\n const move = (e) => {\n if (!dragging.current || !splitRef.current) return;\n const r = splitRef.current.getBoundingClientRect();\n applyRef.current((e.clientX - r.left) / r.width);\n };\n const up = () => {\n dragging.current = false;\n document.body.style.cursor = \"\";\n };\n window.addEventListener(\"mousemove\", move);\n window.addEventListener(\"mouseup\", up);\n return () => {\n window.removeEventListener(\"mousemove\", move);\n window.removeEventListener(\"mouseup\", up);\n };\n }, []);\n\n return (\n <div className=\"ax-dshell\">\n <div className=\"ax-dshell__top\">\n {brand || <BrandMark size={18} wordmark blink={false} />}\n {crumb ? (\n <React.Fragment>\n <span className=\"ax-dshell__div\">/</span>\n <span className=\"ax-dshell__crumb\">{crumb}</span>\n </React.Fragment>\n ) : null}\n <div className=\"ax-dshell__title\">{title}</div>\n <div className=\"ax-dshell__actions\">\n {actions}\n {actions && account ? <span className=\"ax-dshell__sep\"></span> : null}\n {account}\n </div>\n </div>\n\n <div className=\"ax-dshell__mbar\">\n <button\n className={\"ax-dshell__mseg\" + (mview === \"chat\" ? \" is-on\" : \"\")}\n onClick={() => setView(\"chat\")}\n >\n {mobileLabels.chat}\n </button>\n <button\n className={\"ax-dshell__mseg\" + (mview === \"preview\" ? \" is-on\" : \"\")}\n onClick={() => setView(\"preview\")}\n >\n {mobileLabels.preview}\n </button>\n </div>\n\n <div className=\"ax-dshell__split\" ref={splitRef} data-mview={mview}>\n <div className=\"ax-dshell__pane ax-dshell__pane--chat\" style={{ width: leftW * 100 + \"%\" }}>\n {chat}\n </div>\n <div\n className=\"ax-dshell__divider\"\n onMouseDown={() => {\n dragging.current = true;\n document.body.style.cursor = \"col-resize\";\n }}\n >\n <span className=\"ax-dshell__grip\"></span>\n </div>\n <div className=\"ax-dshell__pane ax-dshell__pane--preview\">{preview}</div>\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * DeepSeekCard — a **pure-display** connection card for a DeepSeek LLM key.
3
+ *
4
+ * It renders one card (header · masked key field · model select · optional
5
+ * usage-cap block · help · TestRow) and nothing else. It holds **no state**,
6
+ * touches **no localStorage**, and has **no save bar / no readiness gating** —
7
+ * props in, events out. The caller owns the config object, persistence, the Save
8
+ * button, backend-error display, and any "both connected?" gate. This mirrors the
9
+ * DS philosophy (state lives with the caller; components only render) and makes
10
+ * the card reusable by any product wiring a DeepSeek key, not just one modal.
11
+ *
12
+ * `status` / `result` are driven entirely by the caller: run your probe in
13
+ * response to `onTest`, then push `status: "testing" → "ok" | "error"` and a
14
+ * `result` line back in. To clear a green pill when the key is edited, reset
15
+ * `status` to `"idle"` from your `onApiKeyChange` handler.
16
+ */
17
+ export interface DeepSeekModelOption {
18
+ value: string;
19
+ label: string;
20
+ }
21
+ export interface ConnectionHelp {
22
+ title: string;
23
+ steps: React.ReactNode[];
24
+ link?: { href: string; label: string };
25
+ defaultOpen?: boolean;
26
+ }
27
+ export interface DeepSeekCardProps {
28
+ /** API key value (controlled). Empty while a stored key is `masked` + untouched. */
29
+ apiKey?: string;
30
+ onApiKeyChange?: (value: string) => void;
31
+ /** Selected model id. @default "deepseek-chat" */
32
+ model?: string;
33
+ onModelChange?: (value: string) => void;
34
+ /** Model `<Select>` options. @default deepseek-chat + deepseek-reasoner */
35
+ models?: DeepSeekModelOption[];
36
+ /** Connection status — caller-controlled; drives the StatusPill, card tint, and TestRow. @default "idle" */
37
+ status?: "idle" | "testing" | "ok" | "error";
38
+ /** Result line shown in the TestRow for ok / error. */
39
+ result?: string;
40
+ /** Fires when the user clicks Test — run your probe and push `status`/`result` back. */
41
+ onTest?: () => void;
42
+ /**
43
+ * A key is already stored server-side. While true and `apiKey` is empty, the
44
+ * field shows a masked placeholder + "leave blank to keep" hint and still
45
+ * counts as present for the Test button. Typing (non-empty `apiKey`) overrides.
46
+ * @default false
47
+ */
48
+ masked?: boolean;
49
+ /** Field-level error under the key input (e.g. a backend 400 on the key). */
50
+ keyError?: string;
51
+ /** Show the monthly usage-cap warning + toggle. @default false */
52
+ showUsageCap?: boolean;
53
+ capOn?: boolean;
54
+ onCapOnChange?: (on: boolean) => void;
55
+ /** Monthly cap amount (digits-only string). */
56
+ cap?: string;
57
+ onCapChange?: (value: string) => void;
58
+ /** Override the "如何获取…" guide — a `{title, steps, link}` object or a ready React node. */
59
+ help?: ConnectionHelp | React.ReactNode;
60
+ /** Override the derived Test-enabled state. Default: enabled when a key is present or `masked`. */
61
+ canTest?: boolean;
62
+ /** Idle hint in the TestRow before the first test. */
63
+ idleHint?: string;
64
+ }
65
+ export declare function DeepSeekCard(props: DeepSeekCardProps): JSX.Element;
@@ -0,0 +1,197 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import { Alert } from "../feedback/Alert.js";
4
+ import { HelpSteps } from "./HelpSteps.js";
5
+ import { Icon } from "../utilities/Icon.js";
6
+ import { SecretField } from "../inputs/SecretField.js";
7
+ import { Select } from "../inputs/Select.js";
8
+ import { StatusPill } from "../display/StatusPill.js";
9
+ import { Switch } from "../inputs/Switch.js";
10
+ import { TestRow } from "./TestRow.js";
11
+ const AX_CONNCARD_CSS = `
12
+ .s-card { position: relative; background: var(--surface-card); border: 1px solid var(--border-default);
13
+ border-radius: var(--radius-3); overflow: hidden; transition: border-color var(--dur-2) var(--ease-out); }
14
+ .s-card.is-ok { border-color: rgba(62, 207, 142, 0.4); }
15
+ .s-card.is-error { border-color: rgba(229, 72, 77, 0.4); }
16
+ .s-card__head { padding: 20px 24px 18px; }
17
+ .s-card__toprow { display: flex; align-items: center; gap: 11px; margin-bottom: 15px; }
18
+ .s-card__icon { flex: none; width: 30px; height: 30px; border-radius: var(--radius-2);
19
+ border: 1px solid var(--border-strong); background: var(--surface-raised); color: var(--text-body);
20
+ display: flex; align-items: center; justify-content: center; }
21
+ .s-card__eyebrow { color: var(--text-faint); }
22
+ .s-card__status { margin-left: auto; flex: none; }
23
+ .s-card__title { font-family: var(--font-display); font-size: var(--text-xl); font-weight: 500;
24
+ letter-spacing: var(--tracking-tight); line-height: var(--leading-tight); color: var(--text-body); margin: 0; }
25
+ .s-card__desc { font-size: var(--text-sm); color: var(--text-muted); line-height: var(--leading-snug); margin: 9px 0 0; max-width: 58ch; }
26
+ .s-card__body { padding: 4px 24px 22px; display: flex; flex-direction: column; gap: 18px; }
27
+ .s-row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
28
+ @media (max-width: 560px) { .s-row2 { grid-template-columns: 1fr; } }
29
+ .s-lock { display: flex; align-items: flex-start; gap: 8px; font-size: var(--text-xs); color: var(--text-faint); line-height: var(--leading-snug); }
30
+ .s-lock svg { flex: none; margin-top: 1px; color: var(--text-muted); }
31
+ .s-lock strong { color: var(--text-muted); font-weight: 500; }
32
+ `;
33
+ const AX_DEEPSEEKCARD_CSS = `
34
+ .s-cap { display: flex; align-items: center; gap: 12px; padding-top: 4px; }
35
+ .s-cap__field { display: flex; align-items: center; gap: 8px; }
36
+ .s-cap__field .ax-input { width: 132px; }
37
+ .s-cap.is-off { opacity: 0.5; }
38
+ `;
39
+ function s_injectCss(id, css) {
40
+ if (typeof document === "undefined" || document.getElementById(id)) return;
41
+ const s = document.createElement("style");
42
+ s.id = id;
43
+ s.textContent = css;
44
+ document.head.appendChild(s);
45
+ }
46
+ s_injectCss("ax-conncard-css", AX_CONNCARD_CSS);
47
+ s_injectCss("ax-deepseekcard-css", AX_DEEPSEEKCARD_CSS);
48
+ const DS_DEFAULT_MODELS = [
49
+ { value: "deepseek-chat", label: "deepseek-chat · 通用 · 快" },
50
+ { value: "deepseek-reasoner", label: "deepseek-reasoner · 深度推理" }
51
+ ];
52
+ const DS_DEFAULT_HELP = {
53
+ title: "如何获取 DeepSeek API Key?",
54
+ steps: [
55
+ /* @__PURE__ */ jsxs(React.Fragment, { children: [
56
+ "登录 ",
57
+ /* @__PURE__ */ jsx("code", { children: "platform.deepseek.com" }),
58
+ ",进入「API Keys」页面。"
59
+ ] }),
60
+ /* @__PURE__ */ jsx(React.Fragment, { children: "点击「创建 API Key」,命名后立即复制——密钥只在创建时完整显示一次。" }),
61
+ /* @__PURE__ */ jsx(React.Fragment, { children: "在「充值」中确认账户余额充足,对话才能持续调用。" }),
62
+ /* @__PURE__ */ jsxs(React.Fragment, { children: [
63
+ "把以 ",
64
+ /* @__PURE__ */ jsx("code", { children: "sk-" }),
65
+ " 开头的密钥粘贴到上方输入框。"
66
+ ] })
67
+ ],
68
+ link: { href: "https://platform.deepseek.com", label: "打开 DeepSeek 开放平台" }
69
+ };
70
+ function DeepSeekCard({
71
+ apiKey = "",
72
+ onApiKeyChange,
73
+ model = "deepseek-chat",
74
+ onModelChange,
75
+ models = DS_DEFAULT_MODELS,
76
+ status = "idle",
77
+ result,
78
+ onTest,
79
+ masked = false,
80
+ keyError,
81
+ showUsageCap = false,
82
+ capOn = false,
83
+ onCapOnChange,
84
+ cap = "",
85
+ onCapChange,
86
+ help,
87
+ canTest,
88
+ idleHint = "填写密钥后测试连通性"
89
+ }) {
90
+ const maskedNow = masked && !(apiKey || "").trim();
91
+ const testDisabled = canTest !== void 0 ? !canTest : !(apiKey || "").trim() && !masked;
92
+ const help_ = help || DS_DEFAULT_HELP;
93
+ return /* @__PURE__ */ jsxs(
94
+ "section",
95
+ {
96
+ className: "s-card" + (status === "ok" ? " is-ok" : status === "error" ? " is-error" : ""),
97
+ children: [
98
+ /* @__PURE__ */ jsxs("div", { className: "s-card__head", children: [
99
+ /* @__PURE__ */ jsxs("div", { className: "s-card__toprow", children: [
100
+ /* @__PURE__ */ jsx("div", { className: "s-card__icon", children: /* @__PURE__ */ jsx(Icon, { name: "key", size: 16 }) }),
101
+ /* @__PURE__ */ jsx("span", { className: "ax-label s-card__eyebrow", children: "对话引擎 · LLM" }),
102
+ /* @__PURE__ */ jsx("span", { className: "s-card__status", children: /* @__PURE__ */ jsx(StatusPill, { status }) })
103
+ ] }),
104
+ /* @__PURE__ */ jsx("h2", { className: "s-card__title", children: "DeepSeek" }),
105
+ /* @__PURE__ */ jsx("p", { className: "s-card__desc", children: "驱动对话式交互。用户发送的每一条消息,都通过这把密钥调用 DeepSeek 补全。" })
106
+ ] }),
107
+ /* @__PURE__ */ jsxs("div", { className: "s-card__body", children: [
108
+ /* @__PURE__ */ jsx(
109
+ SecretField,
110
+ {
111
+ label: "API KEY",
112
+ value: apiKey,
113
+ onChange: onApiKeyChange,
114
+ placeholder: maskedNow ? "已保存 ········ · 留空则保持不变" : "sk-xxxxxxxxxxxxxxxxxxxxxxxx",
115
+ hint: maskedNow ? "已存密钥 · 留空表示不修改,输入新值即覆盖" : void 0,
116
+ error: keyError
117
+ }
118
+ ),
119
+ /* @__PURE__ */ jsxs("div", { className: "s-lock", children: [
120
+ /* @__PURE__ */ jsx(Icon, { name: "lock", size: 14 }),
121
+ /* @__PURE__ */ jsxs("span", { children: [
122
+ /* @__PURE__ */ jsx("strong", { children: "密钥加密存储" }),
123
+ ",仅在服务端发起调用。用户",
124
+ /* @__PURE__ */ jsx("strong", { children: "永远看不到、也拿不到这把密钥。" })
125
+ ] })
126
+ ] }),
127
+ /* @__PURE__ */ jsx("div", { className: "s-row2", children: /* @__PURE__ */ jsxs("div", { children: [
128
+ /* @__PURE__ */ jsx("label", { className: "s-field__label ax-label", children: "对话模型" }),
129
+ /* @__PURE__ */ jsx(
130
+ Select,
131
+ {
132
+ value: model,
133
+ onChange: (e) => onModelChange && onModelChange(e.target.value),
134
+ options: models
135
+ }
136
+ )
137
+ ] }) }),
138
+ showUsageCap ? /* @__PURE__ */ jsxs(React.Fragment, { children: [
139
+ /* @__PURE__ */ jsx(Alert, { variant: "warn", icon: /* @__PURE__ */ jsx(Icon, { name: "zap", size: 15 }), title: "用量由你承担", children: "每一轮对话消耗的都是你自己 DeepSeek 账户的额度。用得越多,调用越多——建议设置每月上限以防意外超支。" }),
140
+ /* @__PURE__ */ jsxs("div", { className: "s-cap" + (capOn ? "" : " is-off"), children: [
141
+ /* @__PURE__ */ jsx(
142
+ Switch,
143
+ {
144
+ label: "启用每月用量上限",
145
+ checked: capOn,
146
+ onChange: (e) => onCapOnChange && onCapOnChange(e.target.checked)
147
+ }
148
+ ),
149
+ /* @__PURE__ */ jsxs("div", { className: "s-cap__field", style: { display: capOn ? "flex" : "none" }, children: [
150
+ /* @__PURE__ */ jsx(
151
+ "input",
152
+ {
153
+ className: "ax-input ax-input--mono",
154
+ type: "text",
155
+ inputMode: "numeric",
156
+ value: cap,
157
+ onChange: (e) => onCapChange && onCapChange(e.target.value.replace(/[^0-9]/g, "")),
158
+ "aria-label": "每月上限(元)"
159
+ }
160
+ ),
161
+ /* @__PURE__ */ jsx("span", { className: "s-field__hint", style: { margin: 0 }, children: "元 / 月,达到后暂停对话" })
162
+ ] })
163
+ ] })
164
+ ] }) : null,
165
+ /* @__PURE__ */ jsx(HelpStepsSlot, { help: help_ })
166
+ ] }),
167
+ /* @__PURE__ */ jsx(
168
+ TestRow,
169
+ {
170
+ status,
171
+ result,
172
+ onTest,
173
+ disabled: testDisabled,
174
+ idleHint
175
+ }
176
+ )
177
+ ]
178
+ }
179
+ );
180
+ }
181
+ function HelpStepsSlot({ help }) {
182
+ if (!help) return null;
183
+ if (React.isValidElement(help)) return help;
184
+ return /* @__PURE__ */ jsx(
185
+ HelpSteps,
186
+ {
187
+ title: help.title,
188
+ steps: help.steps,
189
+ link: help.link,
190
+ defaultOpen: help.defaultOpen
191
+ }
192
+ );
193
+ }
194
+ export {
195
+ DeepSeekCard
196
+ };
197
+ //# sourceMappingURL=DeepSeekCard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeepSeekCard.js","sources":["../../../src/components/settings/DeepSeekCard.jsx"],"sourcesContent":["import React from \"react\";\nimport { Alert } from \"../feedback/Alert.jsx\";\nimport { HelpSteps } from \"./HelpSteps.jsx\";\nimport { Icon } from \"../utilities/Icon.jsx\";\nimport { SecretField } from \"../inputs/SecretField.jsx\";\nimport { Select } from \"../inputs/Select.jsx\";\nimport { StatusPill } from \"../display/StatusPill.jsx\";\nimport { Switch } from \"../inputs/Switch.jsx\";\nimport { TestRow } from \"./TestRow.jsx\";\n\n// DeepSeekCard — a PURE-DISPLAY connection card for a DeepSeek LLM key. It only\n// renders: props in (apiKey, model, status, result, masked, errors, help…),\n// events out (onApiKeyChange / onModelChange / onTest). Zero state, zero\n// localStorage, zero save bar, zero readiness gating — the caller owns all of\n// that. Composes SecretField / StatusPill / TestRow / HelpSteps. Reusable by any\n// product that needs to wire a DeepSeek key, not just the integration modal.\n\n// Shared connection-card shell — injected once under a single id so DeepSeekCard\n// and FeishuCard render identically whether one or both are mounted.\nconst AX_CONNCARD_CSS = `\n.s-card { position: relative; background: var(--surface-card); border: 1px solid var(--border-default);\n border-radius: var(--radius-3); overflow: hidden; transition: border-color var(--dur-2) var(--ease-out); }\n.s-card.is-ok { border-color: rgba(62, 207, 142, 0.4); }\n.s-card.is-error { border-color: rgba(229, 72, 77, 0.4); }\n.s-card__head { padding: 20px 24px 18px; }\n.s-card__toprow { display: flex; align-items: center; gap: 11px; margin-bottom: 15px; }\n.s-card__icon { flex: none; width: 30px; height: 30px; border-radius: var(--radius-2);\n border: 1px solid var(--border-strong); background: var(--surface-raised); color: var(--text-body);\n display: flex; align-items: center; justify-content: center; }\n.s-card__eyebrow { color: var(--text-faint); }\n.s-card__status { margin-left: auto; flex: none; }\n.s-card__title { font-family: var(--font-display); font-size: var(--text-xl); font-weight: 500;\n letter-spacing: var(--tracking-tight); line-height: var(--leading-tight); color: var(--text-body); margin: 0; }\n.s-card__desc { font-size: var(--text-sm); color: var(--text-muted); line-height: var(--leading-snug); margin: 9px 0 0; max-width: 58ch; }\n.s-card__body { padding: 4px 24px 22px; display: flex; flex-direction: column; gap: 18px; }\n.s-row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }\n@media (max-width: 560px) { .s-row2 { grid-template-columns: 1fr; } }\n.s-lock { display: flex; align-items: flex-start; gap: 8px; font-size: var(--text-xs); color: var(--text-faint); line-height: var(--leading-snug); }\n.s-lock svg { flex: none; margin-top: 1px; color: var(--text-muted); }\n.s-lock strong { color: var(--text-muted); font-weight: 500; }\n`;\n\nconst AX_DEEPSEEKCARD_CSS = `\n.s-cap { display: flex; align-items: center; gap: 12px; padding-top: 4px; }\n.s-cap__field { display: flex; align-items: center; gap: 8px; }\n.s-cap__field .ax-input { width: 132px; }\n.s-cap.is-off { opacity: 0.5; }\n`;\n\nfunction s_injectCss(id, css) {\n if (typeof document === \"undefined\" || document.getElementById(id)) return;\n const s = document.createElement(\"style\");\n s.id = id;\n s.textContent = css;\n document.head.appendChild(s);\n}\ns_injectCss(\"ax-conncard-css\", AX_CONNCARD_CSS);\ns_injectCss(\"ax-deepseekcard-css\", AX_DEEPSEEKCARD_CSS);\n\nconst DS_DEFAULT_MODELS = [\n { value: \"deepseek-chat\", label: \"deepseek-chat · 通用 · 快\" },\n { value: \"deepseek-reasoner\", label: \"deepseek-reasoner · 深度推理\" },\n];\n\nconst DS_DEFAULT_HELP = {\n title: \"如何获取 DeepSeek API Key?\",\n steps: [\n <React.Fragment>\n 登录 <code>platform.deepseek.com</code>,进入「API Keys」页面。\n </React.Fragment>,\n <React.Fragment>\n 点击「创建 API Key」,命名后立即复制——密钥只在创建时完整显示一次。\n </React.Fragment>,\n <React.Fragment>在「充值」中确认账户余额充足,对话才能持续调用。</React.Fragment>,\n <React.Fragment>\n 把以 <code>sk-</code> 开头的密钥粘贴到上方输入框。\n </React.Fragment>,\n ],\n link: { href: \"https://platform.deepseek.com\", label: \"打开 DeepSeek 开放平台\" },\n};\n\nexport function DeepSeekCard({\n apiKey = \"\",\n onApiKeyChange,\n model = \"deepseek-chat\",\n onModelChange,\n models = DS_DEFAULT_MODELS,\n status = \"idle\",\n result,\n onTest,\n masked = false,\n keyError,\n showUsageCap = false,\n capOn = false,\n onCapOnChange,\n cap = \"\",\n onCapChange,\n help,\n canTest,\n idleHint = \"填写密钥后测试连通性\",\n}) {\n // Masked echo is derived, not stateful: a stored key is present (masked) and\n // the field is still empty → show the masked placeholder + \"leave blank\" hint,\n // and treat it as \"present\" for the Test button. The moment the caller's value\n // is non-empty (user typed), the field behaves normally.\n const maskedNow = masked && !(apiKey || \"\").trim();\n const testDisabled = canTest !== undefined ? !canTest : !(apiKey || \"\").trim() && !masked;\n const help_ = help || DS_DEFAULT_HELP;\n\n return (\n <section\n className={\"s-card\" + (status === \"ok\" ? \" is-ok\" : status === \"error\" ? \" is-error\" : \"\")}\n >\n <div className=\"s-card__head\">\n <div className=\"s-card__toprow\">\n <div className=\"s-card__icon\">\n <Icon name=\"key\" size={16} />\n </div>\n <span className=\"ax-label s-card__eyebrow\">对话引擎 · LLM</span>\n <span className=\"s-card__status\">\n <StatusPill status={status} />\n </span>\n </div>\n <h2 className=\"s-card__title\">DeepSeek</h2>\n <p className=\"s-card__desc\">\n 驱动对话式交互。用户发送的每一条消息,都通过这把密钥调用 DeepSeek 补全。\n </p>\n </div>\n\n <div className=\"s-card__body\">\n <SecretField\n label=\"API KEY\"\n value={apiKey}\n onChange={onApiKeyChange}\n placeholder={\n maskedNow ? \"已保存 ········ · 留空则保持不变\" : \"sk-xxxxxxxxxxxxxxxxxxxxxxxx\"\n }\n hint={maskedNow ? \"已存密钥 · 留空表示不修改,输入新值即覆盖\" : undefined}\n error={keyError}\n />\n\n <div className=\"s-lock\">\n <Icon name=\"lock\" size={14} />\n <span>\n <strong>密钥加密存储</strong>,仅在服务端发起调用。用户\n <strong>永远看不到、也拿不到这把密钥。</strong>\n </span>\n </div>\n\n <div className=\"s-row2\">\n <div>\n <label className=\"s-field__label ax-label\">对话模型</label>\n <Select\n value={model}\n onChange={(e) => onModelChange && onModelChange(e.target.value)}\n options={models}\n />\n </div>\n </div>\n\n {showUsageCap ? (\n <React.Fragment>\n <Alert variant=\"warn\" icon={<Icon name=\"zap\" size={15} />} title=\"用量由你承担\">\n 每一轮对话消耗的都是你自己 DeepSeek\n 账户的额度。用得越多,调用越多——建议设置每月上限以防意外超支。\n </Alert>\n <div className={\"s-cap\" + (capOn ? \"\" : \" is-off\")}>\n <Switch\n label=\"启用每月用量上限\"\n checked={capOn}\n onChange={(e) => onCapOnChange && onCapOnChange(e.target.checked)}\n />\n <div className=\"s-cap__field\" style={{ display: capOn ? \"flex\" : \"none\" }}>\n <input\n className=\"ax-input ax-input--mono\"\n type=\"text\"\n inputMode=\"numeric\"\n value={cap}\n onChange={(e) =>\n onCapChange && onCapChange(e.target.value.replace(/[^0-9]/g, \"\"))\n }\n aria-label=\"每月上限(元)\"\n />\n <span className=\"s-field__hint\" style={{ margin: 0 }}>\n 元 / 月,达到后暂停对话\n </span>\n </div>\n </div>\n </React.Fragment>\n ) : null}\n\n <HelpStepsSlot help={help_} />\n </div>\n\n <TestRow\n status={status}\n result={result}\n onTest={onTest}\n disabled={testDisabled}\n idleHint={idleHint}\n />\n </section>\n );\n}\n\n// Renders a HelpSteps node from either a {title, steps, link} object or a\n// ready-made React node passed straight through.\nfunction HelpStepsSlot({ help }) {\n if (!help) return null;\n if (React.isValidElement(help)) return help;\n return (\n <HelpSteps\n title={help.title}\n steps={help.steps}\n link={help.link}\n defaultOpen={help.defaultOpen}\n />\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;AAmBA,MAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuBxB,MAAM,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAO5B,SAAS,YAAY,IAAI,KAAK;AAC5B,MAAI,OAAO,aAAa,eAAe,SAAS,eAAe,EAAE,EAAG;AACpE,QAAM,IAAI,SAAS,cAAc,OAAO;AACxC,IAAE,KAAK;AACP,IAAE,cAAc;AAChB,WAAS,KAAK,YAAY,CAAC;AAC7B;AACA,YAAY,mBAAmB,eAAe;AAC9C,YAAY,uBAAuB,mBAAmB;AAEtD,MAAM,oBAAoB;AAAA,EACxB,EAAE,OAAO,iBAAiB,OAAO,yBAAA;AAAA,EACjC,EAAE,OAAO,qBAAqB,OAAO,2BAAA;AACvC;AAEA,MAAM,kBAAkB;AAAA,EACtB,OAAO;AAAA,EACP,OAAO;AAAA,IACL,qBAAC,MAAM,UAAN,EAAe,UAAA;AAAA,MAAA;AAAA,MACX,oBAAC,UAAK,UAAA,wBAAA,CAAqB;AAAA,MAAO;AAAA,IAAA,GACvC;AAAA,IACA,oBAAC,MAAM,UAAN,EAAe,UAAA,yCAAA,CAEhB;AAAA,IACA,oBAAC,MAAM,UAAN,EAAe,UAAA,2BAAA,CAAwB;AAAA,IACxC,qBAAC,MAAM,UAAN,EAAe,UAAA;AAAA,MAAA;AAAA,MACX,oBAAC,UAAK,UAAA,MAAA,CAAG;AAAA,MAAO;AAAA,IAAA,EAAA,CACrB;AAAA,EAAA;AAAA,EAEF,MAAM,EAAE,MAAM,iCAAiC,OAAO,mBAAA;AACxD;AAEO,SAAS,aAAa;AAAA,EAC3B,SAAS;AAAA,EACT;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,SAAS;AAAA,EACT,SAAS;AAAA,EACT;AAAA,EACA;AAAA,EACA,SAAS;AAAA,EACT;AAAA,EACA,eAAe;AAAA,EACf,QAAQ;AAAA,EACR;AAAA,EACA,MAAM;AAAA,EACN;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AACb,GAAG;AAKD,QAAM,YAAY,UAAU,EAAE,UAAU,IAAI,KAAA;AAC5C,QAAM,eAAe,YAAY,SAAY,CAAC,UAAU,EAAE,UAAU,IAAI,KAAA,KAAU,CAAC;AACnF,QAAM,QAAQ,QAAQ;AAEtB,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,YAAY,WAAW,OAAO,WAAW,WAAW,UAAU,cAAc;AAAA,MAEvF,UAAA;AAAA,QAAA,qBAAC,OAAA,EAAI,WAAU,gBACb,UAAA;AAAA,UAAA,qBAAC,OAAA,EAAI,WAAU,kBACb,UAAA;AAAA,YAAA,oBAAC,OAAA,EAAI,WAAU,gBACb,UAAA,oBAAC,QAAK,MAAK,OAAM,MAAM,GAAA,CAAI,EAAA,CAC7B;AAAA,YACA,oBAAC,QAAA,EAAK,WAAU,4BAA2B,UAAA,cAAU;AAAA,gCACpD,QAAA,EAAK,WAAU,kBACd,UAAA,oBAAC,YAAA,EAAW,QAAgB,EAAA,CAC9B;AAAA,UAAA,GACF;AAAA,UACA,oBAAC,MAAA,EAAG,WAAU,iBAAgB,UAAA,YAAQ;AAAA,UACtC,oBAAC,KAAA,EAAE,WAAU,gBAAe,UAAA,4CAAA,CAE5B;AAAA,QAAA,GACF;AAAA,QAEA,qBAAC,OAAA,EAAI,WAAU,gBACb,UAAA;AAAA,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,OAAM;AAAA,cACN,OAAO;AAAA,cACP,UAAU;AAAA,cACV,aACE,YAAY,6BAA6B;AAAA,cAE3C,MAAM,YAAY,2BAA2B;AAAA,cAC7C,OAAO;AAAA,YAAA;AAAA,UAAA;AAAA,UAGT,qBAAC,OAAA,EAAI,WAAU,UACb,UAAA;AAAA,YAAA,oBAAC,MAAA,EAAK,MAAK,QAAO,MAAM,IAAI;AAAA,iCAC3B,QAAA,EACC,UAAA;AAAA,cAAA,oBAAC,YAAO,UAAA,SAAA,CAAM;AAAA,cAAS;AAAA,cACvB,oBAAC,YAAO,UAAA,kBAAA,CAAe;AAAA,YAAA,EAAA,CACzB;AAAA,UAAA,GACF;AAAA,UAEA,oBAAC,OAAA,EAAI,WAAU,UACb,+BAAC,OAAA,EACC,UAAA;AAAA,YAAA,oBAAC,SAAA,EAAM,WAAU,2BAA0B,UAAA,QAAI;AAAA,YAC/C;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,OAAO;AAAA,gBACP,UAAU,CAAC,MAAM,iBAAiB,cAAc,EAAE,OAAO,KAAK;AAAA,gBAC9D,SAAS;AAAA,cAAA;AAAA,YAAA;AAAA,UACX,EAAA,CACF,EAAA,CACF;AAAA,UAEC,eACC,qBAAC,MAAM,UAAN,EACC,UAAA;AAAA,YAAA,oBAAC,OAAA,EAAM,SAAQ,QAAO,MAAM,oBAAC,MAAA,EAAK,MAAK,OAAM,MAAM,IAAI,GAAI,OAAM,UAAS,UAAA,2DAG1E;AAAA,iCACC,OAAA,EAAI,WAAW,WAAW,QAAQ,KAAK,YACtC,UAAA;AAAA,cAAA;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,OAAM;AAAA,kBACN,SAAS;AAAA,kBACT,UAAU,CAAC,MAAM,iBAAiB,cAAc,EAAE,OAAO,OAAO;AAAA,gBAAA;AAAA,cAAA;AAAA,cAElE,qBAAC,OAAA,EAAI,WAAU,gBAAe,OAAO,EAAE,SAAS,QAAQ,SAAS,OAAA,GAC/D,UAAA;AAAA,gBAAA;AAAA,kBAAC;AAAA,kBAAA;AAAA,oBACC,WAAU;AAAA,oBACV,MAAK;AAAA,oBACL,WAAU;AAAA,oBACV,OAAO;AAAA,oBACP,UAAU,CAAC,MACT,eAAe,YAAY,EAAE,OAAO,MAAM,QAAQ,WAAW,EAAE,CAAC;AAAA,oBAElE,cAAW;AAAA,kBAAA;AAAA,gBAAA;AAAA,gBAEb,oBAAC,UAAK,WAAU,iBAAgB,OAAO,EAAE,QAAQ,EAAA,GAAK,UAAA,gBAAA,CAEtD;AAAA,cAAA,EAAA,CACF;AAAA,YAAA,EAAA,CACF;AAAA,UAAA,EAAA,CACF,IACE;AAAA,UAEJ,oBAAC,eAAA,EAAc,MAAM,MAAA,CAAO;AAAA,QAAA,GAC9B;AAAA,QAEA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC;AAAA,YACA;AAAA,YACA;AAAA,YACA,UAAU;AAAA,YACV;AAAA,UAAA;AAAA,QAAA;AAAA,MACF;AAAA,IAAA;AAAA,EAAA;AAGN;AAIA,SAAS,cAAc,EAAE,QAAQ;AAC/B,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,MAAM,eAAe,IAAI,EAAG,QAAO;AACvC,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,OAAO,KAAK;AAAA,MACZ,OAAO,KAAK;AAAA,MACZ,MAAM,KAAK;AAAA,MACX,aAAa,KAAK;AAAA,IAAA;AAAA,EAAA;AAGxB;"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * FeishuCard — a **pure-display** connection card for a Feishu (Lark) Bitable
3
+ * data sink.
4
+ *
5
+ * It renders one card (header · App ID · App Secret · share link · parsed
6
+ * App-Token/table read-out · security note · optional field-mapping table ·
7
+ * help · TestRow) and nothing else. **No state, no localStorage, no save bar, no
8
+ * gating** — props in, events out. The caller owns the config object,
9
+ * persistence, the Save button, backend-error display, and the "both connected?"
10
+ * gate. The only thing the card derives locally is the App-Token / table read-out
11
+ * parsed from the share link (pure display).
12
+ *
13
+ * `status` / `result` are caller-controlled: run your probe on `onTest`, then
14
+ * push `status` + `result` back. Reset `status` to `"idle"` from your change
15
+ * handlers to clear a green pill after an edit.
16
+ */
17
+ import type { ConnectionHelp } from "./DeepSeekCard";
18
+ export interface FeishuFieldMapRow {
19
+ /** Source field label, or null for an auto-filled column. */
20
+ from: string | null;
21
+ /** Destination Bitable column. */
22
+ to: string;
23
+ /** Small mono tag after the column, e.g. "单选" | "自动". */
24
+ tag?: string | null;
25
+ }
26
+ export interface FeishuCardProps {
27
+ /** Feishu app id (public). */
28
+ appId?: string;
29
+ onAppIdChange?: (value: string) => void;
30
+ /** Feishu app secret. Empty while a stored secret is `masked` + untouched. */
31
+ secret?: string;
32
+ onSecretChange?: (value: string) => void;
33
+ /** Bitable share URL — App Token + table id are auto-parsed for the read-out. */
34
+ link?: string;
35
+ onLinkChange?: (value: string) => void;
36
+ /** Connection status — caller-controlled; drives the StatusPill, card tint, and TestRow. @default "idle" */
37
+ status?: "idle" | "testing" | "ok" | "error";
38
+ /** Result line shown in the TestRow for ok / error. */
39
+ result?: string;
40
+ /** Fires when the user clicks Test — run your probe and push `status`/`result` back. */
41
+ onTest?: () => void;
42
+ /**
43
+ * A secret is already stored server-side. While true and `secret` is empty, the
44
+ * field shows a masked placeholder and still counts as present for the Test
45
+ * button. Typing overrides. @default false
46
+ */
47
+ masked?: boolean;
48
+ /** Field-level errors (e.g. backend 400s) under the matching inputs. */
49
+ appIdError?: string;
50
+ secretError?: string;
51
+ linkError?: string;
52
+ /** Rows for the auto field-mapping table. @default a 7-row RSVP sample */
53
+ fieldMap?: FeishuFieldMapRow[];
54
+ /** Show the field-mapping table. @default true once `status === "ok"` */
55
+ showMapping?: boolean;
56
+ /** Override the "如何获取…" guide — a `{title, steps, link}` object or a ready React node. */
57
+ help?: ConnectionHelp | React.ReactNode;
58
+ /** Override the derived Test-enabled state. Default: App ID + (secret or masked) + a parseable link. */
59
+ canTest?: boolean;
60
+ /** Idle hint in the TestRow before the first test. */
61
+ idleHint?: string;
62
+ }
63
+ export declare function FeishuCard(props: FeishuCardProps): JSX.Element;
64
+ /** Parse `{ token, table }` out of a Bitable share URL (table may be ""). Returns null if no app_token. */
65
+ export declare function parseFeishuLink(url: string): { token: string; table: string } | null;