@oro.ad/nuxt-claude-devtools 1.3.0 → 1.5.1

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 (111) hide show
  1. package/README.md +2 -1
  2. package/dist/client/200.html +1 -1
  3. package/dist/client/404.html +1 -1
  4. package/dist/client/_nuxt/{COus5Ssl.js → 0BAoaFXM.js} +1 -1
  5. package/dist/client/_nuxt/{B_BoWmnX.js → 88aFj9mk.js} +1 -1
  6. package/dist/client/_nuxt/{DolUcBed.js → B5vRr8Ti.js} +1 -1
  7. package/dist/client/_nuxt/{CDQtmRaX.js → BI4BFakr.js} +1 -1
  8. package/dist/client/_nuxt/{CPA0s6N9.js → BJuQJ8yP.js} +1 -1
  9. package/dist/client/_nuxt/{BB1-kxmm.js → BMxGt8ZG.js} +1 -1
  10. package/dist/client/_nuxt/{TQi6eIO6.js → Baq9Hzkz.js} +1 -1
  11. package/dist/client/_nuxt/{DbJLoP3G.js → Bf779K50.js} +1 -1
  12. package/dist/client/_nuxt/BmF8r-gh.js +1 -0
  13. package/dist/client/_nuxt/BryQ1L4F.js +1 -0
  14. package/dist/client/_nuxt/{V4UvAcd3.js → BsG0VKct.js} +1 -1
  15. package/dist/client/_nuxt/Bt-DdLhQ.js +12 -0
  16. package/dist/client/_nuxt/{CHeJJZL9.js → C6Xm_PzB.js} +1 -1
  17. package/dist/client/_nuxt/{DC_XB519.js → C8AkZ-W3.js} +1 -1
  18. package/dist/client/_nuxt/{BSVkH7b6.js → CEM1iRz_.js} +1 -1
  19. package/dist/client/_nuxt/{BcZxFXBD.js → CETmet01.js} +1 -1
  20. package/dist/client/_nuxt/CG1tBJBN.js +1 -0
  21. package/dist/client/_nuxt/{D2l4TRxW.js → CXOn8Upj.js} +1 -1
  22. package/dist/client/_nuxt/CYomf6PL.js +1 -0
  23. package/dist/client/_nuxt/{DEys9N1G.js → CrhTgxiU.js} +1 -1
  24. package/dist/client/_nuxt/{BYp73eMl.js → CtWfW1XP.js} +1 -1
  25. package/dist/client/_nuxt/{d8BPa19J.js → CuUOvg0u.js} +1 -1
  26. package/dist/client/_nuxt/CxYvL5O5.js +1 -0
  27. package/dist/client/_nuxt/D8DUrd91.js +1 -0
  28. package/dist/client/_nuxt/{DSt96JPY.js → DFkDlupw.js} +2 -2
  29. package/dist/client/_nuxt/{B8uzckkK.js → DG-9quB_.js} +1 -1
  30. package/dist/client/_nuxt/DHlhBFAg.js +1 -0
  31. package/dist/client/_nuxt/{BnXQTjo-.js → DJw5U4z8.js} +1 -1
  32. package/dist/client/_nuxt/{M6QPYocW.js → DRReoGGV.js} +1 -1
  33. package/dist/client/_nuxt/{DGQ4s7ae.js → DSMailoF.js} +1 -1
  34. package/dist/client/_nuxt/{qbS8UemQ.js → DUPgidXP.js} +1 -1
  35. package/dist/client/_nuxt/{QumocfwJ.js → D_JyZ6Hh.js} +1 -1
  36. package/dist/client/_nuxt/{BAb1fJOF.js → Dgm9zqhP.js} +2 -2
  37. package/dist/client/_nuxt/builds/latest.json +1 -1
  38. package/dist/client/_nuxt/builds/meta/024950b2-9b05-4789-9918-c859c91d6b13.json +1 -0
  39. package/dist/client/_nuxt/{C--9REmc.js → h1c4XpR7.js} +1 -1
  40. package/dist/client/_nuxt/kN0L7CSs.js +1 -0
  41. package/dist/client/_nuxt/{BMZIbUUD.js → n4TwJfzb.js} +1 -1
  42. package/dist/client/_nuxt/zY60w9UT.js +1 -0
  43. package/dist/client/agents/index.html +1 -1
  44. package/dist/client/commands/index.html +1 -1
  45. package/dist/client/docs/index.html +1 -1
  46. package/dist/client/index.html +1 -1
  47. package/dist/client/mcp/index.html +1 -1
  48. package/dist/client/plugins/index.html +1 -1
  49. package/dist/client/settings/index.html +1 -1
  50. package/dist/client/skills/index.html +1 -1
  51. package/dist/module.json +1 -1
  52. package/dist/module.mjs +10 -4
  53. package/dist/runtime/overlay/components/ChatOverlay.d.vue.ts +0 -1
  54. package/dist/runtime/overlay/components/ChatOverlay.vue +221 -731
  55. package/dist/runtime/overlay/components/ChatOverlay.vue.d.ts +0 -1
  56. package/dist/runtime/overlay/components/MarkdownContent.vue +1 -1
  57. package/dist/runtime/overlay/components/ToolCallBlock.vue +1 -1
  58. package/dist/runtime/overlay/components/chat/ChatHeader.d.vue.ts +18 -0
  59. package/dist/runtime/overlay/components/chat/ChatHeader.vue +82 -0
  60. package/dist/runtime/overlay/components/chat/ChatHeader.vue.d.ts +18 -0
  61. package/dist/runtime/overlay/components/chat/ChatInput.d.vue.ts +22 -0
  62. package/dist/runtime/overlay/components/chat/ChatInput.vue +526 -0
  63. package/dist/runtime/overlay/components/chat/ChatInput.vue.d.ts +22 -0
  64. package/dist/runtime/overlay/components/chat/ChatMessages.d.vue.ts +13 -0
  65. package/dist/runtime/overlay/components/chat/ChatMessages.vue +160 -0
  66. package/dist/runtime/overlay/components/chat/ChatMessages.vue.d.ts +13 -0
  67. package/dist/runtime/overlay/components/chat/ClaudeBadge.d.vue.ts +22 -0
  68. package/dist/runtime/overlay/components/chat/ClaudeBadge.vue +85 -0
  69. package/dist/runtime/overlay/components/chat/ClaudeBadge.vue.d.ts +22 -0
  70. package/dist/runtime/overlay/components/chat/HistoryPanel.d.vue.ts +17 -0
  71. package/dist/runtime/overlay/components/chat/HistoryPanel.vue +65 -0
  72. package/dist/runtime/overlay/components/chat/HistoryPanel.vue.d.ts +17 -0
  73. package/dist/runtime/overlay/components/chat/NicknameModal.d.vue.ts +13 -0
  74. package/dist/runtime/overlay/components/chat/NicknameModal.vue +89 -0
  75. package/dist/runtime/overlay/components/chat/NicknameModal.vue.d.ts +13 -0
  76. package/dist/runtime/overlay/components/chat/index.d.ts +6 -0
  77. package/dist/runtime/overlay/components/chat/index.js +6 -0
  78. package/dist/runtime/overlay/composables/index.d.ts +3 -0
  79. package/dist/runtime/overlay/composables/index.js +3 -0
  80. package/dist/runtime/overlay/composables/useMessageContext.d.ts +25 -0
  81. package/dist/runtime/overlay/composables/useMessageContext.js +29 -0
  82. package/dist/runtime/overlay/composables/useMobileSwipe.d.ts +15 -0
  83. package/dist/runtime/overlay/composables/useMobileSwipe.js +79 -0
  84. package/dist/runtime/overlay/composables/usePanelInteraction.d.ts +30 -0
  85. package/dist/runtime/overlay/composables/usePanelInteraction.js +184 -0
  86. package/dist/runtime/overlay/composables/usePanelPosition.d.ts +69 -0
  87. package/dist/runtime/overlay/composables/usePanelPosition.js +147 -0
  88. package/dist/runtime/server/claude-session.d.ts +11 -0
  89. package/dist/runtime/server/claude-session.js +166 -6
  90. package/dist/runtime/shared/composables/useClaudeChat.d.ts +7 -3
  91. package/dist/runtime/shared/composables/useClaudeChat.js +27 -251
  92. package/dist/runtime/shared/composables/useClaudeChatCore.d.ts +40 -0
  93. package/dist/runtime/shared/composables/useClaudeChatCore.js +350 -0
  94. package/dist/runtime/shared/composables/useMessageContext.d.ts +54 -0
  95. package/dist/runtime/shared/composables/useMessageContext.js +195 -0
  96. package/dist/runtime/shared/composables/useShare.d.ts +14 -4
  97. package/dist/runtime/shared/composables/useShare.js +57 -35
  98. package/dist/runtime/shared/composables/useVoiceInput.js +40 -11
  99. package/dist/runtime/shared/types.d.ts +36 -0
  100. package/dist/runtime/types.d.ts +14 -0
  101. package/package.json +1 -1
  102. package/dist/client/_nuxt/BSF2Vz9o.js +0 -1
  103. package/dist/client/_nuxt/BflmC3YB.js +0 -1
  104. package/dist/client/_nuxt/CRkq21kc.js +0 -1
  105. package/dist/client/_nuxt/Cgba93Y9.js +0 -1
  106. package/dist/client/_nuxt/DH8Ugy8E.js +0 -1
  107. package/dist/client/_nuxt/DeGmaFBY.js +0 -1
  108. package/dist/client/_nuxt/DgfRwrFR.js +0 -1
  109. package/dist/client/_nuxt/builds/meta/2be12f06-336a-4fdd-b982-2f6c682c14a6.json +0 -1
  110. package/dist/client/_nuxt/wDw60tEC.js +0 -10
  111. package/dist/client/_nuxt/xEjB6ozD.js +0 -1
@@ -0,0 +1,526 @@
1
+ <script setup>
2
+ import { computed, nextTick, onMounted, onUnmounted, ref } from "vue";
3
+ const props = defineProps({
4
+ isConnected: { type: Boolean, required: true },
5
+ isProcessing: { type: Boolean, required: true },
6
+ isRecording: { type: Boolean, required: true },
7
+ isSpeechSupported: { type: Boolean, required: true },
8
+ commands: { type: Array, required: true }
9
+ });
10
+ const emit = defineEmits(["submit", "voiceInput", "stop"]);
11
+ const inputMessage = ref("");
12
+ const textareaRef = ref(null);
13
+ const inputContainerRef = ref(null);
14
+ const fileInputRef = ref(null);
15
+ const showCommandsAutocomplete = ref(false);
16
+ const showAttachMenu = ref(false);
17
+ const isMobile = ref(false);
18
+ const isDragOver = ref(false);
19
+ const pendingImages = ref([]);
20
+ const IMAGE_TYPES = ["image/png", "image/jpeg", "image/jpg", "image/gif", "image/webp"];
21
+ const MAX_FILE_SIZE = 10 * 1024 * 1024;
22
+ function checkMobile() {
23
+ if (typeof window === "undefined") return false;
24
+ return window.innerWidth <= 640 || /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
25
+ }
26
+ onMounted(() => {
27
+ isMobile.value = checkMobile();
28
+ window.addEventListener("resize", () => {
29
+ isMobile.value = checkMobile();
30
+ });
31
+ document.addEventListener("paste", handlePaste);
32
+ document.addEventListener("click", closeAttachMenuOnClickOutside);
33
+ });
34
+ onUnmounted(() => {
35
+ window.removeEventListener("resize", () => {
36
+ isMobile.value = checkMobile();
37
+ });
38
+ document.removeEventListener("paste", handlePaste);
39
+ document.removeEventListener("click", closeAttachMenuOnClickOutside);
40
+ });
41
+ const hasContent = computed(() => inputMessage.value.trim().length > 0 || pendingImages.value.length > 0);
42
+ const hasText = computed(() => inputMessage.value.trim().length > 0);
43
+ const filteredCommands = computed(() => {
44
+ if (!showCommandsAutocomplete.value) return [];
45
+ const match = inputMessage.value.match(/(?:^|\s)\/(\S*)$/);
46
+ if (!match) return [];
47
+ const query = match[1].toLowerCase();
48
+ return props.commands.filter(
49
+ (cmd) => cmd.name.toLowerCase().includes(query)
50
+ ).slice(0, 5);
51
+ });
52
+ function handleInput() {
53
+ const match = inputMessage.value.match(/(?:^|\s)\/\S*$/);
54
+ showCommandsAutocomplete.value = !!match;
55
+ autoResizeTextarea();
56
+ }
57
+ function autoResizeTextarea() {
58
+ const textarea = textareaRef.value;
59
+ if (!textarea) return;
60
+ textarea.style.height = "auto";
61
+ textarea.style.height = `${Math.min(textarea.scrollHeight, 280)}px`;
62
+ }
63
+ function selectCommand(cmd) {
64
+ const match = inputMessage.value.match(/(?:^|\s)(\/\S*)$/);
65
+ if (match) {
66
+ const slashIndex = inputMessage.value.length - match[1].length;
67
+ inputMessage.value = inputMessage.value.slice(0, slashIndex) + `/${cmd.name} `;
68
+ }
69
+ showCommandsAutocomplete.value = false;
70
+ textareaRef.value?.focus();
71
+ nextTick(autoResizeTextarea);
72
+ }
73
+ function handleSubmit() {
74
+ if (!inputMessage.value.trim() && pendingImages.value.length === 0 || props.isProcessing) return;
75
+ emit("submit", inputMessage.value, pendingImages.value.length > 0 ? [...pendingImages.value] : void 0);
76
+ inputMessage.value = "";
77
+ pendingImages.value = [];
78
+ showCommandsAutocomplete.value = false;
79
+ if (textareaRef.value) {
80
+ textareaRef.value.style.height = "auto";
81
+ }
82
+ }
83
+ function handleKeydown(e) {
84
+ if (e.key === "Enter" && !e.shiftKey && !isMobile.value) {
85
+ e.preventDefault();
86
+ handleSubmit();
87
+ }
88
+ if (e.key === "Escape" && showCommandsAutocomplete.value) {
89
+ showCommandsAutocomplete.value = false;
90
+ }
91
+ }
92
+ function generateImageId() {
93
+ return `img_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
94
+ }
95
+ function isImageType(mimeType) {
96
+ return IMAGE_TYPES.includes(mimeType) || mimeType.startsWith("image/");
97
+ }
98
+ async function processFile(file) {
99
+ if (file.size > MAX_FILE_SIZE) {
100
+ console.warn("File too large:", file.size);
101
+ return null;
102
+ }
103
+ return new Promise((resolve) => {
104
+ const reader = new FileReader();
105
+ reader.onload = (e) => {
106
+ const dataUrl = e.target?.result;
107
+ const base64Data = dataUrl.split(",")[1];
108
+ if (!base64Data) {
109
+ resolve(null);
110
+ return;
111
+ }
112
+ const ext = file.name.split(".").pop() || "bin";
113
+ resolve({
114
+ id: generateImageId(),
115
+ filename: file.name || `file_${Date.now()}.${ext}`,
116
+ mimeType: file.type || "application/octet-stream",
117
+ data: base64Data,
118
+ size: file.size
119
+ });
120
+ };
121
+ reader.onerror = () => resolve(null);
122
+ reader.readAsDataURL(file);
123
+ });
124
+ }
125
+ async function handlePaste(e) {
126
+ const items = e.clipboardData?.items;
127
+ if (!items) return;
128
+ for (const item of items) {
129
+ if (item.kind === "file") {
130
+ e.preventDefault();
131
+ const file = item.getAsFile();
132
+ if (file) {
133
+ const attachment = await processFile(file);
134
+ if (attachment) {
135
+ pendingImages.value.push(attachment);
136
+ }
137
+ }
138
+ return;
139
+ }
140
+ }
141
+ }
142
+ function handleDragOver(e) {
143
+ e.preventDefault();
144
+ e.stopPropagation();
145
+ isDragOver.value = true;
146
+ }
147
+ function handleDragLeave(e) {
148
+ e.preventDefault();
149
+ e.stopPropagation();
150
+ isDragOver.value = false;
151
+ }
152
+ async function handleDrop(e) {
153
+ e.preventDefault();
154
+ e.stopPropagation();
155
+ isDragOver.value = false;
156
+ const files = e.dataTransfer?.files;
157
+ if (!files) return;
158
+ for (const file of files) {
159
+ const attachment = await processFile(file);
160
+ if (attachment) {
161
+ pendingImages.value.push(attachment);
162
+ }
163
+ }
164
+ }
165
+ function removeImage(id) {
166
+ pendingImages.value = pendingImages.value.filter((img) => img.id !== id);
167
+ }
168
+ function getImagePreviewUrl(attachment) {
169
+ return `data:${attachment.mimeType};base64,${attachment.data}`;
170
+ }
171
+ function toggleAttachMenu() {
172
+ showAttachMenu.value = !showAttachMenu.value;
173
+ }
174
+ function openImage() {
175
+ showAttachMenu.value = false;
176
+ if (fileInputRef.value) {
177
+ fileInputRef.value.accept = "image/*";
178
+ fileInputRef.value.removeAttribute("capture");
179
+ fileInputRef.value.click();
180
+ }
181
+ }
182
+ function openCamera() {
183
+ showAttachMenu.value = false;
184
+ if (fileInputRef.value) {
185
+ fileInputRef.value.accept = "image/*";
186
+ fileInputRef.value.setAttribute("capture", "environment");
187
+ fileInputRef.value.click();
188
+ }
189
+ }
190
+ async function openFile() {
191
+ showAttachMenu.value = false;
192
+ if ("showOpenFilePicker" in window) {
193
+ try {
194
+ const handles = await window.showOpenFilePicker({ multiple: true });
195
+ for (const handle of handles) {
196
+ const file = await handle.getFile();
197
+ const attachment = await processFile(file);
198
+ if (attachment) {
199
+ pendingImages.value.push(attachment);
200
+ }
201
+ }
202
+ return;
203
+ } catch (e) {
204
+ if (e.name === "AbortError") return;
205
+ }
206
+ }
207
+ if (fileInputRef.value) {
208
+ fileInputRef.value.accept = ".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.rtf,.csv,.json,.xml,.html,.css,.js,.ts,.py,.md,.zip,.rar,.7z,.tar,.gz,.log,.yaml,.yml,.toml,.env,.sh,.sql";
209
+ fileInputRef.value.removeAttribute("capture");
210
+ fileInputRef.value.click();
211
+ }
212
+ }
213
+ async function handleFileSelect(e) {
214
+ const input = e.target;
215
+ const files = input.files;
216
+ if (!files) return;
217
+ for (const file of files) {
218
+ const attachment = await processFile(file);
219
+ if (attachment) {
220
+ pendingImages.value.push(attachment);
221
+ }
222
+ }
223
+ input.value = "";
224
+ }
225
+ function closeAttachMenuOnClickOutside(e) {
226
+ const target = e.target;
227
+ if (!target.closest(".claude-attach-menu") && !target.closest(".claude-attach-btn")) {
228
+ showAttachMenu.value = false;
229
+ }
230
+ }
231
+ defineExpose({
232
+ focus: () => textareaRef.value?.focus(),
233
+ appendText: (text) => {
234
+ inputMessage.value += text + " ";
235
+ nextTick(autoResizeTextarea);
236
+ }
237
+ });
238
+ </script>
239
+
240
+ <template>
241
+ <div
242
+ ref="inputContainerRef"
243
+ :class="['claude-input-container', { 'claude-drag-over': isDragOver }]"
244
+ @dragover="handleDragOver"
245
+ @dragleave="handleDragLeave"
246
+ @drop="handleDrop"
247
+ >
248
+ <!-- Commands autocomplete -->
249
+ <div
250
+ v-if="showCommandsAutocomplete && filteredCommands.length > 0"
251
+ class="claude-autocomplete"
252
+ >
253
+ <button
254
+ v-for="cmd in filteredCommands"
255
+ :key="cmd.name"
256
+ class="claude-autocomplete-item"
257
+ @click="selectCommand(cmd)"
258
+ >
259
+ <span class="claude-autocomplete-name">/{{ cmd.name }}</span>
260
+ <span
261
+ v-if="cmd.description"
262
+ class="claude-autocomplete-desc"
263
+ >{{ cmd.description }}</span>
264
+ </button>
265
+ </div>
266
+
267
+ <!-- File/Image previews -->
268
+ <div
269
+ v-if="pendingImages.length > 0"
270
+ class="claude-file-previews"
271
+ >
272
+ <div
273
+ v-for="file in pendingImages"
274
+ :key="file.id"
275
+ :class="['claude-file-preview', { 'claude-file-preview-image': isImageType(file.mimeType) }]"
276
+ :title="file.filename"
277
+ >
278
+ <!-- Image preview -->
279
+ <img
280
+ v-if="isImageType(file.mimeType)"
281
+ :src="getImagePreviewUrl(file)"
282
+ :alt="file.filename"
283
+ >
284
+ <!-- File icon + name -->
285
+ <template v-else>
286
+ <svg
287
+ class="claude-file-icon"
288
+ fill="currentColor"
289
+ height="20"
290
+ viewBox="0 0 24 24"
291
+ width="20"
292
+ >
293
+ <path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z" />
294
+ </svg>
295
+ <span class="claude-file-name">{{ file.filename }}</span>
296
+ </template>
297
+ <button
298
+ class="claude-file-remove"
299
+ title="Remove"
300
+ @click="removeImage(file.id)"
301
+ >
302
+ <svg
303
+ fill="currentColor"
304
+ height="14"
305
+ viewBox="0 0 24 24"
306
+ width="14"
307
+ >
308
+ <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
309
+ </svg>
310
+ </button>
311
+ </div>
312
+ </div>
313
+
314
+ <!-- Drag overlay -->
315
+ <div
316
+ v-if="isDragOver"
317
+ class="claude-drag-overlay"
318
+ >
319
+ <svg
320
+ fill="currentColor"
321
+ height="32"
322
+ viewBox="0 0 24 24"
323
+ width="32"
324
+ >
325
+ <path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z" />
326
+ </svg>
327
+ <span>Drop file here</span>
328
+ </div>
329
+
330
+ <!-- Hidden file input -->
331
+ <input
332
+ ref="fileInputRef"
333
+ accept="image/*"
334
+ class="claude-file-input"
335
+ multiple
336
+ type="file"
337
+ @change="handleFileSelect"
338
+ >
339
+
340
+ <div class="claude-input-wrapper">
341
+ <!-- Attachment button -->
342
+ <div class="claude-attach-wrapper">
343
+ <button
344
+ class="claude-attach-btn"
345
+ :disabled="!isConnected || isProcessing"
346
+ title="Attach file"
347
+ @click.stop="toggleAttachMenu"
348
+ >
349
+ <svg
350
+ fill="currentColor"
351
+ height="20"
352
+ viewBox="0 0 24 24"
353
+ width="20"
354
+ >
355
+ <path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z" />
356
+ </svg>
357
+ </button>
358
+
359
+ <!-- Attachment menu -->
360
+ <Transition name="claude-menu">
361
+ <div
362
+ v-if="showAttachMenu"
363
+ class="claude-attach-menu"
364
+ >
365
+ <button
366
+ class="claude-attach-option"
367
+ @click="openImage"
368
+ >
369
+ <svg
370
+ fill="currentColor"
371
+ height="18"
372
+ viewBox="0 0 24 24"
373
+ width="18"
374
+ >
375
+ <path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z" />
376
+ </svg>
377
+ <span>Image</span>
378
+ </button>
379
+ <button
380
+ class="claude-attach-option"
381
+ @click="openCamera"
382
+ >
383
+ <svg
384
+ fill="currentColor"
385
+ height="18"
386
+ viewBox="0 0 24 24"
387
+ width="18"
388
+ >
389
+ <path d="M12 15.2c1.77 0 3.2-1.43 3.2-3.2s-1.43-3.2-3.2-3.2-3.2 1.43-3.2 3.2 1.43 3.2 3.2 3.2zM9 2L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2h-3.17L15 2H9zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z" />
390
+ </svg>
391
+ <span>Camera</span>
392
+ </button>
393
+ <button
394
+ class="claude-attach-option"
395
+ @click="openFile"
396
+ >
397
+ <svg
398
+ fill="currentColor"
399
+ height="18"
400
+ viewBox="0 0 24 24"
401
+ width="18"
402
+ >
403
+ <path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z" />
404
+ </svg>
405
+ <span>File</span>
406
+ </button>
407
+ </div>
408
+ </Transition>
409
+ </div>
410
+
411
+ <textarea
412
+ ref="textareaRef"
413
+ v-model="inputMessage"
414
+ :disabled="!isConnected || isProcessing"
415
+ class="claude-input"
416
+ :placeholder="pendingImages.length > 0 ? 'Add a message (optional)...' : 'Ask Claude...'"
417
+ rows="1"
418
+ @input="handleInput"
419
+ @keydown="handleKeydown"
420
+ />
421
+ <div class="claude-input-actions">
422
+ <Transition
423
+ name="claude-btn"
424
+ mode="out-in"
425
+ >
426
+ <!-- Stop button (when AI is processing) -->
427
+ <button
428
+ v-if="isProcessing"
429
+ key="stop"
430
+ class="claude-stop-btn"
431
+ title="Stop generation"
432
+ @click="emit('stop')"
433
+ >
434
+ <svg
435
+ fill="currentColor"
436
+ height="20"
437
+ viewBox="0 0 24 24"
438
+ width="20"
439
+ >
440
+ <rect
441
+ height="14"
442
+ rx="2"
443
+ width="14"
444
+ x="5"
445
+ y="5"
446
+ />
447
+ </svg>
448
+ </button>
449
+ <!-- Voice recording button (when recording - always show to allow stop) -->
450
+ <button
451
+ v-else-if="isRecording"
452
+ key="recording"
453
+ class="claude-voice-btn claude-voice-recording"
454
+ title="Stop recording"
455
+ @click="emit('voiceInput')"
456
+ >
457
+ <svg
458
+ fill="currentColor"
459
+ height="20"
460
+ viewBox="0 0 24 24"
461
+ width="20"
462
+ >
463
+ <path d="M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z" />
464
+ </svg>
465
+ </button>
466
+ <!-- Send button (when there's content and not recording) -->
467
+ <button
468
+ v-else-if="hasContent"
469
+ key="send"
470
+ :disabled="!isConnected"
471
+ class="claude-send-btn"
472
+ @click="handleSubmit"
473
+ >
474
+ <svg
475
+ fill="currentColor"
476
+ height="20"
477
+ viewBox="0 0 24 24"
478
+ width="20"
479
+ >
480
+ <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
481
+ </svg>
482
+ </button>
483
+ <!-- Voice input button (default - when no text, not recording, voice supported) -->
484
+ <button
485
+ v-else-if="isSpeechSupported"
486
+ key="voice"
487
+ class="claude-voice-btn"
488
+ :disabled="!isConnected"
489
+ title="Voice input"
490
+ @click="emit('voiceInput')"
491
+ >
492
+ <svg
493
+ fill="currentColor"
494
+ height="20"
495
+ viewBox="0 0 24 24"
496
+ width="20"
497
+ >
498
+ <path d="M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z" />
499
+ </svg>
500
+ </button>
501
+ <!-- Fallback send button (when voice not supported) -->
502
+ <button
503
+ v-else
504
+ key="send-fallback"
505
+ :disabled="!hasText || !isConnected"
506
+ class="claude-send-btn"
507
+ @click="handleSubmit"
508
+ >
509
+ <svg
510
+ fill="currentColor"
511
+ height="20"
512
+ viewBox="0 0 24 24"
513
+ width="20"
514
+ >
515
+ <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
516
+ </svg>
517
+ </button>
518
+ </Transition>
519
+ </div>
520
+ </div>
521
+ </div>
522
+ </template>
523
+
524
+ <style>
525
+ .claude-input-container{background:transparent;bottom:0;left:0;padding:20px;pointer-events:none;position:absolute;right:0;z-index:10}.claude-input-container>*{pointer-events:auto}.claude-autocomplete{backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);background:rgba(0,0,0,.7);border:1px solid hsla(0,0%,100%,.1);border-radius:var(--claude-radius-sm);bottom:100%;box-shadow:0 8px 32px rgba(0,0,0,.4);left:20px;margin-bottom:8px;overflow:hidden;position:absolute;right:20px}.claude-autocomplete-item{background:transparent;border:none;border-bottom:1px solid hsla(0,0%,100%,.05);color:var(--claude-text);cursor:pointer;display:flex;flex-direction:column;gap:4px;padding:12px 16px;text-align:left;transition:all .15s ease;width:100%}.claude-autocomplete-item:last-child{border-bottom:none}.claude-autocomplete-item:hover{background:rgba(254,154,0,.15)}.claude-autocomplete-name{color:var(--claude-primary);font-family:JetBrains Mono,Fira Code,monospace;font-size:13px;text-shadow:0 0 15px var(--claude-primary-glow)}.claude-autocomplete-desc{color:var(--claude-text-muted);font-size:12px}.claude-file-input{display:none}.claude-input-wrapper{align-items:flex-end;display:flex;gap:10px}.claude-attach-wrapper{flex-shrink:0;position:relative}.claude-attach-btn{align-items:center;background:transparent;border:none;border-radius:8px;color:var(--claude-text-muted);cursor:pointer;display:flex;height:48px;justify-content:center;transition:all .2s ease;width:40px}.claude-attach-btn:hover:not(:disabled){background:hsla(0,0%,100%,.1);color:var(--claude-text)}.claude-attach-btn:disabled{cursor:not-allowed;opacity:.4}.claude-attach-menu{backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);background:rgba(0,0,0,.85);border:1px solid hsla(0,0%,100%,.15);border-radius:12px;bottom:100%;box-shadow:0 8px 32px rgba(0,0,0,.4);left:0;margin-bottom:8px;min-width:140px;overflow:hidden;position:absolute}.claude-attach-option{align-items:center;background:transparent;border:none;color:var(--claude-text);cursor:pointer;display:flex;font-size:14px;gap:12px;padding:12px 16px;transition:all .15s ease;width:100%}.claude-attach-option:hover{background:rgba(254,154,0,.15)}.claude-attach-option:not(:last-child){border-bottom:1px solid hsla(0,0%,100%,.1)}.claude-attach-option svg{color:var(--claude-primary)}.claude-menu-enter-active,.claude-menu-leave-active{transition:all .2s ease}.claude-menu-enter-from,.claude-menu-leave-to{opacity:0;transform:translateY(8px) scale(.95)}.claude-input{backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);background:rgba(0,0,0,.5);border:1px solid hsla(0,0%,100%,.1);border-radius:var(--claude-radius-sm);color:var(--claude-text);flex:1;font-family:inherit;font-size:14px;line-height:1.5;max-height:280px;min-height:48px;overflow-y:auto;padding:14px 18px;resize:none;scrollbar-width:none;-ms-overflow-style:none;box-shadow:0 8px 32px rgba(0,0,0,.3);transition:all .2s ease}.claude-input::-webkit-scrollbar{display:none}.claude-input:focus{background:rgba(0,0,0,.6);border-color:var(--claude-primary);box-shadow:0 8px 32px rgba(0,0,0,.4),0 0 0 3px rgba(254,154,0,.2),0 0 30px rgba(254,154,0,.15);outline:none}.claude-input::-moz-placeholder{color:var(--claude-text-dim)}.claude-input::placeholder{color:var(--claude-text-dim)}.claude-input:disabled{cursor:not-allowed;opacity:.4}.claude-input-actions{display:flex;flex-shrink:0;gap:10px}.claude-voice-btn{align-items:center;backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);background:rgba(0,0,0,.5);border:1px solid hsla(0,0%,100%,.1);border-radius:var(--claude-radius-sm);box-shadow:0 8px 32px rgba(0,0,0,.3);color:hsla(0,0%,100%,.6);cursor:pointer;display:flex;height:48px;justify-content:center;transition:all .2s ease;width:48px}.claude-voice-btn:hover:not(:disabled){background:rgba(0,0,0,.6);border-color:hsla(0,0%,100%,.2);box-shadow:0 12px 40px rgba(0,0,0,.4);color:#fff;transform:translateY(-2px)}.claude-voice-btn:disabled{cursor:not-allowed;opacity:.4}.claude-voice-recording{animation:claude-recording-pulse 1.5s ease-in-out infinite;background:linear-gradient(135deg,#ef4444,#dc2626)!important;border-color:rgba(239,68,68,.5)!important;box-shadow:0 8px 32px rgba(239,68,68,.4);color:#fff!important}@keyframes claude-recording-pulse{0%,to{box-shadow:0 8px 32px rgba(239,68,68,.4)}50%{box-shadow:0 8px 40px rgba(239,68,68,.6)}}.claude-send-btn{align-items:center;background:linear-gradient(135deg,#fe9a00,#f59e0b 50%,#fbbf24);border:none;border-radius:var(--claude-radius-sm);box-shadow:0 8px 32px rgba(254,154,0,.35);color:#fff;cursor:pointer;display:flex;height:48px;justify-content:center;transition:all .2s ease;width:48px}.claude-send-btn:hover:not(:disabled){box-shadow:0 12px 40px rgba(254,154,0,.45);transform:translateY(-2px)}.claude-send-btn:disabled{box-shadow:none;cursor:not-allowed;opacity:.4;transform:none}.claude-stop-btn{align-items:center;animation:claude-stop-pulse 2s ease-in-out infinite;background:linear-gradient(135deg,#ef4444,#dc2626);border:none;border-radius:var(--claude-radius-sm);box-shadow:0 8px 32px rgba(239,68,68,.35);color:#fff;cursor:pointer;display:flex;height:48px;justify-content:center;transition:all .2s ease;width:48px}.claude-stop-btn:hover{box-shadow:0 12px 40px rgba(239,68,68,.5);transform:translateY(-2px)}@keyframes claude-stop-pulse{0%,to{box-shadow:0 8px 32px rgba(239,68,68,.35)}50%{box-shadow:0 8px 40px rgba(239,68,68,.5)}}.claude-btn-enter-active,.claude-btn-leave-active{transition:all .2s ease}.claude-btn-enter-from{opacity:0;transform:scale(.8) rotate(-10deg)}.claude-btn-leave-to{opacity:0;transform:scale(.8) rotate(10deg)}.claude-file-previews{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:10px;padding:0}.claude-file-preview{align-items:center;background:rgba(0,0,0,.5);border:1px solid hsla(0,0%,100%,.1);border-radius:8px;display:flex;gap:6px;max-width:150px;padding:8px 12px;position:relative}.claude-file-preview-image{height:60px;overflow:hidden;padding:0;width:60px}.claude-file-preview-image img{height:100%;-o-object-fit:cover;object-fit:cover;width:100%}.claude-file-icon{color:var(--claude-primary);flex-shrink:0}.claude-file-name{color:var(--claude-text);font-size:12px;max-width:80px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.claude-file-remove{align-items:center;background:rgba(0,0,0,.7);border:none;border-radius:50%;color:#fff;cursor:pointer;display:flex;height:20px;justify-content:center;opacity:0;position:absolute;right:2px;top:2px;transition:all .15s ease;width:20px}.claude-file-preview:hover .claude-file-remove{opacity:1}.claude-file-remove:hover{background:#ef4444}.claude-input-container.claude-drag-over{position:relative}.claude-drag-overlay{align-items:center;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);background:rgba(254,154,0,.15);border:2px dashed var(--claude-primary);border-radius:var(--claude-radius-sm);color:var(--claude-primary);display:flex;flex-direction:column;font-size:14px;font-weight:500;gap:8px;inset:0;justify-content:center;position:absolute;z-index:20}@media (max-width:640px){.claude-input{font-size:16px}.claude-send-btn,.claude-stop-btn,.claude-voice-btn{height:52px;width:52px}.claude-input-container{padding:20px 16px calc(20px + env(safe-area-inset-bottom, 0px))}}
526
+ </style>
@@ -0,0 +1,22 @@
1
+ import type { ImageAttachment, SlashCommand } from '../../../shared/types.js';
2
+ type __VLS_Props = {
3
+ isConnected: boolean;
4
+ isProcessing: boolean;
5
+ isRecording: boolean;
6
+ isSpeechSupported: boolean;
7
+ commands: SlashCommand[];
8
+ };
9
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
10
+ focus: () => void | undefined;
11
+ appendText: (text: string) => void;
12
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
13
+ submit: (message: string, attachments?: ImageAttachment[] | undefined) => any;
14
+ stop: () => any;
15
+ voiceInput: () => any;
16
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
17
+ onSubmit?: ((message: string, attachments?: ImageAttachment[] | undefined) => any) | undefined;
18
+ onStop?: (() => any) | undefined;
19
+ onVoiceInput?: (() => any) | undefined;
20
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
21
+ declare const _default: typeof __VLS_export;
22
+ export default _default;
@@ -0,0 +1,13 @@
1
+ import type { ContentBlock, Message } from '../../../shared/types.js';
2
+ type __VLS_Props = {
3
+ messages: Message[];
4
+ isShareMode: boolean;
5
+ findToolResult: (blocks: ContentBlock[] | undefined, toolUseId: string) => ContentBlock | undefined;
6
+ isOwnMessage: (senderId?: string) => boolean;
7
+ };
8
+ declare function scrollToBottom(): void;
9
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
10
+ scrollToBottom: typeof scrollToBottom;
11
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
12
+ declare const _default: typeof __VLS_export;
13
+ export default _default;