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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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/9c3f4183-f791-4377-86dc-370ce38cc49b.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 +0 -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
@@ -1,173 +1,76 @@
1
1
  <script setup>
2
- import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue";
2
+ import { nextTick, onMounted, onUnmounted, ref, watch } from "vue";
3
3
  import { useClaudeChat } from "../../shared/composables/useClaudeChat";
4
4
  import { useVoiceInput } from "../../shared/composables/useVoiceInput";
5
5
  import { useShare } from "../../shared/composables/useShare";
6
- import MarkdownContent from "./MarkdownContent.vue";
7
- import ToolCallBlock from "./ToolCallBlock.vue";
6
+ import { useMessageContext } from "../composables/useMessageContext";
7
+ import { useMobileSwipe, usePanelInteraction, usePanelPosition } from "../composables";
8
+ import { ChatHeader, ChatInput, ChatMessages, ClaudeBadge, HistoryPanel, NicknameModal } from "./chat";
8
9
  const props = defineProps({
9
10
  socketUrl: { type: String, required: false }
10
11
  });
11
12
  const isOpen = ref(false);
12
- const inputMessage = ref("");
13
- const messagesContainer = ref(null);
14
- const textareaRef = ref(null);
13
+ const isMobile = ref(false);
15
14
  const panelRef = ref(null);
16
15
  const headerRef = ref(null);
17
- const fabRef = ref(null);
18
- const FAB_STORAGE_KEY = "claude-overlay-fab-position";
19
- const fabPosition = ref(null);
20
- const isDraggingFab = ref(false);
21
- const fabDragStart = ref({ x: 0, y: 0, fabX: 0, fabY: 0 });
22
- const fabDragMoved = ref(false);
23
- function loadFabPosition() {
24
- try {
25
- const saved = localStorage.getItem(FAB_STORAGE_KEY);
26
- if (saved) {
27
- fabPosition.value = JSON.parse(saved);
28
- }
29
- } catch {
30
- }
31
- }
32
- function saveFabPosition() {
33
- if (fabPosition.value) {
34
- try {
35
- localStorage.setItem(FAB_STORAGE_KEY, JSON.stringify(fabPosition.value));
36
- } catch {
37
- }
38
- }
39
- }
40
- function getFabStyle() {
41
- if (!fabPosition.value) return {};
42
- return {
43
- left: `${fabPosition.value.x}px`,
44
- top: `${fabPosition.value.y}px`,
45
- right: "auto",
46
- bottom: "auto"
47
- };
48
- }
49
- function handleFabDragStart(e) {
50
- const clientX = "touches" in e ? e.touches[0].clientX : e.clientX;
51
- const clientY = "touches" in e ? e.touches[0].clientY : e.clientY;
52
- const fab = fabRef.value;
53
- if (!fab) return;
54
- const rect = fab.getBoundingClientRect();
55
- fabDragStart.value = {
56
- x: clientX,
57
- y: clientY,
58
- fabX: rect.left,
59
- fabY: rect.top
60
- };
61
- isDraggingFab.value = true;
62
- fabDragMoved.value = false;
63
- window.addEventListener("mousemove", handleFabDragMove);
64
- window.addEventListener("mouseup", handleFabDragEnd);
65
- window.addEventListener("touchmove", handleFabDragMove, { passive: false });
66
- window.addEventListener("touchend", handleFabDragEnd);
67
- }
68
- function handleFabDragMove(e) {
69
- if (!isDraggingFab.value) return;
70
- e.preventDefault();
71
- const clientX = "touches" in e ? e.touches[0].clientX : e.clientX;
72
- const clientY = "touches" in e ? e.touches[0].clientY : e.clientY;
73
- const deltaX = clientX - fabDragStart.value.x;
74
- const deltaY = clientY - fabDragStart.value.y;
75
- if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
76
- fabDragMoved.value = true;
77
- }
78
- const fabSize = 56;
79
- const padding = 16;
80
- let newX = fabDragStart.value.fabX + deltaX;
81
- let newY = fabDragStart.value.fabY + deltaY;
82
- newX = Math.max(padding, Math.min(window.innerWidth - fabSize - padding, newX));
83
- newY = Math.max(padding, Math.min(window.innerHeight - fabSize - padding, newY));
84
- fabPosition.value = { x: newX, y: newY };
16
+ const chatInputRef = ref(null);
17
+ const chatMessagesRef = ref(null);
18
+ function checkMobile() {
19
+ isMobile.value = window.innerWidth <= 640;
85
20
  }
86
- function handleFabDragEnd() {
87
- isDraggingFab.value = false;
88
- window.removeEventListener("mousemove", handleFabDragMove);
89
- window.removeEventListener("mouseup", handleFabDragEnd);
90
- window.removeEventListener("touchmove", handleFabDragMove);
91
- window.removeEventListener("touchend", handleFabDragEnd);
92
- if (fabDragMoved.value) {
93
- saveFabPosition();
94
- }
95
- }
96
- function handleFabClick() {
97
- if (!fabDragMoved.value) {
98
- toggleOverlay();
99
- }
100
- }
101
- const touchStartY = ref(0);
102
- const touchDeltaY = ref(0);
103
- const isSwiping = ref(false);
104
- const isSwipeClosing = ref(false);
105
- const SWIPE_THRESHOLD = 100;
106
- function handleTouchStart(e) {
107
- if (window.innerWidth > 640) return;
108
- touchStartY.value = e.touches[0].clientY;
109
- touchDeltaY.value = 0;
110
- isSwiping.value = true;
111
- }
112
- function handleTouchMove(e) {
113
- if (!isSwiping.value || window.innerWidth > 640) return;
114
- const currentY = e.touches[0].clientY;
115
- const delta = currentY - touchStartY.value;
116
- if (delta > 0) {
117
- e.preventDefault();
118
- touchDeltaY.value = delta;
119
- if (panelRef.value) {
120
- panelRef.value.style.transform = `translateY(${delta}px)`;
121
- panelRef.value.style.transition = "none";
122
- }
123
- }
21
+ const {
22
+ badgePos,
23
+ panelSize,
24
+ panelScreenPos,
25
+ load: loadPanelState,
26
+ openPanel,
27
+ closePanel,
28
+ onBadgeDragEnd,
29
+ getPanelStyle
30
+ } = usePanelPosition();
31
+ const {
32
+ isDragging: isPanelDragging,
33
+ isResizing: isPanelResizing,
34
+ activeEdge: activeResizeEdge,
35
+ hoveredEdge,
36
+ cursor: resizeCursor,
37
+ startDrag: handlePanelDragStart,
38
+ startResize: handlePanelResizeStart,
39
+ onMouseMove: handlePanelMouseMove,
40
+ onMouseLeave: handlePanelMouseLeave
41
+ } = usePanelInteraction({
42
+ panelScreenPos,
43
+ panelSize,
44
+ panelRef,
45
+ isMobile
46
+ });
47
+ function lockBodyScroll(lock) {
48
+ if (typeof document === "undefined") return;
49
+ document.body.style.overflow = lock ? "hidden" : "";
50
+ document.body.style.touchAction = lock ? "none" : "";
124
51
  }
125
- function handleTouchEnd() {
126
- if (!isSwiping.value || window.innerWidth > 640) return;
127
- isSwiping.value = false;
128
- if (panelRef.value) {
129
- panelRef.value.style.transition = "transform 0.3s ease-out";
130
- if (touchDeltaY.value > SWIPE_THRESHOLD) {
131
- isSwipeClosing.value = true;
132
- panelRef.value.style.transform = "translateY(100%)";
133
- setTimeout(() => {
134
- isOpen.value = false;
135
- lockBodyScroll(false);
136
- nextTick(() => {
137
- isSwipeClosing.value = false;
138
- if (panelRef.value) {
139
- panelRef.value.style.transform = "";
140
- panelRef.value.style.transition = "";
141
- }
142
- });
143
- }, 300);
144
- } else {
145
- panelRef.value.style.transform = "translateY(0)";
146
- setTimeout(() => {
147
- if (panelRef.value) {
148
- panelRef.value.style.transition = "";
149
- }
150
- }, 300);
151
- }
52
+ const {
53
+ isSwipeClosing,
54
+ handleTouchStart,
55
+ handleTouchEnd,
56
+ setupTouchMoveListener,
57
+ cleanupTouchMoveListener
58
+ } = useMobileSwipe({
59
+ panelRef,
60
+ headerRef,
61
+ onSwipeClose: () => {
62
+ isOpen.value = false;
63
+ lockBodyScroll(false);
152
64
  }
153
- touchDeltaY.value = 0;
154
- }
65
+ });
155
66
  const commands = ref([]);
156
- const showCommandsAutocomplete = ref(false);
157
67
  const showHistory = ref(false);
158
- const contextEnabled = ref({
159
- viewport: true,
160
- userAgent: true,
161
- routing: true
162
- });
163
68
  const {
164
69
  userId,
165
70
  nickname,
166
- users: _users,
167
71
  showNicknameModal,
168
72
  nicknameError,
169
73
  isShareMode,
170
- sharingActiveOnServer: _sharingActiveOnServer,
171
74
  initShare,
172
75
  setNickname,
173
76
  needsNicknameImmediate,
@@ -179,11 +82,9 @@ const {
179
82
  isOwnMessage,
180
83
  copyShareLink
181
84
  } = useShare({
182
- getTunnelUrl: () => props.socketUrl || null,
85
+ getBaseUrl: () => props.socketUrl || window.location.origin,
183
86
  log: (...args) => {
184
- if (import.meta.env.DEV) {
185
- console.log("[ChatOverlay:Share]", ...args);
186
- }
87
+ if (import.meta.env.DEV) console.log("[ChatOverlay:Share]", ...args);
187
88
  }
188
89
  });
189
90
  const {
@@ -192,11 +93,11 @@ const {
192
93
  conversations,
193
94
  isConnected,
194
95
  isProcessing,
195
- statusText,
196
96
  statusColor,
197
97
  connectSocket,
198
98
  disconnect,
199
99
  sendMessage: sendChatMessage,
100
+ stopGeneration,
200
101
  newChat,
201
102
  toggleHistory,
202
103
  selectConversation,
@@ -208,9 +109,7 @@ const {
208
109
  commands.value = cmds;
209
110
  },
210
111
  log: (...args) => {
211
- if (import.meta.env.DEV) {
212
- console.log("[ChatOverlay]", ...args);
213
- }
112
+ if (import.meta.env.DEV) console.log("[ChatOverlay]", ...args);
214
113
  }
215
114
  });
216
115
  const {
@@ -221,227 +120,121 @@ const {
221
120
  } = useVoiceInput();
222
121
  const showShareCopied = ref(false);
223
122
  const pendingNicknameAction = ref(null);
224
- const nicknameInput = ref("");
225
- const filteredCommands = computed(() => {
226
- if (!showCommandsAutocomplete.value) return [];
227
- const match = inputMessage.value.match(/(?:^|\s)\/(\S*)$/);
228
- if (!match) return [];
229
- const query = match[1].toLowerCase();
230
- return commands.value.filter(
231
- (cmd) => cmd.name.toLowerCase().includes(query)
232
- ).slice(0, 5);
233
- });
234
- function stripContextBlock(content) {
235
- return content.replace(/^\[context\]\n[\s\S]*?\n\[\/context\]\n?/, "").trim();
236
- }
237
- function getDisplayContent(message) {
238
- return stripContextBlock(message.content);
239
- }
123
+ const { generateContextBlock } = useMessageContext();
240
124
  function collectContext() {
241
- const parts = [];
242
- if (contextEnabled.value.viewport) {
243
- parts.push(`viewport: ${window.innerWidth}x${window.innerHeight}`);
244
- }
245
- if (contextEnabled.value.userAgent) {
246
- const ua = navigator.userAgent;
247
- let browser = "Unknown";
248
- if (ua.includes("Firefox/")) browser = "Firefox";
249
- else if (ua.includes("Edg/")) browser = "Edge";
250
- else if (ua.includes("Chrome/")) browser = "Chrome";
251
- else if (ua.includes("Safari/") && !ua.includes("Chrome")) browser = "Safari";
252
- let os = "Unknown";
253
- if (ua.includes("Windows")) os = "Windows";
254
- else if (ua.includes("Mac OS")) os = "macOS";
255
- else if (ua.includes("Linux")) os = "Linux";
256
- else if (ua.includes("Android")) os = "Android";
257
- else if (ua.includes("iPhone") || ua.includes("iPad")) os = "iOS";
258
- parts.push(`browser: ${browser} on ${os}`);
259
- }
260
- if (contextEnabled.value.routing) {
261
- parts.push(`route: ${window.location.pathname}`);
262
- if (window.location.search) {
263
- parts.push(`query: ${window.location.search}`);
125
+ const context = {
126
+ viewport: {
127
+ width: window.innerWidth,
128
+ height: window.innerHeight
129
+ },
130
+ userAgent: navigator.userAgent,
131
+ routing: {
132
+ path: window.location.pathname,
133
+ fullPath: window.location.pathname + window.location.search,
134
+ query: window.location.search ? Object.fromEntries(new URLSearchParams(window.location.search)) : void 0
264
135
  }
265
- }
266
- if (parts.length === 0) return null;
267
- return `[context]
268
- ${parts.join("\n")}
269
- [/context]
270
- `;
271
- }
272
- function handleInput() {
273
- const match = inputMessage.value.match(/(?:^|\s)\/\S*$/);
274
- showCommandsAutocomplete.value = !!match;
275
- autoResizeTextarea();
276
- }
277
- function autoResizeTextarea() {
278
- const textarea = textareaRef.value;
279
- if (!textarea) return;
280
- textarea.style.height = "auto";
281
- textarea.style.height = `${Math.min(textarea.scrollHeight, 280)}px`;
282
- }
283
- function selectCommand(cmd) {
284
- const match = inputMessage.value.match(/(?:^|\s)(\/\S*)$/);
285
- if (match) {
286
- const slashIndex = inputMessage.value.length - match[1].length;
287
- inputMessage.value = inputMessage.value.slice(0, slashIndex) + `/${cmd.name} `;
288
- }
289
- showCommandsAutocomplete.value = false;
290
- textareaRef.value?.focus();
291
- nextTick(autoResizeTextarea);
136
+ };
137
+ return generateContextBlock(context) + "\n";
292
138
  }
293
- function handleSubmit() {
294
- if (!inputMessage.value.trim() || isProcessing.value) return;
139
+ function handleMessageSubmit(message, attachments) {
295
140
  if (needsNicknameForMessage()) {
296
141
  pendingNicknameAction.value = "message";
297
142
  showNicknameModal.value = true;
298
- nicknameInput.value = "";
299
143
  return;
300
144
  }
301
145
  const context = collectContext();
302
- const messageWithContext = context ? context + inputMessage.value : inputMessage.value;
303
- sendChatMessage(messageWithContext, isShareMode.value ? userId.value || void 0 : void 0, nickname.value || void 0);
304
- inputMessage.value = "";
305
- showCommandsAutocomplete.value = false;
306
- if (textareaRef.value) {
307
- textareaRef.value.style.height = "auto";
308
- }
309
- }
310
- function handleKeydown(e) {
311
- if (e.key === "Enter" && !e.shiftKey) {
312
- e.preventDefault();
313
- handleSubmit();
314
- }
315
- if (e.key === "Escape") {
316
- if (showCommandsAutocomplete.value) {
317
- showCommandsAutocomplete.value = false;
318
- } else if (showHistory.value) {
319
- showHistory.value = false;
320
- } else {
321
- isOpen.value = false;
322
- }
323
- }
324
- }
325
- function lockBodyScroll(lock) {
326
- if (typeof document === "undefined") return;
327
- if (lock) {
328
- document.body.style.overflow = "hidden";
329
- document.body.style.touchAction = "none";
330
- } else {
331
- document.body.style.overflow = "";
332
- document.body.style.touchAction = "";
333
- }
334
- }
335
- function toggleOverlay() {
336
- isOpen.value = !isOpen.value;
337
- if (isOpen.value) {
338
- if (window.innerWidth <= 640) {
339
- lockBodyScroll(true);
340
- }
341
- nextTick(() => {
342
- textareaRef.value?.focus();
343
- scrollToBottom();
344
- });
345
- } else {
346
- lockBodyScroll(false);
347
- }
348
- }
349
- function scrollToBottom() {
350
- if (messagesContainer.value) {
351
- messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
352
- }
353
- }
354
- function handleToggleHistory() {
355
- showHistory.value = !showHistory.value;
356
- if (showHistory.value) {
357
- toggleHistory();
358
- }
359
- }
360
- function handleSelectConversation(id) {
361
- selectConversation(id);
362
- showHistory.value = false;
363
- }
364
- function handleVoiceInput() {
365
- toggleVoiceInput((transcript) => {
366
- inputMessage.value += transcript + " ";
367
- nextTick(autoResizeTextarea);
368
- });
146
+ sendChatMessage(
147
+ context + message,
148
+ isShareMode.value ? userId.value || void 0 : void 0,
149
+ nickname.value || void 0,
150
+ attachments
151
+ );
369
152
  }
370
153
  function handleShareClick() {
371
154
  if (needsNicknameForShare()) {
372
155
  pendingNicknameAction.value = "share";
373
156
  showNicknameModal.value = true;
374
- nicknameInput.value = "";
375
157
  } else {
376
158
  doShareCopy();
377
159
  }
378
160
  }
379
161
  async function doShareCopy() {
380
- const success = await copyShareLink();
381
- if (success) {
162
+ if (await copyShareLink()) {
382
163
  showShareCopied.value = true;
383
164
  setTimeout(() => {
384
165
  showShareCopied.value = false;
385
166
  }, 2e3);
386
167
  }
387
168
  }
388
- function handleNicknameSubmit() {
389
- const name = nicknameInput.value.trim();
390
- if (name.length < 2 || name.length > 20) return;
169
+ function handleNicknameSubmit(name) {
391
170
  setNickname(name);
392
- if (socket.value) {
393
- registerUser(socket.value);
394
- }
171
+ if (socket.value) registerUser(socket.value);
395
172
  showNicknameModal.value = false;
396
173
  if (pendingNicknameAction.value === "share") {
397
174
  doShareCopy();
398
- } else if (pendingNicknameAction.value === "message") {
399
- handleSubmit();
400
175
  }
401
176
  pendingNicknameAction.value = null;
402
177
  }
403
178
  function handleNicknameCancel() {
404
179
  showNicknameModal.value = false;
405
180
  pendingNicknameAction.value = null;
406
- nicknameInput.value = "";
407
181
  }
408
- watch(isOpen, (open) => {
409
- if (!open) {
410
- lockBodyScroll(false);
411
- }
412
- nextTick(() => {
413
- if (open && headerRef.value) {
414
- headerRef.value.addEventListener("touchmove", handleTouchMove, { passive: false });
415
- }
182
+ function handleToggleHistory() {
183
+ showHistory.value = !showHistory.value;
184
+ if (showHistory.value) toggleHistory();
185
+ }
186
+ function handleSelectConversation(id) {
187
+ selectConversation(id);
188
+ showHistory.value = false;
189
+ }
190
+ function handleVoiceInput() {
191
+ toggleVoiceInput((transcript) => {
192
+ chatInputRef.value?.appendText(transcript);
416
193
  });
417
- });
418
- watch(
419
- () => messages.value.length,
420
- () => {
421
- nextTick(scrollToBottom);
194
+ }
195
+ function handleStopGeneration() {
196
+ stopGeneration();
197
+ }
198
+ function handleClose() {
199
+ closePanel();
200
+ isOpen.value = false;
201
+ lockBodyScroll(false);
202
+ }
203
+ function toggleOverlay() {
204
+ if (!isOpen.value) {
205
+ if (window.innerWidth <= 640) lockBodyScroll(true);
206
+ openPanel();
207
+ isOpen.value = true;
208
+ nextTick(() => {
209
+ chatInputRef.value?.focus();
210
+ chatMessagesRef.value?.scrollToBottom();
211
+ });
212
+ } else {
213
+ handleClose();
422
214
  }
423
- );
424
- watch(
425
- () => messages.value[messages.value.length - 1]?.content,
426
- () => {
427
- nextTick(scrollToBottom);
215
+ }
216
+ function handleKeydown(e) {
217
+ if (e.key === "Escape") {
218
+ if (showHistory.value) {
219
+ showHistory.value = false;
220
+ } else {
221
+ handleClose();
222
+ }
428
223
  }
429
- );
224
+ }
430
225
  function handleGlobalKeydown(e) {
431
226
  if (e.ctrlKey && e.shiftKey && e.key === "K") {
432
227
  e.preventDefault();
433
228
  toggleOverlay();
434
229
  }
435
230
  }
436
- onMounted(() => {
437
- initShare();
438
- connectSocket();
439
- loadFabPosition();
440
- window.addEventListener("keydown", handleGlobalKeydown);
441
- if (needsNicknameImmediate()) {
442
- showNicknameModal.value = true;
443
- pendingNicknameAction.value = null;
444
- }
231
+ watch(isOpen, (open) => {
232
+ if (!open) lockBodyScroll(false);
233
+ nextTick(() => {
234
+ if (open) {
235
+ setupTouchMoveListener();
236
+ }
237
+ });
445
238
  });
446
239
  watch(isConnected, (connected) => {
447
240
  if (connected && socket.value) {
@@ -452,442 +245,139 @@ watch(isConnected, (connected) => {
452
245
  }
453
246
  }
454
247
  });
248
+ onMounted(() => {
249
+ initShare();
250
+ connectSocket();
251
+ loadPanelState();
252
+ checkMobile();
253
+ window.addEventListener("keydown", handleGlobalKeydown);
254
+ window.addEventListener("resize", checkMobile);
255
+ if (needsNicknameImmediate()) {
256
+ showNicknameModal.value = true;
257
+ pendingNicknameAction.value = null;
258
+ }
259
+ });
455
260
  onUnmounted(() => {
456
261
  disconnect();
457
262
  cleanupVoice();
458
263
  lockBodyScroll(false);
459
264
  window.removeEventListener("keydown", handleGlobalKeydown);
460
- if (headerRef.value) {
461
- headerRef.value.removeEventListener("touchmove", handleTouchMove);
462
- }
265
+ window.removeEventListener("resize", checkMobile);
266
+ cleanupTouchMoveListener();
463
267
  });
464
- function getMessageClass(role) {
465
- if (role === "user") return "claude-overlay-message-user";
466
- if (role === "assistant") return "claude-overlay-message-assistant";
467
- return "claude-overlay-message-system";
468
- }
469
- function formatDate(dateStr) {
470
- const date = new Date(dateStr);
471
- return date.toLocaleDateString() + " " + date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
472
- }
473
268
  </script>
474
269
 
475
270
  <template>
476
271
  <Teleport to="body">
477
- <!-- FAB Button (draggable) -->
478
- <button
272
+ <!-- Badge -->
273
+ <ClaudeBadge
479
274
  v-if="!isOpen"
480
- ref="fabRef"
481
- :class="['claude-overlay-fab', { 'claude-overlay-fab-dragging': isDraggingFab, 'claude-overlay-fab-default': !fabPosition }]"
482
- :style="getFabStyle()"
483
- title="Chat with Claude (Ctrl+Shift+K) — drag to move"
484
- @mousedown="handleFabDragStart"
485
- @touchstart="handleFabDragStart"
486
- @click="handleFabClick"
487
- >
488
- <svg
489
- xmlns="http://www.w3.org/2000/svg"
490
- viewBox="0 0 24 24"
491
- fill="currentColor"
492
- class="claude-overlay-fab-icon"
493
- >
494
- <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z" />
495
- </svg>
496
- <span
497
- :class="['claude-overlay-fab-status', `claude-overlay-fab-status-${statusColor}`]"
498
- />
499
- </button>
275
+ :position="badgePos"
276
+ :status-color="statusColor"
277
+ @click="toggleOverlay"
278
+ @drag-end="onBadgeDragEnd"
279
+ />
500
280
 
501
- <!-- Backdrop (click outside to close) -->
502
- <Transition name="claude-overlay-fade">
281
+ <!-- Backdrop -->
282
+ <Transition name="claude-fade">
503
283
  <div
504
284
  v-if="isOpen"
505
- class="claude-overlay-backdrop"
506
- @click="isOpen = false"
285
+ class="claude-backdrop"
286
+ @click="handleClose"
507
287
  />
508
288
  </Transition>
509
289
 
510
290
  <!-- Nickname Modal -->
511
- <Transition name="claude-overlay-fade">
512
- <div
513
- v-if="showNicknameModal"
514
- class="claude-overlay-modal-backdrop"
515
- @click.self="handleNicknameCancel"
516
- >
517
- <div class="claude-overlay-modal">
518
- <div class="claude-overlay-modal-header">
519
- <h3>Enter your nickname</h3>
520
- <button
521
- class="claude-overlay-btn-icon"
522
- @click="handleNicknameCancel"
523
- >
524
- <svg
525
- xmlns="http://www.w3.org/2000/svg"
526
- viewBox="0 0 24 24"
527
- fill="currentColor"
528
- width="16"
529
- height="16"
530
- >
531
- <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" />
532
- </svg>
533
- </button>
534
- </div>
535
- <div class="claude-overlay-modal-body">
536
- <p class="claude-overlay-modal-hint">
537
- Choose a nickname for collaborative chat sessions
538
- </p>
539
- <input
540
- v-model="nicknameInput"
541
- type="text"
542
- class="claude-overlay-modal-input"
543
- placeholder="Your nickname (2-20 characters)"
544
- maxlength="20"
545
- @keydown.enter="handleNicknameSubmit"
546
- >
547
- <p
548
- v-if="nicknameError"
549
- class="claude-overlay-modal-error"
550
- >
551
- {{ nicknameError }}
552
- </p>
553
- </div>
554
- <div class="claude-overlay-modal-actions">
555
- <button
556
- class="claude-overlay-modal-btn-secondary"
557
- @click="handleNicknameCancel"
558
- >
559
- Cancel
560
- </button>
561
- <button
562
- class="claude-overlay-modal-btn-primary"
563
- :disabled="nicknameInput.trim().length < 2"
564
- @click="handleNicknameSubmit"
565
- >
566
- Save
567
- </button>
568
- </div>
569
- </div>
570
- </div>
571
- </Transition>
291
+ <NicknameModal
292
+ :error="nicknameError"
293
+ :show="showNicknameModal"
294
+ @cancel="handleNicknameCancel"
295
+ @submit="handleNicknameSubmit"
296
+ />
572
297
 
573
298
  <!-- Chat Panel -->
574
- <Transition :name="isSwipeClosing ? '' : 'claude-overlay-slide'">
299
+ <Transition :name="isSwipeClosing ? '' : 'claude-slide'">
575
300
  <div
576
301
  v-if="isOpen"
577
302
  ref="panelRef"
578
- class="claude-overlay-panel"
303
+ :class="['claude-panel', { 'claude-panel-dragging': isPanelDragging, 'claude-panel-resizing': isPanelResizing }]"
304
+ :style="[isMobile ? {} : getPanelStyle(), { cursor: resizeCursor }]"
305
+ @keydown="handleKeydown"
306
+ @mouseleave="handlePanelMouseLeave"
307
+ @mousemove="handlePanelMouseMove"
579
308
  >
580
- <!-- Header (swipe handle on mobile) -->
309
+ <!-- Resize edges -->
310
+ <template v-if="!isMobile">
311
+ <div
312
+ v-for="edge in ['n', 's', 'e', 'w']"
313
+ :key="edge"
314
+ :class="`claude-resize-edge claude-resize-${edge}`"
315
+ @mousedown="(e) => handlePanelResizeStart(edge, e)"
316
+ />
317
+ <div
318
+ v-for="corner in ['nw', 'ne', 'sw', 'se']"
319
+ :key="corner"
320
+ :class="['claude-resize-corner', `claude-resize-${corner}`, { 'claude-resize-active': hoveredEdge === corner || activeResizeEdge === corner }]"
321
+ @mousedown="(e) => handlePanelResizeStart(corner, e)"
322
+ />
323
+ </template>
324
+
325
+ <!-- Mobile drag bumper -->
581
326
  <div
327
+ v-if="isMobile"
582
328
  ref="headerRef"
583
- class="claude-overlay-header"
584
- @touchstart="handleTouchStart"
329
+ class="claude-drag-bumper"
585
330
  @touchend="handleTouchEnd"
331
+ @touchstart="handleTouchStart"
586
332
  >
587
- <div class="claude-overlay-header-left">
588
- <svg
589
- xmlns="http://www.w3.org/2000/svg"
590
- viewBox="0 0 24 24"
591
- fill="currentColor"
592
- class="claude-overlay-header-icon"
593
- >
594
- <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z" />
595
- </svg>
596
- <span class="claude-overlay-header-title">Claude</span>
597
- <span :class="['claude-overlay-status-badge', `claude-overlay-status-${statusColor}`]">
598
- {{ statusText }}
599
- </span>
600
- </div>
601
- <div class="claude-overlay-header-actions">
602
- <!-- Share button -->
603
- <button
604
- class="claude-overlay-btn-icon"
605
- :class="{ 'claude-overlay-btn-success': showShareCopied }"
606
- :title="showShareCopied ? 'Link copied!' : 'Share chat'"
607
- @click="handleShareClick"
608
- >
609
- <svg
610
- v-if="showShareCopied"
611
- xmlns="http://www.w3.org/2000/svg"
612
- viewBox="0 0 24 24"
613
- fill="currentColor"
614
- width="18"
615
- height="18"
616
- >
617
- <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
618
- </svg>
619
- <svg
620
- v-else
621
- xmlns="http://www.w3.org/2000/svg"
622
- viewBox="0 0 24 24"
623
- fill="currentColor"
624
- width="18"
625
- height="18"
626
- >
627
- <path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92s2.92-1.31 2.92-2.92-1.31-2.92-2.92-2.92z" />
628
- </svg>
629
- </button>
630
- <button
631
- class="claude-overlay-btn-icon"
632
- title="History"
633
- @click="handleToggleHistory"
634
- >
635
- <svg
636
- xmlns="http://www.w3.org/2000/svg"
637
- viewBox="0 0 24 24"
638
- fill="currentColor"
639
- width="18"
640
- height="18"
641
- >
642
- <path d="M13 3a9 9 0 0 0-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42A8.954 8.954 0 0 0 13 21a9 9 0 0 0 0-18zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z" />
643
- </svg>
644
- </button>
645
- <button
646
- class="claude-overlay-btn-icon"
647
- title="New chat"
648
- @click="newChat"
649
- >
650
- <svg
651
- xmlns="http://www.w3.org/2000/svg"
652
- viewBox="0 0 24 24"
653
- fill="currentColor"
654
- width="18"
655
- height="18"
656
- >
657
- <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
658
- </svg>
659
- </button>
660
- </div>
333
+ <div class="claude-drag-handle-bar" />
661
334
  </div>
662
335
 
663
- <!-- History Panel -->
664
- <Transition name="claude-overlay-slide-right">
665
- <div
666
- v-if="showHistory"
667
- class="claude-overlay-history"
668
- >
669
- <div class="claude-overlay-history-header">
670
- <span>History</span>
671
- <button
672
- class="claude-overlay-btn-icon"
673
- @click="showHistory = false"
674
- >
675
- <svg
676
- xmlns="http://www.w3.org/2000/svg"
677
- viewBox="0 0 24 24"
678
- fill="currentColor"
679
- width="16"
680
- height="16"
681
- >
682
- <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" />
683
- </svg>
684
- </button>
685
- </div>
686
- <div class="claude-overlay-history-list">
687
- <div
688
- v-for="conv in conversations"
689
- :key="conv.id"
690
- class="claude-overlay-history-item"
691
- @click="handleSelectConversation(conv.id)"
692
- >
693
- <div class="claude-overlay-history-title">
694
- {{ stripContextBlock(conv.title || "") || "Untitled" }}
695
- </div>
696
- <div class="claude-overlay-history-date">
697
- {{ formatDate(conv.updatedAt) }}
698
- </div>
699
- </div>
700
- <div
701
- v-if="conversations.length === 0"
702
- class="claude-overlay-history-empty"
703
- >
704
- No conversations yet
705
- </div>
706
- </div>
707
- </div>
708
- </Transition>
709
-
710
- <!-- Messages -->
711
- <div
712
- ref="messagesContainer"
713
- class="claude-overlay-messages"
714
- >
715
- <div
716
- v-if="messages.length === 0"
717
- class="claude-overlay-empty"
718
- >
719
- <p>Start a conversation with Claude</p>
720
- <p class="claude-overlay-empty-hint">
721
- Type a message or use /commands
722
- </p>
723
- </div>
724
-
725
- <div
726
- v-for="message in messages"
727
- :key="message.id"
728
- :class="[
729
- 'claude-overlay-message',
730
- getMessageClass(message.role),
731
- {
732
- 'claude-overlay-message-own': message.role === 'user' && isOwnMessage(message.senderId),
733
- 'claude-overlay-message-other': message.role === 'user' && !isOwnMessage(message.senderId)
734
- }
735
- ]"
736
- >
737
- <!-- User message -->
738
- <template v-if="message.role === 'user'">
739
- <div
740
- v-if="isShareMode && message.senderNickname"
741
- class="claude-overlay-message-sender"
742
- >
743
- {{ message.senderNickname }}
744
- <span
745
- v-if="isOwnMessage(message.senderId)"
746
- class="claude-overlay-message-you"
747
- >(you)</span>
748
- </div>
749
- <div class="claude-overlay-message-content">
750
- {{ getDisplayContent(message) }}
751
- </div>
752
- </template>
336
+ <!-- Header -->
337
+ <ChatHeader
338
+ :is-mobile="isMobile"
339
+ :show-share-copied="showShareCopied"
340
+ :status-color="statusColor"
341
+ @history="handleToggleHistory"
342
+ @share="handleShareClick"
343
+ @new-chat="newChat"
344
+ @drag-start="handlePanelDragStart"
345
+ />
753
346
 
754
- <!-- Assistant message -->
755
- <template v-else-if="message.role === 'assistant'">
756
- <div class="claude-overlay-message-content">
757
- <template v-if="message.contentBlocks?.length">
758
- <template
759
- v-for="(block, idx) in message.contentBlocks"
760
- :key="idx"
761
- >
762
- <MarkdownContent
763
- v-if="block.type === 'text' && block.text"
764
- :content="stripContextBlock(block.text)"
765
- />
766
- <ToolCallBlock
767
- v-else-if="block.type === 'tool_use'"
768
- :block="block"
769
- :result="findToolResult(message.contentBlocks, block.id)"
770
- />
771
- </template>
772
- </template>
773
- <template v-else-if="message.content">
774
- <MarkdownContent :content="getDisplayContent(message)" />
775
- </template>
776
- <span
777
- v-if="message.streaming"
778
- class="claude-overlay-cursor"
779
- />
780
- </div>
781
- </template>
347
+ <!-- History -->
348
+ <HistoryPanel
349
+ :conversations="conversations"
350
+ :show="showHistory"
351
+ @close="showHistory = false"
352
+ @select="handleSelectConversation"
353
+ />
782
354
 
783
- <!-- System message -->
784
- <template v-else>
785
- <div class="claude-overlay-message-content claude-overlay-message-system-content">
786
- {{ getDisplayContent(message) }}
787
- </div>
788
- </template>
789
- </div>
790
- </div>
355
+ <!-- Messages -->
356
+ <ChatMessages
357
+ ref="chatMessagesRef"
358
+ :find-tool-result="findToolResult"
359
+ :is-own-message="isOwnMessage"
360
+ :is-share-mode="isShareMode"
361
+ :messages="messages"
362
+ />
791
363
 
792
364
  <!-- Input -->
793
- <div class="claude-overlay-input-container">
794
- <!-- Commands autocomplete -->
795
- <div
796
- v-if="showCommandsAutocomplete && filteredCommands.length > 0"
797
- class="claude-overlay-autocomplete"
798
- >
799
- <button
800
- v-for="cmd in filteredCommands"
801
- :key="cmd.name"
802
- class="claude-overlay-autocomplete-item"
803
- @click="selectCommand(cmd)"
804
- >
805
- <span class="claude-overlay-autocomplete-name">/{{ cmd.name }}</span>
806
- <span
807
- v-if="cmd.description"
808
- class="claude-overlay-autocomplete-desc"
809
- >{{ cmd.description }}</span>
810
- </button>
811
- </div>
812
-
813
- <div class="claude-overlay-input-wrapper">
814
- <textarea
815
- ref="textareaRef"
816
- v-model="inputMessage"
817
- class="claude-overlay-input"
818
- placeholder="Ask Claude..."
819
- rows="1"
820
- :disabled="!isConnected || isProcessing"
821
- @input="handleInput"
822
- @keydown="handleKeydown"
823
- />
824
- <div class="claude-overlay-input-actions">
825
- <!-- Voice input button -->
826
- <button
827
- v-if="isSpeechSupported"
828
- :class="['claude-overlay-voice-btn', { 'claude-overlay-voice-recording': isRecording }]"
829
- :disabled="!isConnected || isProcessing"
830
- title="Voice input"
831
- @click="handleVoiceInput"
832
- >
833
- <svg
834
- xmlns="http://www.w3.org/2000/svg"
835
- viewBox="0 0 24 24"
836
- fill="currentColor"
837
- width="20"
838
- height="20"
839
- >
840
- <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" />
841
- </svg>
842
- </button>
843
- <!-- Send button -->
844
- <button
845
- class="claude-overlay-send-btn"
846
- :disabled="!inputMessage.trim() || !isConnected || isProcessing"
847
- @click="handleSubmit"
848
- >
849
- <svg
850
- v-if="isProcessing"
851
- class="claude-overlay-spinner"
852
- xmlns="http://www.w3.org/2000/svg"
853
- viewBox="0 0 24 24"
854
- fill="none"
855
- stroke="currentColor"
856
- width="20"
857
- height="20"
858
- >
859
- <circle
860
- cx="12"
861
- cy="12"
862
- r="10"
863
- stroke-width="2"
864
- opacity="0.25"
865
- />
866
- <path
867
- d="M12 2a10 10 0 0 1 10 10"
868
- stroke-width="2"
869
- stroke-linecap="round"
870
- />
871
- </svg>
872
- <svg
873
- v-else
874
- xmlns="http://www.w3.org/2000/svg"
875
- viewBox="0 0 24 24"
876
- fill="currentColor"
877
- width="20"
878
- height="20"
879
- >
880
- <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
881
- </svg>
882
- </button>
883
- </div>
884
- </div>
885
- </div>
365
+ <ChatInput
366
+ ref="chatInputRef"
367
+ :commands="commands"
368
+ :is-connected="isConnected"
369
+ :is-processing="isProcessing"
370
+ :is-recording="isRecording"
371
+ :is-speech-supported="isSpeechSupported"
372
+ @submit="handleMessageSubmit"
373
+ @voice-input="handleVoiceInput"
374
+ @stop="handleStopGeneration"
375
+ />
886
376
  </div>
887
377
  </Transition>
888
378
  </Teleport>
889
379
  </template>
890
380
 
891
381
  <style>
892
- :root{--claude-primary:#10a37f;--claude-primary-hover:#0d8a6a;--claude-bg:#1a1a1a;--claude-bg-elevated:#252525;--claude-bg-hover:#2a2a2a;--claude-text:#fff;--claude-text-muted:#a0a0a0;--claude-border:#333;--claude-user-bg:#2d4a3e;--claude-system-bg:#3d3d3d;--claude-radius:12px;--claude-radius-sm:8px}.claude-overlay-fab{align-items:center;background:var(--claude-primary);border:none;border-radius:50%;bottom:20px;box-shadow:0 4px 12px rgba(0,0,0,.2);color:#fff;cursor:pointer;display:flex;height:56px;justify-content:center;opacity:.6;position:fixed;transition:transform .2s,background .2s,opacity .2s,box-shadow .2s;width:56px;z-index:99998}.claude-overlay-fab:hover{background:var(--claude-primary-hover);box-shadow:0 6px 20px rgba(0,0,0,.35);opacity:1;transform:scale(1.05)}.claude-overlay-fab-dragging{box-shadow:0 8px 24px rgba(0,0,0,.4);cursor:grabbing;opacity:1;transform:scale(1.1);transition:none}.claude-overlay-backdrop{background:rgba(0,0,0,.3);inset:0;position:fixed;z-index:99997}.claude-overlay-fade-enter-active,.claude-overlay-fade-leave-active{transition:opacity .2s ease}.claude-overlay-fade-enter-from,.claude-overlay-fade-leave-to{opacity:0}.claude-overlay-fab-default{right:20px}.claude-overlay-fab-icon{height:28px;width:28px}.claude-overlay-fab-status{border:2px solid var(--claude-bg);border-radius:50%;height:12px;position:absolute;right:4px;top:4px;width:12px}.claude-overlay-fab-status-green{background:#22c55e}.claude-overlay-fab-status-blue{background:#3b82f6}.claude-overlay-fab-status-red{background:#ef4444}.claude-overlay-panel{background:var(--claude-bg);border-radius:var(--claude-radius);bottom:20px;box-shadow:0 8px 32px rgba(0,0,0,.4);color:var(--claude-text);display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;height:calc(100vh - 40px);max-width:calc(100vw - 40px);overflow:hidden;position:fixed;right:20px;width:420px;z-index:99999}.claude-overlay-header{align-items:center;background:var(--claude-bg-elevated);border-bottom:1px solid var(--claude-border);display:flex;flex-shrink:0;justify-content:space-between;padding:12px 16px}.claude-overlay-header-left{align-items:center;display:flex;gap:8px}.claude-overlay-header-icon{color:var(--claude-primary);height:24px;width:24px}.claude-overlay-header-title{font-size:16px;font-weight:600}.claude-overlay-status-badge{background:var(--claude-bg);border-radius:10px;font-size:11px;padding:2px 8px}.claude-overlay-status-green{color:#22c55e}.claude-overlay-status-blue{color:#3b82f6}.claude-overlay-status-red{color:#ef4444}.claude-overlay-header-actions{display:flex;gap:4px}.claude-overlay-btn-icon{align-items:center;background:transparent;border:none;border-radius:var(--claude-radius-sm);color:var(--claude-text-muted);cursor:pointer;display:flex;height:32px;justify-content:center;transition:background .2s,color .2s;width:32px}.claude-overlay-btn-icon:hover{background:var(--claude-bg-hover);color:var(--claude-text)}.claude-overlay-btn-success{color:#22c55e!important}.claude-overlay-history{background:var(--claude-bg);bottom:0;display:flex;flex-direction:column;left:0;position:absolute;right:0;top:0;z-index:10}.claude-overlay-history-header{align-items:center;background:var(--claude-bg-elevated);border-bottom:1px solid var(--claude-border);display:flex;font-weight:600;justify-content:space-between;padding:12px 16px}.claude-overlay-history-list{flex:1;overflow-y:auto;padding:8px}.claude-overlay-history-item{border-radius:var(--claude-radius-sm);cursor:pointer;padding:12px;transition:background .15s}.claude-overlay-history-item:hover{background:var(--claude-bg-hover)}.claude-overlay-history-title{font-weight:500;margin-bottom:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.claude-overlay-history-date{color:var(--claude-text-muted);font-size:12px}.claude-overlay-history-empty{color:var(--claude-text-muted);padding:32px;text-align:center}.claude-overlay-messages{display:flex;flex:1;flex-direction:column;gap:12px;overflow-y:auto;padding:16px}.claude-overlay-empty{align-items:center;color:var(--claude-text-muted);display:flex;flex:1;flex-direction:column;justify-content:center;text-align:center}.claude-overlay-empty-hint{font-size:12px;margin-top:4px;opacity:.7}.claude-overlay-message{max-width:90%}.claude-overlay-message-user{align-self:flex-end}.claude-overlay-message-sender{color:var(--claude-primary);font-size:11px;font-weight:600;margin-bottom:4px;padding-left:2px}.claude-overlay-message-you{font-weight:400;opacity:.5}.claude-overlay-message-own{align-self:flex-end}.claude-overlay-message-own .claude-overlay-message-content{background:var(--claude-user-bg);border-radius:var(--claude-radius-sm);padding:10px 14px;white-space:pre-wrap;word-break:break-word}.claude-overlay-message-other{align-self:flex-start}.claude-overlay-message-other .claude-overlay-message-content{background:rgba(168,85,247,.2);border-radius:var(--claude-radius-sm);padding:10px 14px;white-space:pre-wrap;word-break:break-word}.claude-overlay-message-user .claude-overlay-message-content{background:var(--claude-user-bg);border-radius:var(--claude-radius-sm);padding:10px 14px;white-space:pre-wrap;word-break:break-word}.claude-overlay-message-assistant{align-self:flex-start}.claude-overlay-message-assistant .claude-overlay-message-content{background:var(--claude-bg-elevated);border-radius:var(--claude-radius-sm);padding:10px 14px}.claude-overlay-message-system{align-self:center}.claude-overlay-message-system-content{background:var(--claude-system-bg);border-radius:var(--claude-radius-sm);color:var(--claude-text-muted);font-size:12px;padding:8px 12px}.claude-overlay-cursor{animation:claude-blink 1s infinite;background:var(--claude-primary);display:inline-block;height:16px;margin-left:2px;width:8px}@keyframes claude-blink{0%,50%{opacity:1}51%,to{opacity:0}}.claude-overlay-input-container{background:var(--claude-bg-elevated);border-top:1px solid var(--claude-border);flex-shrink:0;padding:12px 16px;position:relative}.claude-overlay-autocomplete{background:var(--claude-bg);border:1px solid var(--claude-border);border-radius:var(--claude-radius-sm);bottom:100%;box-shadow:0 -4px 12px rgba(0,0,0,.2);left:16px;overflow:hidden;position:absolute;right:16px}.claude-overlay-autocomplete-item{background:transparent;border:none;color:var(--claude-text);cursor:pointer;display:flex;flex-direction:column;gap:2px;padding:10px 12px;text-align:left;transition:background .15s;width:100%}.claude-overlay-autocomplete-item:hover{background:var(--claude-bg-hover)}.claude-overlay-autocomplete-name{color:var(--claude-primary);font-family:monospace}.claude-overlay-autocomplete-desc{color:var(--claude-text-muted);font-size:12px}.claude-overlay-input-wrapper{align-items:flex-end;display:flex;gap:8px}.claude-overlay-input{background:var(--claude-bg);border:1px solid var(--claude-border);border-radius:var(--claude-radius-sm);color:var(--claude-text);flex:1;font-family:inherit;font-size:14px;line-height:1.4;max-height:280px;min-height:40px;overflow-y:auto;padding:10px 12px;resize:none}.claude-overlay-input:focus{border-color:var(--claude-primary);outline:none}.claude-overlay-input::-moz-placeholder{color:var(--claude-text-muted)}.claude-overlay-input::placeholder{color:var(--claude-text-muted)}.claude-overlay-input:disabled{cursor:not-allowed;opacity:.5}.claude-overlay-input-actions{display:flex;flex-shrink:0;gap:4px}.claude-overlay-voice-btn{align-items:center;background:var(--claude-bg);border:none;border-radius:var(--claude-radius-sm);color:var(--claude-text-muted);cursor:pointer;display:flex;height:40px;justify-content:center;transition:background .2s,color .2s;width:40px}.claude-overlay-voice-btn:hover:not(:disabled){background:var(--claude-bg-hover);color:var(--claude-text)}.claude-overlay-voice-btn:disabled{cursor:not-allowed;opacity:.5}.claude-overlay-voice-recording{animation:claude-pulse 1s infinite;background:#ef4444!important;color:#fff!important}@keyframes claude-pulse{0%,to{opacity:1}50%{opacity:.7}}.claude-overlay-send-btn{align-items:center;background:var(--claude-primary);border:none;border-radius:var(--claude-radius-sm);color:#fff;cursor:pointer;display:flex;height:40px;justify-content:center;transition:background .2s;width:40px}.claude-overlay-send-btn:hover:not(:disabled){background:var(--claude-primary-hover)}.claude-overlay-send-btn:disabled{cursor:not-allowed;opacity:.5}.claude-overlay-spinner{animation:claude-spin 1s linear infinite}@keyframes claude-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.claude-overlay-slide-enter-active,.claude-overlay-slide-leave-active{transition:opacity .2s,transform .2s}.claude-overlay-slide-enter-from,.claude-overlay-slide-leave-to{opacity:0;transform:translateY(20px) scale(.95)}.claude-overlay-slide-right-enter-active,.claude-overlay-slide-right-leave-active{transition:transform .2s ease-out}.claude-overlay-slide-right-enter-from,.claude-overlay-slide-right-leave-to{transform:translateX(100%)}.claude-overlay-history-list::-webkit-scrollbar,.claude-overlay-messages::-webkit-scrollbar{width:6px}.claude-overlay-history-list::-webkit-scrollbar-track,.claude-overlay-messages::-webkit-scrollbar-track{background:transparent}.claude-overlay-history-list::-webkit-scrollbar-thumb,.claude-overlay-messages::-webkit-scrollbar-thumb{background:var(--claude-border);border-radius:3px}.claude-overlay-history-list::-webkit-scrollbar-thumb:hover,.claude-overlay-messages::-webkit-scrollbar-thumb:hover{background:var(--claude-text-muted)}@media (max-width:640px){.claude-overlay-fab{bottom:16px;height:52px;width:52px}.claude-overlay-fab-default{right:16px}.claude-overlay-panel{border-radius:var(--claude-radius) var(--claude-radius) 0 0;bottom:0;height:92vh;height:92dvh;left:0!important;max-height:92vh;max-height:92dvh;max-width:100%;right:0!important;width:100%}.claude-overlay-slide-enter-from,.claude-overlay-slide-leave-to{opacity:1;transform:translateY(100%)}.claude-overlay-slide-enter-active,.claude-overlay-slide-leave-active{transition:transform .3s ease-out}.claude-overlay-header{cursor:grab;touch-action:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.claude-overlay-header:active{cursor:grabbing}.claude-overlay-header:before{background:var(--claude-border);border-radius:2px;content:"";height:4px;left:50%;position:absolute;top:8px;transform:translateX(-50%);width:36px}.claude-overlay-header{padding-top:20px;position:relative}.claude-overlay-btn-icon{height:40px;width:40px}.claude-overlay-input{font-size:16px}.claude-overlay-send-btn,.claude-overlay-voice-btn{height:44px;width:44px}.claude-overlay-input-container{padding-bottom:calc(12px + env(safe-area-inset-bottom, 0px))}}.claude-overlay-modal-backdrop{align-items:center;background:rgba(0,0,0,.6);display:flex;inset:0;justify-content:center;padding:20px;position:fixed;z-index:100000}.claude-overlay-modal{background:var(--claude-bg);border-radius:var(--claude-radius);box-shadow:0 8px 32px rgba(0,0,0,.4);max-width:360px;width:100%}.claude-overlay-modal-header{align-items:center;border-bottom:1px solid var(--claude-border);display:flex;justify-content:space-between;padding:16px 16px 12px}.claude-overlay-modal-header h3{font-size:16px;font-weight:600;margin:0}.claude-overlay-modal-body{padding:16px}.claude-overlay-modal-hint{color:var(--claude-text-muted);font-size:13px;margin:0 0 12px}.claude-overlay-modal-input{background:var(--claude-bg-elevated);border:1px solid var(--claude-border);border-radius:var(--claude-radius-sm);color:var(--claude-text);font-family:inherit;font-size:14px;padding:10px 12px;width:100%}.claude-overlay-modal-input:focus{border-color:var(--claude-primary);outline:none}.claude-overlay-modal-input::-moz-placeholder{color:var(--claude-text-muted)}.claude-overlay-modal-input::placeholder{color:var(--claude-text-muted)}.claude-overlay-modal-error{color:#ef4444;font-size:13px;margin:8px 0 0}.claude-overlay-modal-actions{display:flex;gap:8px;justify-content:flex-end;padding:12px 16px 16px}.claude-overlay-modal-btn-primary,.claude-overlay-modal-btn-secondary{border:none;border-radius:var(--claude-radius-sm);cursor:pointer;font-size:14px;font-weight:500;padding:8px 16px;transition:background .2s,opacity .2s}.claude-overlay-modal-btn-secondary{background:var(--claude-bg-elevated);color:var(--claude-text)}.claude-overlay-modal-btn-secondary:hover{background:var(--claude-bg-hover)}.claude-overlay-modal-btn-primary{background:var(--claude-primary);color:#fff}.claude-overlay-modal-btn-primary:hover:not(:disabled){background:var(--claude-primary-hover)}.claude-overlay-modal-btn-primary:disabled{cursor:not-allowed;opacity:.5}
382
+ :root{--claude-primary:#fe9a00;--claude-primary-hover:#ffb340;--claude-primary-dark:#e58a00;--claude-primary-glow:rgba(254,154,0,.5);--claude-glass:hsla(0,0%,100%,.05);--claude-glass-elevated:hsla(0,0%,100%,.08);--claude-glass-hover:hsla(0,0%,100%,.12);--claude-glass-solid:rgba(12,12,18,.88);--claude-text:hsla(0,0%,100%,.95);--claude-text-muted:hsla(0,0%,100%,.5);--claude-text-dim:hsla(0,0%,100%,.3);--claude-border:hsla(0,0%,100%,.1);--claude-border-light:hsla(0,0%,100%,.15);--claude-user-bg:linear-gradient(135deg,rgba(254,154,0,.25),rgba(251,191,36,.15));--claude-assistant-bg:hsla(0,0%,100%,.06);--claude-other-user-bg:linear-gradient(135deg,rgba(244,63,94,.2),rgba(254,154,0,.15));--claude-system-bg:hsla(0,0%,100%,.04);--claude-blur:20px;--claude-blur-heavy:40px;--claude-radius:20px;--claude-radius-sm:12px;--claude-radius-xs:8px;--claude-shadow:0 8px 32px rgba(0,0,0,.3);--claude-shadow-lg:0 25px 50px -12px rgba(0,0,0,.5);--claude-glow:0 0 40px var(--claude-primary-glow)}.claude-backdrop{background:rgba(0,0,0,.4);inset:0;position:fixed;z-index:99997}.claude-fade-enter-active,.claude-fade-leave-active{transition:opacity .3s ease}.claude-fade-enter-from,.claude-fade-leave-to{opacity:0}.claude-panel{backdrop-filter:blur(var(--claude-blur-heavy));-webkit-backdrop-filter:blur(var(--claude-blur-heavy));background:var(--claude-glass-solid);border:1px solid var(--claude-border-light);border-radius:var(--claude-radius);box-shadow:var(--claude-shadow-lg),inset 0 1px 0 hsla(0,0%,100%,.1),inset 0 -1px 0 rgba(0,0,0,.1);color:var(--claude-text);display:flex;flex-direction:column;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;max-height:calc(100vh - 48px);max-width:calc(100vw - 48px);min-height:400px;min-width:320px;overflow:hidden;position:fixed;z-index:99999}.claude-panel:before{background:linear-gradient(180deg,rgba(254,154,0,.04),transparent 30%,transparent 70%,rgba(251,191,36,.02));border-radius:inherit;content:"";inset:0;pointer-events:none;position:absolute}.claude-panel-dragging,.claude-panel-resizing{transition:none!important;-webkit-user-select:none;-moz-user-select:none;user-select:none}.claude-panel-dragging{cursor:grabbing}.claude-resize-edge{position:absolute;z-index:100}.claude-resize-n{top:0}.claude-resize-n,.claude-resize-s{cursor:ns-resize;height:8px;left:16px;right:16px}.claude-resize-s{bottom:0}.claude-resize-e{right:0}.claude-resize-e,.claude-resize-w{bottom:16px;cursor:ew-resize;top:16px;width:8px}.claude-resize-w{left:0}.claude-resize-corner{height:16px;position:absolute;width:16px;z-index:101}.claude-resize-corner:before{content:"";height:12px;opacity:0;position:absolute;transition:opacity .2s ease;width:12px}.claude-resize-active:before,.claude-resize-corner:hover:before{opacity:1}.claude-resize-nw{cursor:nwse-resize;left:0;top:0}.claude-resize-ne{cursor:nesw-resize;right:0;top:0}.claude-resize-sw{bottom:0;cursor:nesw-resize;left:0}.claude-resize-se{bottom:0;cursor:nwse-resize;right:0}.claude-resize-nw:before{border-left:3px solid var(--claude-primary);border-radius:4px 0 0 0;left:2px}.claude-resize-ne:before,.claude-resize-nw:before{border-top:3px solid var(--claude-primary);box-shadow:0 0 10px var(--claude-primary-glow);top:2px}.claude-resize-ne:before{border-radius:0 4px 0 0;border-right:3px solid var(--claude-primary);right:2px}.claude-resize-sw:before{border-left:3px solid var(--claude-primary);border-radius:0 0 0 4px;left:2px}.claude-resize-se:before,.claude-resize-sw:before{border-bottom:3px solid var(--claude-primary);bottom:2px;box-shadow:0 0 10px var(--claude-primary-glow)}.claude-resize-se:before{border-radius:0 0 4px 0;border-right:3px solid var(--claude-primary);right:2px}.claude-drag-bumper,.claude-drag-handle-bar{display:none}.claude-slide-enter-active,.claude-slide-leave-active{transition:all .35s cubic-bezier(.4,0,.2,1)}.claude-slide-enter-from,.claude-slide-leave-to{opacity:0;transform:scale(.3)}@media (max-width:640px){.claude-panel{border-radius:var(--claude-radius) var(--claude-radius) 0 0;bottom:0!important;height:96dvh!important;left:0!important;max-height:96dvh;max-width:100%;min-height:auto;min-width:auto;right:0!important;top:auto!important;transform-origin:bottom center;width:100%!important}.claude-slide-enter-from,.claude-slide-leave-to{opacity:1;transform:translateY(100%)}.claude-slide-enter-active,.claude-slide-leave-active{transition:transform .35s cubic-bezier(.4,0,.2,1)}.claude-drag-bumper{align-items:center;background:hsla(0,0%,100%,.06);border-bottom:1px solid hsla(0,0%,100%,.08);cursor:grab;display:flex;flex-shrink:0;justify-content:center;padding:10px 20px;touch-action:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%}.claude-drag-bumper:active{background:hsla(0,0%,100%,.1);cursor:grabbing}.claude-drag-handle-bar{background:hsla(0,0%,100%,.35);border-radius:2px;display:block;height:4px;width:40px}}
893
383
  </style>