@antelopejs/dms-ai 0.1.4 → 0.1.6

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 (250) hide show
  1. package/README.md +55 -29
  2. package/dist/channels/bridge.d.ts +2 -8
  3. package/dist/channels/bridge.js +26 -42
  4. package/dist/channels/bridge.js.map +1 -1
  5. package/dist/channels/channel-service.d.ts +2 -2
  6. package/dist/channels/channel-service.js +12 -31
  7. package/dist/channels/channel-service.js.map +1 -1
  8. package/dist/channels/registry.d.ts +2 -2
  9. package/dist/channels/registry.js +7 -7
  10. package/dist/channels/registry.js.map +1 -1
  11. package/dist/config.d.ts +1 -0
  12. package/dist/config.js +8 -1
  13. package/dist/config.js.map +1 -1
  14. package/dist/constants/channels.d.ts +5 -10
  15. package/dist/constants/channels.js +6 -10
  16. package/dist/constants/channels.js.map +1 -1
  17. package/dist/constants/sidecar.d.ts +5 -1
  18. package/dist/constants/sidecar.js +6 -2
  19. package/dist/constants/sidecar.js.map +1 -1
  20. package/dist/index.js +8 -0
  21. package/dist/index.js.map +1 -1
  22. package/dist/lifecycle/build-id.d.ts +1 -0
  23. package/dist/lifecycle/build-id.js +32 -0
  24. package/dist/lifecycle/build-id.js.map +1 -0
  25. package/dist/lifecycle/sidecar-owner.d.ts +13 -0
  26. package/dist/lifecycle/sidecar-owner.js +89 -0
  27. package/dist/lifecycle/sidecar-owner.js.map +1 -0
  28. package/dist/lifecycle/sidecar-socket.d.ts +1 -1
  29. package/dist/lifecycle/sidecar-socket.js +2 -2
  30. package/dist/lifecycle/sidecar-socket.js.map +1 -1
  31. package/dist/lifecycle/spawn-sidecar.d.ts +0 -1
  32. package/dist/lifecycle/spawn-sidecar.js +51 -159
  33. package/dist/lifecycle/spawn-sidecar.js.map +1 -1
  34. package/dist/lifecycle/terminate-process.d.ts +1 -0
  35. package/dist/lifecycle/terminate-process.js +34 -0
  36. package/dist/lifecycle/terminate-process.js.map +1 -0
  37. package/dist/routes/channels.d.ts +3 -3
  38. package/dist/routes/channels.js +11 -12
  39. package/dist/routes/channels.js.map +1 -1
  40. package/dist/routes/index.d.ts +0 -1
  41. package/dist/routes/index.js +0 -1
  42. package/dist/routes/index.js.map +1 -1
  43. package/frontend-vue/.prettierignore +3 -0
  44. package/frontend-vue/.prettierrc +10 -0
  45. package/frontend-vue/app/chat/components/ChatView.vue +582 -0
  46. package/frontend-vue/app/chat/components/ComposerInput.vue +742 -0
  47. package/frontend-vue/app/chat/components/ConnectionBanner.vue +105 -0
  48. package/frontend-vue/app/chat/components/ConversationDrawer.vue +266 -0
  49. package/frontend-vue/app/chat/components/MarkdownContent.vue +196 -0
  50. package/frontend-vue/app/chat/components/MessageList.vue +474 -0
  51. package/frontend-vue/app/chat/components/PermissionRequests.vue +323 -0
  52. package/frontend-vue/app/chat/components/QuestionPrompt.vue +275 -0
  53. package/frontend-vue/app/chat/components/QueuedMessages.vue +109 -0
  54. package/frontend-vue/app/chat/components/TodoList.vue +291 -0
  55. package/frontend-vue/app/chat/components/ToolCallEntry.vue +218 -0
  56. package/frontend-vue/app/chat/components/ToolCluster.vue +282 -0
  57. package/frontend-vue/app/chat/composables/useChatChannel.ts +116 -0
  58. package/frontend-vue/app/chat/composables/useConversation.ts +838 -0
  59. package/frontend-vue/app/chat/composables/useConversationId.ts +34 -0
  60. package/frontend-vue/app/chat/composables/useConversationList.ts +56 -0
  61. package/frontend-vue/app/chat/composables/usePermissionQueue.ts +59 -0
  62. package/frontend-vue/app/chat/composables/useQuestionQueue.ts +50 -0
  63. package/frontend-vue/app/chat/composables/useRunClock.ts +28 -0
  64. package/frontend-vue/app/chat/composables/useSettings.ts +67 -0
  65. package/frontend-vue/app/chat/constants/attachments.ts +16 -0
  66. package/frontend-vue/app/chat/constants/connection-banner.ts +7 -0
  67. package/frontend-vue/app/chat/constants/conversation-drawer.ts +13 -0
  68. package/frontend-vue/app/chat/constants/conversation.ts +75 -0
  69. package/frontend-vue/app/chat/constants/permissions.ts +50 -0
  70. package/frontend-vue/app/chat/constants/protocol.ts +48 -0
  71. package/frontend-vue/app/chat/constants/run-status.ts +50 -0
  72. package/frontend-vue/app/chat/constants/settings.ts +60 -0
  73. package/frontend-vue/app/chat/types/conversation.ts +87 -0
  74. package/frontend-vue/app/chat/types/permission.ts +7 -0
  75. package/frontend-vue/app/chat/types/question.ts +17 -0
  76. package/frontend-vue/app/chat/types/settings.ts +27 -0
  77. package/frontend-vue/app/chat/utils/attachments.ts +62 -0
  78. package/frontend-vue/app/chat/utils/format.ts +14 -0
  79. package/frontend-vue/app/chat/utils/ids.ts +34 -0
  80. package/frontend-vue/app/chat/utils/markdown.ts +188 -0
  81. package/frontend-vue/app/chat/utils/run-status.ts +90 -0
  82. package/frontend-vue/app/chat/utils/todos.ts +64 -0
  83. package/frontend-vue/app/chat/utils/tool-summary.ts +53 -0
  84. package/frontend-vue/app/components/ActivityView.vue +34 -34
  85. package/frontend-vue/app/components/ChatPanel.vue +276 -0
  86. package/frontend-vue/app/components/SettingsView.vue +120 -112
  87. package/frontend-vue/app/components/SkillsView.vue +55 -53
  88. package/frontend-vue/app/plugins/ai.client.ts +145 -129
  89. package/frontend-vue/app/runtime/assistant-session.ts +21 -0
  90. package/frontend-vue/app/runtime/backoff.ts +11 -14
  91. package/frontend-vue/app/runtime/channel-client.ts +130 -141
  92. package/frontend-vue/app/runtime/chat-transport.ts +25 -55
  93. package/frontend-vue/app/runtime/constants.ts +69 -59
  94. package/frontend-vue/app/runtime/current-page.ts +49 -15
  95. package/frontend-vue/app/runtime/event-stream.ts +35 -35
  96. package/frontend-vue/app/runtime/frame-router.ts +30 -0
  97. package/frontend-vue/app/runtime/header-action.ts +18 -18
  98. package/frontend-vue/app/runtime/host-commands.ts +57 -56
  99. package/frontend-vue/app/runtime/listeners.ts +31 -0
  100. package/frontend-vue/app/runtime/navigation-complete.ts +7 -7
  101. package/frontend-vue/app/runtime/overlay-prefs.ts +36 -36
  102. package/frontend-vue/app/runtime/panel-state.ts +88 -0
  103. package/frontend-vue/app/runtime/session-gate.ts +8 -8
  104. package/frontend-vue/app/runtime/shortcuts.ts +12 -12
  105. package/frontend-vue/app/runtime/sidecar-status.ts +61 -61
  106. package/frontend-vue/app/runtime/typed-message.ts +11 -0
  107. package/frontend-vue/dms.frontend.ts +12 -12
  108. package/frontend-vue/eslint.config.mjs +13 -13
  109. package/frontend-vue/package.json +11 -4
  110. package/frontend-vue/pnpm-lock.yaml +298 -96
  111. package/frontend-vue/tests/backoff.test.ts +26 -30
  112. package/frontend-vue/tests/channel-client.test.ts +187 -188
  113. package/frontend-vue/tests/chat/markdown.test.ts +271 -0
  114. package/frontend-vue/tests/chat/useChatChannel.test.ts +124 -0
  115. package/frontend-vue/tests/chat/useConversation.test.ts +283 -0
  116. package/frontend-vue/tests/chat-panel.test.ts +354 -0
  117. package/frontend-vue/tests/chat-transport.test.ts +84 -57
  118. package/frontend-vue/tests/host-commands.test.ts +126 -87
  119. package/frontend-vue/tests/navigation.test.ts +65 -34
  120. package/frontend-vue/tests/overlay-prefs.test.ts +49 -49
  121. package/frontend-vue/tests/panel-state.test.ts +58 -0
  122. package/frontend-vue/tests/session-gate.test.ts +33 -33
  123. package/frontend-vue/tests/sidecar-guest.test.ts +46 -46
  124. package/frontend-vue/tests/sidecar-status.test.ts +29 -29
  125. package/frontend-vue/tsconfig.chat.json +16 -0
  126. package/frontend-vue/vitest.config.ts +6 -4
  127. package/package.json +6 -5
  128. package/sidecar/dist/agent/effective-mode.d.ts +2 -2
  129. package/sidecar/dist/agent/effective-mode.js +1 -1
  130. package/sidecar/dist/agent/effective-mode.js.map +1 -1
  131. package/sidecar/dist/agent/host-context.js +7 -9
  132. package/sidecar/dist/agent/host-context.js.map +1 -1
  133. package/sidecar/dist/agent/permission-bus.d.ts +1 -1
  134. package/sidecar/dist/agent/permission-bus.js +2 -2
  135. package/sidecar/dist/agent/permission-bus.js.map +1 -1
  136. package/sidecar/dist/agent/provider.d.ts +2 -2
  137. package/sidecar/dist/agent/question-bus.d.ts +1 -1
  138. package/sidecar/dist/agent/question-bus.js +2 -2
  139. package/sidecar/dist/agent/question-bus.js.map +1 -1
  140. package/sidecar/dist/agent/runner.d.ts +2 -2
  141. package/sidecar/dist/agent/runner.js +24 -8
  142. package/sidecar/dist/agent/runner.js.map +1 -1
  143. package/sidecar/dist/agent/session.d.ts +2 -2
  144. package/sidecar/dist/agent/session.js +49 -9
  145. package/sidecar/dist/agent/session.js.map +1 -1
  146. package/sidecar/dist/agent/switching-runner.js +10 -5
  147. package/sidecar/dist/agent/switching-runner.js.map +1 -1
  148. package/sidecar/dist/agent/turn-end-reasons.d.ts +2 -0
  149. package/sidecar/dist/agent/turn-end-reasons.js +6 -1
  150. package/sidecar/dist/agent/turn-end-reasons.js.map +1 -1
  151. package/sidecar/dist/constants/agent.d.ts +4 -1
  152. package/sidecar/dist/constants/agent.js +4 -1
  153. package/sidecar/dist/constants/agent.js.map +1 -1
  154. package/sidecar/dist/constants/cli.d.ts +0 -1
  155. package/sidecar/dist/constants/cli.js +0 -1
  156. package/sidecar/dist/constants/cli.js.map +1 -1
  157. package/sidecar/dist/constants/codex.d.ts +5 -2
  158. package/sidecar/dist/constants/codex.js +5 -2
  159. package/sidecar/dist/constants/codex.js.map +1 -1
  160. package/sidecar/dist/constants/http.d.ts +0 -9
  161. package/sidecar/dist/constants/http.js +0 -19
  162. package/sidecar/dist/constants/http.js.map +1 -1
  163. package/sidecar/dist/constants/mcp.d.ts +1 -1
  164. package/sidecar/dist/constants/mcp.js +1 -1
  165. package/sidecar/dist/constants/mcp.js.map +1 -1
  166. package/sidecar/dist/constants/paths.d.ts +0 -1
  167. package/sidecar/dist/constants/paths.js +0 -1
  168. package/sidecar/dist/constants/paths.js.map +1 -1
  169. package/sidecar/dist/constants/ws.d.ts +3 -4
  170. package/sidecar/dist/constants/ws.js +3 -4
  171. package/sidecar/dist/constants/ws.js.map +1 -1
  172. package/sidecar/dist/index.d.ts +4 -1
  173. package/sidecar/dist/index.js +13 -13
  174. package/sidecar/dist/index.js.map +1 -1
  175. package/sidecar/dist/mcp/tools/find-pages-using.js +5 -10
  176. package/sidecar/dist/mcp/tools/find-pages-using.js.map +1 -1
  177. package/sidecar/dist/mcp/types.d.ts +1 -1
  178. package/sidecar/dist/pages/page-filepath.d.ts +4 -0
  179. package/sidecar/dist/pages/page-filepath.js +28 -0
  180. package/sidecar/dist/pages/page-filepath.js.map +1 -0
  181. package/sidecar/dist/prompts/agent.d.ts +1 -1
  182. package/sidecar/dist/prompts/agent.js +1 -1
  183. package/sidecar/dist/prompts/agent.js.map +1 -1
  184. package/sidecar/dist/protocol/events.js +2 -2
  185. package/sidecar/dist/protocol/events.js.map +1 -1
  186. package/sidecar/dist/protocol/messages.d.ts +4 -15
  187. package/sidecar/dist/protocol/messages.js +4 -11
  188. package/sidecar/dist/protocol/messages.js.map +1 -1
  189. package/sidecar/dist/providers/claude/config.d.ts +2 -2
  190. package/sidecar/dist/providers/claude/config.js +2 -2
  191. package/sidecar/dist/providers/claude/config.js.map +1 -1
  192. package/sidecar/dist/providers/claude/provider.js +1 -1
  193. package/sidecar/dist/providers/claude/provider.js.map +1 -1
  194. package/sidecar/dist/providers/codex/config.d.ts +3 -2
  195. package/sidecar/dist/providers/codex/config.js +3 -3
  196. package/sidecar/dist/providers/codex/config.js.map +1 -1
  197. package/sidecar/dist/providers/codex/process.js +107 -74
  198. package/sidecar/dist/providers/codex/process.js.map +1 -1
  199. package/sidecar/dist/providers/codex/provider.js +68 -31
  200. package/sidecar/dist/providers/codex/provider.js.map +1 -1
  201. package/sidecar/dist/providers/codex/skills.d.ts +1 -0
  202. package/sidecar/dist/providers/codex/skills.js +4 -1
  203. package/sidecar/dist/providers/codex/skills.js.map +1 -1
  204. package/sidecar/dist/providers/codex/stop.d.ts +4 -0
  205. package/sidecar/dist/providers/codex/stop.js +71 -0
  206. package/sidecar/dist/providers/codex/stop.js.map +1 -0
  207. package/sidecar/dist/server/{iframe-socket-registry.d.ts → chat-socket-registry.d.ts} +3 -2
  208. package/sidecar/dist/server/{iframe-socket-registry.js → chat-socket-registry.js} +4 -3
  209. package/sidecar/dist/server/chat-socket-registry.js.map +1 -0
  210. package/sidecar/dist/server/client-auth.js +2 -14
  211. package/sidecar/dist/server/client-auth.js.map +1 -1
  212. package/sidecar/dist/server/host-command-router.d.ts +5 -0
  213. package/sidecar/dist/server/host-command-router.js +4 -0
  214. package/sidecar/dist/server/host-command-router.js.map +1 -0
  215. package/sidecar/dist/server/host-socket-registry.d.ts +1 -1
  216. package/sidecar/dist/server/host-socket-registry.js +16 -13
  217. package/sidecar/dist/server/host-socket-registry.js.map +1 -1
  218. package/sidecar/dist/server/http.d.ts +0 -1
  219. package/sidecar/dist/server/http.js +1 -44
  220. package/sidecar/dist/server/http.js.map +1 -1
  221. package/sidecar/dist/server/routing.d.ts +5 -6
  222. package/sidecar/dist/server/routing.js +18 -28
  223. package/sidecar/dist/server/routing.js.map +1 -1
  224. package/sidecar/dist/server/ws.d.ts +2 -2
  225. package/sidecar/dist/server/ws.js +41 -39
  226. package/sidecar/dist/server/ws.js.map +1 -1
  227. package/sidecar/dist/state/host-state.d.ts +10 -2
  228. package/sidecar/dist/state/host-state.js +13 -3
  229. package/sidecar/dist/state/host-state.js.map +1 -1
  230. package/sidecar/dist/state/settings-store.js +2 -2
  231. package/sidecar/dist/state/settings-store.js.map +1 -1
  232. package/sidecar/dist/state/settings-types.d.ts +3 -3
  233. package/sidecar/dist/state/settings-types.js +1 -1
  234. package/sidecar/dist/state/settings-types.js.map +1 -1
  235. package/sidecar/package.json +3 -5
  236. package/dist/chatbox/passthrough.d.ts +0 -3
  237. package/dist/chatbox/passthrough.js +0 -53
  238. package/dist/chatbox/passthrough.js.map +0 -1
  239. package/dist/constants/chatbox.d.ts +0 -10
  240. package/dist/constants/chatbox.js +0 -35
  241. package/dist/constants/chatbox.js.map +0 -1
  242. package/dist/routes/chatbox.d.ts +0 -7
  243. package/dist/routes/chatbox.js +0 -47
  244. package/dist/routes/chatbox.js.map +0 -1
  245. package/frontend-vue/app/runtime/overlay.ts +0 -400
  246. package/frontend-vue/app/runtime/theme-bridge.ts +0 -189
  247. package/sidecar/chatbox/dist/assets/index-C8u7mkHn.css +0 -1
  248. package/sidecar/chatbox/dist/assets/index-tdZvY7ee.js +0 -52
  249. package/sidecar/chatbox/dist/index.html +0 -13
  250. package/sidecar/dist/server/iframe-socket-registry.js.map +0 -1
@@ -9,12 +9,10 @@
9
9
  "main": "dist/index.js",
10
10
  "scripts": {
11
11
  "build": "rimraf dist && tsc",
12
- "build:chatbox": "cd chatbox && pnpm install && pnpm build",
13
- "build:all": "pnpm build && pnpm build:chatbox",
14
12
  "dev": "tsc -w",
15
- "typecheck": "tsc --noEmit && tsc -p tsconfig.test.json && cd chatbox && pnpm typecheck",
16
- "lint": "oxlint && oxfmt --check . && cd chatbox && pnpm lint",
17
- "lint:fix": "oxlint --fix --max-warnings=1000000 && oxfmt . && cd chatbox && pnpm lint:fix",
13
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.test.json",
14
+ "lint": "oxlint && oxfmt --check .",
15
+ "lint:fix": "oxlint --fix --max-warnings=1000000 && oxfmt .",
18
16
  "test": "vitest run --passWithNoTests",
19
17
  "generate:codex-types": "node scripts/generate-codex-types.mjs",
20
18
  "test:e2e": "echo 'no e2e yet' && exit 0",
@@ -1,3 +0,0 @@
1
- import { HTTPResult, type RequestContext } from "@antelopejs/interface-api";
2
- export type ChatboxLoader = (path: string) => Promise<Response>;
3
- export declare function relayChatboxFile(ctx: RequestContext, path: string, load?: ChatboxLoader): Promise<HTTPResult | undefined>;
@@ -1,53 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.relayChatboxFile = relayChatboxFile;
4
- const node_stream_1 = require("node:stream");
5
- const promises_1 = require("node:stream/promises");
6
- const interface_api_1 = require("@antelopejs/interface-api");
7
- const chatbox_1 = require("../constants/chatbox");
8
- const sidecar_1 = require("../constants/sidecar");
9
- const sidecar_unavailable_error_1 = require("../lifecycle/sidecar-unavailable-error");
10
- const spawn_sidecar_1 = require("../lifecycle/spawn-sidecar");
11
- async function loadFromSidecar(path) {
12
- await (0, spawn_sidecar_1.ensureSidecarRunning)();
13
- const port = (0, spawn_sidecar_1.getSidecarPort)();
14
- if (port === null)
15
- throw new sidecar_unavailable_error_1.SidecarUnavailableError();
16
- return fetch(`http://${sidecar_1.SIDECAR_LOOPBACK_HOST}:${port}${path}`);
17
- }
18
- async function tryLoad(load, path) {
19
- try {
20
- return await load(path);
21
- }
22
- catch {
23
- return null;
24
- }
25
- }
26
- function applyHeaders(ctx, upstream) {
27
- for (const [name, value] of Object.entries(chatbox_1.CHATBOX_RESPONSE_HEADERS)) {
28
- ctx.response.addHeader(name, value);
29
- }
30
- for (const name of chatbox_1.CHATBOX_RELAYED_HEADERS) {
31
- const value = upstream.headers.get(name);
32
- if (value !== null)
33
- ctx.response.addHeader(name, value);
34
- }
35
- }
36
- async function relayChatboxFile(ctx, path, load = loadFromSidecar) {
37
- const upstream = await tryLoad(load, path);
38
- if (upstream === null) {
39
- return new interface_api_1.HTTPResult(sidecar_1.SIDECAR_UNAVAILABLE_STATUS, sidecar_1.SIDECAR_UNAVAILABLE_BODY);
40
- }
41
- applyHeaders(ctx, upstream);
42
- const contentType = upstream.headers.get(chatbox_1.CHATBOX_CONTENT_TYPE_HEADER) ??
43
- chatbox_1.CHATBOX_DEFAULT_CONTENT_TYPE;
44
- const sink = ctx.response.getWriteStream(contentType, upstream.status);
45
- if (upstream.body === null) {
46
- sink.end();
47
- return undefined;
48
- }
49
- const body = node_stream_1.Readable.fromWeb(upstream.body);
50
- void (0, promises_1.pipeline)(body, sink).catch(() => sink.destroy());
51
- return undefined;
52
- }
53
- //# sourceMappingURL=passthrough.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"passthrough.js","sourceRoot":"","sources":["../../src/chatbox/passthrough.ts"],"names":[],"mappings":";;AAyDA,4CAqBC;AA9ED,6CAAuC;AACvC,mDAAgD;AAEhD,6DAA4E;AAC5E,kDAK8B;AAC9B,kDAI8B;AAC9B,sFAAiF;AACjF,8DAGoC;AAKpC,KAAK,UAAU,eAAe,CAAC,IAAY;IACzC,MAAM,IAAA,oCAAoB,GAAE,CAAC;IAC7B,MAAM,IAAI,GAAG,IAAA,8BAAc,GAAE,CAAC;IAC9B,IAAI,IAAI,KAAK,IAAI;QAAE,MAAM,IAAI,mDAAuB,EAAE,CAAC;IACvD,OAAO,KAAK,CAAC,UAAU,+BAAqB,IAAI,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC;AACjE,CAAC;AAED,KAAK,UAAU,OAAO,CACpB,IAAmB,EACnB,IAAY;IAEZ,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,GAAmB,EAAE,QAAkB;IAC3D,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,kCAAwB,CAAC,EAAE,CAAC;QACrE,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACtC,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,iCAAuB,EAAE,CAAC;QAC3C,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,KAAK,KAAK,IAAI;YAAE,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC1D,CAAC;AACH,CAAC;AAOM,KAAK,UAAU,gBAAgB,CACpC,GAAmB,EACnB,IAAY,EACZ,OAAsB,eAAe;IAErC,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC3C,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,OAAO,IAAI,0BAAU,CAAC,oCAA0B,EAAE,kCAAwB,CAAC,CAAC;IAC9E,CAAC;IACD,YAAY,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC5B,MAAM,WAAW,GACf,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,qCAA2B,CAAC;QACjD,sCAA4B,CAAC;IAC/B,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACvE,IAAI,QAAQ,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QAC3B,IAAI,CAAC,GAAG,EAAE,CAAC;QACX,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,IAAI,GAAG,sBAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAqC,CAAC,CAAC;IAC9E,KAAK,IAAA,mBAAQ,EAAC,IAAI,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IACtD,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -1,10 +0,0 @@
1
- export declare const CHATBOX_ROUTE_PREFIX = "/api/ai/chatbox";
2
- export declare const CHATBOX_INDEX_ROUTE = "/";
3
- export declare const CHATBOX_ASSET_ROUTE = "::path";
4
- export declare const CHATBOX_PATH_PARAM = "path";
5
- export declare const CHATBOX_INDEX_PATH = "/";
6
- export declare const CHATBOX_DEFAULT_CONTENT_TYPE = "application/octet-stream";
7
- export declare const CHATBOX_CONTENT_TYPE_HEADER = "content-type";
8
- export declare const CHATBOX_RELAYED_HEADERS: readonly ["content-length"];
9
- export declare const CHATBOX_CONTENT_SECURITY_POLICY: string;
10
- export declare const CHATBOX_RESPONSE_HEADERS: Readonly<Record<string, string>>;
@@ -1,35 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CHATBOX_RESPONSE_HEADERS = exports.CHATBOX_CONTENT_SECURITY_POLICY = exports.CHATBOX_RELAYED_HEADERS = exports.CHATBOX_CONTENT_TYPE_HEADER = exports.CHATBOX_DEFAULT_CONTENT_TYPE = exports.CHATBOX_INDEX_PATH = exports.CHATBOX_PATH_PARAM = exports.CHATBOX_ASSET_ROUTE = exports.CHATBOX_INDEX_ROUTE = exports.CHATBOX_ROUTE_PREFIX = void 0;
4
- exports.CHATBOX_ROUTE_PREFIX = "/api/ai/chatbox";
5
- exports.CHATBOX_INDEX_ROUTE = "/";
6
- exports.CHATBOX_ASSET_ROUTE = "::path";
7
- exports.CHATBOX_PATH_PARAM = "path";
8
- exports.CHATBOX_INDEX_PATH = "/";
9
- exports.CHATBOX_DEFAULT_CONTENT_TYPE = "application/octet-stream";
10
- exports.CHATBOX_CONTENT_TYPE_HEADER = "content-type";
11
- exports.CHATBOX_RELAYED_HEADERS = ["content-length"];
12
- const ICONIFY_API_ORIGINS = [
13
- "https://api.iconify.design",
14
- "https://api.simplesvg.com",
15
- "https://api.unisvg.com",
16
- ];
17
- exports.CHATBOX_CONTENT_SECURITY_POLICY = [
18
- "default-src 'self'",
19
- "script-src 'self'",
20
- "style-src 'self' 'unsafe-inline'",
21
- "img-src 'self' data: blob:",
22
- "font-src 'self' data:",
23
- `connect-src 'self' ${ICONIFY_API_ORIGINS.join(" ")}`,
24
- "frame-ancestors 'self'",
25
- "base-uri 'self'",
26
- "form-action 'none'",
27
- "object-src 'none'",
28
- ].join("; ");
29
- exports.CHATBOX_RESPONSE_HEADERS = {
30
- "Content-Security-Policy": exports.CHATBOX_CONTENT_SECURITY_POLICY,
31
- "X-Content-Type-Options": "nosniff",
32
- "Referrer-Policy": "same-origin",
33
- "Cache-Control": "no-cache",
34
- };
35
- //# sourceMappingURL=chatbox.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"chatbox.js","sourceRoot":"","sources":["../../src/constants/chatbox.ts"],"names":[],"mappings":";;;AAIa,QAAA,oBAAoB,GAAG,iBAAiB,CAAC;AACzC,QAAA,mBAAmB,GAAG,GAAG,CAAC;AAC1B,QAAA,mBAAmB,GAAG,QAAQ,CAAC;AAC/B,QAAA,kBAAkB,GAAG,MAAM,CAAC;AAC5B,QAAA,kBAAkB,GAAG,GAAG,CAAC;AACzB,QAAA,4BAA4B,GAAG,0BAA0B,CAAC;AAC1D,QAAA,2BAA2B,GAAG,cAAc,CAAC;AAG7C,QAAA,uBAAuB,GAAG,CAAC,gBAAgB,CAAU,CAAC;AAEnE,MAAM,mBAAmB,GAAG;IAC1B,4BAA4B;IAC5B,2BAA2B;IAC3B,wBAAwB;CACzB,CAAC;AAUW,QAAA,+BAA+B,GAAG;IAC7C,oBAAoB;IACpB,mBAAmB;IACnB,kCAAkC;IAClC,4BAA4B;IAC5B,uBAAuB;IACvB,sBAAsB,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;IACrD,wBAAwB;IACxB,iBAAiB;IACjB,oBAAoB;IACpB,mBAAmB;CACpB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEA,QAAA,wBAAwB,GAAqC;IACxE,yBAAyB,EAAE,uCAA+B;IAC1D,wBAAwB,EAAE,SAAS;IACnC,iBAAiB,EAAE,aAAa;IAChC,eAAe,EAAE,UAAU;CAC5B,CAAC"}
@@ -1,7 +0,0 @@
1
- import { type HTTPResult, type RequestContext } from "@antelopejs/interface-api";
2
- declare const AIChatboxController_base: import("@antelopejs/interface-api").ControllerClass<object>;
3
- export declare class AIChatboxController extends AIChatboxController_base {
4
- document(ctx: RequestContext): Promise<HTTPResult | undefined>;
5
- asset(ctx: RequestContext, path: string): Promise<HTTPResult | undefined>;
6
- }
7
- export {};
@@ -1,47 +0,0 @@
1
- "use strict";
2
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
- return c > 3 && r && Object.defineProperty(target, key, r), r;
7
- };
8
- var __metadata = (this && this.__metadata) || function (k, v) {
9
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
- };
11
- var __param = (this && this.__param) || function (paramIndex, decorator) {
12
- return function (target, key) { decorator(target, key, paramIndex); }
13
- };
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.AIChatboxController = void 0;
16
- const interface_api_1 = require("@antelopejs/interface-api");
17
- const auth_1 = require("@antelopejs/interface-dms/auth");
18
- const passthrough_1 = require("../chatbox/passthrough");
19
- const chatbox_1 = require("../constants/chatbox");
20
- let AIChatboxController = class AIChatboxController extends (0, interface_api_1.Controller)(chatbox_1.CHATBOX_ROUTE_PREFIX) {
21
- document(ctx) {
22
- return (0, passthrough_1.relayChatboxFile)(ctx, chatbox_1.CHATBOX_INDEX_PATH);
23
- }
24
- asset(ctx, path) {
25
- return (0, passthrough_1.relayChatboxFile)(ctx, `/${path}`);
26
- }
27
- };
28
- exports.AIChatboxController = AIChatboxController;
29
- __decorate([
30
- (0, interface_api_1.Get)(chatbox_1.CHATBOX_INDEX_ROUTE),
31
- __param(0, (0, interface_api_1.Context)()),
32
- __metadata("design:type", Function),
33
- __metadata("design:paramtypes", [Object]),
34
- __metadata("design:returntype", Promise)
35
- ], AIChatboxController.prototype, "document", null);
36
- __decorate([
37
- (0, interface_api_1.Get)(chatbox_1.CHATBOX_ASSET_ROUTE),
38
- __param(0, (0, interface_api_1.Context)()),
39
- __param(1, (0, interface_api_1.Parameter)(chatbox_1.CHATBOX_PATH_PARAM, "param")),
40
- __metadata("design:type", Function),
41
- __metadata("design:paramtypes", [Object, String]),
42
- __metadata("design:returntype", Promise)
43
- ], AIChatboxController.prototype, "asset", null);
44
- exports.AIChatboxController = AIChatboxController = __decorate([
45
- (0, auth_1.AuthOwnerOnly)()
46
- ], AIChatboxController);
47
- //# sourceMappingURL=chatbox.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"chatbox.js","sourceRoot":"","sources":["../../src/routes/chatbox.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,6DAOmC;AACnC,yDAA+D;AAC/D,wDAA0D;AAC1D,kDAM8B;AAOvB,IAAM,mBAAmB,GAAzB,MAAM,mBAAoB,SAAQ,IAAA,0BAAU,EAAC,8BAAoB,CAAC;IAEvE,QAAQ,CAAY,GAAmB;QACrC,OAAO,IAAA,8BAAgB,EAAC,GAAG,EAAE,4BAAkB,CAAC,CAAC;IACnD,CAAC;IAGD,KAAK,CACQ,GAAmB,EACU,IAAY;QAEpD,OAAO,IAAA,8BAAgB,EAAC,GAAG,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC;CACF,CAAA;AAbY,kDAAmB;AAE9B;IADC,IAAA,mBAAG,EAAC,6BAAmB,CAAC;IACf,WAAA,IAAA,uBAAO,GAAE,CAAA;;;;mDAElB;AAGD;IADC,IAAA,mBAAG,EAAC,6BAAmB,CAAC;IAEtB,WAAA,IAAA,uBAAO,GAAE,CAAA;IACT,WAAA,IAAA,yBAAS,EAAC,4BAAkB,EAAE,OAAO,CAAC,CAAA;;;;gDAGxC;8BAZU,mBAAmB;IAD/B,IAAA,oBAAa,GAAE;GACH,mBAAmB,CAa/B"}
@@ -1,400 +0,0 @@
1
- import {
2
- OVERLAY_BOX_SHADOW,
3
- OVERLAY_DEFAULT_WIDTH_PX,
4
- OVERLAY_DOM_ID,
5
- OVERLAY_IFRAME_ID,
6
- OVERLAY_MIN_WIDTH_PX,
7
- OVERLAY_OPEN_CHANGE_EVENT,
8
- OVERLAY_OUTSIDE_TOGGLE_SUPPRESS_MS,
9
- OVERLAY_PLACEHOLDER_ID,
10
- OVERLAY_RESIZE_HANDLE_SIZE_PX,
11
- OVERLAY_Z_INDEX,
12
- PLACEHOLDER_CONNECTING_TEXT,
13
- PLACEHOLDER_REVIVING_TEXT,
14
- PLACEHOLDER_UNAVAILABLE_TEXT,
15
- PLACEHOLDER_UNAVAILABLE_TITLE,
16
- } from './constants'
17
- import {
18
- type OverlayPrefs,
19
- readPrefs,
20
- writePrefsDebounced,
21
- } from './overlay-prefs'
22
- import {
23
- type HostTheme,
24
- installThemeBridge,
25
- isFromFrame,
26
- readHostMode,
27
- } from './theme-bridge'
28
-
29
- // What the host-origin placeholder shows while the sidecar frame can't be shown.
30
- // `connecting`/`reviving` are transient (spinner); `unavailable` is terminal.
31
- export type PlaceholderKind = 'connecting' | 'reviving' | 'unavailable'
32
-
33
- export interface OverlayHandle {
34
- // Toggle the docked panel's slide-in open/closed state.
35
- toggleOpen: () => void
36
- // Launcher-specific toggle: ignores an open request that immediately follows
37
- // an outside-click close, since the launcher's `click` fires after the same
38
- // `pointerdown` that already closed the panel. The keyboard shortcut must
39
- // use `toggleOpen` so it is never suppressed.
40
- toggleFromLauncher: () => void
41
- isOpen: () => boolean
42
- /** Called on every open or close, however the panel was toggled. */
43
- onOpenChange: (listener: (isOpen: boolean) => void) => () => void
44
- // Point the frame at the chat document. A no-op when the URL is
45
- // unchanged and already loaded, so a redundant `connected` notify won't reload
46
- // the chatbox; otherwise it reloads and shows a connecting placeholder until
47
- // the frame paints.
48
- repoint: (url: string) => void
49
- // Cover the frame with the host-origin status panel (never the browser's raw
50
- // error page) while the sidecar is unreachable.
51
- showPlaceholder: (kind: PlaceholderKind) => void
52
- }
53
-
54
- interface OverlayElements {
55
- container: HTMLDivElement
56
- iframe: HTMLIFrameElement
57
- resizeHandle: HTMLDivElement
58
- placeholder: HTMLDivElement
59
- }
60
-
61
- interface OverlayPrefsRef {
62
- value: OverlayPrefs
63
- }
64
-
65
- interface MessageData {
66
- type?: unknown
67
- }
68
-
69
- const MAX_WIDTH_VW_RATIO = 0.94
70
- // Message types accepted from the chatbox iframe to close the panel.
71
- const CLOSE_MESSAGE_TYPES = new Set(['dms-ai:close'])
72
-
73
- // Fallback backdrop shown behind the iframe while it slides in, before the
74
- // bridge reports the host's real `--ui-bg`. Only briefly visible.
75
- const BACKDROP_DARK = '#0a0b12'
76
- const BACKDROP_LIGHT = '#ffffff'
77
-
78
- function backdropFor(mode: 'dark' | 'light'): string {
79
- return mode === 'dark' ? BACKDROP_DARK : BACKDROP_LIGHT
80
- }
81
-
82
- function maxWidth(): number {
83
- const vw = globalThis.innerWidth ?? OVERLAY_DEFAULT_WIDTH_PX * 3
84
- return Math.max(OVERLAY_MIN_WIDTH_PX, Math.round(vw * MAX_WIDTH_VW_RATIO))
85
- }
86
-
87
- function clampWidth(width: number): number {
88
- return Math.min(Math.max(width, OVERLAY_MIN_WIDTH_PX), maxWidth())
89
- }
90
-
91
- function buildContainer(): HTMLDivElement {
92
- const container = document.createElement('div')
93
- container.id = OVERLAY_DOM_ID
94
- const style = container.style
95
- style.position = 'fixed'
96
- style.top = '0'
97
- style.right = '0'
98
- style.height = '100vh'
99
- style.zIndex = String(OVERLAY_Z_INDEX)
100
- style.boxShadow = OVERLAY_BOX_SHADOW
101
- // Backdrop behind the iframe while it slides in — follow the host mode so a
102
- // light-mode host doesn't flash a dark sheet. The iframe fills the container
103
- // once painted, so this is only ever briefly visible.
104
- style.background = backdropFor(readHostMode())
105
- style.borderLeft = '1px solid rgba(140,160,220,0.20)'
106
- style.overflow = 'hidden'
107
- style.display = 'flex'
108
- style.transition = 'transform 220ms cubic-bezier(0.16, 1, 0.3, 1)'
109
- style.willChange = 'transform'
110
- return container
111
- }
112
-
113
- // Built with no `src`: the frame is only pointed at the chat via `repoint` once
114
- // the status controller confirms the sidecar runs, so it never renders the
115
- // error page of a sidecar that is down.
116
- function buildIframe(): HTMLIFrameElement {
117
- const iframe = document.createElement('iframe')
118
- iframe.id = OVERLAY_IFRAME_ID
119
- iframe.style.flex = '1 1 auto'
120
- iframe.style.width = '100%'
121
- iframe.style.height = '100%'
122
- iframe.style.border = '0'
123
- iframe.style.colorScheme = readHostMode()
124
- return iframe
125
- }
126
-
127
- const SPINNER_STYLE_ID = 'dms-ai-overlay-spinner-style'
128
-
129
- function ensureSpinnerKeyframes(): void {
130
- if (document.getElementById(SPINNER_STYLE_ID) !== null) return
131
- const style = document.createElement('style')
132
- style.id = SPINNER_STYLE_ID
133
- style.textContent =
134
- '@keyframes dms-ai-spin{to{transform:rotate(360deg)}}'
135
- document.head.appendChild(style)
136
- }
137
-
138
- function buildPlaceholder(): HTMLDivElement {
139
- const placeholder = document.createElement('div')
140
- placeholder.id = OVERLAY_PLACEHOLDER_ID
141
- const style = placeholder.style
142
- style.position = 'absolute'
143
- style.inset = '0'
144
- // Above the iframe, but transparent to pointers so the resize handle and the
145
- // (harmless) dead frame beneath stay reachable.
146
- style.zIndex = '2'
147
- style.pointerEvents = 'none'
148
- style.display = 'none'
149
- style.flexDirection = 'column'
150
- style.alignItems = 'center'
151
- style.justifyContent = 'center'
152
- style.gap = '14px'
153
- style.padding = '24px'
154
- style.textAlign = 'center'
155
- style.font =
156
- '500 13px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif'
157
- style.background = backdropFor(readHostMode())
158
- return placeholder
159
- }
160
-
161
- function spinnerHtml(color: string): string {
162
- return `<span style="width:22px;height:22px;border:2.5px solid ${color}33;border-top-color:${color};border-radius:50%;animation:dms-ai-spin 0.7s linear infinite;"></span>`
163
- }
164
-
165
- function placeholderContent(kind: PlaceholderKind): string {
166
- if (kind === 'unavailable') {
167
- return `<div style="font-size:15px;font-weight:600;">${PLACEHOLDER_UNAVAILABLE_TITLE}</div><div style="opacity:0.7;">${PLACEHOLDER_UNAVAILABLE_TEXT}</div>`
168
- }
169
- const text =
170
- kind === 'reviving'
171
- ? PLACEHOLDER_REVIVING_TEXT
172
- : PLACEHOLDER_CONNECTING_TEXT
173
- return `${spinnerHtml('currentColor')}<div style="opacity:0.75;">${text}</div>`
174
- }
175
-
176
- function buildResizeHandle(): HTMLDivElement {
177
- const handle = document.createElement('div')
178
- const style = handle.style
179
- style.position = 'absolute'
180
- style.left = '0'
181
- style.top = '0'
182
- style.width = `${OVERLAY_RESIZE_HANDLE_SIZE_PX}px`
183
- style.height = '100%'
184
- style.cursor = 'ew-resize'
185
- style.touchAction = 'none'
186
- style.zIndex = '1'
187
- return handle
188
- }
189
-
190
- function applyPrefs(elements: OverlayElements, prefs: OverlayPrefs): void {
191
- const width = clampWidth(prefs.width)
192
- elements.container.style.width = `${width}px`
193
- elements.container.style.transform = prefs.isOpen
194
- ? 'translateX(0)'
195
- : 'translateX(100%)'
196
- }
197
-
198
- function resolveInitialPrefs(): OverlayPrefs {
199
- const stored = readPrefs()
200
- if (stored !== null)
201
- return { width: clampWidth(stored.width), isOpen: stored.isOpen }
202
- return { width: OVERLAY_DEFAULT_WIDTH_PX, isOpen: false }
203
- }
204
-
205
- interface InteractionContext {
206
- elements: OverlayElements
207
- stateRef: OverlayPrefsRef
208
- }
209
-
210
- function installWidthResize(ctx: InteractionContext): void {
211
- const { resizeHandle, container, iframe } = ctx.elements
212
- let startX = 0
213
- let startWidth = 0
214
-
215
- const onMove = (event: PointerEvent): void => {
216
- // Panel is pinned right, so dragging left (smaller clientX) grows it.
217
- const next = clampWidth(startWidth + (startX - event.clientX))
218
- ctx.stateRef.value = { ...ctx.stateRef.value, width: next }
219
- container.style.width = `${next}px`
220
- }
221
-
222
- const onUp = (): void => {
223
- globalThis.removeEventListener('pointermove', onMove)
224
- globalThis.removeEventListener('pointerup', onUp)
225
- // Restore the iframe's pointer capture now the drag is over.
226
- iframe.style.pointerEvents = ''
227
- writePrefsDebounced(ctx.stateRef.value)
228
- }
229
-
230
- resizeHandle.addEventListener('pointerdown', (event: PointerEvent) => {
231
- event.preventDefault()
232
- startX = event.clientX
233
- startWidth = ctx.stateRef.value.width
234
- // While dragging, dragging over the iframe would let it swallow the
235
- // pointer stream, so the parent never sees pointermove/up and the drag
236
- // sticks. Make the iframe transparent to pointers for the drag's duration.
237
- iframe.style.pointerEvents = 'none'
238
- globalThis.addEventListener('pointermove', onMove)
239
- globalThis.addEventListener('pointerup', onUp)
240
- })
241
- }
242
-
243
- // Close the panel when the user clicks anywhere in the host page outside it.
244
- // Clicks inside the iframe never reach the host document, so they can't close
245
- // the panel. Returns a predicate telling whether an outside click just closed
246
- // the panel: the header launcher toggles on `click`, which follows the same
247
- // `pointerdown` that closed the panel — without this guard, clicking the
248
- // launcher while open would close then immediately re-open it.
249
- function installOutsideClickClose(ctx: InteractionContext): () => boolean {
250
- let lastCloseAt = Number.NEGATIVE_INFINITY
251
- document.addEventListener(
252
- 'pointerdown',
253
- (event: PointerEvent) => {
254
- if (!ctx.stateRef.value.isOpen) return
255
- if (event.composedPath().includes(ctx.elements.container)) return
256
- lastCloseAt = event.timeStamp
257
- setOpen(ctx.elements, ctx.stateRef, false)
258
- },
259
- { capture: true },
260
- )
261
- return (): boolean =>
262
- performance.now() - lastCloseAt < OVERLAY_OUTSIDE_TOGGLE_SUPPRESS_MS
263
- }
264
-
265
- function appendElements(elements: OverlayElements): void {
266
- elements.container.appendChild(elements.iframe)
267
- elements.container.appendChild(elements.placeholder)
268
- elements.container.appendChild(elements.resizeHandle)
269
- document.body.appendChild(elements.container)
270
- }
271
-
272
- interface PlaceholderController {
273
- show: (kind: PlaceholderKind) => void
274
- hide: () => void
275
- recolor: (theme: HostTheme) => void
276
- }
277
-
278
- function createPlaceholderController(
279
- placeholder: HTMLDivElement,
280
- ): PlaceholderController {
281
- return {
282
- show: (kind) => {
283
- placeholder.innerHTML = placeholderContent(kind)
284
- placeholder.style.display = 'flex'
285
- },
286
- hide: () => {
287
- placeholder.style.display = 'none'
288
- },
289
- recolor: (theme) => {
290
- placeholder.style.background =
291
- theme.tokens['--ui-bg'] || backdropFor(theme.mode)
292
- placeholder.style.color = theme.tokens['--ui-text'] || ''
293
- },
294
- }
295
- }
296
-
297
- // Repoint the frame at `url`, deferring the placeholder-hide until it paints so a
298
- // reload of the chatbox never flashes the previous frame. A no-op reload when
299
- // the URL is already loaded.
300
- function createRepointer(
301
- iframe: HTMLIFrameElement,
302
- placeholder: PlaceholderController,
303
- ): (url: string) => void {
304
- let currentUrl = ''
305
- let loaded = false
306
- iframe.addEventListener('load', () => {
307
- if (iframe.src === '') return
308
- loaded = true
309
- placeholder.hide()
310
- })
311
- return (url: string): void => {
312
- if (url === currentUrl && loaded) {
313
- placeholder.hide()
314
- return
315
- }
316
- currentUrl = url
317
- loaded = false
318
- placeholder.show('connecting')
319
- iframe.src = url
320
- }
321
- }
322
-
323
- function setOpen(
324
- elements: OverlayElements,
325
- stateRef: OverlayPrefsRef,
326
- isOpen: boolean,
327
- ): void {
328
- const hasChanged = stateRef.value.isOpen !== isOpen
329
- stateRef.value = { ...stateRef.value, isOpen }
330
- applyPrefs(elements, stateRef.value)
331
- writePrefsDebounced(stateRef.value)
332
- if (!hasChanged) return
333
- elements.container.dispatchEvent(
334
- new CustomEvent(OVERLAY_OPEN_CHANGE_EVENT, { detail: isOpen }),
335
- )
336
- }
337
-
338
- export function injectOverlay(): OverlayHandle | null {
339
- const isAlreadyInjected = document.getElementById(OVERLAY_DOM_ID) !== null
340
- if (isAlreadyInjected) return null
341
- ensureSpinnerKeyframes()
342
- const elements: OverlayElements = {
343
- container: buildContainer(),
344
- iframe: buildIframe(),
345
- resizeHandle: buildResizeHandle(),
346
- placeholder: buildPlaceholder(),
347
- }
348
- appendElements(elements)
349
- const placeholder = createPlaceholderController(elements.placeholder)
350
- const repoint = createRepointer(elements.iframe, placeholder)
351
- const stateRef = { value: resolveInitialPrefs() }
352
- applyPrefs(elements, stateRef.value)
353
- installWidthResize({ elements, stateRef })
354
- const wasJustClosedByOutsideClick = installOutsideClickClose({
355
- elements,
356
- stateRef,
357
- })
358
- // Mirror the host's primary palette + light/dark mode into the iframe, and
359
- // keep the panel backdrop / iframe color-scheme (and the status placeholder)
360
- // following live mode changes (e.g. an OS-driven dark switch) so no light edge
361
- // lingers until a reload.
362
- installThemeBridge(elements.iframe, (theme) => {
363
- elements.container.style.background =
364
- theme.tokens['--ui-bg'] || backdropFor(theme.mode)
365
- elements.iframe.style.colorScheme = theme.mode
366
- placeholder.recolor(theme)
367
- })
368
-
369
- // The chatbox header's close button lives inside the iframe; it asks the
370
- // host to slide the panel away via postMessage.
371
- globalThis.addEventListener('message', (event: MessageEvent) => {
372
- if (!isFromFrame(event, elements.iframe)) return
373
- const type = (event.data as MessageData)?.type
374
- if (typeof type === 'string' && CLOSE_MESSAGE_TYPES.has(type)) {
375
- setOpen(elements, stateRef, false)
376
- }
377
- })
378
-
379
- const toggle = (): void =>
380
- setOpen(elements, stateRef, !stateRef.value.isOpen)
381
-
382
- return {
383
- toggleOpen: toggle,
384
- toggleFromLauncher: (): void => {
385
- const wouldOpen = !stateRef.value.isOpen
386
- if (wouldOpen && wasJustClosedByOutsideClick()) return
387
- toggle()
388
- },
389
- isOpen: (): boolean => stateRef.value.isOpen,
390
- onOpenChange: (listener) => {
391
- const onChange = (event: Event): void =>
392
- listener((event as CustomEvent<boolean>).detail)
393
- elements.container.addEventListener(OVERLAY_OPEN_CHANGE_EVENT, onChange)
394
- return () =>
395
- elements.container.removeEventListener(OVERLAY_OPEN_CHANGE_EVENT, onChange)
396
- },
397
- repoint,
398
- showPlaceholder: (kind) => placeholder.show(kind),
399
- }
400
- }