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,136 @@
1
+ import { pick } from '../../core/language.ts';
2
+ import type { EnvPromptOrigin } from '../../../../core/prefix.ts';
3
+
4
+ const zh = {
5
+ // index.ts
6
+ title: '系统提示词',
7
+ introDesc: "Ctrl+S 保存模板,重载后更新当前 session 前缀。来自代码的段只读。",
8
+ reloadSession: '重载当前 session',
9
+ reloading: '正在重载…',
10
+ prefixReloaded: '前缀已重载',
11
+ leaveUnsaved: "未保存的修改将丢失。离开?",
12
+ saving: '保存中…',
13
+ saved: '已保存',
14
+ saveFailedFor: (title: string, msg: string) => `${title} 保存失败:${msg}`,
15
+ savedList: (names: string) => `✓ 已保存 ${names};重载后生效`,
16
+ dirtyCount: (n: number) => `${n} 处改动还没保存(Ctrl+S)`,
17
+ fillNowMultiline: '此刻填入(多行块)',
18
+ fillNow: '此刻填入',
19
+ varUnreported: "未提供此变量;占位符将原样保留。",
20
+ varEmpty: '(此刻为空,会落到缺省文案)',
21
+ flagEditable: (key: string, origin: EnvPromptOrigin | undefined) =>
22
+ `可编辑:${key}${origin === 'deployment' ? '(本部署的覆盖)' : origin === 'package' ? '(bot 包覆盖;保存写入部署覆盖)' : origin === 'module' ? '(World 默认;保存后成为本部署的覆盖)' : ''}`,
23
+ flagReadonly: '来自代码,只读',
24
+ sourceMissing: (key: string) => `源「${key}」找不到`,
25
+ fromCode: '来自代码',
26
+ originDeployment: '本部署的覆盖',
27
+ originPackage: "bot 包覆盖",
28
+ originWorld: 'World 默认',
29
+ listSep: '、',
30
+ warnSep: ';',
31
+ prefixEmpty: '前缀现在是空的',
32
+ prefixUnavailable: "前缀预览不可用。",
33
+ prefixStats: (n: number, tok: string) => `${n} 块 · 渲染后约 ${tok} tokens`,
34
+ loadFailed: (msg: string) => `加载失败:${msg}`,
35
+
36
+ // view.ts
37
+ warnUnknownVars: (list: string) => `模板里的 ${list} 未提供值,占位符将原样保留`,
38
+ warnUnusedVars: (list: string) => `${list} 声明了但没用到`,
39
+ varPanelTitle: '这份模板的占位符',
40
+ multiline: '多行',
41
+ varUnreportedShort: "未提供此变量",
42
+ varEmptyShort: '(此刻为空,会落到缺省文案)',
43
+ leaveUnsavedNamed: (names: string) => `有未保存的模板改动(${names})。离开并放弃?`,
44
+ editableTemplate: '可编辑模板',
45
+ cardDescDefault: '保存到源文件;重载系统前缀后对当前 session 生效。',
46
+ loadedFromDeployment: "已载入本部署覆盖文件。",
47
+ loadedFromPackage: "已载入 bot 包覆盖;保存写入本部署覆盖文件。",
48
+ loadedFromWorld: "已载入 World 默认模板;保存写入本部署覆盖文件。",
49
+ loadedFromSource: '已从源文件载入',
50
+ unsaved: '● 未保存',
51
+ saveToFile: '保存到文件',
52
+ resetToWorld: '移除部署覆盖',
53
+ savedResult: (result: string) => `✓ ${result};重载前缀后生效`,
54
+ saveFailed: (msg: string) => `保存失败:${msg}`,
55
+ saveFailedToast: '保存失败',
56
+ resetTitle: (title: string) => `删除「${title}」的部署覆盖?`,
57
+ resetBody: '删除后使用 bot 包的覆盖版本;没有包覆盖时使用 World 默认版本。未保存的修改将丢失。',
58
+ resetting: '恢复中…',
59
+ resetDone: '已移除部署覆盖',
60
+ resetFailed: (msg: string) => `恢复失败:${msg}`,
61
+ resetFailedToast: '恢复失败',
62
+ noTemplatesScoped: '这个归属下没有可编辑的模板',
63
+ noTemplates: '没有可编辑的提示词模板',
64
+ templatesLoadFailed: (msg: string) => `模板加载失败:${msg}`,
65
+
66
+ // editor.ts
67
+ saveHintDirty: '按 Ctrl+S 保存',
68
+ };
69
+
70
+ const en: typeof zh = {
71
+ // index.ts
72
+ title: 'System prompt',
73
+ introDesc: "Ctrl+S saves templates; reload to update the current session prefix. Sections from code are read-only.",
74
+ reloadSession: 'Reload current session',
75
+ reloading: 'Reloading…',
76
+ prefixReloaded: 'Prefix reloaded',
77
+ leaveUnsaved: "Unsaved changes will be lost. Leave?",
78
+ saving: 'Saving…',
79
+ saved: 'Saved',
80
+ saveFailedFor: (title: string, msg: string) => `${title} failed to save: ${msg}`,
81
+ savedList: (names: string) => `✓ Saved ${names}; takes effect after reload`,
82
+ dirtyCount: (n: number) => `${n} unsaved changes (Ctrl+S)`,
83
+ fillNowMultiline: 'Filled in now (multi-line block)',
84
+ fillNow: 'Filled in now',
85
+ varUnreported: "Variable not provided; its placeholder remains unchanged.",
86
+ varEmpty: '(empty right now; falls back to the default text)',
87
+ flagEditable: (key: string, origin: EnvPromptOrigin | undefined) =>
88
+ `Editable: ${key}${origin === 'deployment' ? ' (this deployment\'s override)' : origin === 'package' ? ' (the package\'s override; saving makes it this deployment\'s override)' : origin === 'module' ? ' (World default; saving makes it this deployment\'s override)' : ''}`,
89
+ flagReadonly: 'From code, read-only',
90
+ sourceMissing: (key: string) => `Source "${key}" not found`,
91
+ fromCode: 'From code',
92
+ originDeployment: 'This deployment\'s override',
93
+ originPackage: "Bot package override",
94
+ originWorld: 'World default',
95
+ listSep: ', ',
96
+ warnSep: '; ',
97
+ prefixEmpty: 'The prefix is empty right now',
98
+ prefixUnavailable: "Prefix preview unavailable.",
99
+ prefixStats: (n: number, tok: string) => `${n} blocks · about ${tok} tokens rendered`,
100
+ loadFailed: (msg: string) => `Load failed: ${msg}`,
101
+
102
+ // view.ts
103
+ warnUnknownVars: (list: string) => `${list} has no provided value; placeholders remain unchanged`,
104
+ warnUnusedVars: (list: string) => `${list} declared but unused`,
105
+ varPanelTitle: 'Placeholders in this template',
106
+ multiline: 'multi-line',
107
+ varUnreportedShort: "Variable not provided",
108
+ varEmptyShort: '(empty right now; falls back to the default text)',
109
+ leaveUnsavedNamed: (names: string) => `There are unsaved template changes (${names}). Leave and discard them?`,
110
+ editableTemplate: 'editable template',
111
+ cardDescDefault: 'Saved to the source file; takes effect for the current session after a system prefix reload.',
112
+ loadedFromDeployment: "Loaded the deployment override.",
113
+ loadedFromPackage: "Loaded the bot package override; saving writes the deployment override.",
114
+ loadedFromWorld: "Loaded the World default; saving writes the deployment override.",
115
+ loadedFromSource: 'Loaded from the source file',
116
+ unsaved: '● Unsaved',
117
+ saveToFile: 'Save to file',
118
+ resetToWorld: 'Remove deployment override',
119
+ savedResult: (result: string) => `✓ ${result}; takes effect after a prefix reload`,
120
+ saveFailed: (msg: string) => `Save failed: ${msg}`,
121
+ saveFailedToast: 'Save failed',
122
+ resetTitle: (title: string) => `Delete the deployment override for "${title}"?`,
123
+ resetBody: 'Uses the bot package\'s override after deletion, or the World default if the package has no override. Unsaved changes will be lost.',
124
+ resetting: 'Restoring…',
125
+ resetDone: 'Deployment override removed',
126
+ resetFailed: (msg: string) => `Restore failed: ${msg}`,
127
+ resetFailedToast: 'Restore failed',
128
+ noTemplatesScoped: 'No editable templates under this owner',
129
+ noTemplates: 'No editable prompt templates',
130
+ templatesLoadFailed: (msg: string) => `Failed to load templates: ${msg}`,
131
+
132
+ // editor.ts
133
+ saveHintDirty: 'Ctrl+S to save',
134
+ };
135
+
136
+ export const S = pick({ zh, en });
@@ -0,0 +1,256 @@
1
+ /** 可编辑提示词模板的共享视图。统一管理页、core 与 provider 归属页复用同一实现。 */
2
+
3
+ import type { ConsoleUi, Disposable } from '../../../shared/client-panel.ts';
4
+ import { get, post } from '../../core/api.ts';
5
+ import type { Lifecycle } from '../../core/lifecycle.ts';
6
+ import { S } from './strings.ts';
7
+ import type { EnvPromptOrigin } from '../../../../core/prefix.ts';
8
+
9
+ /** 一个占位符:声明 + 此刻的实际展开值。 */
10
+ export interface PromptVar {
11
+ name: string;
12
+ description: string;
13
+ multiline?: boolean;
14
+ /** 缺席 = 没人报这个值(模板里用了它就会原样留在前缀里) */
15
+ value?: string;
16
+ }
17
+
18
+ export interface PromptDoc {
19
+ key: string;
20
+ title: string;
21
+ scope?: string;
22
+ description?: string;
23
+ content: string;
24
+ revision?: string;
25
+ role?: 'envPrompt' | 'prefix';
26
+ /** 当前模板来源:部署覆盖、bot 包覆盖或 World 默认。 */
27
+ origin?: EnvPromptOrigin;
28
+ vars?: PromptVar[];
29
+ }
30
+
31
+ export interface PromptsViewDeps {
32
+ ui: ConsoleUi;
33
+ lifecycle: Lifecycle;
34
+ signal: AbortSignal;
35
+ addLeaveGuard(fn: () => string | null): Disposable;
36
+ onError(err: unknown): void;
37
+ /** 只展示这些源;省略时展示全部。 */
38
+ keys?: readonly string[];
39
+ }
40
+
41
+ export interface PromptsView {
42
+ el: HTMLElement;
43
+ load(): Promise<void>;
44
+ }
45
+
46
+ /** 模板里用到的占位符名(与后端 templateVarNames 同一套语法)。 */
47
+ export function usedVarNames(template: string): string[] {
48
+ const names: string[] = [];
49
+ const seen = new Set<string>();
50
+ for (const m of template.matchAll(/\{\{\s*([\w.]+)\s*(?:\|[^}]*)?\}\}/g)) {
51
+ if (!seen.has(m[1])) { seen.add(m[1]); names.push(m[1]); }
52
+ }
53
+ return names;
54
+ }
55
+
56
+ /**
57
+ * 模板与声明对不上的地方。**警告不阻止保存**——改坏了自己负责,是这套编辑器的
58
+ * 明确取舍;框架只把事实摆出来。
59
+ */
60
+ export function varWarnings(template: string, vars: readonly PromptVar[]): string[] {
61
+ const declared = new Set(vars.map((v) => v.name));
62
+ const used = new Set(usedVarNames(template));
63
+ const out: string[] = [];
64
+ const unknown = [...used].filter((n) => !declared.has(n));
65
+ if (unknown.length) out.push(S.warnUnknownVars(unknown.map((n) => `{{${n}}}`).join(S.listSep)));
66
+ const unused = vars.filter((v) => !used.has(v.name)).map((v) => v.name);
67
+ if (unused.length) out.push(S.warnUnusedVars(unused.join(S.listSep)));
68
+ return out;
69
+ }
70
+
71
+ /** 占位符旁注:名字 + 说明 + **此刻会填进去的东西**(比说明直观)。 */
72
+ function renderVarPanel(ui: ConsoleUi, vars: readonly PromptVar[], onInsert?: (name: string) => void): HTMLElement {
73
+ const box = ui.h('div', 'prompt-vars');
74
+ box.appendChild(ui.h('b', null, S.varPanelTitle));
75
+ for (const v of vars) {
76
+ const row = ui.h('div', 'prompt-var');
77
+ const head = ui.h('div', 'prompt-var-head');
78
+ const name = ui.button(`{{${v.name}}}`, {
79
+ size: 'sm',
80
+ onClick: () => onInsert?.(v.name),
81
+ });
82
+ name.className += ' prompt-var-name';
83
+ head.appendChild(name);
84
+ if (v.multiline) head.appendChild(ui.chip(S.multiline, 'plain'));
85
+ row.appendChild(head);
86
+ row.appendChild(ui.h('div', 'prompt-var-desc', v.description));
87
+ const now = ui.h('pre', 'mono prompt-var-value');
88
+ now.textContent = v.value === undefined
89
+ ? S.varUnreportedShort
90
+ : v.value === ''
91
+ ? S.varEmptyShort
92
+ : v.value;
93
+ row.appendChild(now);
94
+ box.appendChild(row);
95
+ }
96
+ return box;
97
+ }
98
+
99
+ export function createPromptsView(deps: PromptsViewDeps): PromptsView {
100
+ const { ui } = deps;
101
+ const el = ui.h('div', 'prompt-library');
102
+ const dirty = new Map<string, boolean>();
103
+ const wanted = deps.keys ? new Set(deps.keys) : null;
104
+
105
+ deps.lifecycle.own(
106
+ deps.addLeaveGuard(() => {
107
+ const names = [...dirty.entries()].filter(([, changed]) => changed).map(([name]) => name);
108
+ return names.length ? S.leaveUnsavedNamed(names.join(S.listSep)) : null;
109
+ }),
110
+ );
111
+
112
+ const card = (doc: PromptDoc): HTMLElement => {
113
+ const sheet = ui.sheet({
114
+ title: doc.title,
115
+ en: S.editableTemplate,
116
+ desc: doc.description || S.cardDescDefault,
117
+ });
118
+ sheet.el.className += ' prompt-card';
119
+ let revision = doc.revision;
120
+ /** World 的环境提示词多说一句此刻读的是哪一层:保存永远落到部署层,包与 World 那两份不动。 */
121
+ const loadedText = (): string => doc.origin === 'deployment'
122
+ ? S.loadedFromDeployment
123
+ : doc.origin === 'package'
124
+ ? S.loadedFromPackage
125
+ : doc.origin === 'module'
126
+ ? S.loadedFromWorld
127
+ : S.loadedFromSource;
128
+ const state = ui.msgline(loadedText());
129
+ state.className += ' prompt-state';
130
+ const warn = ui.msgline('');
131
+ warn.className += ' prompt-warn';
132
+ const area = ui.textarea({
133
+ value: doc.content,
134
+ rows: 16,
135
+ cls: 'mono',
136
+ onInput: () => {
137
+ const changed = area.value !== doc.content;
138
+ dirty.set(doc.title, changed);
139
+ state.textContent = changed ? S.unsaved : loadedText();
140
+ refreshWarn();
141
+ },
142
+ onCommit: () => void save(),
143
+ });
144
+ const saveBtn = ui.button(S.saveToFile, { variant: 'primary', onClick: () => void save() });
145
+ const resetBtn = ui.button(S.resetToWorld, { onClick: () => void reset() });
146
+ resetBtn.hidden = doc.origin !== 'deployment';
147
+
148
+ function refreshWarn(): void {
149
+ const msgs = varWarnings(area.value, doc.vars ?? []);
150
+ warn.textContent = msgs.join(S.warnSep);
151
+ warn.className = msgs.length ? 'msgline prompt-warn bad' : 'msgline prompt-warn';
152
+ }
153
+
154
+ async function save(): Promise<void> {
155
+ const off = ui.disable(saveBtn, area);
156
+ state.textContent = S.saving;
157
+ try {
158
+ const out = await post<{ error?: string; result?: string; revision?: string }>(
159
+ '/api/prompts',
160
+ { key: doc.key, content: area.value, baseRevision: revision },
161
+ { signal: deps.signal },
162
+ );
163
+ if (out?.error) throw new Error(out.error);
164
+ doc.content = area.value;
165
+ revision = out?.revision;
166
+ // 保存永远写部署层,所以存过一次之后这份就来自部署层了。
167
+ if (doc.origin && doc.origin !== 'deployment') {
168
+ doc.origin = 'deployment';
169
+ resetBtn.hidden = false;
170
+ }
171
+ dirty.set(doc.title, false);
172
+ state.textContent = S.savedResult(out?.result ?? S.saved);
173
+ } catch (err) {
174
+ if ((err as { name?: string } | null)?.name === 'AbortError') return;
175
+ deps.onError(err);
176
+ state.textContent = S.saveFailed(String((err as Error)?.message ?? err));
177
+ ui.toast(S.saveFailedToast, 'bad');
178
+ } finally {
179
+ off.dispose();
180
+ }
181
+ }
182
+
183
+ /** 删掉 bot 侧覆盖文件,整页重载:这份模板回到 World 自带的版本。 */
184
+ async function reset(): Promise<void> {
185
+ const yes = await ui.confirm({
186
+ title: S.resetTitle(doc.title),
187
+ body: S.resetBody,
188
+ danger: true,
189
+ });
190
+ if (!yes) return;
191
+ const off = ui.disable(saveBtn, resetBtn, area);
192
+ state.textContent = S.resetting;
193
+ try {
194
+ const out = await post<{ error?: string; result?: string }>(
195
+ '/api/prompts/reset',
196
+ { key: doc.key },
197
+ { signal: deps.signal },
198
+ );
199
+ if (out?.error) throw new Error(out.error);
200
+ dirty.set(doc.title, false);
201
+ ui.toast(out?.result ?? S.resetDone, 'ok');
202
+ await load();
203
+ } catch (err) {
204
+ if ((err as { name?: string } | null)?.name === 'AbortError') return;
205
+ deps.onError(err);
206
+ state.textContent = S.resetFailed(String((err as Error)?.message ?? err));
207
+ ui.toast(S.resetFailedToast, 'bad');
208
+ } finally {
209
+ off.dispose();
210
+ }
211
+ }
212
+
213
+ const bar = ui.actions();
214
+ bar.append(state, ui.h('span', 'grow'), resetBtn, saveBtn);
215
+ sheet.body.append(area, warn, bar);
216
+ if (doc.vars?.length) {
217
+ sheet.body.appendChild(renderVarPanel(ui, doc.vars, (name) => {
218
+ insertAtCursor(area, `{{${name}}}`);
219
+ dirty.set(doc.title, true);
220
+ state.textContent = S.unsaved;
221
+ refreshWarn();
222
+ }));
223
+ }
224
+ refreshWarn();
225
+ return sheet.el;
226
+ };
227
+
228
+ async function load(): Promise<void> {
229
+ el.replaceChildren();
230
+ dirty.clear();
231
+ try {
232
+ const data = await get<{ prompts?: PromptDoc[] }>('/api/prompts', { signal: deps.signal });
233
+ const docs = (data?.prompts ?? []).filter((doc) => !wanted || wanted.has(doc.key));
234
+ if (!docs.length) {
235
+ el.appendChild(ui.placeholder(wanted ? S.noTemplatesScoped : S.noTemplates));
236
+ return;
237
+ }
238
+ for (const doc of docs) el.appendChild(card(doc));
239
+ } catch (err) {
240
+ if ((err as { name?: string } | null)?.name === 'AbortError') return;
241
+ deps.onError(err);
242
+ el.appendChild(ui.placeholder(S.templatesLoadFailed(String((err as Error)?.message ?? err))));
243
+ }
244
+ }
245
+
246
+ return { el, load };
247
+ }
248
+
249
+ /** 在光标处插入文本(没有选区就追加到末尾)。 */
250
+ function insertAtCursor(area: HTMLTextAreaElement, text: string): void {
251
+ const start = area.selectionStart ?? area.value.length;
252
+ const end = area.selectionEnd ?? start;
253
+ area.value = area.value.slice(0, start) + text + area.value.slice(end);
254
+ area.selectionStart = area.selectionEnd = start + text.length;
255
+ area.focus();
256
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * 「语言模型」页 —— 模型供应模块的入口。
3
+ *
4
+ * 左侧次级菜单列出 manifest 里 kind 为 `llm` 的页(每个供应模块一条),右侧由
5
+ * 嵌入的控制台页宿主渲染选中模块的面板:实例与模型、授权、托管、配置。这一页不认识
6
+ * 任何具体模块——名字、灯、徽标与面板全部来自 manifest。
7
+ *
8
+ * 路由 `#/providers/<pageId>/<panelId>`:第二段选模块,第三段选面板;缺省取第一个
9
+ * 模块及其第一个面板。面板页签由宿主渲染并指向同一前缀,所以切换留在本页内。
10
+ */
11
+
12
+ import { PROVIDERS_LAMP_ID } from '../../../shared/console-protocol.ts';
13
+ import { lampRow, paintLamps, subscribeLamps } from '../../ui/lamp.ts';
14
+ import { pageIntro } from '../../ui/page.ts';
15
+ import type { Route } from '../../core/router.ts';
16
+ import type { FeatureContext, FrameworkFeature } from '../feature.ts';
17
+ import { S } from './strings.ts';
18
+
19
+ const PROVIDERS_ROUTE = 'providers';
20
+ /** 这一页只列供应模块;人格与 IO 各有自己的入口。 */
21
+ const PROVIDER_KIND = 'llm';
22
+
23
+ export async function mountProviders(ctx: FeatureContext): Promise<void> {
24
+ const { ui, root } = ctx;
25
+ const doc = root.ownerDocument;
26
+ const intro = pageIntro(ui, S.pageTitle);
27
+ const layout = ui.h('div', 'settings-layout providerhub');
28
+ const index = ui.h('nav', 'settings-index');
29
+ index.setAttribute('aria-label', S.modulesAria);
30
+ index.setAttribute('role', 'tablist');
31
+ const content = ui.h('div', 'settings-content');
32
+ layout.append(index, content);
33
+ root.append(intro, layout);
34
+
35
+ if (!ctx.consolePageHost) {
36
+ content.appendChild(ui.placeholder(S.needHost));
37
+ return;
38
+ }
39
+ const host = ctx.consolePageHost({
40
+ root: content,
41
+ route: (pageId, panelId) => [PROVIDERS_ROUTE, pageId, panelId],
42
+ });
43
+ ctx.lifecycle.own({ dispose: () => host.unmount() });
44
+ content.appendChild(ui.placeholder(S.loading));
45
+ await host.load();
46
+ if (ctx.signal.aborted) return;
47
+
48
+ const providers = host.pages.filter((p) => p.kind === PROVIDER_KIND);
49
+ if (!providers.length) {
50
+ content.replaceChildren(ui.placeholder(S.none));
51
+ return;
52
+ }
53
+
54
+ const jumps = new Map<string, HTMLButtonElement>();
55
+ const lampNodes = new Map<string, HTMLSpanElement>();
56
+ for (const p of providers) {
57
+ const jump = ui.h('button', 'settings-jump providerhub-jump');
58
+ jump.type = 'button';
59
+ jump.setAttribute('role', 'tab');
60
+ jump.append(ui.h('span', 'lbl', p.label || p.id));
61
+ const lamps = lampRow(doc, p.lamps ?? []);
62
+ jump.appendChild(lamps);
63
+ lampNodes.set(p.id, lamps);
64
+ jump.addEventListener('click', () => {
65
+ try { ctx.router.navigate([PROVIDERS_ROUTE, p.id]); } catch (err) { ctx.onError(err); }
66
+ }, { signal: ctx.signal });
67
+ index.appendChild(jump);
68
+ jumps.set(p.id, jump);
69
+ }
70
+ // 灯的活数据只改那几个点,不重排菜单(与左栏同一节拍)。
71
+ ctx.lifecycle.own(subscribeLamps(doc, (lamps) => {
72
+ for (const [id, el] of lampNodes) paintLamps(el, lamps[id] ?? []);
73
+ }));
74
+
75
+ const show = (route: Route): void => {
76
+ const wanted = route.segments[1];
77
+ const provider = providers.find((p) => p.id === wanted) ?? providers[0]!;
78
+ for (const [id, jump] of jumps) {
79
+ const on = id === provider.id;
80
+ jump.classList.toggle('active', on);
81
+ jump.setAttribute('aria-selected', String(on));
82
+ }
83
+ const panel = route.segments[2];
84
+ void host.show(provider.id, typeof panel === 'string' && panel !== '' ? panel : undefined);
85
+ };
86
+ show(ctx.route);
87
+ ctx.lifecycle.own(ctx.router.onChange((route) => {
88
+ if (route.segments[0] !== PROVIDERS_ROUTE) return;
89
+ show(route);
90
+ }));
91
+ }
92
+
93
+ export const providersFeature: FrameworkFeature = {
94
+ route: PROVIDERS_ROUTE,
95
+ label: S.navLabel,
96
+ icon: 'cpu',
97
+ lampId: PROVIDERS_LAMP_ID,
98
+ navGroup: S.navGroup,
99
+ mount: mountProviders,
100
+ };
@@ -0,0 +1,23 @@
1
+ import { pick } from '../../core/language.ts';
2
+
3
+ const zh = {
4
+ pageTitle: '模型提供商',
5
+ modulesAria: '供应模块',
6
+ needHost: "模型提供商设置不可用。",
7
+ loading: '读取供应模块…',
8
+ none: '没有已注册的供应模块。',
9
+ navLabel: '模型提供商',
10
+ navGroup: 'Core',
11
+ };
12
+
13
+ const en: typeof zh = {
14
+ pageTitle: 'LLM Provider',
15
+ modulesAria: 'Provider modules',
16
+ needHost: "LLM provider settings unavailable.",
17
+ loading: 'Loading provider modules…',
18
+ none: 'No provider modules registered.',
19
+ navLabel: 'LLM Provider',
20
+ navGroup: 'Core',
21
+ };
22
+
23
+ export const S = pick({ zh, en });
@@ -0,0 +1,28 @@
1
+ import { LANGUAGE, saveLanguage, type Language } from '../../core/language.ts';
2
+ import type { FeatureContext } from '../feature.ts';
3
+ import { S } from './strings.ts';
4
+
5
+ export function mountGeneral(ctx: FeatureContext): void {
6
+ const { ui, root, signal } = ctx;
7
+ const win = root.ownerDocument.defaultView!;
8
+ const sheet = ui.sheet({ title: S.language, en: 'language', desc: S.languageDesc });
9
+ const group = ui.h('div', 'rowbar');
10
+ group.setAttribute('role', 'group');
11
+ group.setAttribute('aria-label', S.language);
12
+ sheet.body.appendChild(group);
13
+ root.append(sheet.el);
14
+ for (const [language, label] of [['zh', '简体中文'], ['en', 'English']] as const) {
15
+ const button = ui.h('button', 'btn', label);
16
+ button.type = 'button';
17
+ button.setAttribute('aria-pressed', String(language === LANGUAGE));
18
+ button.disabled = language === LANGUAGE;
19
+ button.addEventListener('click', () => { void changeLanguage(language).catch(ctx.onError); }, { signal });
20
+ group.append(button);
21
+ }
22
+
23
+ async function changeLanguage(language: Language): Promise<void> {
24
+ if (!await ui.confirm({ title: S.reloadTitle, body: S.reloadBody }) || signal.aborted) return;
25
+ saveLanguage(language, win.localStorage);
26
+ win.location.reload();
27
+ }
28
+ }
@@ -0,0 +1,82 @@
1
+ import { pageIntro } from '../../ui/page.ts';
2
+ import { mountAppearance } from '../appearance/index.ts';
3
+ import type { FeatureContext, FrameworkFeature } from '../feature.ts';
4
+ import { mountGeneral } from './general.ts';
5
+ import { S } from './strings.ts';
6
+
7
+ interface SettingSection {
8
+ id: string;
9
+ label: string;
10
+ description: string;
11
+ need?: string;
12
+ mount(ctx: FeatureContext): void | Promise<void>;
13
+ }
14
+
15
+ const SECTIONS: readonly SettingSection[] = [
16
+ {
17
+ id: 'general',
18
+ label: S.general,
19
+ description: S.generalDesc,
20
+ mount: mountGeneral,
21
+ },
22
+ {
23
+ id: 'appearance',
24
+ label: S.appearance,
25
+ description: S.appearanceDesc,
26
+ mount: (ctx) => mountAppearance(ctx, { embedded: true }),
27
+ },
28
+ ];
29
+
30
+ export function mountSettings(ctx: FeatureContext): void {
31
+ const { ui } = ctx;
32
+ const intro = pageIntro(ui, S.pageTitle);
33
+ const layout = ui.h('div', 'settings-layout');
34
+ const index = ui.h('nav', 'settings-index');
35
+ index.setAttribute('aria-label', S.sectionsAria);
36
+ index.setAttribute('role', 'tablist');
37
+ const content = ui.h('div', 'settings-content');
38
+ const visible = SECTIONS.filter((section) => !section.need || ctx.capabilities[section.need] === true);
39
+ const mounted: Array<{ button: HTMLButtonElement; section: HTMLElement }> = [];
40
+
41
+ const select = (id: string): void => {
42
+ for (const item of mounted) {
43
+ const active = item.section.id === `settings-${id}`;
44
+ item.section.classList.toggle('active', active);
45
+ item.button.classList.toggle('active', active);
46
+ item.button.setAttribute('aria-selected', String(active));
47
+ }
48
+ };
49
+
50
+ for (const spec of visible) {
51
+ const section = ui.h('section', 'settings-section');
52
+ section.id = `settings-${spec.id}`;
53
+ const heading = ui.h('div', 'settings-section-head');
54
+ heading.append(ui.h('h2', null, spec.label), ui.h('p', null, spec.description));
55
+ const body = ui.h('div', 'settings-section-body');
56
+ section.append(heading, body);
57
+ content.appendChild(section);
58
+
59
+ const jump = ui.h('button', 'settings-jump', spec.label);
60
+ jump.type = 'button';
61
+ jump.setAttribute('role', 'tab');
62
+ jump.setAttribute('aria-controls', section.id);
63
+ jump.addEventListener('click', () => select(spec.id), { signal: ctx.signal });
64
+ index.appendChild(jump);
65
+ mounted.push({ button: jump, section });
66
+
67
+ const child = { ...ctx, root: body };
68
+ void Promise.resolve(spec.mount(child)).catch(ctx.onError);
69
+ }
70
+
71
+ layout.append(index, content);
72
+ ctx.root.append(intro, layout);
73
+ if (visible[0]) select(visible[0].id);
74
+ }
75
+
76
+ export const settingsFeature: FrameworkFeature = {
77
+ route: 'settings',
78
+ label: S.navLabel,
79
+ icon: 'settings',
80
+ navMode: 'hidden',
81
+ mount: mountSettings,
82
+ };
@@ -0,0 +1,33 @@
1
+ import { pick } from '../../core/language.ts';
2
+
3
+ const zh = {
4
+ general: '通用',
5
+ generalDesc: '控制台语言偏好。',
6
+ language: '界面语言 / Language',
7
+ languageDesc: '仅保存在当前浏览器,刷新页面后生效。',
8
+ reloadTitle: '切换界面语言?',
9
+ reloadBody: '页面将刷新,未保存的编辑会丢失。Bot 将继续运行。',
10
+ appearance: '外观',
11
+ appearanceDesc: '控制台主题、明暗模式与自定义配色。',
12
+
13
+ pageTitle: '设置',
14
+ sectionsAria: '设置分区',
15
+ navLabel: '设置',
16
+ };
17
+
18
+ const en: typeof zh = {
19
+ general: 'General',
20
+ generalDesc: 'Console language preferences.',
21
+ language: '界面语言 / Language',
22
+ languageDesc: 'Saved in this browser; takes effect after reloading.',
23
+ reloadTitle: 'Change interface language?',
24
+ reloadBody: 'The page will reload and unsaved edits will be lost. The bot will keep running.',
25
+ appearance: 'Appearance',
26
+ appearanceDesc: 'Console theme, light/dark mode and custom palettes.',
27
+
28
+ pageTitle: 'Settings',
29
+ sectionsAria: 'Settings sections',
30
+ navLabel: 'Settings',
31
+ };
32
+
33
+ export const S = pick({ zh, en });
@@ -0,0 +1 @@
1
+ export { createStorageView, type StoragePartView, type StorageView, type StorageViewDeps } from './view.ts';