@xgspz/cli-ui 0.0.0-stage → 0.6.15
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/CHANGELOG.md +165 -0
- package/README.md +517 -2
- package/dist/cli-ui-embed-vIXVNs6k.js +50295 -0
- package/dist/cli-ui-index-R24x28OX.js +11209 -0
- package/dist/cli-ui-jszip.min-DUghzByz.js +2324 -0
- package/dist/cli-ui-jszip.min-DfH6rdQD.js +26 -0
- package/dist/cli-ui-xlsx-CP0lJhff.js +93 -0
- package/dist/cli-ui.css +32 -0
- package/dist/cli-ui.js +17 -0
- package/package.json +60 -4
- package/src/board/BrowserPane.tsx +969 -0
- package/src/board/ChangesPane.tsx +185 -0
- package/src/board/CliBoard.tsx +484 -0
- package/src/board/FileViewer.tsx +721 -0
- package/src/board/FilesPane.tsx +262 -0
- package/src/board/HtmlReadingPreview.tsx +52 -0
- package/src/board/MarkdownPreview.tsx +134 -0
- package/src/board/PanelControls.tsx +40 -0
- package/src/board/blocks.tsx +97 -0
- package/src/board/context.ts +65 -0
- package/src/board/diffLines.ts +46 -0
- package/src/board/fileKind.ts +250 -0
- package/src/board/fileOps.ts +85 -0
- package/src/board/fileScope.ts +32 -0
- package/src/board/fileSync.ts +351 -0
- package/src/board/fileSyncCache.ts +250 -0
- package/src/board/fileSyncProcessor.ts +72 -0
- package/src/board/fileSyncQueue.ts +56 -0
- package/src/board/fileSyncTransport.ts +85 -0
- package/src/board/fileWatch.ts +211 -0
- package/src/board/folderDownload.ts +211 -0
- package/src/board/frameQuote.ts +52 -0
- package/src/board/htmlReading.ts +148 -0
- package/src/board/panels.ts +33 -0
- package/src/board/textLoad.ts +76 -0
- package/src/board/useBoardUpload.tsx +51 -0
- package/src/board/useBoardVisibility.ts +35 -0
- package/src/board/workspace.ts +200 -0
- package/src/board/zip.ts +85 -0
- package/src/chat/CliChat.tsx +960 -0
- package/src/chat/Composer.tsx +239 -0
- package/src/chat/GroupChat.tsx +113 -0
- package/src/chat/RunControls.tsx +196 -0
- package/src/chat/configuration.ts +15 -0
- package/src/chat/draft.ts +49 -0
- package/src/chat/export.ts +34 -0
- package/src/chat/queueEdit.ts +27 -0
- package/src/chat/useExecutionConfig.ts +93 -0
- package/src/components.tsx +13 -0
- package/src/core/blocks.ts +205 -0
- package/src/core/callbacks.ts +8 -0
- package/src/core/client.ts +822 -0
- package/src/core/config.ts +252 -0
- package/src/core/conversation.ts +581 -0
- package/src/core/documentUpload.ts +196 -0
- package/src/core/execution.ts +24 -0
- package/src/core/id.ts +15 -0
- package/src/core/identity.ts +21 -0
- package/src/core/provider.tsx +141 -0
- package/src/core/sha256.ts +21 -0
- package/src/core/sse.ts +51 -0
- package/src/core/store.ts +1109 -0
- package/src/core/subagents.ts +78 -0
- package/src/core/textDecode.ts +24 -0
- package/src/core/types.ts +507 -0
- package/src/core/xlsx.ts +142 -0
- package/src/elements.tsx +376 -0
- package/src/embed.ts +56 -0
- package/src/index.ts +56 -0
- package/src/layout/CliSplit.tsx +53 -0
- package/src/layout/split.ts +251 -0
- package/src/msglist/CliMsgList.tsx +422 -0
- package/src/operator/branchFlow.ts +35 -0
- package/src/operator/index.ts +9 -0
- package/src/operator/liveStream.ts +1 -0
- package/src/operator/node/AgentConnect.tsx +619 -0
- package/src/operator/node/AppearancePanel.tsx +35 -0
- package/src/operator/node/BranchHistory.tsx +67 -0
- package/src/operator/node/BranchNavigator.tsx +36 -0
- package/src/operator/node/BrowserPane.tsx +128 -0
- package/src/operator/node/BrowserPopup.tsx +22 -0
- package/src/operator/node/Chat.tsx +538 -0
- package/src/operator/node/CommandPalette.tsx +213 -0
- package/src/operator/node/Composer.tsx +316 -0
- package/src/operator/node/InsightsView.tsx +831 -0
- package/src/operator/node/Layout.tsx +192 -0
- package/src/operator/node/Markdown.tsx +61 -0
- package/src/operator/node/Modal.tsx +179 -0
- package/src/operator/node/NeedsYouMenu.tsx +54 -0
- package/src/operator/node/OnboardingWizard.tsx +248 -0
- package/src/operator/node/Orchestrator.tsx +644 -0
- package/src/operator/node/ProjectLanding.tsx +86 -0
- package/src/operator/node/ProjectsColumn.tsx +137 -0
- package/src/operator/node/ProviderAuth.tsx +559 -0
- package/src/operator/node/RunControls.tsx +2 -0
- package/src/operator/node/RunSwitch.tsx +113 -0
- package/src/operator/node/Services.tsx +245 -0
- package/src/operator/node/SessionRail.tsx +299 -0
- package/src/operator/node/SessionView.tsx +292 -0
- package/src/operator/node/SettingsView.tsx +690 -0
- package/src/operator/node/TaskBoard.tsx +352 -0
- package/src/operator/node/TaskChanges.tsx +526 -0
- package/src/operator/node/TaskHero.tsx +50 -0
- package/src/operator/node/TasksColumn.tsx +200 -0
- package/src/operator/node/Terminal.tsx +143 -0
- package/src/operator/node/Transcript.tsx +485 -0
- package/src/operator/node/ViewFile.tsx +79 -0
- package/src/operator/node/Welcome.tsx +101 -0
- package/src/operator/node/WorktreePrune.tsx +333 -0
- package/src/operator/node/agents.ts +58 -0
- package/src/operator/node/api.ts +57 -0
- package/src/operator/node/boardBackend.ts +63 -0
- package/src/operator/node/boardEvents.ts +40 -0
- package/src/operator/node/boardInteraction.ts +15 -0
- package/src/operator/node/boardState.ts +74 -0
- package/src/operator/node/boardWorkspace.ts +15 -0
- package/src/operator/node/boundedPoll.ts +47 -0
- package/src/operator/node/branchFlow.ts +1 -0
- package/src/operator/node/composerDraft.ts +78 -0
- package/src/operator/node/fileLinks.ts +81 -0
- package/src/operator/node/format.ts +292 -0
- package/src/operator/node/github.tsx +232 -0
- package/src/operator/node/icons.tsx +88 -0
- package/src/operator/node/liveStream.ts +323 -0
- package/src/operator/node/modals.tsx +766 -0
- package/src/operator/node/navHistory.ts +70 -0
- package/src/operator/node/persist.ts +26 -0
- package/src/operator/node/protocol/agents/optIn.ts +9 -0
- package/src/operator/node/protocol/agents/types.ts +394 -0
- package/src/operator/node/protocol/approvalFailure.ts +77 -0
- package/src/operator/node/protocol/authFailure.ts +66 -0
- package/src/operator/node/protocol/customModels.ts +23 -0
- package/src/operator/node/protocol/events.ts +13 -0
- package/src/operator/node/protocol/features.ts +70 -0
- package/src/operator/node/protocol/internalUsage.ts +1 -0
- package/src/operator/node/protocol/promptLimits.ts +52 -0
- package/src/operator/node/protocol/storageRetention.ts +13 -0
- package/src/operator/node/protocol/types.ts +335 -0
- package/src/operator/node/protocol/usageLimit.ts +72 -0
- package/src/operator/node/shared.tsx +89 -0
- package/src/operator/node/transcriptModel.ts +129 -0
- package/src/operator/node/types.ts +305 -0
- package/src/operator/node/useGlobalEvents.ts +97 -0
- package/src/operator/node/useOrchestrator.ts +697 -0
- package/src/operator/node/usePrefs.ts +114 -0
- package/src/operator/node/useRecaps.ts +102 -0
- package/src/operator/node/useTaskStream.ts +286 -0
- package/src/operator.base.css +1952 -0
- package/src/operator.css +1952 -0
- package/src/styles.css +579 -0
- package/src/ui/ErrorBoundary.tsx +38 -0
- package/src/ui/Markdown.tsx +60 -0
- package/src/ui/Popover.tsx +95 -0
- package/src/ui/QueuedMessages.tsx +110 -0
- package/src/ui/ReferenceCard.tsx +28 -0
- package/src/ui/SubagentChat.tsx +233 -0
- package/src/ui/TextBlock.tsx +51 -0
- package/src/ui/Transcript.tsx +435 -0
- package/src/ui/UploadProgress.tsx +46 -0
- package/src/ui/WorkspaceDrawer.tsx +64 -0
- package/src/ui/WorkspaceTabs.tsx +60 -0
- package/src/ui/fileLinks.ts +52 -0
- package/src/ui/format.ts +103 -0
- package/src/ui/icons.tsx +92 -0
- package/src/ui/reference.ts +40 -0
- package/src/ui/shared.tsx +38 -0
- package/src/ui/transcriptModel.ts +151 -0
- package/src/ui/useChatScrollbar.ts +29 -0
- package/src/ui/useComposerAutosize.ts +54 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 组件的界面配置:哪些模块显示、文案怎么写。全部可选,缺省值见下方 DEFAULT_*_CONFIG。
|
|
3
|
+
*
|
|
4
|
+
* <CliChat config={{ header: { title: true, status: true }, composer: { placeholder: "问点什么…", fileUpload: false } }} />
|
|
5
|
+
* <CliMsgList config={{ header: { refresh: false }, card: { turns: false } }} />
|
|
6
|
+
*
|
|
7
|
+
* 开关类的模块写 false 就整块隐藏,写对象就逐项开关;文案里可以用 {title}(对话标题)、{agent}(智能体名)。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ── cli-chat ─────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
export interface CliChatConfig {
|
|
13
|
+
/** 顶部栏。缺省隐藏;true = 每一项都显示;对象 = 只显示写了 true 的项。 */
|
|
14
|
+
header?: boolean | {
|
|
15
|
+
/** 面包屑:色块 + 名称 / 对话。 */
|
|
16
|
+
crumb?: boolean;
|
|
17
|
+
/** 「导出执行过程」按钮。 */
|
|
18
|
+
export?: boolean;
|
|
19
|
+
/** 对话标题。 */
|
|
20
|
+
title?: boolean;
|
|
21
|
+
/** 智能体标签(Codex / Claude Code)。 */
|
|
22
|
+
agent?: boolean;
|
|
23
|
+
/** token 用量。 */
|
|
24
|
+
usage?: boolean;
|
|
25
|
+
/** 模型(默认 + 实际模型)。 */
|
|
26
|
+
model?: boolean;
|
|
27
|
+
/** 对话状态(运行中 / 需要你处理 / 已结束)。 */
|
|
28
|
+
status?: boolean;
|
|
29
|
+
/** 「结束对话」按钮。 */
|
|
30
|
+
close?: boolean;
|
|
31
|
+
};
|
|
32
|
+
/** 底部输入框。false = 不显示输入框(只读);对象 = 逐项配置。 */
|
|
33
|
+
composer?: boolean | {
|
|
34
|
+
/** 回复时的占位文案,可用 {title} {agent}。 */
|
|
35
|
+
placeholder?: string;
|
|
36
|
+
/** 新对话(还没有 jobId)时的占位文案。 */
|
|
37
|
+
placeholderNew?: string;
|
|
38
|
+
/** 智能体正在执行、发的消息会排队时的占位文案。 */
|
|
39
|
+
placeholderRunning?: string;
|
|
40
|
+
/** 不能发送时(对话已结束、只读)的占位文案。 */
|
|
41
|
+
placeholderDisabled?: string;
|
|
42
|
+
/** 底部「Shift + Enter 换行」提示;发送快捷键仍为 Enter。 */
|
|
43
|
+
hints?: boolean;
|
|
44
|
+
/** 底部字数统计。 */
|
|
45
|
+
charCount?: boolean;
|
|
46
|
+
/** 文件上传(按钮、拖拽、粘贴文件)。只收文档:Word、Excel、Markdown、TXT、HTML、CSV。 */
|
|
47
|
+
fileUpload?: boolean;
|
|
48
|
+
/** 智能体选择入口,默认显示;缺少服务端能力时禁用。 */
|
|
49
|
+
agentSelector?: boolean;
|
|
50
|
+
/** 执行模式选择入口,默认显示;缺少服务端能力时禁用。 */
|
|
51
|
+
executionMode?: boolean;
|
|
52
|
+
/** 「文件」按钮的文字。 */
|
|
53
|
+
fileButtonText?: string;
|
|
54
|
+
/** 执行控制:普通对话停止当前轮次,一次性子任务暂停 / 继续。 */
|
|
55
|
+
stop?: boolean;
|
|
56
|
+
/** 运行中允许发消息排队;false = 运行中不能发。 */
|
|
57
|
+
queue?: boolean;
|
|
58
|
+
};
|
|
59
|
+
/** 对话区。 */
|
|
60
|
+
transcript?: {
|
|
61
|
+
/** 右下角的上一条 / 下一条 / 跳到最新按钮。 */
|
|
62
|
+
nav?: boolean;
|
|
63
|
+
/** 「第 1 次会话 · 当前」分隔线。 */
|
|
64
|
+
sessionLabel?: boolean;
|
|
65
|
+
/** 消息时间。 */
|
|
66
|
+
timestamps?: boolean;
|
|
67
|
+
/** 已随项目上下文发送的初始提示。缺省不渲染;true 显示原文。 */
|
|
68
|
+
initialPrompt?: boolean;
|
|
69
|
+
/** 首条消息上的「初始提示」「已随项目上下文发送」标记。 */
|
|
70
|
+
initialBadge?: boolean;
|
|
71
|
+
/** 工具调用卡片(命令、读写文件……);false 只留文字往来(提问卡片照常显示)。 */
|
|
72
|
+
tools?: boolean;
|
|
73
|
+
/** 「智能体 · 运行中 0:12」那一行。 */
|
|
74
|
+
runningIndicator?: boolean;
|
|
75
|
+
/** 状态说明行:新对话的「尚未开始」、结束时的「对话已结束」「子任务已完成」等。缺省不显示。 */
|
|
76
|
+
status?: boolean;
|
|
77
|
+
};
|
|
78
|
+
/** 消息头上的称呼。 */
|
|
79
|
+
labels?: { user?: string; agent?: string };
|
|
80
|
+
/** 新对话的空白页。false = 不显示。 */
|
|
81
|
+
empty?: boolean | { title?: string; description?: string };
|
|
82
|
+
/**
|
|
83
|
+
* 输入框上方显示哪些看板引用(不显示的照样随下一条消息带给智能体,只是不占地方、也不能点掉)。
|
|
84
|
+
* 种类:viewing(看板上正在看的文件 / 浏览器页面,缺省带上)、file、selection(选中的一段)、browser、changes,或宿主自定义的种类;
|
|
85
|
+
* "*" = 没列出的种类。true 全显示,false 全不显示。缺省 { viewing: false }:正在看的不显示,明确引用的都显示。
|
|
86
|
+
*/
|
|
87
|
+
refs?: boolean | Record<string, boolean>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface ResolvedChatConfig {
|
|
91
|
+
header: { show: boolean; crumb: boolean; export: boolean; title: boolean; agent: boolean; usage: boolean; model: boolean; status: boolean; close: boolean };
|
|
92
|
+
composer: {
|
|
93
|
+
show: boolean; placeholder: string; placeholderNew: string; placeholderRunning: string; placeholderDisabled: string | null;
|
|
94
|
+
hints: boolean; charCount: boolean; fileUpload: boolean; agentSelector: boolean; executionMode: boolean; fileButtonText: string; stop: boolean; queue: boolean;
|
|
95
|
+
};
|
|
96
|
+
transcript: { nav: boolean; sessionLabel: boolean; timestamps: boolean; initialPrompt: boolean; initialBadge: boolean; tools: boolean; runningIndicator: boolean; status: boolean };
|
|
97
|
+
labels: { user: string; agent: string };
|
|
98
|
+
empty: { show: boolean; title: string; description: string };
|
|
99
|
+
/** 按种类显示 / 不显示;"*" 是没列出的种类。用 refShown 判断。 */
|
|
100
|
+
refs: Record<string, boolean>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** 这个种类的看板引用("viewing" = 正在看的)要不要显示在输入框上方。 */
|
|
104
|
+
export const refShown = (cfg: Pick<ResolvedChatConfig, "refs">, kind: string) => cfg.refs[kind] ?? cfg.refs["*"] ?? true;
|
|
105
|
+
|
|
106
|
+
export const DEFAULT_CHAT_CONFIG: ResolvedChatConfig = {
|
|
107
|
+
// 顶部栏缺省整栏隐藏(模型、状态、用量这些对使用者是干扰),需要的项在 config.header 里打开。
|
|
108
|
+
header: { show: true, crumb: false, export: false, title: false, agent: false, usage: false, model: false, status: false, close: false },
|
|
109
|
+
composer: {
|
|
110
|
+
show: true,
|
|
111
|
+
placeholder: "回复 {agent}…",
|
|
112
|
+
placeholderNew: "输入消息,开始新对话…",
|
|
113
|
+
placeholderRunning: "排队一条后续消息…(本轮结束后发送)",
|
|
114
|
+
placeholderDisabled: null,
|
|
115
|
+
hints: false, charCount: false, fileUpload: true, agentSelector: true, executionMode: true, fileButtonText: "", stop: true, queue: true,
|
|
116
|
+
},
|
|
117
|
+
transcript: { nav: true, sessionLabel: true, timestamps: true, initialPrompt: false, initialBadge: true, tools: true, runningIndicator: true, status: false },
|
|
118
|
+
labels: { user: "你", agent: "智能体" },
|
|
119
|
+
empty: { show: true, title: "开始一个新对话", description: "在下面输入第一条消息。可以附上 Word、Excel、Markdown、TXT、HTML、CSV 文档,内容会转成文字一起发送。" },
|
|
120
|
+
refs: { "*": true, viewing: false },
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export function resolveChatConfig(config: CliChatConfig = {}): ResolvedChatConfig {
|
|
124
|
+
const d = DEFAULT_CHAT_CONFIG;
|
|
125
|
+
const header = config.header === true ? allOn(d.header) : toggle(config.header, d.header);
|
|
126
|
+
const composer = toggle(config.composer, d.composer);
|
|
127
|
+
const empty = toggle(config.empty, d.empty);
|
|
128
|
+
return {
|
|
129
|
+
header,
|
|
130
|
+
composer,
|
|
131
|
+
transcript: { ...d.transcript, ...defined(config.transcript) },
|
|
132
|
+
labels: { ...d.labels, ...defined(config.labels) },
|
|
133
|
+
empty,
|
|
134
|
+
refs: config.refs === true ? { "*": true } : config.refs === false ? { "*": false } : { ...d.refs, ...(defined(isObj(config.refs) ? config.refs : undefined) as Record<string, boolean>) },
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── cli-msglist ──────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
export interface CliMsgListConfig {
|
|
141
|
+
/** 顶部栏。false = 整栏隐藏;对象 = 逐项开关。 */
|
|
142
|
+
header?: boolean | {
|
|
143
|
+
/** 色块 + 名称。 */
|
|
144
|
+
title?: boolean;
|
|
145
|
+
/** 「+ 对话」按钮。 */
|
|
146
|
+
newButton?: boolean;
|
|
147
|
+
/** 「+ 对话」按钮的文字。 */
|
|
148
|
+
newButtonText?: string;
|
|
149
|
+
/** 手动刷新按钮,缺省隐藏。列表不定时轮询:打开着的对话实时更新,其余在页面回到前台、获得焦点、网络恢复时刷新。 */
|
|
150
|
+
refresh?: boolean;
|
|
151
|
+
/** 上下文说明框(需同时给 context 或 onEditContext)。 */
|
|
152
|
+
context?: boolean;
|
|
153
|
+
};
|
|
154
|
+
/** 搜索框:true 总是显示,false 不显示,"auto"(缺省)对话数 ≥ 6 时显示。 */
|
|
155
|
+
search?: boolean | "auto";
|
|
156
|
+
searchPlaceholder?: string;
|
|
157
|
+
/**
|
|
158
|
+
* 按状态分组(需要你处理 / 进行中 / 已结束 / 已取消),配合 groups 改组名。缺省不分组:一个列表,最近活动的在前。
|
|
159
|
+
*/
|
|
160
|
+
grouped?: boolean;
|
|
161
|
+
/** 卡片上显示哪些内容。 */
|
|
162
|
+
card?: {
|
|
163
|
+
/** 状态点、状态文字,以及底部的「运行中 / 等你处理 / 已完成」等字样。缺省不显示(底部只有时间)。 */
|
|
164
|
+
status?: boolean;
|
|
165
|
+
/** 智能体标签。 */
|
|
166
|
+
agent?: boolean;
|
|
167
|
+
/** 第一条消息摘要。 */
|
|
168
|
+
description?: boolean;
|
|
169
|
+
/** 底部活动(3 分钟前;打开 status 时带上运行中、排队等状态)。 */
|
|
170
|
+
activity?: boolean;
|
|
171
|
+
/** 「N 轮对话」。 */
|
|
172
|
+
turns?: boolean;
|
|
173
|
+
/** 「+」按钮:在这个任务(对话任务)里新开一个对话,共用同一个工作区。 */
|
|
174
|
+
newInJob?: boolean;
|
|
175
|
+
};
|
|
176
|
+
/** 分组标题(grouped 时);写 false 隐藏这一组。 */
|
|
177
|
+
groups?: { needs?: string | false; active?: string | false; done?: string | false; cancelled?: string | false };
|
|
178
|
+
/** 没有对话时的提示。 */
|
|
179
|
+
emptyTitle?: string;
|
|
180
|
+
emptyDescription?: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface ResolvedMsgListConfig {
|
|
184
|
+
header: { show: boolean; title: boolean; newButton: boolean; newButtonText: string; refresh: boolean; context: boolean };
|
|
185
|
+
search: boolean | "auto";
|
|
186
|
+
searchPlaceholder: string;
|
|
187
|
+
grouped: boolean;
|
|
188
|
+
card: { status: boolean; agent: boolean; description: boolean; activity: boolean; turns: boolean; newInJob: boolean };
|
|
189
|
+
groups: { needs: string | false; active: string | false; done: string | false; cancelled: string | false };
|
|
190
|
+
emptyTitle: string;
|
|
191
|
+
emptyDescription: string;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export const DEFAULT_MSGLIST_CONFIG: ResolvedMsgListConfig = {
|
|
195
|
+
header: { show: true, title: true, newButton: true, newButtonText: "对话", refresh: false, context: true },
|
|
196
|
+
search: "auto",
|
|
197
|
+
searchPlaceholder: "搜索对话…",
|
|
198
|
+
grouped: false,
|
|
199
|
+
// 状态缺省不显示(未开始、进行中、已完成……对使用者是干扰),需要时打开 card.status / grouped。
|
|
200
|
+
card: { status: false, agent: true, description: true, activity: true, turns: true, newInJob: true },
|
|
201
|
+
groups: { needs: "需要你处理", active: "进行中", done: "已结束", cancelled: "已取消" },
|
|
202
|
+
emptyTitle: "还没有对话",
|
|
203
|
+
emptyDescription: "点「对话」开始一个新的对话。",
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export function resolveMsgListConfig(config: CliMsgListConfig = {}): ResolvedMsgListConfig {
|
|
207
|
+
const d = DEFAULT_MSGLIST_CONFIG;
|
|
208
|
+
return {
|
|
209
|
+
header: toggle(config.header, d.header),
|
|
210
|
+
search: config.search ?? d.search,
|
|
211
|
+
searchPlaceholder: config.searchPlaceholder ?? d.searchPlaceholder,
|
|
212
|
+
grouped: config.grouped ?? d.grouped,
|
|
213
|
+
card: { ...d.card, ...defined(config.card) },
|
|
214
|
+
groups: { ...d.groups, ...defined(config.groups) },
|
|
215
|
+
emptyTitle: config.emptyTitle ?? d.emptyTitle,
|
|
216
|
+
emptyDescription: config.emptyDescription ?? d.emptyDescription,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ── 小工具 ───────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
/** 文案模板:{title}、{agent}。 */
|
|
223
|
+
export function fillTemplate(template: string, vars: Record<string, string>) {
|
|
224
|
+
return template.replace(/\{(\w+)\}/g, (match, key: string) => (key in vars ? vars[key] : match));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const isObj = (value: unknown): value is Record<string, unknown> => !!value && typeof value === "object";
|
|
228
|
+
|
|
229
|
+
/** 去掉值为 undefined 的键,免得盖掉缺省值。 */
|
|
230
|
+
function defined<T extends object>(value: T | undefined): Partial<T> {
|
|
231
|
+
if (!value) return {};
|
|
232
|
+
return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== undefined)) as Partial<T>;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* 开关型模块:false → 全关(show=false 且布尔项全 false,文案保留);true / 不给 → 缺省;对象 → 合并缺省。
|
|
237
|
+
*/
|
|
238
|
+
function allOn<T extends { show: boolean }>(defaults: T): T {
|
|
239
|
+
const on = { ...defaults } as Record<string, unknown>;
|
|
240
|
+
for (const key of Object.keys(on)) if (typeof on[key] === "boolean") on[key] = true;
|
|
241
|
+
return on as T;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function toggle<T extends { show: boolean }>(value: boolean | object | undefined, defaults: T): T {
|
|
245
|
+
if (value === false) {
|
|
246
|
+
const off = { ...defaults, show: false } as Record<string, unknown>;
|
|
247
|
+
for (const key of Object.keys(off)) if (typeof off[key] === "boolean") off[key] = false;
|
|
248
|
+
return off as T;
|
|
249
|
+
}
|
|
250
|
+
if (!isObj(value)) return { ...defaults };
|
|
251
|
+
return { ...defaults, ...defined(value as Partial<T>), show: true };
|
|
252
|
+
}
|