@antelopejs/dms-ai 0.1.4 → 0.1.5

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 (244) hide show
  1. package/README.md +48 -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 +2 -1
  18. package/dist/constants/sidecar.js +3 -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-socket.d.ts +1 -1
  26. package/dist/lifecycle/sidecar-socket.js +2 -2
  27. package/dist/lifecycle/sidecar-socket.js.map +1 -1
  28. package/dist/lifecycle/spawn-sidecar.d.ts +0 -1
  29. package/dist/lifecycle/spawn-sidecar.js +5 -27
  30. package/dist/lifecycle/spawn-sidecar.js.map +1 -1
  31. package/dist/routes/channels.d.ts +3 -3
  32. package/dist/routes/channels.js +11 -12
  33. package/dist/routes/channels.js.map +1 -1
  34. package/dist/routes/index.d.ts +0 -1
  35. package/dist/routes/index.js +0 -1
  36. package/dist/routes/index.js.map +1 -1
  37. package/frontend-vue/.prettierignore +3 -0
  38. package/frontend-vue/.prettierrc +10 -0
  39. package/frontend-vue/app/chat/components/ChatView.vue +582 -0
  40. package/frontend-vue/app/chat/components/ComposerInput.vue +742 -0
  41. package/frontend-vue/app/chat/components/ConnectionBanner.vue +105 -0
  42. package/frontend-vue/app/chat/components/ConversationDrawer.vue +266 -0
  43. package/frontend-vue/app/chat/components/MarkdownContent.vue +196 -0
  44. package/frontend-vue/app/chat/components/MessageList.vue +474 -0
  45. package/frontend-vue/app/chat/components/PermissionRequests.vue +323 -0
  46. package/frontend-vue/app/chat/components/QuestionPrompt.vue +275 -0
  47. package/frontend-vue/app/chat/components/QueuedMessages.vue +109 -0
  48. package/frontend-vue/app/chat/components/TodoList.vue +291 -0
  49. package/frontend-vue/app/chat/components/ToolCallEntry.vue +218 -0
  50. package/frontend-vue/app/chat/components/ToolCluster.vue +282 -0
  51. package/frontend-vue/app/chat/composables/useChatChannel.ts +116 -0
  52. package/frontend-vue/app/chat/composables/useConversation.ts +838 -0
  53. package/frontend-vue/app/chat/composables/useConversationId.ts +34 -0
  54. package/frontend-vue/app/chat/composables/useConversationList.ts +56 -0
  55. package/frontend-vue/app/chat/composables/usePermissionQueue.ts +59 -0
  56. package/frontend-vue/app/chat/composables/useQuestionQueue.ts +50 -0
  57. package/frontend-vue/app/chat/composables/useRunClock.ts +28 -0
  58. package/frontend-vue/app/chat/composables/useSettings.ts +67 -0
  59. package/frontend-vue/app/chat/constants/attachments.ts +16 -0
  60. package/frontend-vue/app/chat/constants/connection-banner.ts +7 -0
  61. package/frontend-vue/app/chat/constants/conversation-drawer.ts +13 -0
  62. package/frontend-vue/app/chat/constants/conversation.ts +75 -0
  63. package/frontend-vue/app/chat/constants/permissions.ts +50 -0
  64. package/frontend-vue/app/chat/constants/protocol.ts +48 -0
  65. package/frontend-vue/app/chat/constants/run-status.ts +50 -0
  66. package/frontend-vue/app/chat/constants/settings.ts +60 -0
  67. package/frontend-vue/app/chat/types/conversation.ts +87 -0
  68. package/frontend-vue/app/chat/types/permission.ts +7 -0
  69. package/frontend-vue/app/chat/types/question.ts +17 -0
  70. package/frontend-vue/app/chat/types/settings.ts +27 -0
  71. package/frontend-vue/app/chat/utils/attachments.ts +62 -0
  72. package/frontend-vue/app/chat/utils/format.ts +14 -0
  73. package/frontend-vue/app/chat/utils/ids.ts +34 -0
  74. package/frontend-vue/app/chat/utils/markdown.ts +188 -0
  75. package/frontend-vue/app/chat/utils/run-status.ts +90 -0
  76. package/frontend-vue/app/chat/utils/todos.ts +64 -0
  77. package/frontend-vue/app/chat/utils/tool-summary.ts +53 -0
  78. package/frontend-vue/app/components/ActivityView.vue +34 -34
  79. package/frontend-vue/app/components/ChatPanel.vue +276 -0
  80. package/frontend-vue/app/components/SettingsView.vue +120 -112
  81. package/frontend-vue/app/components/SkillsView.vue +55 -53
  82. package/frontend-vue/app/plugins/ai.client.ts +145 -129
  83. package/frontend-vue/app/runtime/assistant-session.ts +21 -0
  84. package/frontend-vue/app/runtime/backoff.ts +11 -14
  85. package/frontend-vue/app/runtime/channel-client.ts +130 -141
  86. package/frontend-vue/app/runtime/chat-transport.ts +25 -55
  87. package/frontend-vue/app/runtime/constants.ts +69 -59
  88. package/frontend-vue/app/runtime/current-page.ts +49 -15
  89. package/frontend-vue/app/runtime/event-stream.ts +35 -35
  90. package/frontend-vue/app/runtime/frame-router.ts +30 -0
  91. package/frontend-vue/app/runtime/header-action.ts +18 -18
  92. package/frontend-vue/app/runtime/host-commands.ts +57 -56
  93. package/frontend-vue/app/runtime/listeners.ts +31 -0
  94. package/frontend-vue/app/runtime/navigation-complete.ts +7 -7
  95. package/frontend-vue/app/runtime/overlay-prefs.ts +36 -36
  96. package/frontend-vue/app/runtime/panel-state.ts +88 -0
  97. package/frontend-vue/app/runtime/session-gate.ts +8 -8
  98. package/frontend-vue/app/runtime/shortcuts.ts +12 -12
  99. package/frontend-vue/app/runtime/sidecar-status.ts +61 -61
  100. package/frontend-vue/app/runtime/typed-message.ts +11 -0
  101. package/frontend-vue/dms.frontend.ts +12 -12
  102. package/frontend-vue/eslint.config.mjs +13 -13
  103. package/frontend-vue/package.json +11 -4
  104. package/frontend-vue/pnpm-lock.yaml +298 -96
  105. package/frontend-vue/tests/backoff.test.ts +26 -30
  106. package/frontend-vue/tests/channel-client.test.ts +187 -188
  107. package/frontend-vue/tests/chat/markdown.test.ts +271 -0
  108. package/frontend-vue/tests/chat/useChatChannel.test.ts +124 -0
  109. package/frontend-vue/tests/chat/useConversation.test.ts +283 -0
  110. package/frontend-vue/tests/chat-panel.test.ts +354 -0
  111. package/frontend-vue/tests/chat-transport.test.ts +84 -57
  112. package/frontend-vue/tests/host-commands.test.ts +126 -87
  113. package/frontend-vue/tests/navigation.test.ts +65 -34
  114. package/frontend-vue/tests/overlay-prefs.test.ts +49 -49
  115. package/frontend-vue/tests/panel-state.test.ts +58 -0
  116. package/frontend-vue/tests/session-gate.test.ts +33 -33
  117. package/frontend-vue/tests/sidecar-guest.test.ts +46 -46
  118. package/frontend-vue/tests/sidecar-status.test.ts +29 -29
  119. package/frontend-vue/tsconfig.chat.json +16 -0
  120. package/frontend-vue/vitest.config.ts +6 -4
  121. package/package.json +5 -5
  122. package/sidecar/dist/agent/effective-mode.d.ts +2 -2
  123. package/sidecar/dist/agent/effective-mode.js +1 -1
  124. package/sidecar/dist/agent/effective-mode.js.map +1 -1
  125. package/sidecar/dist/agent/host-context.js +7 -9
  126. package/sidecar/dist/agent/host-context.js.map +1 -1
  127. package/sidecar/dist/agent/permission-bus.d.ts +1 -1
  128. package/sidecar/dist/agent/permission-bus.js +2 -2
  129. package/sidecar/dist/agent/permission-bus.js.map +1 -1
  130. package/sidecar/dist/agent/provider.d.ts +2 -2
  131. package/sidecar/dist/agent/question-bus.d.ts +1 -1
  132. package/sidecar/dist/agent/question-bus.js +2 -2
  133. package/sidecar/dist/agent/question-bus.js.map +1 -1
  134. package/sidecar/dist/agent/runner.d.ts +2 -2
  135. package/sidecar/dist/agent/runner.js +24 -8
  136. package/sidecar/dist/agent/runner.js.map +1 -1
  137. package/sidecar/dist/agent/session.d.ts +2 -2
  138. package/sidecar/dist/agent/session.js +49 -9
  139. package/sidecar/dist/agent/session.js.map +1 -1
  140. package/sidecar/dist/agent/switching-runner.js +10 -5
  141. package/sidecar/dist/agent/switching-runner.js.map +1 -1
  142. package/sidecar/dist/agent/turn-end-reasons.d.ts +2 -0
  143. package/sidecar/dist/agent/turn-end-reasons.js +6 -1
  144. package/sidecar/dist/agent/turn-end-reasons.js.map +1 -1
  145. package/sidecar/dist/constants/agent.d.ts +4 -1
  146. package/sidecar/dist/constants/agent.js +4 -1
  147. package/sidecar/dist/constants/agent.js.map +1 -1
  148. package/sidecar/dist/constants/cli.d.ts +0 -1
  149. package/sidecar/dist/constants/cli.js +0 -1
  150. package/sidecar/dist/constants/cli.js.map +1 -1
  151. package/sidecar/dist/constants/codex.d.ts +5 -2
  152. package/sidecar/dist/constants/codex.js +5 -2
  153. package/sidecar/dist/constants/codex.js.map +1 -1
  154. package/sidecar/dist/constants/http.d.ts +0 -9
  155. package/sidecar/dist/constants/http.js +0 -19
  156. package/sidecar/dist/constants/http.js.map +1 -1
  157. package/sidecar/dist/constants/mcp.d.ts +1 -1
  158. package/sidecar/dist/constants/mcp.js +1 -1
  159. package/sidecar/dist/constants/mcp.js.map +1 -1
  160. package/sidecar/dist/constants/paths.d.ts +0 -1
  161. package/sidecar/dist/constants/paths.js +0 -1
  162. package/sidecar/dist/constants/paths.js.map +1 -1
  163. package/sidecar/dist/constants/ws.d.ts +3 -4
  164. package/sidecar/dist/constants/ws.js +3 -4
  165. package/sidecar/dist/constants/ws.js.map +1 -1
  166. package/sidecar/dist/index.d.ts +0 -1
  167. package/sidecar/dist/index.js +6 -10
  168. package/sidecar/dist/index.js.map +1 -1
  169. package/sidecar/dist/mcp/tools/find-pages-using.js +5 -10
  170. package/sidecar/dist/mcp/tools/find-pages-using.js.map +1 -1
  171. package/sidecar/dist/mcp/types.d.ts +1 -1
  172. package/sidecar/dist/pages/page-filepath.d.ts +4 -0
  173. package/sidecar/dist/pages/page-filepath.js +28 -0
  174. package/sidecar/dist/pages/page-filepath.js.map +1 -0
  175. package/sidecar/dist/prompts/agent.d.ts +1 -1
  176. package/sidecar/dist/prompts/agent.js +1 -1
  177. package/sidecar/dist/prompts/agent.js.map +1 -1
  178. package/sidecar/dist/protocol/events.js +2 -2
  179. package/sidecar/dist/protocol/events.js.map +1 -1
  180. package/sidecar/dist/protocol/messages.d.ts +4 -15
  181. package/sidecar/dist/protocol/messages.js +4 -11
  182. package/sidecar/dist/protocol/messages.js.map +1 -1
  183. package/sidecar/dist/providers/claude/config.d.ts +2 -2
  184. package/sidecar/dist/providers/claude/config.js +2 -2
  185. package/sidecar/dist/providers/claude/config.js.map +1 -1
  186. package/sidecar/dist/providers/claude/provider.js +1 -1
  187. package/sidecar/dist/providers/claude/provider.js.map +1 -1
  188. package/sidecar/dist/providers/codex/config.d.ts +3 -2
  189. package/sidecar/dist/providers/codex/config.js +3 -3
  190. package/sidecar/dist/providers/codex/config.js.map +1 -1
  191. package/sidecar/dist/providers/codex/process.js +107 -74
  192. package/sidecar/dist/providers/codex/process.js.map +1 -1
  193. package/sidecar/dist/providers/codex/provider.js +68 -31
  194. package/sidecar/dist/providers/codex/provider.js.map +1 -1
  195. package/sidecar/dist/providers/codex/skills.d.ts +1 -0
  196. package/sidecar/dist/providers/codex/skills.js +4 -1
  197. package/sidecar/dist/providers/codex/skills.js.map +1 -1
  198. package/sidecar/dist/providers/codex/stop.d.ts +4 -0
  199. package/sidecar/dist/providers/codex/stop.js +71 -0
  200. package/sidecar/dist/providers/codex/stop.js.map +1 -0
  201. package/sidecar/dist/server/{iframe-socket-registry.d.ts → chat-socket-registry.d.ts} +3 -2
  202. package/sidecar/dist/server/{iframe-socket-registry.js → chat-socket-registry.js} +4 -3
  203. package/sidecar/dist/server/chat-socket-registry.js.map +1 -0
  204. package/sidecar/dist/server/client-auth.js +2 -14
  205. package/sidecar/dist/server/client-auth.js.map +1 -1
  206. package/sidecar/dist/server/host-command-router.d.ts +5 -0
  207. package/sidecar/dist/server/host-command-router.js +4 -0
  208. package/sidecar/dist/server/host-command-router.js.map +1 -0
  209. package/sidecar/dist/server/host-socket-registry.d.ts +1 -1
  210. package/sidecar/dist/server/host-socket-registry.js +16 -13
  211. package/sidecar/dist/server/host-socket-registry.js.map +1 -1
  212. package/sidecar/dist/server/http.d.ts +0 -1
  213. package/sidecar/dist/server/http.js +1 -44
  214. package/sidecar/dist/server/http.js.map +1 -1
  215. package/sidecar/dist/server/routing.d.ts +5 -6
  216. package/sidecar/dist/server/routing.js +18 -28
  217. package/sidecar/dist/server/routing.js.map +1 -1
  218. package/sidecar/dist/server/ws.d.ts +2 -2
  219. package/sidecar/dist/server/ws.js +41 -39
  220. package/sidecar/dist/server/ws.js.map +1 -1
  221. package/sidecar/dist/state/host-state.d.ts +10 -2
  222. package/sidecar/dist/state/host-state.js +13 -3
  223. package/sidecar/dist/state/host-state.js.map +1 -1
  224. package/sidecar/dist/state/settings-store.js +2 -2
  225. package/sidecar/dist/state/settings-store.js.map +1 -1
  226. package/sidecar/dist/state/settings-types.d.ts +3 -3
  227. package/sidecar/dist/state/settings-types.js +1 -1
  228. package/sidecar/dist/state/settings-types.js.map +1 -1
  229. package/sidecar/package.json +3 -5
  230. package/dist/chatbox/passthrough.d.ts +0 -3
  231. package/dist/chatbox/passthrough.js +0 -53
  232. package/dist/chatbox/passthrough.js.map +0 -1
  233. package/dist/constants/chatbox.d.ts +0 -10
  234. package/dist/constants/chatbox.js +0 -35
  235. package/dist/constants/chatbox.js.map +0 -1
  236. package/dist/routes/chatbox.d.ts +0 -7
  237. package/dist/routes/chatbox.js +0 -47
  238. package/dist/routes/chatbox.js.map +0 -1
  239. package/frontend-vue/app/runtime/overlay.ts +0 -400
  240. package/frontend-vue/app/runtime/theme-bridge.ts +0 -189
  241. package/sidecar/chatbox/dist/assets/index-C8u7mkHn.css +0 -1
  242. package/sidecar/chatbox/dist/assets/index-tdZvY7ee.js +0 -52
  243. package/sidecar/chatbox/dist/index.html +0 -13
  244. package/sidecar/dist/server/iframe-socket-registry.js.map +0 -1
@@ -0,0 +1,742 @@
1
+ <script setup lang="ts">
2
+ import { computed, nextTick, ref } from "vue";
3
+ import { ATTACH_LABEL, MAX_ATTACHMENTS } from "../constants/attachments";
4
+ import {
5
+ NEWLINE_HOTKEY_MODIFIER,
6
+ SEND_HOTKEY,
7
+ } from "../constants/conversation";
8
+ import {
9
+ exceedsSizeLimit,
10
+ formatBytes,
11
+ isInlineImage,
12
+ type PendingAttachment,
13
+ readFileAsAttachment,
14
+ } from "../utils/attachments";
15
+
16
+ import type { GenerationMode } from "../types/settings";
17
+
18
+ interface Props {
19
+ isDisabled: boolean;
20
+ isRunning: boolean;
21
+ generationMode: GenerationMode;
22
+ builderAvailable: boolean;
23
+ }
24
+
25
+ const props = defineProps<Props>();
26
+
27
+ const ATTACH_ICON = "i-ph-paperclip";
28
+ const ATTACH_REMOVE_ICON = "i-ph-x";
29
+ const ATTACH_FILE_ICON = "i-ph-file";
30
+ interface Emits {
31
+ submit: [content: string, attachments: PendingAttachment[]];
32
+ stop: [];
33
+ "update:generationMode": [mode: GenerationMode];
34
+ }
35
+
36
+ const emit = defineEmits<Emits>();
37
+
38
+ const activeMode = computed<GenerationMode>(() =>
39
+ props.generationMode === "safe" && props.builderAvailable ? "safe" : "vibe",
40
+ );
41
+
42
+ function selectGeneration(mode: GenerationMode): void {
43
+ if (mode === "safe" && !props.builderAvailable) return;
44
+ if (mode !== props.generationMode) emit("update:generationMode", mode);
45
+ }
46
+
47
+ const draft = ref("");
48
+ const textareaRef = ref<HTMLTextAreaElement | null>(null);
49
+ const fileInputRef = ref<HTMLInputElement | null>(null);
50
+ const attachments = ref<PendingAttachment[]>([]);
51
+ const errorMsg = ref("");
52
+ const dragDepth = ref(0);
53
+ const isDragging = ref(false);
54
+
55
+ const isInputDisabled = computed<boolean>(() => props.isDisabled);
56
+
57
+ const MAX_HEIGHT_PX = 160;
58
+
59
+ function autoGrow(): void {
60
+ const el = textareaRef.value;
61
+ if (el === null) return;
62
+ el.style.height = "auto";
63
+ el.style.height = `${Math.min(el.scrollHeight, MAX_HEIGHT_PX)}px`;
64
+ }
65
+
66
+ const isSendDisabled = computed<boolean>(() => {
67
+ if (isInputDisabled.value) return true;
68
+ return draft.value.trim().length === 0 && attachments.value.length === 0;
69
+ });
70
+
71
+ async function addFiles(files: FileList | File[]): Promise<void> {
72
+ errorMsg.value = "";
73
+ const incoming = Array.from(files);
74
+ if (incoming.length === 0) return;
75
+
76
+ const room = MAX_ATTACHMENTS - attachments.value.length;
77
+ if (room <= 0) {
78
+ errorMsg.value = `Up to ${MAX_ATTACHMENTS} files per message.`;
79
+ return;
80
+ }
81
+
82
+ const accepted = incoming.slice(0, room);
83
+ if (accepted.length < incoming.length) {
84
+ errorMsg.value = `Up to ${MAX_ATTACHMENTS} files per message.`;
85
+ }
86
+
87
+ for (const file of accepted) {
88
+ if (exceedsSizeLimit(file)) {
89
+ errorMsg.value = `"${file.name}" is too large (max 25 MB).`;
90
+ continue;
91
+ }
92
+ try {
93
+ attachments.value = [
94
+ ...attachments.value,
95
+ await readFileAsAttachment(file),
96
+ ];
97
+ } catch {
98
+ errorMsg.value = `Could not read "${file.name}".`;
99
+ }
100
+ }
101
+ }
102
+
103
+ function removeAttachment(id: string): void {
104
+ attachments.value = attachments.value.filter((a) => a.id !== id);
105
+ }
106
+
107
+ function openFilePicker(): void {
108
+ if (isInputDisabled.value) return;
109
+ fileInputRef.value?.click();
110
+ }
111
+
112
+ function onFileChange(event: Event): void {
113
+ const input = event.target as HTMLInputElement;
114
+ if (input.files !== null) void addFiles(input.files);
115
+ input.value = "";
116
+ }
117
+
118
+ function onPaste(event: ClipboardEvent): void {
119
+ const files = event.clipboardData?.files;
120
+ if (files === undefined || files.length === 0) return;
121
+ event.preventDefault();
122
+ void addFiles(files);
123
+ }
124
+
125
+ function hasFiles(event: DragEvent): boolean {
126
+ return event.dataTransfer?.types.includes("Files") ?? false;
127
+ }
128
+
129
+ function onDragEnter(event: DragEvent): void {
130
+ if (isInputDisabled.value || !hasFiles(event)) return;
131
+ dragDepth.value += 1;
132
+ isDragging.value = true;
133
+ }
134
+
135
+ function onDragOver(event: DragEvent): void {
136
+ if (isInputDisabled.value || !hasFiles(event)) return;
137
+ event.preventDefault();
138
+ }
139
+
140
+ function onDragLeave(): void {
141
+ if (dragDepth.value === 0) return;
142
+ dragDepth.value -= 1;
143
+ if (dragDepth.value === 0) isDragging.value = false;
144
+ }
145
+
146
+ function onDrop(event: DragEvent): void {
147
+ dragDepth.value = 0;
148
+ isDragging.value = false;
149
+ if (isInputDisabled.value) return;
150
+ const files = event.dataTransfer?.files;
151
+ if (files === undefined || files.length === 0) return;
152
+ event.preventDefault();
153
+ void addFiles(files);
154
+ }
155
+
156
+ function submitDraft(): void {
157
+ if (isSendDisabled.value) return;
158
+ const content = draft.value;
159
+ emit("submit", content, attachments.value);
160
+ draft.value = "";
161
+ attachments.value = [];
162
+ errorMsg.value = "";
163
+ void nextTick(autoGrow);
164
+ }
165
+
166
+ function onStop(): void {
167
+ emit("stop");
168
+ }
169
+
170
+ function shouldSubmitOnKey(event: KeyboardEvent): boolean {
171
+ if (event.key !== SEND_HOTKEY) return false;
172
+ if (event[NEWLINE_HOTKEY_MODIFIER]) return false;
173
+ return true;
174
+ }
175
+
176
+ function onKeydown(event: KeyboardEvent): void {
177
+ if (!shouldSubmitOnKey(event)) return;
178
+ event.preventDefault();
179
+ submitDraft();
180
+ }
181
+ </script>
182
+
183
+ <template>
184
+ <form
185
+ class="composer"
186
+ :class="{ 'composer-dragging': isDragging }"
187
+ @submit.prevent="submitDraft"
188
+ @dragenter="onDragEnter"
189
+ @dragover="onDragOver"
190
+ @dragleave="onDragLeave"
191
+ @drop="onDrop"
192
+ >
193
+ <div v-if="isDragging" class="composer-dropzone">
194
+ <UIcon :name="ATTACH_ICON" class="size-[18px]" />
195
+ Drop files to attach
196
+ </div>
197
+
198
+ <ul v-if="attachments.length > 0" class="composer-chips">
199
+ <li v-for="att in attachments" :key="att.id" class="chip">
200
+ <img
201
+ v-if="isInlineImage(att.mimeType) && att.dataUrl"
202
+ :src="att.dataUrl"
203
+ :alt="att.name"
204
+ class="chip-thumb"
205
+ />
206
+ <UIcon v-else :name="ATTACH_FILE_ICON" class="chip-ic size-[16px]" />
207
+ <span class="chip-meta">
208
+ <span class="chip-name">{{ att.name }}</span>
209
+ <span class="chip-size">{{ formatBytes(att.size) }}</span>
210
+ </span>
211
+ <button
212
+ type="button"
213
+ class="chip-remove"
214
+ :aria-label="`Remove ${att.name}`"
215
+ title="Remove"
216
+ @click="removeAttachment(att.id)"
217
+ >
218
+ <UIcon :name="ATTACH_REMOVE_ICON" class="size-[13px]" />
219
+ </button>
220
+ </li>
221
+ </ul>
222
+
223
+ <p v-if="errorMsg" class="composer-error" role="alert">{{ errorMsg }}</p>
224
+
225
+ <div class="composer-field">
226
+ <textarea
227
+ ref="textareaRef"
228
+ v-model="draft"
229
+ class="composer-input"
230
+ :placeholder="
231
+ isRunning
232
+ ? 'Queue a follow-up message…'
233
+ : 'Ask AntelopeJS to modify this page'
234
+ "
235
+ rows="1"
236
+ aria-label="message input"
237
+ :disabled="isInputDisabled"
238
+ @keydown="onKeydown"
239
+ @input="autoGrow"
240
+ @paste="onPaste"
241
+ />
242
+ <button
243
+ type="button"
244
+ class="composer-attach"
245
+ :aria-label="ATTACH_LABEL"
246
+ :title="ATTACH_LABEL"
247
+ :disabled="isInputDisabled"
248
+ @click="openFilePicker"
249
+ >
250
+ <UIcon :name="ATTACH_ICON" class="size-[17px]" />
251
+ </button>
252
+ </div>
253
+
254
+ <input
255
+ ref="fileInputRef"
256
+ type="file"
257
+ multiple
258
+ class="composer-fileinput"
259
+ aria-hidden="true"
260
+ tabindex="-1"
261
+ @change="onFileChange"
262
+ />
263
+
264
+ <div class="composer-sendrow">
265
+ <div class="composer-mode">
266
+ <div class="composer-toggle" role="group" aria-label="Generation mode">
267
+ <div class="mode-opt">
268
+ <button
269
+ type="button"
270
+ class="seg"
271
+ :class="{
272
+ 'seg-active': activeMode === 'safe',
273
+ 'seg-disabled': !builderAvailable,
274
+ }"
275
+ aria-describedby="dms-ai-composer-tip-safe"
276
+ :aria-disabled="!builderAvailable"
277
+ @click="selectGeneration('safe')"
278
+ >
279
+ <svg
280
+ class="seg-ic"
281
+ viewBox="0 0 24 24"
282
+ width="13"
283
+ height="13"
284
+ fill="none"
285
+ stroke="currentColor"
286
+ stroke-width="2"
287
+ stroke-linecap="round"
288
+ stroke-linejoin="round"
289
+ >
290
+ <path
291
+ d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"
292
+ />
293
+ </svg>
294
+ Safe code
295
+ </button>
296
+ <div
297
+ id="dms-ai-composer-tip-safe"
298
+ class="composer-tip"
299
+ role="tooltip"
300
+ >
301
+ <b>Safe code</b>
302
+ <p>
303
+ Acts only through the Builder (MCP) — emits configurations it
304
+ accepts, never raw code. Predictable diffs, safer deploys.
305
+ </p>
306
+ <p v-if="!builderAvailable" class="tip-note">
307
+ Safe code unlocks once the Builder module is loaded.
308
+ </p>
309
+ </div>
310
+ </div>
311
+ <div class="mode-opt">
312
+ <button
313
+ type="button"
314
+ class="seg"
315
+ :class="{ 'seg-active': activeMode === 'vibe' }"
316
+ aria-describedby="dms-ai-composer-tip-vibe"
317
+ @click="selectGeneration('vibe')"
318
+ >
319
+ <svg
320
+ class="seg-ic"
321
+ viewBox="0 0 24 24"
322
+ width="13"
323
+ height="13"
324
+ fill="none"
325
+ stroke="currentColor"
326
+ stroke-width="2"
327
+ stroke-linecap="round"
328
+ stroke-linejoin="round"
329
+ >
330
+ <path
331
+ d="m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72Z"
332
+ />
333
+ <path d="m14 7 3 3" />
334
+ <path d="M5 6v4" />
335
+ <path d="M19 14v4" />
336
+ <path d="M10 2v2" />
337
+ <path d="M7 8H3" />
338
+ <path d="M21 16h-4" />
339
+ <path d="M11 3H9" />
340
+ </svg>
341
+ Vibe code
342
+ </button>
343
+ <div
344
+ id="dms-ai-composer-tip-vibe"
345
+ class="composer-tip"
346
+ role="tooltip"
347
+ >
348
+ <b>Vibe code</b>
349
+ <p>
350
+ Free to write custom code and bespoke pages beyond the Builder's
351
+ blocks — full creative range.
352
+ </p>
353
+ </div>
354
+ </div>
355
+ </div>
356
+ </div>
357
+ <div class="composer-actions">
358
+ <button
359
+ v-if="isRunning"
360
+ type="button"
361
+ class="composer-stop"
362
+ title="Stop"
363
+ aria-label="Stop"
364
+ :disabled="isDisabled"
365
+ @click="onStop"
366
+ >
367
+ <svg
368
+ viewBox="0 0 24 24"
369
+ width="16"
370
+ height="16"
371
+ fill="currentColor"
372
+ stroke="none"
373
+ >
374
+ <rect x="6" y="6" width="12" height="12" rx="2" />
375
+ </svg>
376
+ </button>
377
+ <button
378
+ type="submit"
379
+ class="composer-send"
380
+ :disabled="isSendDisabled"
381
+ :title="isRunning ? 'Queue to send after the current turn' : 'Send'"
382
+ aria-label="Send"
383
+ >
384
+ <svg
385
+ viewBox="0 0 24 24"
386
+ width="18"
387
+ height="18"
388
+ fill="none"
389
+ stroke="currentColor"
390
+ stroke-width="2"
391
+ stroke-linecap="round"
392
+ stroke-linejoin="round"
393
+ >
394
+ <line x1="22" y1="2" x2="11" y2="13" />
395
+ <polygon points="22 2 15 22 11 13 2 9 22 2" />
396
+ </svg>
397
+ </button>
398
+ </div>
399
+ </div>
400
+ </form>
401
+ </template>
402
+
403
+ <style scoped>
404
+ .composer {
405
+ position: relative;
406
+ border-top: 1px solid var(--hair);
407
+ padding: 12px 14px 14px;
408
+ background: var(--surface-side);
409
+ }
410
+
411
+ .composer-dragging {
412
+ outline: 2px dashed var(--accent-bg-strong);
413
+ outline-offset: -6px;
414
+ }
415
+
416
+ .composer-dropzone {
417
+ position: absolute;
418
+ inset: 4px;
419
+ z-index: 20;
420
+ display: flex;
421
+ align-items: center;
422
+ justify-content: center;
423
+ gap: 8px;
424
+ border-radius: var(--corner-lg);
425
+ background: color-mix(in oklab, var(--surface-side) 88%, transparent);
426
+ color: var(--accent);
427
+ font-size: 13px;
428
+ font-weight: 600;
429
+ pointer-events: none;
430
+ }
431
+
432
+ .composer-fileinput {
433
+ display: none;
434
+ }
435
+
436
+ .composer-chips {
437
+ list-style: none;
438
+ margin: 0 0 8px;
439
+ padding: 0;
440
+ display: flex;
441
+ flex-wrap: wrap;
442
+ gap: 6px;
443
+ }
444
+
445
+ .chip {
446
+ display: inline-flex;
447
+ align-items: center;
448
+ gap: 7px;
449
+ max-width: 220px;
450
+ padding: 4px 6px 4px 5px;
451
+ background: var(--surface-inset);
452
+ border: 1px solid var(--hair);
453
+ border-radius: var(--corner-md);
454
+ }
455
+
456
+ .chip-thumb {
457
+ width: 28px;
458
+ height: 28px;
459
+ flex: 0 0 auto;
460
+ object-fit: cover;
461
+ border-radius: 5px;
462
+ }
463
+
464
+ .chip-ic {
465
+ flex: 0 0 auto;
466
+ color: var(--fg-tertiary);
467
+ margin-left: 3px;
468
+ }
469
+
470
+ .chip-meta {
471
+ display: flex;
472
+ flex-direction: column;
473
+ min-width: 0;
474
+ line-height: 1.25;
475
+ }
476
+
477
+ .chip-name {
478
+ font-size: 12px;
479
+ font-weight: 500;
480
+ color: var(--fg);
481
+ white-space: nowrap;
482
+ overflow: hidden;
483
+ text-overflow: ellipsis;
484
+ }
485
+
486
+ .chip-size {
487
+ font-size: 10px;
488
+ color: var(--fg-tertiary);
489
+ }
490
+
491
+ .chip-remove {
492
+ flex: 0 0 auto;
493
+ display: grid;
494
+ place-items: center;
495
+ width: 20px;
496
+ height: 20px;
497
+ border: none;
498
+ background: transparent;
499
+ border-radius: 5px;
500
+ color: var(--fg-tertiary);
501
+ cursor: pointer;
502
+ }
503
+
504
+ .chip-remove:hover {
505
+ background: var(--danger-bg);
506
+ color: var(--danger-400);
507
+ }
508
+
509
+ .composer-error {
510
+ margin: 0 0 8px;
511
+ font-size: 11.5px;
512
+ color: var(--danger-400);
513
+ }
514
+
515
+ /* Sits inside the input field, bottom-right of the textarea. */
516
+ .composer-attach {
517
+ width: 26px;
518
+ height: 26px;
519
+ flex: 0 0 auto;
520
+ align-self: flex-end;
521
+ display: grid;
522
+ place-items: center;
523
+ border: none;
524
+ background: transparent;
525
+ border-radius: var(--corner-sm);
526
+ color: var(--fg-tertiary);
527
+ cursor: pointer;
528
+ transition:
529
+ background var(--dur-fast),
530
+ color var(--dur-fast);
531
+ }
532
+
533
+ .composer-attach:hover:not(:disabled) {
534
+ background: var(--surface-card-2);
535
+ color: var(--fg);
536
+ }
537
+
538
+ .composer-attach:disabled {
539
+ opacity: 0.4;
540
+ cursor: not-allowed;
541
+ }
542
+
543
+ .composer-field {
544
+ display: flex;
545
+ align-items: flex-end;
546
+ gap: 6px;
547
+ background: var(--surface-inset);
548
+ border: 1px solid var(--hair);
549
+ border-radius: var(--corner-lg);
550
+ padding: 8px 10px;
551
+ }
552
+
553
+ .composer-field:focus-within {
554
+ border-color: var(--accent-bg-strong);
555
+ }
556
+
557
+ .composer-input {
558
+ flex: 1;
559
+ resize: none;
560
+ background: transparent;
561
+ border: none;
562
+ outline: none;
563
+ color: var(--fg);
564
+ font: inherit;
565
+ font-size: 13.5px;
566
+ line-height: 1.4;
567
+ min-height: 22px;
568
+ max-height: 160px;
569
+ overflow-y: auto;
570
+ }
571
+
572
+ .composer-input::placeholder {
573
+ color: var(--fg-tertiary);
574
+ }
575
+
576
+ .composer-sendrow {
577
+ display: flex;
578
+ align-items: center;
579
+ gap: 10px;
580
+ margin-top: 10px;
581
+ }
582
+
583
+ .composer-mode {
584
+ position: relative;
585
+ }
586
+
587
+ .composer-toggle {
588
+ display: inline-flex;
589
+ gap: 2px;
590
+ padding: 3px;
591
+ background: var(--surface-inset);
592
+ border: 1px solid var(--hair);
593
+ border-radius: var(--corner-md);
594
+ }
595
+
596
+ .mode-opt {
597
+ display: inline-flex;
598
+ }
599
+
600
+ .seg {
601
+ display: inline-flex;
602
+ align-items: center;
603
+ gap: 6px;
604
+ height: 30px;
605
+ padding: 0 12px;
606
+ border: none;
607
+ background: transparent;
608
+ border-radius: var(--corner-sm);
609
+ font: inherit;
610
+ font-size: 12px;
611
+ font-weight: 600;
612
+ color: var(--fg-tertiary);
613
+ cursor: pointer;
614
+ transition:
615
+ background var(--dur-fast),
616
+ color var(--dur-fast);
617
+ }
618
+
619
+ .seg-ic {
620
+ flex: 0 0 auto;
621
+ }
622
+
623
+ .seg-active {
624
+ background: var(--accent);
625
+ color: var(--accent-fg);
626
+ }
627
+
628
+ .seg-disabled {
629
+ opacity: 0.45;
630
+ cursor: not-allowed;
631
+ }
632
+
633
+ .composer-tip {
634
+ position: absolute;
635
+ bottom: calc(100% + 10px);
636
+ left: 0;
637
+ z-index: 10;
638
+ width: 268px;
639
+ max-width: 78vw;
640
+ padding: 13px 14px;
641
+ background: var(--surface-card);
642
+ border: 1px solid var(--hair-strong);
643
+ border-radius: var(--corner-lg);
644
+ box-shadow: 0 10px 30px -8px rgba(0, 0, 0, 0.35);
645
+ opacity: 0;
646
+ visibility: hidden;
647
+ transform: translateY(4px);
648
+ transition:
649
+ opacity var(--dur-fast),
650
+ transform var(--dur-fast),
651
+ visibility var(--dur-fast);
652
+ pointer-events: none;
653
+ }
654
+
655
+ .mode-opt:hover .composer-tip,
656
+ .mode-opt:focus-within .composer-tip {
657
+ opacity: 1;
658
+ visibility: visible;
659
+ transform: none;
660
+ }
661
+
662
+ .composer-tip b {
663
+ display: block;
664
+ margin-bottom: 5px;
665
+ font-size: 12.5px;
666
+ color: var(--fg);
667
+ }
668
+
669
+ .composer-tip p {
670
+ margin: 0 0 8px;
671
+ font-size: 12px;
672
+ line-height: 1.5;
673
+ color: var(--fg-tertiary);
674
+ }
675
+
676
+ .composer-tip p:last-child {
677
+ margin-bottom: 0;
678
+ }
679
+
680
+ .composer-tip .tip-note {
681
+ padding: 7px 9px;
682
+ border: 1px solid color-mix(in oklab, var(--warning-400) 30%, transparent);
683
+ border-radius: 7px;
684
+ background: var(--warning-bg);
685
+ font-size: 11px;
686
+ line-height: 1.45;
687
+ color: var(--warning-400);
688
+ }
689
+
690
+ .composer-actions {
691
+ margin-left: auto;
692
+ display: flex;
693
+ align-items: center;
694
+ gap: 8px;
695
+ }
696
+
697
+ .composer-send {
698
+ width: 40px;
699
+ height: 40px;
700
+ flex: 0 0 auto;
701
+ display: grid;
702
+ place-items: center;
703
+ border: none;
704
+ border-radius: var(--corner-md);
705
+ background: var(--accent);
706
+ color: var(--accent-fg);
707
+ cursor: pointer;
708
+ }
709
+
710
+ .composer-send:hover:not(:disabled) {
711
+ background: var(--accent-strong);
712
+ }
713
+
714
+ .composer-send:disabled {
715
+ opacity: 0.4;
716
+ cursor: not-allowed;
717
+ }
718
+
719
+ .composer-stop {
720
+ width: 40px;
721
+ height: 40px;
722
+ flex: 0 0 auto;
723
+ display: grid;
724
+ place-items: center;
725
+ border: 1px solid var(--hair);
726
+ border-radius: var(--corner-md);
727
+ background: var(--surface-card-2);
728
+ color: var(--fg);
729
+ cursor: pointer;
730
+ }
731
+
732
+ .composer-stop:hover:not(:disabled) {
733
+ background: var(--danger-bg);
734
+ border-color: color-mix(in srgb, var(--danger-400) 35%, transparent);
735
+ color: var(--danger-400);
736
+ }
737
+
738
+ .composer-stop:disabled {
739
+ opacity: 0.4;
740
+ cursor: not-allowed;
741
+ }
742
+ </style>