@agentaily/design-system 0.5.0 → 0.6.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.
package/DESIGN.md CHANGED
@@ -99,7 +99,7 @@ Examples — ✓ "Rate limited. Retry in 18s." ✗ "Oops! Something went wrong
99
99
  | `components/voice/` | **AudioPlayer, MicSelector, VoiceSelector, SpeechInput, Transcription, Persona** |
100
100
  | `components/workflow/` | **Flow (Canvas), Canvas, Node, Edge, Connection, Controls, Panel, Toolbar** |
101
101
  | `components/utilities/` | **Image, OpenInChat, Icon (unified Lucide set), BrandMark, RotatingTagline** |
102
- | `components/settings/` | **TestRow, HelpSteps, IntegrationSettings** — connection-card primitives + the DeepSeek/Feishu integration modal |
102
+ | `components/settings/` | **TestRow, HelpSteps, DeepSeekCard, FeishuCard** — connection-card primitives + two pure-display service cards (state belongs to the caller) |
103
103
  | `components/auth/` | **AuthDialog (+ AuthDialog.useAuth), AccountControl, SignInPage** — sign-in/register modal + persisted session + top-bar account menu + full-page sign-in |
104
104
  | `components/review/` | **MarkupLayer** — point-at-an-element review overlay (`data-mk-label`) |
105
105
  | **Hooks** (headless) | `Queue.useQueue`, `AuthDialog.useAuth`, `Form.useForm` / `Form.useFieldArray` — logic without UI, exposed as statics on the paired component |
@@ -108,7 +108,7 @@ Examples — ✓ "Rate limited. Retry in 18s." ✗ "Oops! Something went wrong
108
108
  | `ui_kits/docs/` | Documentation site (interactive) |
109
109
  | `SKILL.md` | Agent-skill entry point |
110
110
 
111
- Every component ships `<Name>.jsx` + `<Name>.d.ts` (props) + `<Name>.prompt.md` (usage) — **146 component exports** across the primitive categories (buttons, inputs, display, feedback, overlay, layout, chat, ai, code, voice, workflow, utilities) plus product-domain layers (**settings, auth, review**). Full-page frames — `AppShell` / `DesignerShell` / `DocsLayout` / `SettingsPage` (layout), `ConversationThread` (chat), `SignInPage` (auth) — are **live components, not copy-templates**: change one, every consuming project benefits on re-sync. Consume via the compiled bundle: `window.AxiomDesignSystem_7fc962`. Read each component's `.prompt.md` for copy-paste usage.
111
+ Every component ships `<Name>.jsx` + `<Name>.d.ts` (props) + `<Name>.prompt.md` (usage) — **147 component exports** across the primitive categories (buttons, inputs, display, feedback, overlay, layout, chat, ai, code, voice, workflow, utilities) plus product-domain layers (**settings, auth, review**). Full-page frames — `AppShell` / `DesignerShell` / `DocsLayout` / `SettingsPage` (layout), `ConversationThread` (chat), `SignInPage` (auth) — are **live components, not copy-templates**: change one, every consuming project benefits on re-sync. Consume via the compiled bundle: `window.AxiomDesignSystem_7fc962`. Read each component's `.prompt.md` for copy-paste usage.
112
112
 
113
113
  **Forms are layered, not monolithic.** Presentational controls (Input/Select/Field…) own layout and never depend on a form engine. `Form` + `FormActions` add pure structure. `Form.useForm` is an **optional**, zero-dependency orchestration hook (values/errors/touched/validate/submit) exposed off the capitalized `Form` export — drop it for react-hook-form or TanStack and the controls still work. Errors surface only after blur or submit; spread `form.field(name)` onto any value control, or `form.field(name, {type:"checkbox"})` for boolean ones.
114
114
 
@@ -123,13 +123,13 @@ The system sits on **two orthogonal axes**. Don't conflate them: a *domain* is n
123
123
  | **L0 Tokens** | colour / type / space / radius / shadow / motion variables | — | `tokens/*.css` |
124
124
  | **L1 Primitives** (atoms) | no business meaning, usable anywhere | tokens | `Button`, `Input`, `Badge`, `Icon`, `BrandMark`, `RotatingTagline` |
125
125
  | **L2 Composites** (molecules) | a few primitives combined; still cross-product | L1 | `Composer`, `SecretField`, `StatusPill` |
126
- | **L3 Patterns** (organisms) | self-contained interaction + state, configurable | L1–L2 | `AuthDialog`, `IntegrationSettings`, `MarkupLayer`, `ConversationThread` |
126
+ | **L3 Patterns** (organisms) | self-contained interaction + state, configurable | L1–L2 | `AuthDialog`, `MarkupLayer`, `ConversationThread`, `DeepSeekCard` / `FeishuCard` (display-only — caller owns state) |
127
127
  | **L4 Page shells / frames** | full-page layout with slots | L1–L3 | `AppShell`, `DesignerShell`, `DocsLayout`, `SettingsPage`, `SignInPage` |
128
128
  | **L5 Product code** | real content + business logic — **never lives in the DS** | L4 | each app's flow / preview renderer |
129
129
 
130
130
  ### ② 横向 — domains (categorization *inside* a layer, not a level)
131
131
 
132
- `buttons · inputs · display · feedback · overlay · layout · chat · ai · code · voice · workflow · utilities · settings · auth · review`. A product-domain component (e.g. `IntegrationSettings`) lives at its **abstraction layer** (L3) and is merely *filed* under its **domain folder** (`settings/`). Domains are folders, not tiers.
132
+ `buttons · inputs · display · feedback · overlay · layout · chat · ai · code · voice · workflow · utilities · settings · auth · review`. A product-domain component (e.g. `FeishuCard`) lives at its **abstraction layer** and is merely *filed* under its **domain folder** (`settings/`). Domains are folders, not tiers.
133
133
 
134
134
  **Where does a component go?** Ask: (1) what does it depend on? — composites + own state → higher. (2) reused by ≥2 surfaces? — if not, keep it in the app, not the DS. (3) how much business meaning? — more → higher layer, more generic → lower.
135
135
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # agentaily design system
2
2
 
3
- Agentaily(AI chatbot)设计系统:113 个 React 组件 + Storybook,单色亮色优先(暗色可切)。品牌一句话:**极客风格,简约,大气,科技感**。
3
+ Agentaily(AI chatbot)设计系统:114 个 React 组件 + Storybook,单色亮色优先(暗色可切)。品牌一句话:**极客风格,简约,大气,科技感**。
4
4
 
5
5
  📖 **在线 Storybook:** https://agentaily.github.io/design-system/
6
6
 
@@ -28,7 +28,7 @@ npm run build:lib # 产出 dist/:每组件一个 .js + index.d.ts + styles.css
28
28
 
29
29
  | 路径 | 内容 |
30
30
  | ----------------------------------------------- | ----------------------------------------- |
31
- | `dist/index.js` | ESM 入口,re-export 全部 113 个组件符号 |
31
+ | `dist/index.js` | ESM 入口,re-export 全部 114 个组件符号 |
32
32
  | `dist/components/**/*.js` | 每个组件独立模块(含运行时 CSS 注入) |
33
33
  | `dist/index.d.ts` + `dist/components/**/*.d.ts` | TypeScript 类型契约 |
34
34
  | `dist/styles.css` | 内联好的 tokens + 字体,消费方 import 一次 |
@@ -39,7 +39,7 @@ export interface SignInPageProps {
39
39
  defaultMode?: "signin" | "signup";
40
40
  /** Fires when the footer link flips mode — wire to your router (/signin ↔ /signup). */
41
41
  onModeChange?: (mode: "signin" | "signup") => void;
42
- /** Brand-panel lockup. @default <BrandMark wordmark /> (blinking cursor) */
42
+ /** Brand-panel lockup. @default <BrandMark wordmark /> (no cursor) */
43
43
  brand?: React.ReactNode;
44
44
  /**
45
45
  * All user-facing strings, deep-merged over the English defaults. Per-mode
@@ -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":"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"}
@@ -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;