cortico 0.1.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.
Files changed (311) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/package.json +43 -0
  4. package/src/boot.ts +46 -0
  5. package/src/bot.ts +1342 -0
  6. package/src/config-file.ts +41 -0
  7. package/src/core/README.md +92 -0
  8. package/src/core/billing.ts +26 -0
  9. package/src/core/blobs.ts +106 -0
  10. package/src/core/bus.ts +311 -0
  11. package/src/core/config-schema.ts +201 -0
  12. package/src/core/config.ts +262 -0
  13. package/src/core/core.ts +699 -0
  14. package/src/core/cost.ts +244 -0
  15. package/src/core/event-store.ts +304 -0
  16. package/src/core/fork.ts +113 -0
  17. package/src/core/generation.ts +118 -0
  18. package/src/core/instance-lock.ts +215 -0
  19. package/src/core/ipc-logger.ts +105 -0
  20. package/src/core/language.ts +62 -0
  21. package/src/core/log-context.ts +38 -0
  22. package/src/core/loop.ts +1858 -0
  23. package/src/core/markers.ts +48 -0
  24. package/src/core/prefix.ts +121 -0
  25. package/src/core/run.ts +95 -0
  26. package/src/core/secrets.ts +18 -0
  27. package/src/core/session.ts +56 -0
  28. package/src/core/sessions.ts +261 -0
  29. package/src/core/state.ts +104 -0
  30. package/src/core/template.ts +72 -0
  31. package/src/core/timers.ts +129 -0
  32. package/src/core/tool-log.ts +135 -0
  33. package/src/core/transcript.ts +92 -0
  34. package/src/core/truncate.ts +121 -0
  35. package/src/core/types.ts +1140 -0
  36. package/src/core/usage-log.ts +79 -0
  37. package/src/core/util.ts +386 -0
  38. package/src/deploy-listing.ts +160 -0
  39. package/src/deploy.ts +162 -0
  40. package/src/extensions/README.md +72 -0
  41. package/src/extensions/dry-mount.ts +390 -0
  42. package/src/extensions/manifest.ts +153 -0
  43. package/src/extensions/runtime.ts +42 -0
  44. package/src/extensions.ts +519 -0
  45. package/src/launcher.ts +285 -0
  46. package/src/paths.ts +155 -0
  47. package/src/protocol/open-responses/LICENSE +201 -0
  48. package/src/protocol/open-responses/README.md +14 -0
  49. package/src/protocol/open-responses/context-helpers.ts +27 -0
  50. package/src/protocol/open-responses/context-log.ts +59 -0
  51. package/src/protocol/open-responses/context.ts +89 -0
  52. package/src/protocol/open-responses/generated.ts +115 -0
  53. package/src/protocol/open-responses/index.ts +33 -0
  54. package/src/protocol/open-responses/openapi.json +4230 -0
  55. package/src/protocol/open-responses/stream.ts +146 -0
  56. package/src/protocol/open-responses/tokens.ts +31 -0
  57. package/src/providers/README.md +86 -0
  58. package/src/providers/base.ts +111 -0
  59. package/src/providers/configuration.ts +97 -0
  60. package/src/providers/console/config.ts +21 -0
  61. package/src/providers/console/settings.ts +443 -0
  62. package/src/providers/console/strings.ts +74 -0
  63. package/src/providers/console/types.ts +11 -0
  64. package/src/providers/llamacpp/archive.ts +148 -0
  65. package/src/providers/llamacpp/catalog.ts +247 -0
  66. package/src/providers/llamacpp/config.ts +36 -0
  67. package/src/providers/llamacpp/console/client.ts +6 -0
  68. package/src/providers/llamacpp/console/models-panel.ts +120 -0
  69. package/src/providers/llamacpp/console/runtime-panel.ts +173 -0
  70. package/src/providers/llamacpp/console/server.ts +153 -0
  71. package/src/providers/llamacpp/index.ts +95 -0
  72. package/src/providers/llamacpp/native.ts +51 -0
  73. package/src/providers/llamacpp/options.ts +117 -0
  74. package/src/providers/llamacpp/runtime-store.ts +139 -0
  75. package/src/providers/llamacpp/runtime.ts +214 -0
  76. package/src/providers/llamacpp/server.ts +286 -0
  77. package/src/providers/llamacpp/strings.ts +230 -0
  78. package/src/providers/openai-responses-compat/config.ts +20 -0
  79. package/src/providers/openai-responses-compat/index.ts +89 -0
  80. package/src/providers/openai-responses-compat/native.ts +127 -0
  81. package/src/providers/openai-responses-compat/strings.ts +19 -0
  82. package/src/providers/pricebook.ts +75 -0
  83. package/src/providers/registry.ts +137 -0
  84. package/src/providers/strings.ts +71 -0
  85. package/src/providers/transport/chat.ts +156 -0
  86. package/src/providers/transport/errors.ts +48 -0
  87. package/src/providers/transport/history.ts +68 -0
  88. package/src/providers/transport/native-input.ts +83 -0
  89. package/src/providers/transport/native-types.ts +13 -0
  90. package/src/providers/transport/response-assembly.ts +191 -0
  91. package/src/providers/transport/response-http.ts +196 -0
  92. package/src/providers/transport/response-meters.ts +27 -0
  93. package/src/providers/transport/responses-input.ts +56 -0
  94. package/src/web/README.md +84 -0
  95. package/src/web/client/console-pages/builtins/llm-settings/panel.ts +625 -0
  96. package/src/web/client/console-pages/builtins/llm-settings/pricing-panel.ts +168 -0
  97. package/src/web/client/console-pages/builtins/llm-settings/strings.ts +213 -0
  98. package/src/web/client/console-pages/builtins.ts +10 -0
  99. package/src/web/client/console-pages/context.ts +135 -0
  100. package/src/web/client/console-pages/host.ts +633 -0
  101. package/src/web/client/console-pages/loader.ts +132 -0
  102. package/src/web/client/console-pages/strings.ts +90 -0
  103. package/src/web/client/console-pages/tools/strings.ts +49 -0
  104. package/src/web/client/console-pages/tools/view.ts +215 -0
  105. package/src/web/client/core/api.ts +200 -0
  106. package/src/web/client/core/language.ts +56 -0
  107. package/src/web/client/core/lifecycle.ts +132 -0
  108. package/src/web/client/core/router.ts +199 -0
  109. package/src/web/client/core/stream.ts +157 -0
  110. package/src/web/client/core/websocket.ts +82 -0
  111. package/src/web/client/features/appearance/index.ts +333 -0
  112. package/src/web/client/features/appearance/strings.ts +87 -0
  113. package/src/web/client/features/config/index.ts +11 -0
  114. package/src/web/client/features/config/strings.ts +41 -0
  115. package/src/web/client/features/config/view.ts +428 -0
  116. package/src/web/client/features/core/events.ts +197 -0
  117. package/src/web/client/features/core/index.ts +254 -0
  118. package/src/web/client/features/core/run.ts +56 -0
  119. package/src/web/client/features/core/runlog.ts +214 -0
  120. package/src/web/client/features/core/sessions.ts +53 -0
  121. package/src/web/client/features/core/strings.ts +157 -0
  122. package/src/web/client/features/extensions/index.ts +422 -0
  123. package/src/web/client/features/extensions/strings.ts +164 -0
  124. package/src/web/client/features/feature.ts +93 -0
  125. package/src/web/client/features/live/context.ts +280 -0
  126. package/src/web/client/features/live/fork.ts +156 -0
  127. package/src/web/client/features/live/index.ts +392 -0
  128. package/src/web/client/features/live/onboarding.ts +113 -0
  129. package/src/web/client/features/live/protocol.ts +122 -0
  130. package/src/web/client/features/live/sessions.ts +71 -0
  131. package/src/web/client/features/live/status.ts +68 -0
  132. package/src/web/client/features/live/strings.ts +252 -0
  133. package/src/web/client/features/live/timeline.ts +590 -0
  134. package/src/web/client/features/prompts/editor.ts +292 -0
  135. package/src/web/client/features/prompts/index.ts +346 -0
  136. package/src/web/client/features/prompts/strings.ts +136 -0
  137. package/src/web/client/features/prompts/view.ts +256 -0
  138. package/src/web/client/features/providers/index.ts +100 -0
  139. package/src/web/client/features/providers/strings.ts +23 -0
  140. package/src/web/client/features/settings/general.ts +28 -0
  141. package/src/web/client/features/settings/index.ts +82 -0
  142. package/src/web/client/features/settings/strings.ts +33 -0
  143. package/src/web/client/features/storage/index.ts +1 -0
  144. package/src/web/client/features/storage/strings.ts +45 -0
  145. package/src/web/client/features/storage/view.ts +154 -0
  146. package/src/web/client/features/usage/chart.ts +486 -0
  147. package/src/web/client/features/usage/index.ts +407 -0
  148. package/src/web/client/features/usage/labels.ts +46 -0
  149. package/src/web/client/features/usage/range.ts +36 -0
  150. package/src/web/client/features/usage/state.ts +37 -0
  151. package/src/web/client/features/usage/strings.ts +191 -0
  152. package/src/web/client/features/usage/tooltip.ts +86 -0
  153. package/src/web/client/features/usage/types.ts +96 -0
  154. package/src/web/client/features/worlds/index.ts +424 -0
  155. package/src/web/client/features/worlds/strings.ts +121 -0
  156. package/src/web/client/main.ts +273 -0
  157. package/src/web/client/shell/avatar.ts +233 -0
  158. package/src/web/client/shell/index.ts +549 -0
  159. package/src/web/client/shell/strings.ts +89 -0
  160. package/src/web/client/theme/handoff.ts +43 -0
  161. package/src/web/client/theme/palette.ts +109 -0
  162. package/src/web/client/theme/registry.ts +130 -0
  163. package/src/web/client/theme/storage.ts +72 -0
  164. package/src/web/client/theme/strings.ts +123 -0
  165. package/src/web/client/theme/studio.ts +366 -0
  166. package/src/web/client/ui/actions.ts +109 -0
  167. package/src/web/client/ui/data.ts +185 -0
  168. package/src/web/client/ui/dom.ts +23 -0
  169. package/src/web/client/ui/fields.ts +191 -0
  170. package/src/web/client/ui/format.ts +75 -0
  171. package/src/web/client/ui/icons.ts +183 -0
  172. package/src/web/client/ui/images.ts +91 -0
  173. package/src/web/client/ui/index.ts +89 -0
  174. package/src/web/client/ui/lamp.ts +125 -0
  175. package/src/web/client/ui/log.ts +98 -0
  176. package/src/web/client/ui/overlay.ts +184 -0
  177. package/src/web/client/ui/page.ts +8 -0
  178. package/src/web/client/ui/prompt-input.tsx +311 -0
  179. package/src/web/client/ui/sheet.ts +131 -0
  180. package/src/web/client/ui/strings.ts +71 -0
  181. package/src/web/console-pages.ts +367 -0
  182. package/src/web/files.ts +107 -0
  183. package/src/web/path-picker.ts +296 -0
  184. package/src/web/public/index.html +17 -0
  185. package/src/web/public/styles.css +1715 -0
  186. package/src/web/server.ts +1883 -0
  187. package/src/web/shared/client-panel.ts +603 -0
  188. package/src/web/shared/console-protocol.ts +560 -0
  189. package/src/web/shared/css.d.ts +1 -0
  190. package/src/web/shared/path-picker.ts +23 -0
  191. package/src/web/shared/theme.ts +256 -0
  192. package/src/web/theme-store.ts +37 -0
  193. package/src/world.ts +449 -0
  194. package/src/worlds/bilibili/ENV_PROMPT.md +5 -0
  195. package/src/worlds/bilibili/README.md +176 -0
  196. package/src/worlds/bilibili/audience-admission.ts +995 -0
  197. package/src/worlds/bilibili/client.ts +528 -0
  198. package/src/worlds/bilibili/coalescing-buffer.ts +125 -0
  199. package/src/worlds/bilibili/config.ts +185 -0
  200. package/src/worlds/bilibili/console/client.ts +202 -0
  201. package/src/worlds/bilibili/definition.ts +35 -0
  202. package/src/worlds/bilibili/gift-frame.ts +131 -0
  203. package/src/worlds/bilibili/normalize.ts +452 -0
  204. package/src/worlds/bilibili/overlay/announcement.ts +76 -0
  205. package/src/worlds/bilibili/overlay/assets.ts +82 -0
  206. package/src/worlds/bilibili/overlay/model.ts +494 -0
  207. package/src/worlds/bilibili/overlay/project.ts +202 -0
  208. package/src/worlds/bilibili/overlay/server.ts +286 -0
  209. package/src/worlds/bilibili/overlay/types.ts +217 -0
  210. package/src/worlds/bilibili/overlay/web/app.js +690 -0
  211. package/src/worlds/bilibili/overlay/web/editor.css +297 -0
  212. package/src/worlds/bilibili/overlay/web/editor.html +102 -0
  213. package/src/worlds/bilibili/overlay/web/editor.js +2116 -0
  214. package/src/worlds/bilibili/overlay/web/overlay.html +13 -0
  215. package/src/worlds/bilibili/overlay/web/styles.css +258 -0
  216. package/src/worlds/bilibili/protobuf.ts +123 -0
  217. package/src/worlds/bilibili/wire.ts +94 -0
  218. package/src/worlds/bilibili/world.ts +1389 -0
  219. package/src/worlds/console-fixture/ENV_PROMPT.md +1 -0
  220. package/src/worlds/console-fixture/console/client.ts +72 -0
  221. package/src/worlds/console-fixture/world.ts +73 -0
  222. package/src/worlds/index.ts +15 -0
  223. package/src/worlds/minecraft/ENV_PROMPT.md +32 -0
  224. package/src/worlds/minecraft/ENV_PROMPT_CAMERA.md +2 -0
  225. package/src/worlds/minecraft/LICENSE-mineflayer-pathfinder.txt +21 -0
  226. package/src/worlds/minecraft/README.md +917 -0
  227. package/src/worlds/minecraft/blueprint-plan.ts +915 -0
  228. package/src/worlds/minecraft/blueprint-registry.ts +566 -0
  229. package/src/worlds/minecraft/blueprint-repair.ts +236 -0
  230. package/src/worlds/minecraft/blueprint-resource.ts +272 -0
  231. package/src/worlds/minecraft/blueprint.ts +492 -0
  232. package/src/worlds/minecraft/body-lease.ts +340 -0
  233. package/src/worlds/minecraft/bridge.ts +749 -0
  234. package/src/worlds/minecraft/check.ts +651 -0
  235. package/src/worlds/minecraft/chests.ts +292 -0
  236. package/src/worlds/minecraft/client-launch.ts +272 -0
  237. package/src/worlds/minecraft/client-options.ts +85 -0
  238. package/src/worlds/minecraft/client-skins.ts +164 -0
  239. package/src/worlds/minecraft/client-window.ps1 +91 -0
  240. package/src/worlds/minecraft/client.ts +423 -0
  241. package/src/worlds/minecraft/combat-context.ts +109 -0
  242. package/src/worlds/minecraft/combat.ts +1552 -0
  243. package/src/worlds/minecraft/config.ts +499 -0
  244. package/src/worlds/minecraft/console/access.ts +218 -0
  245. package/src/worlds/minecraft/console/client.ts +244 -0
  246. package/src/worlds/minecraft/console/log.ts +110 -0
  247. package/src/worlds/minecraft/console/mount.ts +420 -0
  248. package/src/worlds/minecraft/console/skin.ts +257 -0
  249. package/src/worlds/minecraft/console/style.css +154 -0
  250. package/src/worlds/minecraft/console/world.ts +449 -0
  251. package/src/worlds/minecraft/deaths.ts +135 -0
  252. package/src/worlds/minecraft/definition.ts +18 -0
  253. package/src/worlds/minecraft/engine-child.ts +274 -0
  254. package/src/worlds/minecraft/engine-ipc.ts +98 -0
  255. package/src/worlds/minecraft/entity-facts.ts +132 -0
  256. package/src/worlds/minecraft/escape.ts +407 -0
  257. package/src/worlds/minecraft/executor.ts +14494 -0
  258. package/src/worlds/minecraft/explored.ts +156 -0
  259. package/src/worlds/minecraft/geometry.ts +205 -0
  260. package/src/worlds/minecraft/goal-plan.ts +647 -0
  261. package/src/worlds/minecraft/item-break.ts +126 -0
  262. package/src/worlds/minecraft/item-facts.ts +146 -0
  263. package/src/worlds/minecraft/item-pick.ts +67 -0
  264. package/src/worlds/minecraft/level-dat.ts +176 -0
  265. package/src/worlds/minecraft/log.ts +132 -0
  266. package/src/worlds/minecraft/mineflayer-fixes.ts +1180 -0
  267. package/src/worlds/minecraft/names.ts +377 -0
  268. package/src/worlds/minecraft/pathfinder-lib.d.ts +13 -0
  269. package/src/worlds/minecraft/pathfinder-perf.ts +796 -0
  270. package/src/worlds/minecraft/piglin.ts +49 -0
  271. package/src/worlds/minecraft/policy.ts +547 -0
  272. package/src/worlds/minecraft/precheck.ts +655 -0
  273. package/src/worlds/minecraft/proxy.ts +445 -0
  274. package/src/worlds/minecraft/ranged.ts +635 -0
  275. package/src/worlds/minecraft/readouts.ts +102 -0
  276. package/src/worlds/minecraft/round.ts +26 -0
  277. package/src/worlds/minecraft/search-observation.ts +92 -0
  278. package/src/worlds/minecraft/server-config.ts +467 -0
  279. package/src/worlds/minecraft/server.ts +658 -0
  280. package/src/worlds/minecraft/show.ts +84 -0
  281. package/src/worlds/minecraft/skills.ts +1763 -0
  282. package/src/worlds/minecraft/terrain.ts +1735 -0
  283. package/src/worlds/minecraft/window.ts +96 -0
  284. package/src/worlds/minecraft/works.ts +179 -0
  285. package/src/worlds/minecraft/world.ts +6438 -0
  286. package/src/worlds/qq/ENV_PROMPT.md +5 -0
  287. package/src/worlds/qq/config.ts +108 -0
  288. package/src/worlds/qq/console/client.ts +126 -0
  289. package/src/worlds/qq/console/events.ts +155 -0
  290. package/src/worlds/qq/console/gate.ts +232 -0
  291. package/src/worlds/qq/console/roster.ts +203 -0
  292. package/src/worlds/qq/console/style.css +65 -0
  293. package/src/worlds/qq/conversation.ts +28 -0
  294. package/src/worlds/qq/definition.ts +62 -0
  295. package/src/worlds/qq/driver.ts +375 -0
  296. package/src/worlds/qq/history-tools.ts +209 -0
  297. package/src/worlds/qq/normalize.ts +323 -0
  298. package/src/worlds/qq/qface-map.ts +282 -0
  299. package/src/worlds/qq/vision-prompt.ts +27 -0
  300. package/src/worlds/qq/vision.ts +642 -0
  301. package/src/worlds/qq/vlm.ts +142 -0
  302. package/src/worlds/qq/world.ts +1503 -0
  303. package/src/worlds/terminal/ENV_PROMPT.md +11 -0
  304. package/src/worlds/terminal/config.ts +9 -0
  305. package/src/worlds/terminal/definition.ts +14 -0
  306. package/src/worlds/terminal/world.ts +700 -0
  307. package/src/worlds/websearch/ENV_PROMPT.md +1 -0
  308. package/src/worlds/websearch/brave-client.ts +261 -0
  309. package/src/worlds/websearch/config.ts +75 -0
  310. package/src/worlds/websearch/definition.ts +20 -0
  311. package/src/worlds/websearch/world.ts +171 -0
@@ -0,0 +1,191 @@
1
+ /** 表单控件与标签;事件监听绑定 UI 实例的 signal。 */
2
+
3
+ import type {
4
+ ConsoleCheckbox,
5
+ ConsoleCheckboxOptions,
6
+ ConsoleFieldOptions,
7
+ ConsoleInputOptions,
8
+ ConsoleSegmented,
9
+ ConsoleSegmentedOptions,
10
+ ConsoleSelectOptions,
11
+ ConsoleTextareaOptions,
12
+ } from '../../shared/client-panel.ts';
13
+ import { h } from './dom.ts';
14
+
15
+ /** `field` + 调用方追加的 class(如 `mono` / `grow`) */
16
+ function fieldClass(extra?: string): string {
17
+ return extra ? 'field ' + extra : 'field';
18
+ }
19
+
20
+ /** 初始化值、禁用状态与回调。onInput 监听输入(select 使用 change),onChange 监听 change,onCommit 监听提交键。 */
21
+ function wire(
22
+ el: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement,
23
+ signal: AbortSignal,
24
+ opts: ConsoleFieldOptions,
25
+ event: 'input' | 'change',
26
+ ): void {
27
+ if (opts.value != null) el.value = opts.value;
28
+ if (opts.disabled) el.disabled = true;
29
+ const onInput = opts.onInput;
30
+ if (onInput) el.addEventListener(event, () => onInput(el.value), { signal });
31
+ const onChange = opts.onChange;
32
+ if (onChange) el.addEventListener('change', () => onChange(el.value), { signal });
33
+ const onCommit = opts.onCommit;
34
+ if (onCommit) {
35
+ // 形参写成 `Event` 再收窄:`el` 是三种控件的联合类型,它的 `addEventListener`
36
+ // 只剩下不带事件名类型推导的那条通用重载(TS 对联合方法取的是签名的交集),
37
+ // 直接写 `(ev: KeyboardEvent)` 会被判成不兼容。
38
+ el.addEventListener(
39
+ 'keydown',
40
+ (ev: Event) => {
41
+ if (isCommitKey(el, ev as KeyboardEvent)) onCommit(el.value);
42
+ },
43
+ { signal },
44
+ );
45
+ }
46
+ }
47
+
48
+ /**
49
+ * 判断 Enter 是否提交:IME composition 期间只选词;textarea 裸 Enter 换行,Ctrl/⌘+Enter 提交。
50
+ */
51
+ function isCommitKey(el: { tagName: string }, ev: KeyboardEvent): boolean {
52
+ if (ev.key !== 'Enter' || ev.isComposing) return false;
53
+ if (el.tagName.toLowerCase() !== 'textarea') return true;
54
+ return ev.ctrlKey || ev.metaKey;
55
+ }
56
+
57
+ /** 单行输入。缺省 `type=text`。 */
58
+ export function input(
59
+ doc: Document,
60
+ signal: AbortSignal,
61
+ opts?: ConsoleInputOptions,
62
+ ): HTMLInputElement {
63
+ const o = opts ?? {};
64
+ const el = h(doc, 'input', fieldClass(o.cls));
65
+ el.type = o.type ?? 'text';
66
+ if (o.placeholder != null) el.placeholder = o.placeholder;
67
+ wire(el, signal, o, 'input');
68
+ return el;
69
+ }
70
+
71
+ /** 下拉选项先创建,再设置选中值;字符串选项同时作为 value 与 label。 */
72
+ export function select(
73
+ doc: Document,
74
+ signal: AbortSignal,
75
+ opts?: ConsoleSelectOptions,
76
+ ): HTMLSelectElement {
77
+ const o = opts ?? {};
78
+ const el = h(doc, 'select', fieldClass(o.cls));
79
+ for (const raw of o.options ?? []) {
80
+ const item = typeof raw === 'string' ? { value: raw, label: raw } : raw;
81
+ const opt = h(doc, 'option', null, item.label ?? item.value);
82
+ opt.value = item.value;
83
+ el.appendChild(opt);
84
+ }
85
+ wire(el, signal, o, 'change');
86
+ return el;
87
+ }
88
+
89
+ /** 多行输入。`rows` 缺省交给 CSS 的 `min-height`。 */
90
+ export function textarea(
91
+ doc: Document,
92
+ signal: AbortSignal,
93
+ opts?: ConsoleTextareaOptions,
94
+ ): HTMLTextAreaElement {
95
+ const o = opts ?? {};
96
+ const el = h(doc, 'textarea', fieldClass(o.cls));
97
+ if (o.rows != null) el.rows = o.rows;
98
+ if (o.placeholder != null) el.placeholder = o.placeholder;
99
+ wire(el, signal, o, 'input');
100
+ return el;
101
+ }
102
+
103
+ /**
104
+ * 勾选框采用 label.check > input[type=checkbox] + span。文本单独放入 span,使 inline-flex 的 gap 在控件与整段文本之间一致生效。
105
+ */
106
+ export function checkbox(
107
+ doc: Document,
108
+ signal: AbortSignal,
109
+ label: string,
110
+ opts?: ConsoleCheckboxOptions,
111
+ ): ConsoleCheckbox {
112
+ const o = opts ?? {};
113
+ const el = h(doc, 'label', 'check');
114
+ const box = h(doc, 'input');
115
+ box.type = 'checkbox';
116
+ if (o.checked) box.checked = true;
117
+ if (o.disabled) box.disabled = true;
118
+ if (o.title) el.title = o.title;
119
+ el.append(box, h(doc, 'span', null, label));
120
+ const cb = o.onChange;
121
+ if (cb) box.addEventListener('change', () => cb(box.checked), { signal });
122
+ return {
123
+ el,
124
+ input: box,
125
+ get checked(): boolean {
126
+ return box.checked;
127
+ },
128
+ // 程序设置值不触发用户 change 回调。
129
+ setChecked(v: boolean): void {
130
+ box.checked = v;
131
+ },
132
+ };
133
+ }
134
+
135
+ /**
136
+ * 字段标签 + 控件。控件嵌在 `<label>` 里,点标签即聚焦控件(不必配 `for`/`id`,
137
+ * 扩展也就不必发明一套全局唯一的 id)。
138
+ */
139
+ export function field(doc: Document, label: string, control: HTMLElement): HTMLLabelElement {
140
+ const el = h(doc, 'label', 'fieldrow');
141
+ el.append(h(doc, 'span', 'fieldlabel', label), control);
142
+ return el;
143
+ }
144
+
145
+ /** 分段选择器保留全部重复 value 选项,并统一更新它们的选中态。 */
146
+ export function segmented(
147
+ doc: Document,
148
+ signal: AbortSignal,
149
+ items: readonly (string | { value: string; label?: string })[],
150
+ opts?: ConsoleSegmentedOptions,
151
+ ): ConsoleSegmented {
152
+ const o = opts ?? {};
153
+ const el = h(doc, 'div', o.size === 'sm' ? 'segwrap sm' : 'segwrap');
154
+ const keys: { value: string; btn: HTMLButtonElement }[] = [];
155
+ let current = o.value ?? '';
156
+ const paint = (): void => {
157
+ for (const k of keys) k.btn.className = k.value === current ? 'seg active' : 'seg';
158
+ };
159
+
160
+ for (const raw of items) {
161
+ const item = typeof raw === 'string' ? { value: raw, label: raw } : raw;
162
+ const btn = h(doc, 'button', 'seg', item.label ?? item.value);
163
+ btn.type = 'button';
164
+ keys.push({ value: item.value, btn });
165
+ btn.addEventListener(
166
+ 'click',
167
+ () => {
168
+ // 点已经选中的那颗:不回调。onSelect 后面挂的常是一次重新取数,
169
+ // 白刷一遍不只是浪费,还会把用户刚滚到的位置弹回去。
170
+ if (current === item.value) return;
171
+ current = item.value;
172
+ paint();
173
+ o.onSelect?.(item.value);
174
+ },
175
+ { signal },
176
+ );
177
+ el.appendChild(btn);
178
+ }
179
+ paint();
180
+
181
+ return {
182
+ el,
183
+ get value(): string {
184
+ return current;
185
+ },
186
+ setValue(v: string): void {
187
+ current = v;
188
+ paint();
189
+ },
190
+ };
191
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * ConsoleFormat 提供不依赖 DOM 的纯格式化函数,供各控制台面板使用统一显示口径。
3
+ */
4
+
5
+ import type { ConsoleFormat } from '../../shared/client-panel.ts';
6
+
7
+ /** 补零到两位。`4` → `04` */
8
+ function pad2(n: number): string {
9
+ return n < 10 ? '0' + n : String(n);
10
+ }
11
+
12
+ export const consoleFormat: ConsoleFormat = {
13
+ /** **满 10000 才转 k**(9999 仍是 9999,不是 10.0k) */
14
+ count(n) {
15
+ return n == null ? '—' : n >= 10000 ? (n / 1000).toFixed(1) + 'k' : String(n);
16
+ },
17
+
18
+ /** null 给空串(不是 `—`),单位是 1024 进制的 `B/K/M` */
19
+ bytes(n) {
20
+ return n == null
21
+ ? ''
22
+ : n < 1024
23
+ ? n + 'B'
24
+ : n < 1048576
25
+ ? (n / 1024).toFixed(1) + 'K'
26
+ : (n / 1048576).toFixed(1) + 'M';
27
+ },
28
+
29
+ /** `0.42` → `42%`,四舍五入到整数 */
30
+ percent(r) {
31
+ return r == null ? '—' : Math.round(r * 100) + '%';
32
+ },
33
+
34
+ /**
35
+ * ISO 串直接切 `11..19` 位拿 `HH:MM:SS`。
36
+ * 不 `new Date()` 是有意的——服务端给的就是本地时间串,转一道反而会带上时区偏移。
37
+ * 非字符串或长度不足 19 → 空串。
38
+ */
39
+ clock(ts) {
40
+ return typeof ts === 'string' && ts.length >= 19 ? ts.slice(11, 19) : '';
41
+ },
42
+
43
+ /**
44
+ * `<1` 保 4 位小数(单次调用成本常在 0.0x),`>=1` 保 2 位。
45
+ * 币种是 ISO 代码:`USD`(缺省)渲染成 `$`,其余代码原样前置并空一格(`EUR 1.00`)。
46
+ */
47
+ money(n, currency) {
48
+ const code = currency || 'USD';
49
+ const prefix = code === 'USD' ? '$' : code + ' ';
50
+ return prefix + (n == null ? '0' : n < 1 ? n.toFixed(4) : n.toFixed(2));
51
+ },
52
+
53
+ /**
54
+ * 毫秒时长,口径在这里定死:
55
+ *
56
+ * - `null` / 非有限数 → `—`(与 `count` / `percent` 一致,空值都是破折号)
57
+ * - 负数按 0 处理
58
+ * - `< 1s`:整毫秒,`999` → `999ms`
59
+ * - `< 1min`:一位小数,`1200` → `1.2s`
60
+ * - `< 1h`:`3m 04s`(秒补零,好竖排对齐)
61
+ * - 更长:`1h 00m`
62
+ *
63
+ * 先把 1s 以上的值**量化到 0.1s** 再分档,免得 `59999` 落进秒档印出 `60.0s`
64
+ * 这种自相矛盾的读数(量化后它是 `1m 00s`)。
65
+ */
66
+ duration(ms) {
67
+ if (ms == null || !Number.isFinite(ms)) return '—';
68
+ const raw = ms < 0 ? 0 : ms;
69
+ const t = raw < 1000 ? Math.round(raw) : Math.round(raw / 100) * 100;
70
+ if (t < 1000) return t + 'ms';
71
+ if (t < 60_000) return (t / 1000).toFixed(1) + 's';
72
+ if (t < 3_600_000) return Math.floor(t / 60_000) + 'm ' + pad2(Math.floor((t % 60_000) / 1000)) + 's';
73
+ return Math.floor(t / 3_600_000) + 'h ' + pad2(Math.floor((t % 3_600_000) / 60_000)) + 'm';
74
+ },
75
+ };
@@ -0,0 +1,183 @@
1
+ const SVG_NS = 'http://www.w3.org/2000/svg';
2
+
3
+ export type ConsoleIconName =
4
+ | 'terminal'
5
+ | 'activity'
6
+ | 'chart'
7
+ | 'boxes'
8
+ | 'bot'
9
+ | 'settings'
10
+ | 'play'
11
+ | 'pause'
12
+ | 'power'
13
+ | 'image'
14
+ | 'folder-open'
15
+ | 'download'
16
+ | 'cpu'
17
+ | 'text'
18
+ | 'eye'
19
+ | 'eye-off'
20
+ | 'refresh';
21
+
22
+ type Shape = readonly [tag: 'path' | 'circle' | 'rect' | 'line', attrs: Readonly<Record<string, string>>];
23
+
24
+ const SHAPES: Readonly<Record<ConsoleIconName, readonly Shape[]>> = {
25
+ terminal: [
26
+ ['path', { d: 'm4 17 6-6-6-6' }],
27
+ ['path', { d: 'M12 19h8' }],
28
+ ],
29
+ activity: [
30
+ ['path', { d: 'M3 12h4l2-7 4 14 2-7h6' }],
31
+ ],
32
+ chart: [
33
+ ['path', { d: 'M4 19V9' }],
34
+ ['path', { d: 'M10 19V5' }],
35
+ ['path', { d: 'M16 19v-7' }],
36
+ ['path', { d: 'M22 19H2' }],
37
+ ],
38
+ boxes: [
39
+ ['path', { d: 'm12 2 7 4-7 4-7-4 7-4Z' }],
40
+ ['path', { d: 'm5 10 7 4 7-4' }],
41
+ ['path', { d: 'm5 14 7 4 7-4' }],
42
+ ],
43
+ bot: [
44
+ ['rect', { x: '5', y: '7', width: '14', height: '12', rx: '3' }],
45
+ ['path', { d: 'M12 3v4' }],
46
+ ['circle', { cx: '9', cy: '13', r: '1' }],
47
+ ['circle', { cx: '15', cy: '13', r: '1' }],
48
+ ],
49
+ settings: [
50
+ ['circle', { cx: '12', cy: '12', r: '3' }],
51
+ ['path', { d: 'M19.4 15a1.7 1.7 0 0 0 .3 1.9l.1.1-2.8 2.8-.1-.1a1.7 1.7 0 0 0-1.9-.3 1.7 1.7 0 0 0-1 1.5V21h-4v-.1a1.7 1.7 0 0 0-1-1.5 1.7 1.7 0 0 0-1.9.3l-.1.1L4.2 17l.1-.1a1.7 1.7 0 0 0 .3-1.9 1.7 1.7 0 0 0-1.5-1H3v-4h.1a1.7 1.7 0 0 0 1.5-1 1.7 1.7 0 0 0-.3-1.9L4.2 7 7 4.2l.1.1A1.7 1.7 0 0 0 9 4.6a1.7 1.7 0 0 0 1-1.5V3h4v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.9-.3l.1-.1L19.8 7l-.1.1a1.7 1.7 0 0 0-.3 1.9 1.7 1.7 0 0 0 1.5 1h.1v4h-.1a1.7 1.7 0 0 0-1.5 1Z' }],
52
+ ],
53
+ play: [['path', { d: 'm8 5 11 7-11 7V5Z' }]],
54
+ power: [
55
+ ['path', { d: 'M12 3v8' }],
56
+ ['path', { d: 'M17.7 6.3a8 8 0 1 1-11.4 0' }],
57
+ ],
58
+ pause: [
59
+ ['rect', { x: '7', y: '5', width: '3', height: '14', rx: '1' }],
60
+ ['rect', { x: '14', y: '5', width: '3', height: '14', rx: '1' }],
61
+ ],
62
+ image: [
63
+ ['rect', { x: '3', y: '4', width: '18', height: '16', rx: '3' }],
64
+ ['circle', { cx: '9', cy: '10', r: '2' }],
65
+ ['path', { d: 'm21 15-4-4L5 20' }],
66
+ ],
67
+ 'folder-open': [
68
+ ['path', { d: 'M3 6h6l2 2h10' }],
69
+ ['path', { d: 'M3 6v13h15l3-8H6l-3 8' }],
70
+ ],
71
+ download: [
72
+ ['path', { d: 'M12 3v12' }],
73
+ ['path', { d: 'm7 10 5 5 5-5' }],
74
+ ['path', { d: 'M5 21h14' }],
75
+ ],
76
+ cpu: [
77
+ ['rect', { x: '5', y: '5', width: '14', height: '14', rx: '2' }],
78
+ ['rect', { x: '9', y: '9', width: '6', height: '6', rx: '1' }],
79
+ ['path', { d: 'M9 2v3M15 2v3M9 19v3M15 19v3M2 9h3M2 15h3M19 9h3M19 15h3' }],
80
+ ],
81
+ text: [
82
+ ['path', { d: 'M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z' }],
83
+ ['path', { d: 'M14 2v4a2 2 0 0 0 2 2h4' }],
84
+ ['path', { d: 'M10 9H8M16 13H8M16 17H8' }],
85
+ ],
86
+ eye: [
87
+ ['path', { d: 'M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12Z' }],
88
+ ['circle', { cx: '12', cy: '12', r: '3' }],
89
+ ],
90
+ 'eye-off': [
91
+ ['path', { d: 'M3 3l18 18' }],
92
+ ['path', { d: 'M10.6 5.2A10.9 10.9 0 0 1 12 5c6.5 0 10 7 10 7a17.3 17.3 0 0 1-3.2 4' }],
93
+ ['path', { d: 'M6.6 6.6C3.8 8.5 2 12 2 12s3.5 7 10 7c1.6 0 3-.4 4.3-1' }],
94
+ ['path', { d: 'M9.9 9.9a3 3 0 0 0 4.2 4.2' }],
95
+ ],
96
+ refresh: [
97
+ ['path', { d: 'M21 12a9 9 0 1 1-2.6-6.4' }],
98
+ ['path', { d: 'M21 3v6h-6' }],
99
+ ],
100
+ };
101
+
102
+ /**
103
+ * Cortico 字标。笔画与 README 横幅(assets/cortico-banner.svg)是同一份。
104
+ * 横幅上那四个圈(两只眼睛与两个字母 o)是品牌绿,这里改成跟当前主题的强调色走,
105
+ * 其余笔画取 currentColor。颜色写在样式表里:presentation 属性吃不下 var()。
106
+ */
107
+ const WORDMARK_MARK: readonly Shape[] = [
108
+ ['path', { d: 'M182 63.66A84 84 0 1 0 182 192.34', 'stroke-width': '30' }],
109
+ ['circle', { cx: '113', cy: '117', r: '16', 'stroke-width': '11', class: 'accent' }],
110
+ ['circle', { cx: '163', cy: '117', r: '16', 'stroke-width': '11', class: 'accent' }],
111
+ ];
112
+
113
+ const WORDMARK_TEXT: readonly Shape[] = [
114
+ ['path', { d: 'M230.97 93.03A24 24 0 1 0 230.97 126.97' }],
115
+ ['circle', { cx: '286', cy: '110', r: '24', class: 'accent' }],
116
+ ['path', { d: 'M341 86V134' }],
117
+ ['path', { d: 'M341 112A24 24 0 0 1 365 88' }],
118
+ ['path', { d: 'M412 70V134' }],
119
+ ['path', { d: 'M396 78H428' }],
120
+ ['path', { d: 'M459 86V134' }],
121
+ ['path', { d: 'M530.97 93.03A24 24 0 1 0 530.97 126.97' }],
122
+ ['circle', { cx: '586', cy: '110', r: '24', class: 'accent' }],
123
+ ];
124
+
125
+ function shapesInto(doc: Document, parent: SVGElement, shapes: readonly Shape[]): void {
126
+ for (const [tag, attrs] of shapes) {
127
+ const child = doc.createElementNS(SVG_NS, tag);
128
+ for (const [key, value] of Object.entries(attrs)) child.setAttribute(key, value);
129
+ parent.appendChild(child);
130
+ }
131
+ }
132
+
133
+ export function wordmark(doc: Document, cls = 'wordmark'): SVGSVGElement {
134
+ const svg = doc.createElementNS(SVG_NS, 'svg');
135
+ svg.setAttribute('viewBox', '28 32 594 156');
136
+ svg.setAttribute('fill', 'none');
137
+ svg.setAttribute('stroke', 'currentColor');
138
+ svg.setAttribute('stroke-linecap', 'round');
139
+ svg.setAttribute('aria-hidden', 'true');
140
+ svg.classList.add(cls);
141
+ // 字母前那个符号自带一套尺度,照横幅原样缩放,不重算坐标。
142
+ const mark = doc.createElementNS(SVG_NS, 'g');
143
+ mark.setAttribute('transform', 'translate(10 14) scale(.75)');
144
+ shapesInto(doc, mark, WORDMARK_MARK);
145
+ const text = doc.createElementNS(SVG_NS, 'g');
146
+ text.setAttribute('stroke-width', '16');
147
+ shapesInto(doc, text, WORDMARK_TEXT);
148
+ const dot = doc.createElementNS(SVG_NS, 'circle');
149
+ for (const [key, value] of Object.entries({ cx: '459', cy: '58', r: '9', fill: 'currentColor', stroke: 'none' })) {
150
+ dot.setAttribute(key, value);
151
+ }
152
+ svg.appendChild(mark);
153
+ svg.appendChild(text);
154
+ svg.appendChild(dot);
155
+ return svg;
156
+ }
157
+
158
+ /** 字标前那个符号单独用。视框按它自己的坐标取,描边算在内。 */
159
+ export function brandMark(doc: Document, cls = 'brandmark'): SVGSVGElement {
160
+ const svg = doc.createElementNS(SVG_NS, 'svg');
161
+ svg.setAttribute('viewBox', '29 29 198 198');
162
+ svg.setAttribute('fill', 'none');
163
+ svg.setAttribute('stroke', 'currentColor');
164
+ svg.setAttribute('stroke-linecap', 'round');
165
+ svg.setAttribute('aria-hidden', 'true');
166
+ svg.setAttribute('class', cls);
167
+ shapesInto(doc, svg, WORDMARK_MARK);
168
+ return svg;
169
+ }
170
+
171
+ export function icon(doc: Document, name: ConsoleIconName, cls = 'icon'): SVGSVGElement {
172
+ const svg = doc.createElementNS(SVG_NS, 'svg');
173
+ svg.setAttribute('viewBox', '0 0 24 24');
174
+ svg.setAttribute('fill', 'none');
175
+ svg.setAttribute('stroke', 'currentColor');
176
+ svg.setAttribute('stroke-width', '1.8');
177
+ svg.setAttribute('stroke-linecap', 'round');
178
+ svg.setAttribute('stroke-linejoin', 'round');
179
+ svg.setAttribute('aria-hidden', 'true');
180
+ svg.setAttribute('class', cls);
181
+ shapesInto(doc, svg, SHAPES[name]);
182
+ return svg;
183
+ }
@@ -0,0 +1,91 @@
1
+ /** 归一化输入图片:只缩不放,合规原文件保持原字节。缩放后超限时改用 JPEG,仍超限或解码失败则拒收。 */
2
+
3
+ import type { ConsoleImageAttachment, ConsolePromptImagesOptions } from '../../shared/client-panel.ts';
4
+ import { S } from './strings.ts';
5
+
6
+ export const IMAGE_MIMES: readonly string[] = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
7
+
8
+ export const IMAGE_DEFAULTS: Required<ConsolePromptImagesOptions> = {
9
+ max: 8,
10
+ maxEdge: 2048,
11
+ maxBytes: 6 * 1024 * 1024,
12
+ };
13
+
14
+ export function isImageFile(file: { type: string }): boolean {
15
+ return IMAGE_MIMES.includes(file.type);
16
+ }
17
+
18
+ /** 只缩不放:长边不超过 `maxEdge` 的原样返回;缩放保持比例,四舍五入且不小于 1。 */
19
+ export function fitWithin(width: number, height: number, maxEdge: number): { width: number; height: number } {
20
+ const edge = Math.max(width, height);
21
+ if (!(edge > maxEdge) || maxEdge <= 0) return { width, height };
22
+ const scale = maxEdge / edge;
23
+ return {
24
+ width: Math.max(1, Math.round(width * scale)),
25
+ height: Math.max(1, Math.round(height * scale)),
26
+ };
27
+ }
28
+
29
+ function toBase64(blob: Blob): Promise<string> {
30
+ return new Promise((resolve, reject) => {
31
+ const reader = new FileReader();
32
+ reader.onerror = () => reject(reader.error ?? new Error(S.imageReadFailed));
33
+ reader.onload = () => {
34
+ const url = String(reader.result ?? '');
35
+ resolve(url.slice(url.indexOf(',') + 1));
36
+ };
37
+ reader.readAsDataURL(blob);
38
+ });
39
+ }
40
+
41
+ function encode(bitmap: ImageBitmap, width: number, height: number, mime: string, quality?: number): Promise<Blob> {
42
+ const canvas = document.createElement('canvas');
43
+ canvas.width = width;
44
+ canvas.height = height;
45
+ const ctx = canvas.getContext('2d');
46
+ if (!ctx) throw new Error(S.canvasUnsupported);
47
+ ctx.drawImage(bitmap, 0, 0, width, height);
48
+ return new Promise((resolve, reject) => {
49
+ canvas.toBlob((blob) => (blob ? resolve(blob) : reject(new Error(S.imageEncodeFailed))), mime, quality);
50
+ });
51
+ }
52
+
53
+ /**
54
+ * 归一化一张图。拒收(不是图片、解码失败、缩到上限后仍超字节上限)以异常表达,
55
+ * 消息可直接给用户看。
56
+ */
57
+ export async function normalizeImage(
58
+ file: File,
59
+ opts: ConsolePromptImagesOptions = {},
60
+ ): Promise<ConsoleImageAttachment> {
61
+ const { maxEdge, maxBytes } = { ...IMAGE_DEFAULTS, ...opts };
62
+ if (!isImageFile(file)) throw new Error(S.notImage(file.name || S.fileFallback));
63
+ let bitmap: ImageBitmap;
64
+ try {
65
+ bitmap = await createImageBitmap(file);
66
+ } catch {
67
+ throw new Error(S.undecodable(file.name || S.imageFallback));
68
+ }
69
+ try {
70
+ const name = file.name || 'image';
71
+ const fitted = fitWithin(bitmap.width, bitmap.height, maxEdge);
72
+ const untouched = fitted.width === bitmap.width && fitted.height === bitmap.height && file.size <= maxBytes;
73
+ if (untouched) {
74
+ return { name, mime: file.type, base64: await toBase64(file), bytes: file.size, width: bitmap.width, height: bitmap.height };
75
+ }
76
+ // 缩放后先试原格式(GIF 没有 canvas 编码器,退 PNG);超字节上限再退 JPEG。
77
+ const preferred = file.type === 'image/gif' ? 'image/png' : file.type;
78
+ let blob = await encode(bitmap, fitted.width, fitted.height, preferred);
79
+ let mime = preferred;
80
+ if (blob.size > maxBytes && preferred !== 'image/jpeg') {
81
+ blob = await encode(bitmap, fitted.width, fitted.height, 'image/jpeg', 0.85);
82
+ mime = 'image/jpeg';
83
+ }
84
+ if (blob.size > maxBytes) {
85
+ throw new Error(S.stillTooLarge(name, Math.round(maxBytes / 1024 / 1024)));
86
+ }
87
+ return { name, mime, base64: await toBase64(blob), bytes: blob.size, width: fitted.width, height: fitted.height };
88
+ } finally {
89
+ bitmap.close();
90
+ }
91
+ }
@@ -0,0 +1,89 @@
1
+ /** 每次面板挂载创建 ConsoleUi,绑定 memo 命名空间、signal 与浮层宿主。 */
2
+
3
+ import type { ConsoleMemo, ConsoleUi, Disposable } from '../../shared/client-panel.ts';
4
+ import { esc, h } from './dom.ts';
5
+ import { consoleFormat } from './format.ts';
6
+ import { kv, progress, statgrid, stat, table } from './data.ts';
7
+ import { checkbox, field, input, segmented, select, textarea } from './fields.ts';
8
+ import { copyButton, disable } from './actions.ts';
9
+ import { log } from './log.ts';
10
+ import { promptInput } from './prompt-input.tsx';
11
+ import {
12
+ actions,
13
+ button,
14
+ chip,
15
+ foldSheet,
16
+ msgline,
17
+ pill,
18
+ placeholder,
19
+ rowbar,
20
+ section,
21
+ sheet,
22
+ } from './sheet.ts';
23
+ import type { Overlay, OverlayEnv } from './overlay.ts';
24
+ import { busy, confirm, drawer, noopDisposable, toast } from './overlay.ts';
25
+
26
+ export interface ConsoleUiDeps {
27
+ /** 面板局部持久化。`foldSheet` 的展开状态存这儿。 */
28
+ memo: ConsoleMemo;
29
+ /**
30
+ * toast / confirm / drawer 的挂载点。host 传 `document.body` 或一个专用容器。
31
+ * **不能是面板的 `root`**——那个在 unmount 时会被清空。
32
+ */
33
+ overlayHost: HTMLElement;
34
+ /** 面板的 ctx.signal,用于取消弹窗与释放资源。 */
35
+ signal: AbortSignal;
36
+ /** 缺省为 overlayHost.ownerDocument。 */
37
+ doc?: Document;
38
+ }
39
+
40
+ export function createConsoleUi(deps: ConsoleUiDeps): ConsoleUi {
41
+ const doc = deps.doc ?? deps.overlayHost.ownerDocument;
42
+ const signal = deps.signal;
43
+ const env: OverlayEnv = { doc, host: deps.overlayHost, signal };
44
+ // 同一实例只留一条 toast:后来的顶掉前面的,免得叠成一摞
45
+ let liveToast: Overlay | null = null;
46
+ // 抽出来是因为 copyButton 也要弹 toast,而"只留一条"这条规矩记在上面那个闭包里
47
+ const showToast = (text: string, tone?: 'ok' | 'bad'): Disposable => {
48
+ liveToast = toast(env, text, tone, liveToast);
49
+ // abort 之后 toast 不显示,但仍要给回一个句柄:调用方 `.dispose()` 不该炸
50
+ return liveToast ?? noopDisposable;
51
+ };
52
+
53
+ return {
54
+ h: (tag, cls, text) => h(doc, tag, cls, text),
55
+ esc,
56
+ sheet: (opts) => sheet(doc, opts),
57
+ foldSheet: (id, opts) => foldSheet(doc, deps.memo, signal, id, opts),
58
+ rowbar: () => rowbar(doc),
59
+ section: (title, description) => section(doc, title, description),
60
+ actions: () => actions(doc),
61
+ button: (label, opts) => button(doc, signal, label, opts),
62
+ copyButton: (text, opts) => copyButton(env, showToast, text, opts),
63
+ pill: (text, tone) => pill(doc, text, tone),
64
+ chip: (text, tone) => chip(doc, text, tone),
65
+ msgline: (text, bad) => msgline(doc, text, bad),
66
+ placeholder: (text) => placeholder(doc, text),
67
+ input: (opts) => input(doc, signal, opts),
68
+ select: (opts) => select(doc, signal, opts),
69
+ textarea: (opts) => textarea(doc, signal, opts),
70
+ promptInput: (opts) => promptInput(doc, signal, opts),
71
+ checkbox: (label, opts) => checkbox(doc, signal, label, opts),
72
+ field: (label, control) => field(doc, label, control),
73
+ segmented: (items, opts) => segmented(doc, signal, items, opts),
74
+ table: (opts) => table(doc, opts),
75
+ kv: (rows) => kv(doc, rows),
76
+ log: (opts) => log(doc, signal, opts),
77
+ stat: (item) => stat(doc, item),
78
+ statgrid: (items) => statgrid(doc, items),
79
+ progress: (opts) => progress(doc, opts),
80
+ toast: showToast,
81
+ confirm: (opts) => confirm(env, opts),
82
+ drawer: (title, body) => drawer(env, title, body),
83
+ busy: (title, text) => busy(env, title, text),
84
+ disable: (...els) => disable(els),
85
+ fmt: consoleFormat,
86
+ };
87
+ }
88
+
89
+ export { LOG_STICK_PX, shouldStick } from './log.ts';