@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.
- package/README.md +2 -1
- package/dist/client/200.html +1 -1
- package/dist/client/404.html +1 -1
- package/dist/client/_nuxt/{COus5Ssl.js → 0BAoaFXM.js} +1 -1
- package/dist/client/_nuxt/{B_BoWmnX.js → 88aFj9mk.js} +1 -1
- package/dist/client/_nuxt/{DolUcBed.js → B5vRr8Ti.js} +1 -1
- package/dist/client/_nuxt/{CDQtmRaX.js → BI4BFakr.js} +1 -1
- package/dist/client/_nuxt/{CPA0s6N9.js → BJuQJ8yP.js} +1 -1
- package/dist/client/_nuxt/{BB1-kxmm.js → BMxGt8ZG.js} +1 -1
- package/dist/client/_nuxt/{TQi6eIO6.js → Baq9Hzkz.js} +1 -1
- package/dist/client/_nuxt/{DbJLoP3G.js → Bf779K50.js} +1 -1
- package/dist/client/_nuxt/BmF8r-gh.js +1 -0
- package/dist/client/_nuxt/BryQ1L4F.js +1 -0
- package/dist/client/_nuxt/{V4UvAcd3.js → BsG0VKct.js} +1 -1
- package/dist/client/_nuxt/Bt-DdLhQ.js +12 -0
- package/dist/client/_nuxt/{CHeJJZL9.js → C6Xm_PzB.js} +1 -1
- package/dist/client/_nuxt/{DC_XB519.js → C8AkZ-W3.js} +1 -1
- package/dist/client/_nuxt/{BSVkH7b6.js → CEM1iRz_.js} +1 -1
- package/dist/client/_nuxt/{BcZxFXBD.js → CETmet01.js} +1 -1
- package/dist/client/_nuxt/CG1tBJBN.js +1 -0
- package/dist/client/_nuxt/{D2l4TRxW.js → CXOn8Upj.js} +1 -1
- package/dist/client/_nuxt/CYomf6PL.js +1 -0
- package/dist/client/_nuxt/{DEys9N1G.js → CrhTgxiU.js} +1 -1
- package/dist/client/_nuxt/{BYp73eMl.js → CtWfW1XP.js} +1 -1
- package/dist/client/_nuxt/{d8BPa19J.js → CuUOvg0u.js} +1 -1
- package/dist/client/_nuxt/CxYvL5O5.js +1 -0
- package/dist/client/_nuxt/D8DUrd91.js +1 -0
- package/dist/client/_nuxt/{DSt96JPY.js → DFkDlupw.js} +2 -2
- package/dist/client/_nuxt/{B8uzckkK.js → DG-9quB_.js} +1 -1
- package/dist/client/_nuxt/DHlhBFAg.js +1 -0
- package/dist/client/_nuxt/{BnXQTjo-.js → DJw5U4z8.js} +1 -1
- package/dist/client/_nuxt/{M6QPYocW.js → DRReoGGV.js} +1 -1
- package/dist/client/_nuxt/{DGQ4s7ae.js → DSMailoF.js} +1 -1
- package/dist/client/_nuxt/{qbS8UemQ.js → DUPgidXP.js} +1 -1
- package/dist/client/_nuxt/{QumocfwJ.js → D_JyZ6Hh.js} +1 -1
- package/dist/client/_nuxt/{BAb1fJOF.js → Dgm9zqhP.js} +2 -2
- package/dist/client/_nuxt/builds/latest.json +1 -1
- package/dist/client/_nuxt/builds/meta/024950b2-9b05-4789-9918-c859c91d6b13.json +1 -0
- package/dist/client/_nuxt/{C--9REmc.js → h1c4XpR7.js} +1 -1
- package/dist/client/_nuxt/kN0L7CSs.js +1 -0
- package/dist/client/_nuxt/{BMZIbUUD.js → n4TwJfzb.js} +1 -1
- package/dist/client/_nuxt/zY60w9UT.js +1 -0
- package/dist/client/agents/index.html +1 -1
- package/dist/client/commands/index.html +1 -1
- package/dist/client/docs/index.html +1 -1
- package/dist/client/index.html +1 -1
- package/dist/client/mcp/index.html +1 -1
- package/dist/client/plugins/index.html +1 -1
- package/dist/client/settings/index.html +1 -1
- package/dist/client/skills/index.html +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +10 -4
- package/dist/runtime/overlay/components/ChatOverlay.d.vue.ts +0 -1
- package/dist/runtime/overlay/components/ChatOverlay.vue +221 -731
- package/dist/runtime/overlay/components/ChatOverlay.vue.d.ts +0 -1
- package/dist/runtime/overlay/components/MarkdownContent.vue +1 -1
- package/dist/runtime/overlay/components/ToolCallBlock.vue +1 -1
- package/dist/runtime/overlay/components/chat/ChatHeader.d.vue.ts +18 -0
- package/dist/runtime/overlay/components/chat/ChatHeader.vue +82 -0
- package/dist/runtime/overlay/components/chat/ChatHeader.vue.d.ts +18 -0
- package/dist/runtime/overlay/components/chat/ChatInput.d.vue.ts +22 -0
- package/dist/runtime/overlay/components/chat/ChatInput.vue +526 -0
- package/dist/runtime/overlay/components/chat/ChatInput.vue.d.ts +22 -0
- package/dist/runtime/overlay/components/chat/ChatMessages.d.vue.ts +13 -0
- package/dist/runtime/overlay/components/chat/ChatMessages.vue +160 -0
- package/dist/runtime/overlay/components/chat/ChatMessages.vue.d.ts +13 -0
- package/dist/runtime/overlay/components/chat/ClaudeBadge.d.vue.ts +22 -0
- package/dist/runtime/overlay/components/chat/ClaudeBadge.vue +85 -0
- package/dist/runtime/overlay/components/chat/ClaudeBadge.vue.d.ts +22 -0
- package/dist/runtime/overlay/components/chat/HistoryPanel.d.vue.ts +17 -0
- package/dist/runtime/overlay/components/chat/HistoryPanel.vue +65 -0
- package/dist/runtime/overlay/components/chat/HistoryPanel.vue.d.ts +17 -0
- package/dist/runtime/overlay/components/chat/NicknameModal.d.vue.ts +13 -0
- package/dist/runtime/overlay/components/chat/NicknameModal.vue +89 -0
- package/dist/runtime/overlay/components/chat/NicknameModal.vue.d.ts +13 -0
- package/dist/runtime/overlay/components/chat/index.d.ts +6 -0
- package/dist/runtime/overlay/components/chat/index.js +6 -0
- package/dist/runtime/overlay/composables/index.d.ts +3 -0
- package/dist/runtime/overlay/composables/index.js +3 -0
- package/dist/runtime/overlay/composables/useMessageContext.d.ts +25 -0
- package/dist/runtime/overlay/composables/useMessageContext.js +29 -0
- package/dist/runtime/overlay/composables/useMobileSwipe.d.ts +15 -0
- package/dist/runtime/overlay/composables/useMobileSwipe.js +79 -0
- package/dist/runtime/overlay/composables/usePanelInteraction.d.ts +30 -0
- package/dist/runtime/overlay/composables/usePanelInteraction.js +184 -0
- package/dist/runtime/overlay/composables/usePanelPosition.d.ts +69 -0
- package/dist/runtime/overlay/composables/usePanelPosition.js +147 -0
- package/dist/runtime/server/claude-session.d.ts +11 -0
- package/dist/runtime/server/claude-session.js +166 -6
- package/dist/runtime/shared/composables/useClaudeChat.d.ts +7 -3
- package/dist/runtime/shared/composables/useClaudeChat.js +27 -251
- package/dist/runtime/shared/composables/useClaudeChatCore.d.ts +40 -0
- package/dist/runtime/shared/composables/useClaudeChatCore.js +350 -0
- package/dist/runtime/shared/composables/useMessageContext.d.ts +54 -0
- package/dist/runtime/shared/composables/useMessageContext.js +195 -0
- package/dist/runtime/shared/composables/useShare.d.ts +14 -4
- package/dist/runtime/shared/composables/useShare.js +57 -35
- package/dist/runtime/shared/composables/useVoiceInput.js +40 -11
- package/dist/runtime/shared/types.d.ts +36 -0
- package/dist/runtime/types.d.ts +14 -0
- package/package.json +1 -1
- package/dist/client/_nuxt/BSF2Vz9o.js +0 -1
- package/dist/client/_nuxt/BflmC3YB.js +0 -1
- package/dist/client/_nuxt/CRkq21kc.js +0 -1
- package/dist/client/_nuxt/Cgba93Y9.js +0 -1
- package/dist/client/_nuxt/DH8Ugy8E.js +0 -1
- package/dist/client/_nuxt/DeGmaFBY.js +0 -1
- package/dist/client/_nuxt/DgfRwrFR.js +0 -1
- package/dist/client/_nuxt/builds/meta/2be12f06-336a-4fdd-b982-2f6c682c14a6.json +0 -1
- package/dist/client/_nuxt/wDw60tEC.js +0 -10
- package/dist/client/_nuxt/xEjB6ozD.js +0 -1
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { computed, ref } from "vue";
|
|
2
|
+
import { io } from "socket.io-client";
|
|
3
|
+
import { SOCKET_PATH } from "../constants.js";
|
|
4
|
+
export function useClaudeChatCore(options) {
|
|
5
|
+
const {
|
|
6
|
+
getSocketUrl,
|
|
7
|
+
log = () => {
|
|
8
|
+
},
|
|
9
|
+
onDocsReceived,
|
|
10
|
+
onCommandsReceived,
|
|
11
|
+
getCurrentUserId,
|
|
12
|
+
onDisconnected
|
|
13
|
+
} = options;
|
|
14
|
+
const socket = ref(null);
|
|
15
|
+
const messages = ref([]);
|
|
16
|
+
const conversations = ref([]);
|
|
17
|
+
const activeConversationId = ref(null);
|
|
18
|
+
const isConnected = ref(false);
|
|
19
|
+
const isSessionActive = ref(false);
|
|
20
|
+
const isProcessing = ref(false);
|
|
21
|
+
const isHistoryOpen = ref(false);
|
|
22
|
+
const pendingToolCalls = ref(/* @__PURE__ */ new Map());
|
|
23
|
+
const statusText = computed(() => {
|
|
24
|
+
if (!isConnected.value) return "Disconnected";
|
|
25
|
+
if (isProcessing.value) return "Processing...";
|
|
26
|
+
return "Ready";
|
|
27
|
+
});
|
|
28
|
+
const statusColor = computed(() => {
|
|
29
|
+
if (!isConnected.value) return "red";
|
|
30
|
+
if (isProcessing.value) return "blue";
|
|
31
|
+
return "green";
|
|
32
|
+
});
|
|
33
|
+
function generateId() {
|
|
34
|
+
return Math.random().toString(36).substring(2, 9);
|
|
35
|
+
}
|
|
36
|
+
function addMessage(role, content, streaming = false) {
|
|
37
|
+
const message = {
|
|
38
|
+
id: generateId(),
|
|
39
|
+
role,
|
|
40
|
+
content,
|
|
41
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
42
|
+
streaming
|
|
43
|
+
};
|
|
44
|
+
messages.value.push(message);
|
|
45
|
+
return message;
|
|
46
|
+
}
|
|
47
|
+
function connectSocket() {
|
|
48
|
+
const url = getSocketUrl();
|
|
49
|
+
log("Connecting to socket at", url);
|
|
50
|
+
socket.value = io(url, {
|
|
51
|
+
path: SOCKET_PATH,
|
|
52
|
+
transports: ["websocket", "polling"],
|
|
53
|
+
reconnection: true,
|
|
54
|
+
reconnectionAttempts: 5
|
|
55
|
+
});
|
|
56
|
+
setupSocketListeners();
|
|
57
|
+
}
|
|
58
|
+
function setupSocketListeners() {
|
|
59
|
+
if (!socket.value) return;
|
|
60
|
+
socket.value.on("connect", () => {
|
|
61
|
+
log("Connected to socket");
|
|
62
|
+
isConnected.value = true;
|
|
63
|
+
socket.value?.emit("docs:list");
|
|
64
|
+
socket.value?.emit("commands:list");
|
|
65
|
+
});
|
|
66
|
+
socket.value.on("disconnect", () => {
|
|
67
|
+
log("Disconnected from socket");
|
|
68
|
+
isConnected.value = false;
|
|
69
|
+
isSessionActive.value = false;
|
|
70
|
+
isProcessing.value = false;
|
|
71
|
+
onDisconnected?.();
|
|
72
|
+
});
|
|
73
|
+
socket.value.on("session:status", (status) => {
|
|
74
|
+
log("Session status:", status);
|
|
75
|
+
isSessionActive.value = status.active;
|
|
76
|
+
isProcessing.value = status.processing;
|
|
77
|
+
});
|
|
78
|
+
socket.value.on("history:loaded", (conversation) => {
|
|
79
|
+
log("History loaded:", conversation.id, conversation.messages.length, "messages");
|
|
80
|
+
activeConversationId.value = conversation.id;
|
|
81
|
+
messages.value = conversation.messages.map((m) => ({
|
|
82
|
+
...m,
|
|
83
|
+
timestamp: new Date(m.timestamp)
|
|
84
|
+
}));
|
|
85
|
+
});
|
|
86
|
+
socket.value.on("history:list", (convs) => {
|
|
87
|
+
log("Conversations list:", convs.length);
|
|
88
|
+
conversations.value = convs;
|
|
89
|
+
});
|
|
90
|
+
socket.value.on("history:switched", (conversation) => {
|
|
91
|
+
log("Switched to conversation:", conversation.id);
|
|
92
|
+
activeConversationId.value = conversation.id;
|
|
93
|
+
messages.value = conversation.messages.map((m) => ({
|
|
94
|
+
...m,
|
|
95
|
+
timestamp: new Date(m.timestamp)
|
|
96
|
+
}));
|
|
97
|
+
isHistoryOpen.value = false;
|
|
98
|
+
});
|
|
99
|
+
socket.value.on("history:deleted", (data) => {
|
|
100
|
+
log("Conversation deleted:", data);
|
|
101
|
+
});
|
|
102
|
+
socket.value.on("docs:list", (files) => {
|
|
103
|
+
log("Docs list received:", files.length);
|
|
104
|
+
onDocsReceived?.(files);
|
|
105
|
+
});
|
|
106
|
+
socket.value.on("commands:list", (cmds) => {
|
|
107
|
+
log("Commands list received:", cmds.length);
|
|
108
|
+
onCommandsReceived?.(cmds);
|
|
109
|
+
});
|
|
110
|
+
socket.value.on("stream:message_start", (data) => {
|
|
111
|
+
log("Message start:", data.id);
|
|
112
|
+
pendingToolCalls.value.clear();
|
|
113
|
+
const lastMessage = messages.value[messages.value.length - 1];
|
|
114
|
+
if (!lastMessage || lastMessage.role !== "assistant" || !lastMessage.streaming) {
|
|
115
|
+
addMessage("assistant", "", true);
|
|
116
|
+
}
|
|
117
|
+
const streamingMessage = messages.value.findLast((m) => m.role === "assistant" && m.streaming);
|
|
118
|
+
if (streamingMessage && !streamingMessage.contentBlocks) {
|
|
119
|
+
streamingMessage.contentBlocks = [];
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
socket.value.on("stream:tool_use", (data) => {
|
|
123
|
+
log("Tool use:", data.name);
|
|
124
|
+
const toolBlock = {
|
|
125
|
+
type: "tool_use",
|
|
126
|
+
id: data.id,
|
|
127
|
+
name: data.name,
|
|
128
|
+
input: data.input
|
|
129
|
+
};
|
|
130
|
+
pendingToolCalls.value.set(data.id, toolBlock);
|
|
131
|
+
const lastMessage = messages.value.findLast((m) => m.role === "assistant" && m.streaming) || messages.value.findLast((m) => m.role === "assistant");
|
|
132
|
+
if (lastMessage) {
|
|
133
|
+
if (!lastMessage.contentBlocks) {
|
|
134
|
+
lastMessage.contentBlocks = [];
|
|
135
|
+
}
|
|
136
|
+
lastMessage.contentBlocks.push(toolBlock);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
socket.value.on("stream:tool_result", (data) => {
|
|
140
|
+
log("Tool result:", data.tool_use_id, data.is_error ? "ERROR" : "OK");
|
|
141
|
+
const resultBlock = {
|
|
142
|
+
type: "tool_result",
|
|
143
|
+
tool_use_id: data.tool_use_id,
|
|
144
|
+
content: data.content,
|
|
145
|
+
is_error: data.is_error
|
|
146
|
+
};
|
|
147
|
+
const lastMessage = messages.value.findLast((m) => m.role === "assistant" && m.streaming) || messages.value.findLast((m) => m.role === "assistant");
|
|
148
|
+
if (lastMessage) {
|
|
149
|
+
if (!lastMessage.contentBlocks) {
|
|
150
|
+
lastMessage.contentBlocks = [];
|
|
151
|
+
}
|
|
152
|
+
lastMessage.contentBlocks.push(resultBlock);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
socket.value.on("stream:text_delta", (data) => {
|
|
156
|
+
const lastMessage = messages.value.findLast((m) => m.role === "assistant" && m.streaming);
|
|
157
|
+
if (lastMessage) {
|
|
158
|
+
lastMessage.content += data.text;
|
|
159
|
+
if (!lastMessage.contentBlocks) {
|
|
160
|
+
lastMessage.contentBlocks = [];
|
|
161
|
+
}
|
|
162
|
+
const lastBlock = lastMessage.contentBlocks[lastMessage.contentBlocks.length - 1];
|
|
163
|
+
if (lastBlock && lastBlock.type === "text") {
|
|
164
|
+
lastBlock.text = (lastBlock.text || "") + data.text;
|
|
165
|
+
} else {
|
|
166
|
+
const prefix = lastMessage.contentBlocks.length > 0 ? "\n" : "";
|
|
167
|
+
lastMessage.contentBlocks.push({
|
|
168
|
+
type: "text",
|
|
169
|
+
text: prefix + data.text
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
socket.value.on("stream:message_complete", (data) => {
|
|
175
|
+
log("Message complete:", data.id);
|
|
176
|
+
const lastMessage = messages.value.findLast((m) => m.role === "assistant" && m.streaming) || messages.value.findLast((m) => m.role === "assistant");
|
|
177
|
+
if (lastMessage) {
|
|
178
|
+
lastMessage.streaming = false;
|
|
179
|
+
lastMessage.content = data.content;
|
|
180
|
+
lastMessage.contentBlocks = data.contentBlocks;
|
|
181
|
+
lastMessage.model = data.model;
|
|
182
|
+
}
|
|
183
|
+
pendingToolCalls.value.clear();
|
|
184
|
+
});
|
|
185
|
+
socket.value.on("stream:stopped", (data) => {
|
|
186
|
+
log("Generation stopped:", data.message);
|
|
187
|
+
isProcessing.value = false;
|
|
188
|
+
const lastMessage = messages.value.findLast((m) => m.role === "assistant" && m.streaming);
|
|
189
|
+
if (lastMessage) {
|
|
190
|
+
lastMessage.streaming = false;
|
|
191
|
+
if (data.partialContent) {
|
|
192
|
+
lastMessage.content = data.partialContent + "\n\n*[Generation stopped by user]*";
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
socket.value.on("stream:result", (data) => {
|
|
197
|
+
log("Result:", data.subtype, "cost:", data.cost_usd, "duration:", data.duration_ms);
|
|
198
|
+
});
|
|
199
|
+
socket.value.on("output:chunk", (chunk) => {
|
|
200
|
+
log("Output chunk:", chunk.length);
|
|
201
|
+
const lastMessage = messages.value.findLast((m) => m.role === "assistant" && m.streaming);
|
|
202
|
+
if (lastMessage) {
|
|
203
|
+
lastMessage.content += chunk;
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
socket.value.on("output:complete", () => {
|
|
207
|
+
log("Output complete");
|
|
208
|
+
const lastMessage = messages.value.findLast((m) => m.role === "assistant");
|
|
209
|
+
if (lastMessage) {
|
|
210
|
+
lastMessage.streaming = false;
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
socket.value.on("output:error", (error) => {
|
|
214
|
+
log("Output error:", error);
|
|
215
|
+
addMessage("system", `Error: ${error}`);
|
|
216
|
+
});
|
|
217
|
+
socket.value.on("session:error", (error) => {
|
|
218
|
+
log("Session error:", error);
|
|
219
|
+
addMessage("system", `Session error: ${error}`);
|
|
220
|
+
});
|
|
221
|
+
socket.value.on("session:closed", (data) => {
|
|
222
|
+
log("Session closed:", data);
|
|
223
|
+
addMessage("system", `Session ended (exit code: ${data.exitCode})`);
|
|
224
|
+
});
|
|
225
|
+
socket.value.on("stream:critical_file_warning", (data) => {
|
|
226
|
+
log("Critical file warning:", data.file_name);
|
|
227
|
+
addMessage("system", data.message);
|
|
228
|
+
});
|
|
229
|
+
socket.value.on("stream:system_message", (data) => {
|
|
230
|
+
log("System message:", data.type, data.message);
|
|
231
|
+
addMessage("system", data.message);
|
|
232
|
+
});
|
|
233
|
+
socket.value.on("stream:user_message", (data) => {
|
|
234
|
+
const currentUserId = getCurrentUserId?.();
|
|
235
|
+
log("User message from:", data.senderId, "current:", currentUserId, "nickname:", data.senderNickname);
|
|
236
|
+
if (currentUserId && data.senderId === currentUserId) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const existingById = messages.value.find((m) => m.id === data.id);
|
|
240
|
+
if (existingById) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const assistantIndex = messages.value.findIndex((m) => m.role === "assistant" && m.streaming);
|
|
244
|
+
const newMsg = {
|
|
245
|
+
...data,
|
|
246
|
+
timestamp: new Date(data.timestamp)
|
|
247
|
+
};
|
|
248
|
+
if (assistantIndex >= 0) {
|
|
249
|
+
messages.value.splice(assistantIndex, 0, newMsg);
|
|
250
|
+
} else {
|
|
251
|
+
messages.value.push(newMsg);
|
|
252
|
+
addMessage("assistant", "", true);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
function newChat() {
|
|
257
|
+
if (socket.value) {
|
|
258
|
+
socket.value.emit("session:reset");
|
|
259
|
+
messages.value = [];
|
|
260
|
+
isHistoryOpen.value = false;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function sendMessage(content, senderId, senderNickname, attachments) {
|
|
264
|
+
const hasContent = content.trim().length > 0 || attachments && attachments.length > 0;
|
|
265
|
+
if (!hasContent || isProcessing.value || !isConnected.value) return false;
|
|
266
|
+
const userMsg = addMessage("user", content);
|
|
267
|
+
if (senderId) {
|
|
268
|
+
userMsg.senderId = senderId;
|
|
269
|
+
userMsg.senderNickname = senderNickname;
|
|
270
|
+
}
|
|
271
|
+
if (attachments && attachments.length > 0) {
|
|
272
|
+
userMsg.attachments = attachments.map((a) => ({
|
|
273
|
+
type: "image",
|
|
274
|
+
path: `pending:${a.id}`,
|
|
275
|
+
// Will be updated when server responds
|
|
276
|
+
filename: a.filename,
|
|
277
|
+
mimeType: a.mimeType
|
|
278
|
+
}));
|
|
279
|
+
}
|
|
280
|
+
addMessage("assistant", "", true);
|
|
281
|
+
if (socket.value) {
|
|
282
|
+
const payload = { message: content };
|
|
283
|
+
if (senderId) {
|
|
284
|
+
payload.senderId = senderId;
|
|
285
|
+
}
|
|
286
|
+
if (attachments && attachments.length > 0) {
|
|
287
|
+
payload.attachments = attachments;
|
|
288
|
+
}
|
|
289
|
+
socket.value.emit("message:send", payload);
|
|
290
|
+
}
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
function stopGeneration() {
|
|
294
|
+
if (!isProcessing.value || !isConnected.value) return false;
|
|
295
|
+
log("Stopping generation");
|
|
296
|
+
if (socket.value) {
|
|
297
|
+
socket.value.emit("message:stop");
|
|
298
|
+
}
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
function toggleHistory() {
|
|
302
|
+
isHistoryOpen.value = !isHistoryOpen.value;
|
|
303
|
+
if (isHistoryOpen.value && socket.value) {
|
|
304
|
+
socket.value.emit("history:list");
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function selectConversation(id) {
|
|
308
|
+
if (socket.value) {
|
|
309
|
+
socket.value.emit("history:switch", id);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function deleteConversation(id) {
|
|
313
|
+
if (socket.value) {
|
|
314
|
+
socket.value.emit("history:delete", id);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function disconnect() {
|
|
318
|
+
if (socket.value) {
|
|
319
|
+
socket.value.disconnect();
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function findToolResult(blocks, toolUseId) {
|
|
323
|
+
if (!blocks) return void 0;
|
|
324
|
+
return blocks.find((b) => b.type === "tool_result" && b.tool_use_id === toolUseId);
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
// State
|
|
328
|
+
socket,
|
|
329
|
+
messages,
|
|
330
|
+
conversations,
|
|
331
|
+
activeConversationId,
|
|
332
|
+
isConnected,
|
|
333
|
+
isSessionActive,
|
|
334
|
+
isProcessing,
|
|
335
|
+
isHistoryOpen,
|
|
336
|
+
statusText,
|
|
337
|
+
statusColor,
|
|
338
|
+
// Methods
|
|
339
|
+
connectSocket,
|
|
340
|
+
disconnect,
|
|
341
|
+
newChat,
|
|
342
|
+
sendMessage,
|
|
343
|
+
stopGeneration,
|
|
344
|
+
addMessage,
|
|
345
|
+
toggleHistory,
|
|
346
|
+
selectConversation,
|
|
347
|
+
deleteConversation,
|
|
348
|
+
findToolResult
|
|
349
|
+
};
|
|
350
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ContextChip, MessageContextData } from '../types.js';
|
|
2
|
+
export interface MessageContextOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Get viewport dimensions (context-specific)
|
|
5
|
+
* For overlay: use window.innerWidth/innerHeight
|
|
6
|
+
* For DevTools: try host client, window.top, parent windows
|
|
7
|
+
*/
|
|
8
|
+
getViewport?: () => {
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
} | null;
|
|
12
|
+
/**
|
|
13
|
+
* Get current route information (context-specific)
|
|
14
|
+
* For overlay: use window.location
|
|
15
|
+
* For DevTools: use host app $route
|
|
16
|
+
*/
|
|
17
|
+
getRoute?: () => RouteInfo | null;
|
|
18
|
+
/**
|
|
19
|
+
* Get selected components (DevTools only)
|
|
20
|
+
*/
|
|
21
|
+
getComponents?: () => string[];
|
|
22
|
+
/** Log function */
|
|
23
|
+
log?: (...args: unknown[]) => void;
|
|
24
|
+
}
|
|
25
|
+
export interface RouteInfo {
|
|
26
|
+
path: string;
|
|
27
|
+
fullPath?: string;
|
|
28
|
+
query?: Record<string, string | string[] | undefined>;
|
|
29
|
+
params?: Record<string, string | string[]>;
|
|
30
|
+
name?: string;
|
|
31
|
+
pageComponent?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function useMessageContext(options?: MessageContextOptions): {
|
|
34
|
+
contextChips: import("vue").Ref<{
|
|
35
|
+
id: "viewport" | "user-agent" | "routing";
|
|
36
|
+
label: string;
|
|
37
|
+
icon: string;
|
|
38
|
+
active: boolean;
|
|
39
|
+
}[], ContextChip[] | {
|
|
40
|
+
id: "viewport" | "user-agent" | "routing";
|
|
41
|
+
label: string;
|
|
42
|
+
icon: string;
|
|
43
|
+
active: boolean;
|
|
44
|
+
}[]>;
|
|
45
|
+
toggleContextChip: (id: ContextChip["id"]) => void;
|
|
46
|
+
collectContext: () => MessageContextData | null;
|
|
47
|
+
generateContextBlock: (context: MessageContextData) => string;
|
|
48
|
+
parseMessageContext: (content: string) => {
|
|
49
|
+
context: MessageContextData | null;
|
|
50
|
+
body: string;
|
|
51
|
+
};
|
|
52
|
+
collectContextBlock: () => string | null;
|
|
53
|
+
};
|
|
54
|
+
export type { ContextChip, MessageContextData } from '../types.js';
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
function defaultGetViewport() {
|
|
3
|
+
if (typeof window === "undefined") return null;
|
|
4
|
+
return {
|
|
5
|
+
width: window.innerWidth,
|
|
6
|
+
height: window.innerHeight
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function defaultGetRoute() {
|
|
10
|
+
if (typeof window === "undefined") return null;
|
|
11
|
+
return {
|
|
12
|
+
path: window.location.pathname,
|
|
13
|
+
fullPath: window.location.pathname + window.location.search,
|
|
14
|
+
query: window.location.search ? Object.fromEntries(new URLSearchParams(window.location.search)) : void 0
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function parseUserAgent(ua) {
|
|
18
|
+
let browser = "Unknown";
|
|
19
|
+
if (ua.includes("Firefox/")) browser = "Firefox";
|
|
20
|
+
else if (ua.includes("Edg/")) browser = "Edge";
|
|
21
|
+
else if (ua.includes("Chrome/")) browser = "Chrome";
|
|
22
|
+
else if (ua.includes("Safari/") && !ua.includes("Chrome")) browser = "Safari";
|
|
23
|
+
let os = "Unknown";
|
|
24
|
+
if (ua.includes("Windows")) os = "Windows";
|
|
25
|
+
else if (ua.includes("Mac OS")) os = "macOS";
|
|
26
|
+
else if (ua.includes("Linux")) os = "Linux";
|
|
27
|
+
else if (ua.includes("Android")) os = "Android";
|
|
28
|
+
else if (ua.includes("iPhone") || ua.includes("iPad")) os = "iOS";
|
|
29
|
+
return { browser, os };
|
|
30
|
+
}
|
|
31
|
+
export function useMessageContext(options = {}) {
|
|
32
|
+
const {
|
|
33
|
+
getViewport = defaultGetViewport,
|
|
34
|
+
getRoute = defaultGetRoute,
|
|
35
|
+
getComponents = () => [],
|
|
36
|
+
log: _log = () => {
|
|
37
|
+
}
|
|
38
|
+
} = options;
|
|
39
|
+
const contextChips = ref([
|
|
40
|
+
{ id: "viewport", label: "Viewport", icon: "carbon:fit-to-screen", active: false },
|
|
41
|
+
{ id: "user-agent", label: "User Agent", icon: "carbon:application-web", active: false },
|
|
42
|
+
{ id: "routing", label: "Routing", icon: "carbon:location", active: false }
|
|
43
|
+
]);
|
|
44
|
+
function toggleContextChip(id) {
|
|
45
|
+
const chip = contextChips.value.find((c) => c.id === id);
|
|
46
|
+
if (chip) {
|
|
47
|
+
chip.active = !chip.active;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function collectContext() {
|
|
51
|
+
const context = {};
|
|
52
|
+
let hasContext = false;
|
|
53
|
+
if (contextChips.value.find((c) => c.id === "viewport")?.active) {
|
|
54
|
+
const viewport = getViewport();
|
|
55
|
+
if (viewport) {
|
|
56
|
+
context.viewport = viewport;
|
|
57
|
+
hasContext = true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (contextChips.value.find((c) => c.id === "user-agent")?.active) {
|
|
61
|
+
if (typeof navigator !== "undefined") {
|
|
62
|
+
context.userAgent = navigator.userAgent;
|
|
63
|
+
hasContext = true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (contextChips.value.find((c) => c.id === "routing")?.active) {
|
|
67
|
+
const route = getRoute();
|
|
68
|
+
if (route) {
|
|
69
|
+
const query = route.query && Object.keys(route.query).length > 0 ? Object.fromEntries(
|
|
70
|
+
Object.entries(route.query).filter(([_, v]) => v !== void 0).map(([k, v]) => [k, Array.isArray(v) ? v.filter(Boolean).join(",") : v || ""])
|
|
71
|
+
) : void 0;
|
|
72
|
+
const params = route.params && Object.keys(route.params).length > 0 ? Object.fromEntries(
|
|
73
|
+
Object.entries(route.params).map(([k, v]) => [k, Array.isArray(v) ? v.join("/") : v || ""])
|
|
74
|
+
) : void 0;
|
|
75
|
+
context.routing = {
|
|
76
|
+
path: route.path,
|
|
77
|
+
fullPath: route.fullPath,
|
|
78
|
+
query,
|
|
79
|
+
params,
|
|
80
|
+
name: route.name,
|
|
81
|
+
pageComponent: route.pageComponent
|
|
82
|
+
};
|
|
83
|
+
hasContext = true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const components = getComponents();
|
|
87
|
+
if (components.length > 0) {
|
|
88
|
+
context.components = components;
|
|
89
|
+
hasContext = true;
|
|
90
|
+
}
|
|
91
|
+
return hasContext ? context : null;
|
|
92
|
+
}
|
|
93
|
+
function generateContextBlock(context) {
|
|
94
|
+
const parts = [];
|
|
95
|
+
if (context.viewport) {
|
|
96
|
+
parts.push(`viewport: ${context.viewport.width}x${context.viewport.height}`);
|
|
97
|
+
}
|
|
98
|
+
if (context.userAgent) {
|
|
99
|
+
const { browser, os } = parseUserAgent(context.userAgent);
|
|
100
|
+
parts.push(`browser: ${browser} on ${os}`);
|
|
101
|
+
}
|
|
102
|
+
if (context.routing) {
|
|
103
|
+
parts.push(`route: ${context.routing.path}`);
|
|
104
|
+
if (context.routing.query && Object.keys(context.routing.query).length > 0) {
|
|
105
|
+
const queryStr = Object.entries(context.routing.query).map(([k, v]) => `${k}=${Array.isArray(v) ? v.join(",") : v}`).join("&");
|
|
106
|
+
parts.push(`query: ?${queryStr}`);
|
|
107
|
+
}
|
|
108
|
+
if (context.routing.params && Object.keys(context.routing.params).length > 0) {
|
|
109
|
+
const paramsStr = Object.entries(context.routing.params).map(([k, v]) => `${k}=${v}`).join(", ");
|
|
110
|
+
parts.push(`params: ${paramsStr}`);
|
|
111
|
+
}
|
|
112
|
+
if (context.routing.pageComponent) {
|
|
113
|
+
parts.push(`page: ${context.routing.pageComponent}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (context.components && context.components.length > 0) {
|
|
117
|
+
parts.push(`components: ${context.components.join(", ")}`);
|
|
118
|
+
}
|
|
119
|
+
return `[context]
|
|
120
|
+
${parts.join("\n")}
|
|
121
|
+
[/context]`;
|
|
122
|
+
}
|
|
123
|
+
function parseMessageContext(content) {
|
|
124
|
+
const contextBlockRegex = /^\[context\]\n([\s\S]*?)\n\[\/context\]\n?/;
|
|
125
|
+
const match = content.match(contextBlockRegex);
|
|
126
|
+
if (!match || !match[1]) {
|
|
127
|
+
return { context: null, body: content };
|
|
128
|
+
}
|
|
129
|
+
const contextContent = match[1];
|
|
130
|
+
let body = content.slice(match[0].length);
|
|
131
|
+
body = body.replace(/^(@\S+\s+)+/g, "").trim();
|
|
132
|
+
try {
|
|
133
|
+
const context = {};
|
|
134
|
+
const viewportMatch = contextContent.match(/viewport:\s*(\d+)x(\d+)/);
|
|
135
|
+
if (viewportMatch && viewportMatch[1] && viewportMatch[2]) {
|
|
136
|
+
context.viewport = {
|
|
137
|
+
width: Number.parseInt(viewportMatch[1]),
|
|
138
|
+
height: Number.parseInt(viewportMatch[2])
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const browserMatch = contextContent.match(/browser:\s*(\w+)\s+on\s+(\w+)/);
|
|
142
|
+
if (browserMatch && browserMatch[1] && browserMatch[2]) {
|
|
143
|
+
context.userAgent = `${browserMatch[1]} on ${browserMatch[2]}`;
|
|
144
|
+
}
|
|
145
|
+
const routeMatch = contextContent.match(/route:\s*(\S+)/);
|
|
146
|
+
if (routeMatch && routeMatch[1]) {
|
|
147
|
+
context.routing = { path: routeMatch[1] };
|
|
148
|
+
const queryMatch = contextContent.match(/query:\s*\?(.+)/);
|
|
149
|
+
if (queryMatch && queryMatch[1] && context.routing) {
|
|
150
|
+
context.routing.query = {};
|
|
151
|
+
const pairs = queryMatch[1].split("&");
|
|
152
|
+
for (const pair of pairs) {
|
|
153
|
+
const [key, value] = pair.split("=");
|
|
154
|
+
if (key && context.routing.query) context.routing.query[key] = value || "";
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const paramsMatch = contextContent.match(/params:\s*(.+)/);
|
|
158
|
+
if (paramsMatch && paramsMatch[1] && context.routing) {
|
|
159
|
+
context.routing.params = {};
|
|
160
|
+
const pairs = paramsMatch[1].split(",").map((s) => s.trim());
|
|
161
|
+
for (const pair of pairs) {
|
|
162
|
+
const [key, value] = pair.split("=");
|
|
163
|
+
if (key && context.routing.params) context.routing.params[key] = value || "";
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const pageMatch = contextContent.match(/page:\s*(\S+)/);
|
|
167
|
+
if (pageMatch && pageMatch[1] && context.routing) {
|
|
168
|
+
context.routing.pageComponent = pageMatch[1];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const componentsMatch = contextContent.match(/components:\s*(.+)/);
|
|
172
|
+
if (componentsMatch && componentsMatch[1]) {
|
|
173
|
+
context.components = componentsMatch[1].split(",").map((s) => s.trim());
|
|
174
|
+
}
|
|
175
|
+
return { context: Object.keys(context).length > 0 ? context : null, body };
|
|
176
|
+
} catch {
|
|
177
|
+
return { context: null, body: content };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function collectContextBlock() {
|
|
181
|
+
const context = collectContext();
|
|
182
|
+
if (!context) return null;
|
|
183
|
+
return generateContextBlock(context);
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
// State
|
|
187
|
+
contextChips,
|
|
188
|
+
// Methods
|
|
189
|
+
toggleContextChip,
|
|
190
|
+
collectContext,
|
|
191
|
+
generateContextBlock,
|
|
192
|
+
parseMessageContext,
|
|
193
|
+
collectContextBlock
|
|
194
|
+
};
|
|
195
|
+
}
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import type { Socket } from 'socket.io-client';
|
|
2
2
|
import type { ShareUser } from '../types.js';
|
|
3
|
-
interface ShareOptions {
|
|
4
|
-
/**
|
|
5
|
-
|
|
3
|
+
export interface ShareOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Get URL parameter value (context-specific)
|
|
6
|
+
* For overlay: just check window.location
|
|
7
|
+
* For DevTools: check window, parent window, host route
|
|
8
|
+
*/
|
|
9
|
+
getUrlParam?: (param: string) => string | null;
|
|
10
|
+
/**
|
|
11
|
+
* Get base URL for share links (context-specific)
|
|
12
|
+
* For overlay: use tunnel URL or window.location.origin
|
|
13
|
+
* For DevTools: use tunnel URL, parent window, or window.location.origin
|
|
14
|
+
*/
|
|
15
|
+
getBaseUrl?: () => string;
|
|
6
16
|
/** Log function */
|
|
7
17
|
log?: (...args: unknown[]) => void;
|
|
8
18
|
}
|
|
@@ -33,10 +43,10 @@ export declare function useShare(options?: ShareOptions): {
|
|
|
33
43
|
needsNicknameForMessage: () => boolean;
|
|
34
44
|
needsNicknameForShare: () => boolean;
|
|
35
45
|
checkSharingStatus: (socket: Socket | null) => void;
|
|
46
|
+
syncCredentials: (socket: Socket | null) => void;
|
|
36
47
|
registerUser: (socket: Socket | null) => void;
|
|
37
48
|
setupSocketListeners: (socket: Socket) => void;
|
|
38
49
|
getNicknameById: (id: string) => string | null;
|
|
39
50
|
isOwnMessage: (senderId?: string) => boolean;
|
|
40
51
|
copyShareLink: (includeNickname?: boolean) => Promise<boolean>;
|
|
41
52
|
};
|
|
42
|
-
export {};
|