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,311 @@
1
+ /**
2
+ * 对话输入器:表单壳、自适应高度的 textarea、附图钮、发送钮与图片托盘。一个 React 根挂进
3
+ * 控制台面板,`promptInput()` 把它包成 ConsolePromptInput。
4
+ *
5
+ * 图片在进托盘时就归一化成 base64,提交时与文本一并交出。拖入、粘贴、选择三条入口共用
6
+ * addFiles:超出张数整批拒,单张失败只报那一张。
7
+ */
8
+ import * as React from 'react';
9
+ import { flushSync } from 'react-dom';
10
+ import { createRoot } from 'react-dom/client';
11
+ import TextareaAutosize from 'react-textarea-autosize';
12
+ import type {
13
+ ConsoleImageAttachment,
14
+ ConsolePromptInput,
15
+ ConsolePromptInputOptions,
16
+ } from '../../shared/client-panel.ts';
17
+ import { IMAGE_DEFAULTS, isImageFile, normalizeImage } from './images.ts';
18
+ import { S } from './strings.ts';
19
+
20
+ interface ComposerHandle {
21
+ focus(): void;
22
+ setDisabled(disabled: boolean): void;
23
+ setPlaceholder(text: string | null): void;
24
+ }
25
+
26
+ /** 托盘里的一张:归一化结果 + 同一份字节的 data URL 缩略图。 */
27
+ interface Attached {
28
+ id: number;
29
+ image: ConsoleImageAttachment;
30
+ src: string;
31
+ }
32
+
33
+ function sizeCaption(image: ConsoleImageAttachment): string {
34
+ const kb = image.bytes / 1024;
35
+ const size = kb >= 1024 ? `${(kb / 1024).toFixed(1)}MB` : `${Math.round(kb)}KB`;
36
+ return `${size} · ${image.width}×${image.height}`;
37
+ }
38
+
39
+ const FORM_CLASS =
40
+ 'rounded-[var(--radius-composer)] border border-border bg-surface p-2 shadow-[0_2px_14px_color-mix(in_oklab,var(--foreground)_5%,transparent)] transition-[border-color,box-shadow] duration-150 focus-within:border-border-strong focus-within:shadow-[0_4px_20px_color-mix(in_oklab,var(--foreground)_7%,transparent)]';
41
+
42
+ const TEXTAREA_CLASS =
43
+ 'max-h-52 min-h-12 w-full resize-none border-0 bg-transparent px-2.5 py-2 text-[15px] leading-6 text-foreground outline-none placeholder:text-muted-foreground/75';
44
+
45
+ /** 两枚圆形图标钮共用的底:尺寸、焦点环、禁用态。 */
46
+ const ICON_BUTTON =
47
+ 'inline-flex size-8 shrink-0 items-center justify-center rounded-full p-0 outline-none transition-[background-color,color,opacity,transform] duration-150 disabled:pointer-events-none disabled:opacity-40 focus-visible:ring-2 focus-visible:ring-ring/40 [&_svg]:pointer-events-none [&_svg]:size-4';
48
+
49
+ /** 发送钮:实底,是这一栏唯一的主动作。 */
50
+ const SEND_CLASS = `${ICON_BUTTON} self-end bg-foreground text-background hover:bg-foreground/90 active:translate-y-px focus-visible:ring-offset-2 focus-visible:ring-offset-background`;
51
+
52
+ /** 附图钮:无底色,不与发送钮抢份量。 */
53
+ const ATTACH_CLASS = `${ICON_BUTTON} text-muted-foreground hover:bg-surface-hover hover:text-foreground`;
54
+
55
+ const ICON_PROPS = {
56
+ viewBox: '0 0 24 24',
57
+ fill: 'none',
58
+ stroke: 'currentColor',
59
+ strokeLinecap: 'round',
60
+ strokeLinejoin: 'round',
61
+ 'aria-hidden': true,
62
+ } as const;
63
+
64
+ function ArrowUpIcon(): React.ReactElement {
65
+ return (
66
+ <svg {...ICON_PROPS} strokeWidth={2.2}>
67
+ <path d="M12 19.5v-15" />
68
+ <path d="M5.5 11 12 4.5 18.5 11" />
69
+ </svg>
70
+ );
71
+ }
72
+
73
+ function PictureIcon(): React.ReactElement {
74
+ return (
75
+ <svg {...ICON_PROPS} strokeWidth={2}>
76
+ <rect x="3.5" y="4.5" width="17" height="15" rx="2.5" />
77
+ <circle cx="8.5" cy="9" r="1.6" />
78
+ <path d="m4 17.5 5-4.5 3.5 3 3-2.5 4.5 4" />
79
+ </svg>
80
+ );
81
+ }
82
+
83
+ function CrossIcon(): React.ReactElement {
84
+ return (
85
+ <svg {...ICON_PROPS} strokeWidth={2.4}>
86
+ <path d="M7 7l10 10" />
87
+ <path d="M17 7 7 17" />
88
+ </svg>
89
+ );
90
+ }
91
+
92
+ /** 一张缩略图 + 悬停时右上角的移除钮。 */
93
+ function Thumbnail({ item, onRemove }: { item: Attached; onRemove(): void }): React.ReactElement {
94
+ const caption = sizeCaption(item.image);
95
+ return (
96
+ <div
97
+ className="group relative size-20 overflow-hidden rounded-[10px] border border-border bg-surface-hover"
98
+ title={`${item.image.name}\n${caption}`}
99
+ >
100
+ <img src={item.src} alt={item.image.name} className="size-full object-cover" draggable={false} />
101
+ <button
102
+ type="button"
103
+ aria-label={S.removeImage(item.image.name)}
104
+ onClick={onRemove}
105
+ className="absolute top-1 right-1 inline-flex size-5 items-center justify-center rounded-full bg-foreground/80 text-background opacity-0 transition-opacity duration-100 hover:bg-foreground group-hover:opacity-100 focus-visible:opacity-100 [&_svg]:size-3"
106
+ >
107
+ <CrossIcon />
108
+ </button>
109
+ </div>
110
+ );
111
+ }
112
+
113
+ const PromptComposer = React.forwardRef<ComposerHandle, { opts: ConsolePromptInputOptions }>(
114
+ ({ opts }, ref) => {
115
+ const [value, setValue] = React.useState('');
116
+ const [disabled, setDisabled] = React.useState(opts.disabled ?? false);
117
+ const [attached, setAttached] = React.useState<Attached[]>([]);
118
+ /** 还在解码/缩放中的张数。>0 时发送钮暂停,免得把半批图发出去。 */
119
+ const [pending, setPending] = React.useState(0);
120
+ /** 最近一次拒收的理由。下一次成功入托盘或用户改动文本时清掉。 */
121
+ const [note, setNote] = React.useState<string | null>(null);
122
+ const [dragging, setDragging] = React.useState(false);
123
+ /** 盖住 `opts.placeholder` 的那一句;`null` 就是不盖。 */
124
+ const [placeholder, setPlaceholder] = React.useState<string | null>(null);
125
+ const textarea = React.useRef<HTMLTextAreaElement>(null);
126
+ const tools = React.useRef<HTMLSpanElement>(null);
127
+ const picker = React.useRef<HTMLInputElement>(null);
128
+ const nextId = React.useRef(1);
129
+ const imagesOpts = opts.images;
130
+ const maxImages = imagesOpts ? (imagesOpts.max ?? IMAGE_DEFAULTS.max) : 0;
131
+
132
+ React.useLayoutEffect(() => {
133
+ const node = opts.tools;
134
+ const host = tools.current;
135
+ if (!node || !host) return;
136
+ host.appendChild(node);
137
+ return () => node.remove();
138
+ }, [opts.tools]);
139
+
140
+ React.useImperativeHandle(ref, () => ({
141
+ focus: () => textarea.current?.focus(),
142
+ setDisabled,
143
+ setPlaceholder,
144
+ }), []);
145
+
146
+ /** 收一批文件。超出张数的整批拒;单张失败只报那一张,其余照收。 */
147
+ const addFiles = (files: Iterable<File>): void => {
148
+ if (!imagesOpts) return;
149
+ const list = [...files].filter(isImageFile);
150
+ if (list.length === 0) return;
151
+ if (attached.length + pending + list.length > maxImages) {
152
+ setNote(S.tooManyImages(maxImages));
153
+ return;
154
+ }
155
+ setNote(null);
156
+ setPending((n) => n + list.length);
157
+ for (const file of list) {
158
+ // 序号先于归一化分配:大图缩得慢,托盘顺序仍按用户选的顺序,不按完成顺序
159
+ const id = nextId.current++;
160
+ normalizeImage(file, imagesOpts).then(
161
+ (image) => {
162
+ const src = `data:${image.mime};base64,${image.base64}`;
163
+ setAttached((now) => [...now, { id, image, src }].sort((a, b) => a.id - b.id));
164
+ },
165
+ (err: unknown) => setNote(err instanceof Error ? err.message : String(err)),
166
+ ).finally(() => setPending((n) => n - 1));
167
+ }
168
+ };
169
+
170
+ const remove = (id: number): void => setAttached((now) => now.filter((a) => a.id !== id));
171
+
172
+ const canSubmit = !disabled && pending === 0 && (value.trim() !== '' || attached.length > 0);
173
+
174
+ const submit = (): void => {
175
+ if (!canSubmit) return;
176
+ const images = attached.map((a) => a.image);
177
+ if (opts.onSubmit(value.trim(), images) !== false) {
178
+ setValue('');
179
+ setAttached([]);
180
+ setNote(null);
181
+ }
182
+ };
183
+
184
+ return (
185
+ <form
186
+ aria-label={opts.label ?? S.messageInput}
187
+ data-dragging={dragging || undefined}
188
+ className={dragging ? `${FORM_CLASS} border-dashed border-border-strong` : FORM_CLASS}
189
+ onSubmit={(event) => {
190
+ event.preventDefault();
191
+ submit();
192
+ }}
193
+ onDragOver={imagesOpts ? (event) => {
194
+ if (![...event.dataTransfer.types].includes('Files')) return;
195
+ event.preventDefault();
196
+ setDragging(true);
197
+ } : undefined}
198
+ onDragLeave={imagesOpts ? () => setDragging(false) : undefined}
199
+ onDrop={imagesOpts ? (event) => {
200
+ setDragging(false);
201
+ if (event.dataTransfer.files.length === 0) return;
202
+ event.preventDefault();
203
+ addFiles(event.dataTransfer.files);
204
+ } : undefined}
205
+ >
206
+ {attached.length > 0 && (
207
+ <div className="flex flex-wrap gap-2 px-2 pt-1 pb-2">
208
+ {attached.map((a) => (
209
+ <Thumbnail key={a.id} item={a} onRemove={() => remove(a.id)} />
210
+ ))}
211
+ </div>
212
+ )}
213
+ <div className="flex min-w-0 items-start">
214
+ <TextareaAutosize
215
+ ref={textarea}
216
+ minRows={1}
217
+ maxRows={8}
218
+ rows={1}
219
+ value={value}
220
+ disabled={disabled}
221
+ placeholder={placeholder ?? opts.placeholder ?? S.typeMessage}
222
+ className={TEXTAREA_CLASS}
223
+ onChange={(event) => { setValue(event.currentTarget.value); setNote(null); }}
224
+ onKeyDown={(event) => {
225
+ // Enter 发送;Shift+Enter 换行;输入法组合中的 Enter 是选字,不是发送
226
+ if (event.key !== 'Enter' || event.shiftKey || event.nativeEvent.isComposing) return;
227
+ event.preventDefault();
228
+ submit();
229
+ }}
230
+ onPaste={imagesOpts ? (event) => {
231
+ const files = [...event.clipboardData.files].filter(isImageFile);
232
+ if (files.length === 0) return;
233
+ // 粘贴内容包含图片时只处理图片。
234
+ event.preventDefault();
235
+ addFiles(files);
236
+ } : undefined}
237
+ />
238
+ </div>
239
+ <div className="flex min-h-9 items-center justify-between gap-2 px-1">
240
+ <span className="truncate px-2 text-xs text-muted-foreground">
241
+ {note ? <span className="text-danger">{note}</span>
242
+ : pending > 0 ? S.processingImages(pending)
243
+ : opts.hint ?? S.enterToSend}
244
+ </span>
245
+ <div className="flex items-center gap-1">
246
+ {imagesOpts && (
247
+ <>
248
+ <input
249
+ ref={picker}
250
+ type="file"
251
+ accept="image/*"
252
+ multiple
253
+ hidden
254
+ onChange={(event) => {
255
+ if (event.currentTarget.files) addFiles(event.currentTarget.files);
256
+ // 同一个文件再选一次也要触发 change
257
+ event.currentTarget.value = '';
258
+ }}
259
+ />
260
+ <button
261
+ type="button"
262
+ aria-label={S.addImage}
263
+ title={S.addImageHint}
264
+ className={ATTACH_CLASS}
265
+ disabled={disabled || attached.length >= maxImages}
266
+ onClick={() => picker.current?.click()}
267
+ >
268
+ <PictureIcon />
269
+ </button>
270
+ </>
271
+ )}
272
+ <span ref={tools} className="flex items-center" />
273
+ <button
274
+ type="submit"
275
+ aria-label={S.sendMessage}
276
+ title={S.sendMessage}
277
+ className={SEND_CLASS}
278
+ disabled={!canSubmit}
279
+ >
280
+ <ArrowUpIcon />
281
+ </button>
282
+ </div>
283
+ </div>
284
+ </form>
285
+ );
286
+ },
287
+ );
288
+ PromptComposer.displayName = 'PromptComposer';
289
+
290
+ export function promptInput(
291
+ doc: Document,
292
+ signal: AbortSignal,
293
+ opts: ConsolePromptInputOptions,
294
+ ): ConsolePromptInput {
295
+ const el = doc.createElement('div');
296
+ el.className = 'prompt-input-host';
297
+ const handle = React.createRef<ComposerHandle>();
298
+ const root = createRoot(el, { identifierPrefix: 'cortico-' });
299
+ flushSync(() => root.render(<PromptComposer ref={handle} opts={opts} />));
300
+
301
+ const dispose = (): void => root.unmount();
302
+ if (signal.aborted) dispose();
303
+ else signal.addEventListener('abort', dispose, { once: true });
304
+
305
+ return {
306
+ el,
307
+ focus: () => handle.current?.focus(),
308
+ setDisabled: (disabled) => handle.current?.setDisabled(disabled),
309
+ setPlaceholder: (text) => handle.current?.setPlaceholder(text),
310
+ };
311
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * 档案卡与几个行内小件。
3
+ *
4
+ * 视觉全部复用 `src/web/public/styles.css` 里的共享 class。`sheetbody` 是卡片内部
5
+ * 布局的稳定挂点,供等高卡片把动作区压到下沿。
6
+ */
7
+
8
+ import type { ConsoleBadge } from '../../shared/console-protocol.ts';
9
+ import type {
10
+ ConsoleChipTone,
11
+ ConsoleMemo,
12
+ ConsoleSheet,
13
+ ConsoleSheetOptions,
14
+ } from '../../shared/client-panel.ts';
15
+ import { h } from './dom.ts';
16
+
17
+ function heading(doc: Document, title: string, en?: string): HTMLHeadingElement {
18
+ const head = h(doc, 'h3', null, title);
19
+ if (en) head.appendChild(h(doc, 'span', 'en', en));
20
+ return head;
21
+ }
22
+
23
+ /** 有 desc 时将说明行作为 body 的首个子节点,以匹配折叠卡选择器。 */
24
+ function descLine(doc: Document, body: HTMLElement, text?: string): HTMLElement | null {
25
+ if (!text) return null;
26
+ const el = h(doc, 'div', 'sh-desc', text);
27
+ body.appendChild(el);
28
+ return el;
29
+ }
30
+
31
+ /** 档案卡。控制台的基本视觉单元。 */
32
+ export function sheet(doc: Document, opts: ConsoleSheetOptions): ConsoleSheet {
33
+ const el = h(doc, 'div', 'sheet tabbed');
34
+ const note = h(doc, 'span', 'foldnote hidden');
35
+ const body = h(doc, 'div', 'sheetbody');
36
+ const head = heading(doc, opts.title, opts.en);
37
+ // note 照样进 DOM(带 .hidden),这样 `sheet()` 与 `foldSheet()` 的节点关系一致:
38
+ // 扩展往 note 里写字在两种卡上都是合法操作,只是不折叠的卡看不见它。
39
+ head.appendChild(note);
40
+ el.append(head, body);
41
+ return { el, body, note, desc: descLine(doc, body, opts.desc) };
42
+ }
43
+
44
+ /** 折叠状态通过面板 memo 存取,键由面板命名空间隔离。 */
45
+ export function foldSheet(
46
+ doc: Document,
47
+ memo: ConsoleMemo,
48
+ signal: AbortSignal,
49
+ id: string,
50
+ opts: ConsoleSheetOptions & { defaultOpen?: boolean },
51
+ ): ConsoleSheet {
52
+ const el = h(doc, 'details', 'sheet tabbed fold');
53
+ el.open = memo.get<boolean>(foldKey(id), !!opts.defaultOpen);
54
+ const sum = h(doc, 'summary');
55
+ const note = h(doc, 'span', 'foldnote');
56
+ sum.append(heading(doc, opts.title, opts.en), note);
57
+ const body = h(doc, 'div', 'foldbody');
58
+ el.append(sum, body);
59
+ // 卡片本身随 root 一起被丢弃,监听本不至于泄漏;仍挂 signal 是因为 memo 可能比
60
+ // 面板活得久——面板都卸了还回写折叠状态,那是拿旧值盖新值。
61
+ el.addEventListener('toggle', () => memo.set(foldKey(id), el.open), { signal });
62
+ return { el, body, note, desc: descLine(doc, body, opts.desc) };
63
+ }
64
+
65
+ /** memo 里的键。加前缀是为了不跟扩展自己的键撞。 */
66
+ function foldKey(id: string): string {
67
+ return 'fold:' + id;
68
+ }
69
+
70
+ /** 一行按钮/状态条。 */
71
+ export function rowbar(doc: Document): HTMLDivElement {
72
+ return h(doc, 'div', 'rowbar');
73
+ }
74
+
75
+ /** 卡片内一个独立内容组的标题。 */
76
+ export function section(doc: Document, title: string, description?: string): HTMLDivElement {
77
+ const el = h(doc, 'div', 'sectionhead');
78
+ el.appendChild(h(doc, 'h4', null, title));
79
+ if (description) el.appendChild(h(doc, 'div', 'sectiondesc', description));
80
+ return el;
81
+ }
82
+
83
+ /** 提交、保存与破坏性操作所在的卡片脚栏。 */
84
+ export function actions(doc: Document): HTMLDivElement {
85
+ return h(doc, 'div', 'rowbar actionbar');
86
+ }
87
+
88
+ export function button(
89
+ doc: Document,
90
+ signal: AbortSignal,
91
+ label: string,
92
+ opts?: {
93
+ variant?: 'plain' | 'primary' | 'danger';
94
+ size?: 'sm' | 'md';
95
+ onClick?: (ev: MouseEvent) => void;
96
+ },
97
+ ): HTMLButtonElement {
98
+ // class 顺序与既有前端一致:`btn sm primary`
99
+ let cls = 'btn';
100
+ if (opts?.size === 'sm') cls += ' sm';
101
+ if (opts?.variant === 'primary' || opts?.variant === 'danger') cls += ' ' + opts.variant;
102
+ const el = h(doc, 'button', cls, label);
103
+ el.type = 'button';
104
+ if (opts?.onClick) el.addEventListener('click', opts.onClick, { signal });
105
+ return el;
106
+ }
107
+
108
+ /**
109
+ * 药丸。三态各有配色:`on` 用 `--ok`,`off` 用 `--ink-dim`(比中性态更淡的"关闭/静默"),
110
+ * `plain` 与不给 tone 一样落中性 `.pill`。
111
+ */
112
+ export function pill(doc: Document, text: string, tone?: ConsoleBadge['tone']): HTMLSpanElement {
113
+ const cls = tone === 'on' ? 'pill on' : tone === 'off' ? 'pill off' : 'pill';
114
+ return h(doc, 'span', cls, text);
115
+ }
116
+
117
+ /** 筹码。等宽小字的读数标签,`warn` / `accent` 对应既有的 `warnc` / `dreamc` 两个配色。 */
118
+ export function chip(doc: Document, text: string, tone?: ConsoleChipTone): HTMLSpanElement {
119
+ const cls = tone === 'warn' ? 'chip warnc' : tone === 'accent' ? 'chip dreamc' : 'chip';
120
+ return h(doc, 'span', cls, text);
121
+ }
122
+
123
+ /** 一行消息(保存结果、错误)。`bad` 走警示配色。 */
124
+ export function msgline(doc: Document, text?: string, bad?: boolean): HTMLDivElement {
125
+ return h(doc, 'div', bad ? 'msgline bad' : 'msgline', text ?? '');
126
+ }
127
+
128
+ /** 空态。居中灰字,占一片版面——"这里本该有东西,但现在没有"。 */
129
+ export function placeholder(doc: Document, text: string): HTMLDivElement {
130
+ return h(doc, 'div', 'placeholder', text);
131
+ }
@@ -0,0 +1,71 @@
1
+ import { pick } from '../core/language.ts';
2
+
3
+ const zh = {
4
+ copy: '复制',
5
+ copyNothing: '取不到要复制的内容',
6
+ copied: '已复制',
7
+ copyFailed: '复制失败,已展开文本供手动选取',
8
+ imageReadFailed: '读取图片失败',
9
+ canvasUnsupported: '浏览器不支持 canvas,无法缩放图片',
10
+ imageEncodeFailed: '图片重编码失败',
11
+ fileFallback: '文件',
12
+ imageFallback: '图片',
13
+ notImage: (name: string) => `${name} 不是支持的图片格式(JPEG / PNG / WebP / GIF)`,
14
+ undecodable: (name: string) => `${name} 无法解码`,
15
+ stillTooLarge: (name: string, mb: number) => `${name} 缩放后仍超过 ${mb}MB`,
16
+ lamp: {
17
+ online: '正常',
18
+ loading: '启动中',
19
+ error: '故障',
20
+ offline: '未启用',
21
+ },
22
+ cancel: '取消',
23
+ proceedAnyway: '仍要继续',
24
+ confirm: '确认',
25
+ close: '✕ 关闭 (Esc)',
26
+ tooManyImages: (max: number) => `一条消息最多 ${max} 张图`,
27
+ messageInput: '消息输入',
28
+ typeMessage: '输入消息…',
29
+ processingImages: (count: number) => `处理 ${count} 张图片…`,
30
+ enterToSend: 'Enter 发送 · Shift+Enter 换行',
31
+ sendMessage: '发送消息',
32
+ addImage: '添加图片',
33
+ addImageHint: '添加图片(也可粘贴或拖入)',
34
+ removeImage: (name: string) => `移除 ${name}`,
35
+ };
36
+
37
+ const en: typeof zh = {
38
+ copy: 'Copy',
39
+ copyNothing: 'Nothing to copy',
40
+ copied: 'Copied',
41
+ copyFailed: 'Copy failed; text expanded for manual selection',
42
+ imageReadFailed: 'Failed to read image',
43
+ canvasUnsupported: 'This browser does not support canvas; cannot resize image',
44
+ imageEncodeFailed: 'Image re-encoding failed',
45
+ fileFallback: 'File',
46
+ imageFallback: 'Image',
47
+ notImage: (name: string) => `${name} is not a supported image format (JPEG / PNG / WebP / GIF)`,
48
+ undecodable: (name: string) => `${name} could not be decoded`,
49
+ stillTooLarge: (name: string, mb: number) => `${name} still exceeds ${mb}MB after resizing`,
50
+ lamp: {
51
+ online: 'online',
52
+ loading: 'starting',
53
+ error: 'error',
54
+ offline: 'disabled',
55
+ },
56
+ cancel: 'Cancel',
57
+ proceedAnyway: 'Proceed anyway',
58
+ confirm: 'Confirm',
59
+ close: '✕ Close (Esc)',
60
+ tooManyImages: (max: number) => `At most ${max} images per message`,
61
+ messageInput: 'Message input',
62
+ typeMessage: 'Type a message…',
63
+ processingImages: (count: number) => `Processing ${count} image(s)…`,
64
+ enterToSend: 'Enter to send · Shift+Enter for newline',
65
+ sendMessage: 'Send message',
66
+ addImage: 'Add image',
67
+ addImageHint: 'Add image (paste or drop also works)',
68
+ removeImage: (name: string) => `Remove ${name}`,
69
+ };
70
+
71
+ export const S = pick({ zh, en });