@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
@@ -0,0 +1,323 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref } from "vue";
3
+ import {
4
+ PERMISSION_BUTTONS,
5
+ PERMISSION_DECISIONS,
6
+ PERMISSION_INLINE_LABELS,
7
+ PERMISSION_LABELS,
8
+ type PermissionDecision,
9
+ } from "../constants/permissions";
10
+ import type { PermissionRequestData } from "../types/permission";
11
+ import { stringifyValue } from "../utils/format";
12
+
13
+ interface Props {
14
+ requests: PermissionRequestData[];
15
+ }
16
+
17
+ const props = defineProps<Props>();
18
+ interface Emits {
19
+ decide: [requestId: string, decision: PermissionDecision];
20
+ decideAll: [decision: PermissionDecision];
21
+ }
22
+
23
+ const emit = defineEmits<Emits>();
24
+
25
+ const expanded = ref<Set<string>>(new Set());
26
+
27
+ const heading = computed<string>(() =>
28
+ props.requests.length > 1
29
+ ? PERMISSION_INLINE_LABELS.HEADING_MANY
30
+ : PERMISSION_INLINE_LABELS.HEADING_ONE,
31
+ );
32
+ const showBulk = computed<boolean>(() => props.requests.length > 1);
33
+
34
+ const argsTextById = computed<Record<string, string>>(() => {
35
+ const out: Record<string, string> = {};
36
+ for (const req of props.requests) {
37
+ out[req.requestId] = stringifyValue(req.args);
38
+ }
39
+ return out;
40
+ });
41
+
42
+ function isExpanded(id: string): boolean {
43
+ return expanded.value.has(id);
44
+ }
45
+
46
+ function toggleArgs(id: string): void {
47
+ const next = new Set(expanded.value);
48
+ if (next.has(id)) next.delete(id);
49
+ else next.add(id);
50
+ expanded.value = next;
51
+ }
52
+
53
+ function decide(id: string, decision: PermissionDecision): void {
54
+ emit("decide", id, decision);
55
+ }
56
+
57
+ function allowAll(): void {
58
+ emit("decideAll", PERMISSION_DECISIONS.ALLOW_ONCE);
59
+ }
60
+
61
+ function denyAll(): void {
62
+ emit("decideAll", PERMISSION_DECISIONS.DENY);
63
+ }
64
+ </script>
65
+
66
+ <template>
67
+ <section class="perm-tray" role="group" :aria-label="heading">
68
+ <header class="perm-tray-head">
69
+ <span class="perm-tray-title">{{ heading }}</span>
70
+ <div v-if="showBulk" class="perm-bulk">
71
+ <button type="button" class="perm-bulk-btn deny" @click="denyAll">
72
+ {{ PERMISSION_INLINE_LABELS.DENY_ALL }}
73
+ </button>
74
+ <button type="button" class="perm-bulk-btn allow" @click="allowAll">
75
+ {{ PERMISSION_INLINE_LABELS.APPLY_ALL }}
76
+ </button>
77
+ </div>
78
+ </header>
79
+
80
+ <article v-for="req in requests" :key="req.requestId" class="perm-card">
81
+ <div class="perm-card-top">
82
+ <span class="perm-icon" aria-hidden="true">
83
+ <svg
84
+ viewBox="0 0 24 24"
85
+ width="14"
86
+ height="14"
87
+ fill="none"
88
+ stroke="currentColor"
89
+ stroke-width="2"
90
+ stroke-linecap="round"
91
+ stroke-linejoin="round"
92
+ >
93
+ <path
94
+ d="M14.7 6.3a4 4 0 0 0-5.6 5.6l-6 6a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l6-6a4 4 0 0 0 5.6-5.6l-2.5 2.5-2-2 2.5-2.5z"
95
+ />
96
+ </svg>
97
+ </span>
98
+ <div class="perm-card-text">
99
+ <span class="perm-tool">{{ req.toolName }}</span>
100
+ <span class="perm-summary">{{ req.summary }}</span>
101
+ </div>
102
+ </div>
103
+
104
+ <button
105
+ v-if="argsTextById[req.requestId].length > 0"
106
+ type="button"
107
+ class="perm-args-toggle"
108
+ :aria-expanded="isExpanded(req.requestId)"
109
+ @click="toggleArgs(req.requestId)"
110
+ >
111
+ {{
112
+ isExpanded(req.requestId)
113
+ ? PERMISSION_LABELS.ARGS_TOGGLE_HIDE
114
+ : PERMISSION_LABELS.ARGS_TOGGLE_SHOW
115
+ }}
116
+ </button>
117
+ <pre v-if="isExpanded(req.requestId)" class="perm-args-pre">{{
118
+ argsTextById[req.requestId]
119
+ }}</pre>
120
+
121
+ <footer class="perm-actions">
122
+ <button
123
+ v-for="button in PERMISSION_BUTTONS"
124
+ :key="button.decision"
125
+ type="button"
126
+ class="perm-button"
127
+ :data-variant="button.variant"
128
+ @click="decide(req.requestId, button.decision)"
129
+ >
130
+ {{ button.label }}
131
+ </button>
132
+ </footer>
133
+ </article>
134
+ </section>
135
+ </template>
136
+
137
+ <style scoped>
138
+ .perm-tray {
139
+ display: flex;
140
+ flex-direction: column;
141
+ gap: 8px;
142
+ padding: 10px 12px;
143
+ border-top: 1px solid var(--hair-strong);
144
+ background: var(--surface-inset);
145
+ max-height: 46vh;
146
+ overflow-y: auto;
147
+ }
148
+
149
+ .perm-tray-head {
150
+ display: flex;
151
+ align-items: center;
152
+ gap: 10px;
153
+ }
154
+
155
+ .perm-tray-title {
156
+ flex: 1;
157
+ font-size: 11px;
158
+ font-weight: 700;
159
+ letter-spacing: 0.04em;
160
+ text-transform: uppercase;
161
+ color: var(--fg-tertiary);
162
+ }
163
+
164
+ .perm-bulk {
165
+ display: flex;
166
+ gap: 6px;
167
+ flex: 0 0 auto;
168
+ }
169
+
170
+ .perm-bulk-btn {
171
+ padding: 5px 11px;
172
+ border-radius: var(--corner-md);
173
+ border: 1px solid transparent;
174
+ font: inherit;
175
+ font-size: 12px;
176
+ font-weight: 600;
177
+ cursor: pointer;
178
+ }
179
+
180
+ .perm-bulk-btn.allow {
181
+ background: var(--accent);
182
+ color: var(--accent-fg);
183
+ }
184
+
185
+ .perm-bulk-btn.allow:hover {
186
+ background: var(--accent-strong);
187
+ }
188
+
189
+ .perm-bulk-btn.deny {
190
+ background: transparent;
191
+ color: var(--danger-400);
192
+ border-color: color-mix(in srgb, var(--danger-400) 35%, transparent);
193
+ }
194
+
195
+ .perm-bulk-btn.deny:hover {
196
+ background: color-mix(in srgb, var(--danger-400) 18%, transparent);
197
+ }
198
+
199
+ .perm-card {
200
+ display: flex;
201
+ flex-direction: column;
202
+ gap: 8px;
203
+ padding: 11px 12px;
204
+ border: 1px solid var(--hair);
205
+ border-radius: var(--corner-md);
206
+ background: var(--surface-card);
207
+ }
208
+
209
+ .perm-card-top {
210
+ display: flex;
211
+ align-items: center;
212
+ gap: 10px;
213
+ }
214
+
215
+ .perm-icon {
216
+ width: 28px;
217
+ height: 28px;
218
+ flex: 0 0 auto;
219
+ display: grid;
220
+ place-items: center;
221
+ border-radius: 7px;
222
+ background: var(--surface-inset);
223
+ border: 1px solid var(--hair);
224
+ color: var(--fg-secondary);
225
+ }
226
+
227
+ .perm-card-text {
228
+ flex: 1;
229
+ min-width: 0;
230
+ display: flex;
231
+ flex-direction: column;
232
+ gap: 2px;
233
+ }
234
+
235
+ .perm-tool {
236
+ font-family: var(--font-code);
237
+ font-size: 12.5px;
238
+ font-weight: 600;
239
+ color: var(--accent);
240
+ }
241
+
242
+ .perm-summary {
243
+ font-size: 12.5px;
244
+ line-height: 1.4;
245
+ color: var(--fg-secondary);
246
+ word-break: break-word;
247
+ }
248
+
249
+ .perm-args-toggle {
250
+ align-self: flex-start;
251
+ background: none;
252
+ border: none;
253
+ color: var(--accent);
254
+ font: inherit;
255
+ font-size: 12px;
256
+ padding: 0;
257
+ cursor: pointer;
258
+ }
259
+
260
+ .perm-args-toggle:hover {
261
+ text-decoration: underline;
262
+ }
263
+
264
+ .perm-args-pre {
265
+ margin: 0;
266
+ padding: 8px;
267
+ background: var(--surface-inset);
268
+ border: 1px solid var(--hair);
269
+ border-radius: var(--corner-sm);
270
+ font-family: var(--font-code);
271
+ font-size: 11px;
272
+ color: var(--fg-secondary);
273
+ white-space: pre-wrap;
274
+ word-break: break-word;
275
+ max-height: 200px;
276
+ overflow: auto;
277
+ }
278
+
279
+ .perm-actions {
280
+ display: flex;
281
+ gap: 8px;
282
+ flex-wrap: wrap;
283
+ }
284
+
285
+ .perm-button {
286
+ padding: 6px 12px;
287
+ border-radius: var(--corner-md);
288
+ border: 1px solid transparent;
289
+ font: inherit;
290
+ font-size: 12px;
291
+ font-weight: 600;
292
+ cursor: pointer;
293
+ }
294
+
295
+ .perm-button[data-variant="primary"] {
296
+ background: var(--accent);
297
+ color: var(--accent-fg);
298
+ }
299
+
300
+ .perm-button[data-variant="primary"]:hover {
301
+ background: var(--accent-strong);
302
+ }
303
+
304
+ .perm-button[data-variant="secondary"] {
305
+ background: var(--surface-inset);
306
+ color: var(--fg);
307
+ border-color: var(--hair);
308
+ }
309
+
310
+ .perm-button[data-variant="secondary"]:hover {
311
+ background: var(--surface-card-2);
312
+ }
313
+
314
+ .perm-button[data-variant="danger"] {
315
+ background: var(--danger-bg);
316
+ color: var(--danger-400);
317
+ border-color: color-mix(in srgb, var(--danger-400) 35%, transparent);
318
+ }
319
+
320
+ .perm-button[data-variant="danger"]:hover {
321
+ background: color-mix(in srgb, var(--danger-400) 20%, transparent);
322
+ }
323
+ </style>
@@ -0,0 +1,275 @@
1
+ <script setup lang="ts">
2
+ import { computed, reactive } from "vue";
3
+ import type { QuestionRequestData } from "../types/question";
4
+
5
+ interface Props {
6
+ requests: QuestionRequestData[];
7
+ }
8
+
9
+ const props = defineProps<Props>();
10
+ interface Emits {
11
+ answer: [requestId: string, answers: string[]];
12
+ }
13
+
14
+ const emit = defineEmits<Emits>();
15
+
16
+ interface Selection {
17
+ mode: "option" | "other";
18
+ option: string;
19
+ other: string;
20
+ }
21
+
22
+ const state = reactive<Record<string, Selection>>({});
23
+
24
+ function keyOf(requestId: string, qIndex: number): string {
25
+ return `${requestId}#${qIndex}`;
26
+ }
27
+
28
+ function entryOf(requestId: string, qIndex: number): Selection {
29
+ const key = keyOf(requestId, qIndex);
30
+ const existing = state[key];
31
+ if (existing !== undefined) return existing;
32
+ const fresh: Selection = { mode: "option", option: "", other: "" };
33
+ state[key] = fresh;
34
+ return fresh;
35
+ }
36
+
37
+ function answerFor(requestId: string, qIndex: number): string {
38
+ const entry = state[keyOf(requestId, qIndex)];
39
+ if (entry === undefined) return "";
40
+ return entry.mode === "other" ? entry.other.trim() : entry.option;
41
+ }
42
+
43
+ function isOptionSelected(
44
+ requestId: string,
45
+ qIndex: number,
46
+ label: string,
47
+ ): boolean {
48
+ const entry = state[keyOf(requestId, qIndex)];
49
+ return entry?.mode === "option" && entry.option === label;
50
+ }
51
+
52
+ function isOtherActive(requestId: string, qIndex: number): boolean {
53
+ return state[keyOf(requestId, qIndex)]?.mode === "other";
54
+ }
55
+
56
+ function pickOption(requestId: string, qIndex: number, label: string): void {
57
+ const entry = entryOf(requestId, qIndex);
58
+ entry.mode = "option";
59
+ entry.option = label;
60
+ }
61
+
62
+ function pickOther(requestId: string, qIndex: number): void {
63
+ entryOf(requestId, qIndex).mode = "other";
64
+ }
65
+
66
+ function onOtherInput(requestId: string, qIndex: number, event: Event): void {
67
+ entryOf(requestId, qIndex).other = (event.target as HTMLInputElement).value;
68
+ }
69
+
70
+ const answerableById = computed<Record<string, boolean>>(() => {
71
+ const out: Record<string, boolean> = {};
72
+ for (const req of props.requests) {
73
+ out[req.requestId] = req.questions.every(
74
+ (_q, qi) => answerFor(req.requestId, qi).length > 0,
75
+ );
76
+ }
77
+ return out;
78
+ });
79
+
80
+ function submit(req: QuestionRequestData): void {
81
+ if (!answerableById.value[req.requestId]) return;
82
+ const answers = req.questions.map((_q, qi) => answerFor(req.requestId, qi));
83
+ emit("answer", req.requestId, answers);
84
+ }
85
+ </script>
86
+
87
+ <template>
88
+ <section class="q-tray" role="group" aria-label="Question from the assistant">
89
+ <article v-for="req in requests" :key="req.requestId" class="q-card">
90
+ <div v-for="(question, qi) in req.questions" :key="qi" class="q-block">
91
+ <span class="q-header">{{ question.header }}</span>
92
+ <p class="q-question">{{ question.question }}</p>
93
+
94
+ <div class="q-options">
95
+ <button
96
+ v-for="option in question.options"
97
+ :key="option.label"
98
+ type="button"
99
+ class="q-option"
100
+ :data-selected="isOptionSelected(req.requestId, qi, option.label)"
101
+ :title="option.description"
102
+ @click="pickOption(req.requestId, qi, option.label)"
103
+ >
104
+ <span class="q-option-label">{{ option.label }}</span>
105
+ <span class="q-option-desc">{{ option.description }}</span>
106
+ </button>
107
+
108
+ <div class="q-other">
109
+ <button
110
+ type="button"
111
+ class="q-option q-other-toggle"
112
+ :data-selected="isOtherActive(req.requestId, qi)"
113
+ @click="pickOther(req.requestId, qi)"
114
+ >
115
+ <span class="q-option-label">Other…</span>
116
+ </button>
117
+ <input
118
+ v-if="isOtherActive(req.requestId, qi)"
119
+ type="text"
120
+ class="q-other-input"
121
+ placeholder="Type a custom answer"
122
+ @input="onOtherInput(req.requestId, qi, $event)"
123
+ @keydown.enter.prevent="submit(req)"
124
+ />
125
+ </div>
126
+ </div>
127
+ </div>
128
+
129
+ <footer class="q-actions">
130
+ <button
131
+ type="button"
132
+ class="q-send"
133
+ :disabled="!answerableById[req.requestId]"
134
+ @click="submit(req)"
135
+ >
136
+ Send answer
137
+ </button>
138
+ </footer>
139
+ </article>
140
+ </section>
141
+ </template>
142
+
143
+ <style scoped>
144
+ .q-tray {
145
+ display: flex;
146
+ flex-direction: column;
147
+ gap: 8px;
148
+ padding: 10px 12px;
149
+ border-top: 1px solid var(--hair-strong);
150
+ background: var(--surface-inset);
151
+ max-height: 52vh;
152
+ overflow-y: auto;
153
+ }
154
+
155
+ .q-card {
156
+ display: flex;
157
+ flex-direction: column;
158
+ gap: 12px;
159
+ padding: 12px;
160
+ border: 1px solid var(--hair);
161
+ border-radius: var(--corner-md);
162
+ background: var(--surface-card);
163
+ }
164
+
165
+ .q-block {
166
+ display: flex;
167
+ flex-direction: column;
168
+ gap: 6px;
169
+ }
170
+
171
+ .q-header {
172
+ font-size: 11px;
173
+ font-weight: 700;
174
+ letter-spacing: 0.04em;
175
+ text-transform: uppercase;
176
+ color: var(--fg-tertiary);
177
+ }
178
+
179
+ .q-question {
180
+ margin: 0;
181
+ font-size: 13px;
182
+ line-height: 1.4;
183
+ color: var(--fg);
184
+ word-break: break-word;
185
+ }
186
+
187
+ .q-options {
188
+ display: flex;
189
+ flex-direction: column;
190
+ gap: 6px;
191
+ }
192
+
193
+ .q-option {
194
+ display: flex;
195
+ flex-direction: column;
196
+ gap: 2px;
197
+ text-align: left;
198
+ padding: 8px 10px;
199
+ border-radius: var(--corner-md);
200
+ border: 1px solid var(--hair);
201
+ background: var(--surface-inset);
202
+ color: var(--fg);
203
+ font: inherit;
204
+ cursor: pointer;
205
+ }
206
+
207
+ .q-option:hover {
208
+ background: var(--surface-card-2);
209
+ }
210
+
211
+ .q-option[data-selected="true"] {
212
+ border-color: var(--accent);
213
+ background: var(--accent-bg);
214
+ }
215
+
216
+ .q-option-label {
217
+ font-size: 12.5px;
218
+ font-weight: 600;
219
+ color: var(--fg);
220
+ }
221
+
222
+ .q-option-desc {
223
+ font-size: 11.5px;
224
+ line-height: 1.35;
225
+ color: var(--fg-secondary);
226
+ word-break: break-word;
227
+ }
228
+
229
+ .q-other {
230
+ display: flex;
231
+ flex-direction: column;
232
+ gap: 6px;
233
+ }
234
+
235
+ .q-other-input {
236
+ padding: 7px 10px;
237
+ border-radius: var(--corner-md);
238
+ border: 1px solid var(--hair);
239
+ background: var(--surface-inset);
240
+ color: var(--fg);
241
+ font: inherit;
242
+ font-size: 12.5px;
243
+ }
244
+
245
+ .q-other-input:focus {
246
+ outline: none;
247
+ border-color: var(--accent);
248
+ }
249
+
250
+ .q-actions {
251
+ display: flex;
252
+ justify-content: flex-end;
253
+ }
254
+
255
+ .q-send {
256
+ padding: 6px 14px;
257
+ border-radius: var(--corner-md);
258
+ border: 1px solid transparent;
259
+ background: var(--accent);
260
+ color: var(--accent-fg);
261
+ font: inherit;
262
+ font-size: 12px;
263
+ font-weight: 600;
264
+ cursor: pointer;
265
+ }
266
+
267
+ .q-send:hover:not(:disabled) {
268
+ background: var(--accent-strong);
269
+ }
270
+
271
+ .q-send:disabled {
272
+ opacity: 0.5;
273
+ cursor: not-allowed;
274
+ }
275
+ </style>
@@ -0,0 +1,109 @@
1
+ <script setup lang="ts">
2
+ import type { QueuedMessage } from "../types/conversation";
3
+
4
+ interface Props {
5
+ queue: QueuedMessage[];
6
+ }
7
+
8
+ defineProps<Props>();
9
+
10
+ const CANCEL_ICON = "i-ph-x";
11
+ interface Emits {
12
+ cancel: [id: string];
13
+ }
14
+
15
+ defineEmits<Emits>();
16
+
17
+ function attachmentLabel(message: QueuedMessage): string {
18
+ const count = message.attachments.length;
19
+ if (count === 0) return "";
20
+ return count === 1 ? "1 file" : `${count} files`;
21
+ }
22
+ </script>
23
+
24
+ <template>
25
+ <ul class="queued" aria-label="Queued messages">
26
+ <li v-for="message in queue" :key="message.id" class="queued-item">
27
+ <span class="queued-tag" aria-hidden="true">Queued</span>
28
+ <span class="queued-text">{{ message.content }}</span>
29
+ <span v-if="attachmentLabel(message).length > 0" class="queued-att">
30
+ {{ attachmentLabel(message) }}
31
+ </span>
32
+ <button
33
+ type="button"
34
+ class="queued-cancel"
35
+ :aria-label="`Cancel queued message`"
36
+ title="Cancel"
37
+ @click="$emit('cancel', message.id)"
38
+ >
39
+ <UIcon :name="CANCEL_ICON" class="size-[13px]" />
40
+ </button>
41
+ </li>
42
+ </ul>
43
+ </template>
44
+
45
+ <style scoped>
46
+ .queued {
47
+ list-style: none;
48
+ margin: 0;
49
+ padding: 8px 14px 0;
50
+ display: flex;
51
+ flex-direction: column;
52
+ gap: 4px;
53
+ background: var(--surface-side);
54
+ }
55
+
56
+ .queued-item {
57
+ display: flex;
58
+ align-items: center;
59
+ gap: 8px;
60
+ padding: 5px 8px;
61
+ background: var(--surface-inset);
62
+ border: 1px dashed var(--hair-strong);
63
+ border-radius: var(--corner-md);
64
+ font-size: 12.5px;
65
+ }
66
+
67
+ .queued-tag {
68
+ flex: 0 0 auto;
69
+ font-family: var(--font-code);
70
+ font-size: 9px;
71
+ font-weight: 700;
72
+ letter-spacing: 0.04em;
73
+ text-transform: uppercase;
74
+ color: var(--fg-tertiary);
75
+ }
76
+
77
+ .queued-text {
78
+ flex: 1;
79
+ min-width: 0;
80
+ white-space: nowrap;
81
+ overflow: hidden;
82
+ text-overflow: ellipsis;
83
+ color: var(--fg-secondary);
84
+ }
85
+
86
+ .queued-att {
87
+ flex: 0 0 auto;
88
+ font-size: 10px;
89
+ color: var(--fg-tertiary);
90
+ }
91
+
92
+ .queued-cancel {
93
+ flex: 0 0 auto;
94
+ display: grid;
95
+ place-items: center;
96
+ width: 20px;
97
+ height: 20px;
98
+ border: none;
99
+ background: transparent;
100
+ border-radius: 5px;
101
+ color: var(--fg-tertiary);
102
+ cursor: pointer;
103
+ }
104
+
105
+ .queued-cancel:hover {
106
+ background: var(--danger-bg);
107
+ color: var(--danger-400);
108
+ }
109
+ </style>