vite-plugin-opencode-assistant 1.0.3 → 1.0.4
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 +189 -184
- package/dist/constants.d.ts +2 -1
- package/dist/constants.js +19 -18
- package/dist/constants.js.map +1 -1
- package/dist/opencode/plugins/page-context.js +41 -14
- package/dist/opencode/plugins/page-context.js.map +1 -1
- package/dist/types.d.ts +4 -2
- package/dist/types.js.map +1 -1
- package/dist/vite/client.js +580 -400
- package/dist/vite/client.js.map +1 -1
- package/dist/vite/index.d.ts +2 -2
- package/dist/vite/index.js +348 -171
- package/dist/vite/index.js.map +1 -1
- package/package.json +4 -1
package/dist/vite/client.js
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* @description 用于在浏览器中显示 OpenCode AI 助手挂件
|
|
5
5
|
*/
|
|
6
6
|
(function () {
|
|
7
|
-
|
|
7
|
+
"use strict";
|
|
8
8
|
/** @type {string} 初始化标记 */
|
|
9
|
-
const INIT_MARKER =
|
|
9
|
+
const INIT_MARKER = "__OPENCODE_INITIALIZED__";
|
|
10
10
|
/** @type {string} 选中元素存储键 */
|
|
11
|
-
const SELECTED_ELEMENTS_KEY =
|
|
11
|
+
const SELECTED_ELEMENTS_KEY = "__opencode_selected_elements__";
|
|
12
12
|
/** @type {number} 服务器同步间隔(毫秒) */
|
|
13
13
|
const SERVER_SYNC_INTERVAL = 2000;
|
|
14
14
|
/** @type {number} 检查 Vue Inspector 间隔(毫秒) */
|
|
@@ -50,11 +50,26 @@
|
|
|
50
50
|
if (window[INIT_MARKER])
|
|
51
51
|
return;
|
|
52
52
|
window[INIT_MARKER] = true;
|
|
53
|
-
const { webUrl, position, theme, open, sessionUrl, lazy, hotkey, cwd } = config;
|
|
53
|
+
const { webUrl, position, theme, open, sessionUrl: initialSessionUrl, lazy, hotkey, cwd, } = config;
|
|
54
|
+
/** @type {string|undefined} 会话 URL */
|
|
55
|
+
let sessionUrl = initialSessionUrl;
|
|
56
|
+
/** @type {string|null} 当前会话 ID */
|
|
57
|
+
let currentSessionId = null;
|
|
58
|
+
/**
|
|
59
|
+
* 从 URL 中提取会话 ID
|
|
60
|
+
*/
|
|
61
|
+
function extractSessionId(url) {
|
|
62
|
+
if (!url)
|
|
63
|
+
return null;
|
|
64
|
+
const match = url.match(/\/session\/([^/?]+)/);
|
|
65
|
+
return match ? match[1] : null;
|
|
66
|
+
}
|
|
67
|
+
// 从初始 URL 中提取会话 ID
|
|
68
|
+
currentSessionId = extractSessionId(sessionUrl);
|
|
54
69
|
/** @type {string} 当前页面 URL */
|
|
55
|
-
let currentPageUrl =
|
|
70
|
+
let currentPageUrl = "";
|
|
56
71
|
/** @type {string} 当前页面标题 */
|
|
57
|
-
let currentPageTitle =
|
|
72
|
+
let currentPageTitle = "";
|
|
58
73
|
/** @type {boolean} 服务是否已启动 */
|
|
59
74
|
let servicesStarted = !lazy;
|
|
60
75
|
/** @type {boolean} 挂件是否打开 */
|
|
@@ -66,20 +81,22 @@
|
|
|
66
81
|
*/
|
|
67
82
|
function parseHotkey(hotkeyStr) {
|
|
68
83
|
if (!hotkeyStr)
|
|
69
|
-
return { ctrl: true, shift: false, alt: false, key:
|
|
70
|
-
const parts = hotkeyStr.toLowerCase().split(
|
|
84
|
+
return { ctrl: true, shift: false, alt: false, key: "k" };
|
|
85
|
+
const parts = hotkeyStr.toLowerCase().split("+");
|
|
71
86
|
const key = parts.pop();
|
|
72
87
|
return {
|
|
73
|
-
ctrl: parts.includes(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
88
|
+
ctrl: parts.includes("ctrl") ||
|
|
89
|
+
parts.includes("cmd") ||
|
|
90
|
+
parts.includes("meta"),
|
|
91
|
+
shift: parts.includes("shift"),
|
|
92
|
+
alt: parts.includes("alt"),
|
|
93
|
+
key: key || "k",
|
|
77
94
|
};
|
|
78
95
|
}
|
|
79
96
|
/** @type {HotkeyConfig} 主快捷键配置 */
|
|
80
97
|
const mainHotkey = parseHotkey(hotkey);
|
|
81
98
|
/** @type {HotkeyConfig} 选择模式快捷键配置 */
|
|
82
|
-
const selectHotkey = parseHotkey(
|
|
99
|
+
const selectHotkey = parseHotkey("ctrl+p");
|
|
83
100
|
/**
|
|
84
101
|
* 检查键盘事件是否匹配快捷键
|
|
85
102
|
* @param {KeyboardEvent} e - 键盘事件
|
|
@@ -87,7 +104,9 @@
|
|
|
87
104
|
* @returns {boolean} 是否匹配
|
|
88
105
|
*/
|
|
89
106
|
function matchHotkey(e, hotkeyConfig) {
|
|
90
|
-
const ctrlMatch = hotkeyConfig.ctrl
|
|
107
|
+
const ctrlMatch = hotkeyConfig.ctrl
|
|
108
|
+
? e.ctrlKey || e.metaKey
|
|
109
|
+
: !(e.ctrlKey || e.metaKey);
|
|
91
110
|
const shiftMatch = hotkeyConfig.shift ? e.shiftKey : !e.shiftKey;
|
|
92
111
|
const altMatch = hotkeyConfig.alt ? e.altKey : !e.altKey;
|
|
93
112
|
const keyMatch = e.key.toLowerCase() === hotkeyConfig.key.toLowerCase();
|
|
@@ -131,18 +150,26 @@
|
|
|
131
150
|
if (servicesStarted)
|
|
132
151
|
return true;
|
|
133
152
|
try {
|
|
134
|
-
const res = await fetch(
|
|
153
|
+
const res = await fetch("/__opencode_start__");
|
|
135
154
|
const data = await res.json();
|
|
136
155
|
if (data.success) {
|
|
137
156
|
servicesStarted = true;
|
|
138
|
-
if (data.sessionUrl
|
|
139
|
-
|
|
157
|
+
if (data.sessionUrl) {
|
|
158
|
+
sessionUrl = data.sessionUrl;
|
|
159
|
+
currentSessionId = extractSessionId(sessionUrl);
|
|
160
|
+
if (iframe)
|
|
161
|
+
iframe.src = sessionUrl;
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
if (iframe)
|
|
165
|
+
iframe.src = "about:blank";
|
|
166
|
+
showLoading();
|
|
140
167
|
}
|
|
141
168
|
return true;
|
|
142
169
|
}
|
|
143
170
|
}
|
|
144
171
|
catch (e) {
|
|
145
|
-
console.error(
|
|
172
|
+
console.error("[OpenCode Widget] Failed to start services:", e);
|
|
146
173
|
}
|
|
147
174
|
return false;
|
|
148
175
|
}
|
|
@@ -158,10 +185,14 @@
|
|
|
158
185
|
if (force || newUrl !== currentPageUrl || newTitle !== currentPageTitle) {
|
|
159
186
|
currentPageUrl = newUrl;
|
|
160
187
|
currentPageTitle = newTitle;
|
|
161
|
-
fetch(
|
|
162
|
-
method:
|
|
163
|
-
headers: {
|
|
164
|
-
body: JSON.stringify({
|
|
188
|
+
fetch("/__opencode_context__", {
|
|
189
|
+
method: "POST",
|
|
190
|
+
headers: { "Content-Type": "application/json" },
|
|
191
|
+
body: JSON.stringify({
|
|
192
|
+
url: newUrl,
|
|
193
|
+
title: newTitle,
|
|
194
|
+
selectedElements,
|
|
195
|
+
}),
|
|
165
196
|
}).catch(() => { });
|
|
166
197
|
}
|
|
167
198
|
}
|
|
@@ -176,8 +207,8 @@
|
|
|
176
207
|
originalReplaceState.apply(this, args);
|
|
177
208
|
setTimeout(updateContext, 0);
|
|
178
209
|
};
|
|
179
|
-
window.addEventListener(
|
|
180
|
-
window.addEventListener(
|
|
210
|
+
window.addEventListener("popstate", () => setTimeout(updateContext, 0));
|
|
211
|
+
window.addEventListener("hashchange", () => setTimeout(updateContext, 0));
|
|
181
212
|
// 监听标题变化
|
|
182
213
|
const titleObserver = new MutationObserver(() => {
|
|
183
214
|
if (document.title !== currentPageTitle) {
|
|
@@ -187,113 +218,114 @@
|
|
|
187
218
|
if (document.head) {
|
|
188
219
|
titleObserver.observe(document.head, { childList: true, subtree: true });
|
|
189
220
|
}
|
|
221
|
+
/** @type {EventSource|null} SSE 连接实例 */
|
|
222
|
+
let sseConnection = null;
|
|
190
223
|
if (servicesStarted) {
|
|
191
224
|
updateContext(true);
|
|
225
|
+
setupSSEConnection();
|
|
192
226
|
}
|
|
193
|
-
/** @type {string} iframe URL */
|
|
194
|
-
const iframeUrl = sessionUrl || webUrl;
|
|
195
227
|
// 创建样式
|
|
196
|
-
const style = document.createElement(
|
|
228
|
+
const style = document.createElement("style");
|
|
197
229
|
style.textContent = buildWidgetStyles();
|
|
198
230
|
document.head.appendChild(style);
|
|
199
231
|
// 创建容器
|
|
200
|
-
const container = document.createElement(
|
|
232
|
+
const container = document.createElement("div");
|
|
201
233
|
container.className = `opencode-widget ${position}`;
|
|
202
234
|
// 创建按钮
|
|
203
|
-
const button = document.createElement(
|
|
204
|
-
button.className =
|
|
235
|
+
const button = document.createElement("button");
|
|
236
|
+
button.className = "opencode-button";
|
|
205
237
|
button.innerHTML = `
|
|
206
238
|
<svg t="1775402599580" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns=" http://www.w3.org/2000/svg " p-id="5390" xmlns:xlink=" http://www.w3.org/1999/xlink " width="100%" height="100%"><path d="M512 981.33H85.34c-15.85 0-30.38-8.77-37.77-22.81a42.624 42.624 0 0 1 2.6-44.02L135 791.08C75.25 710.5 42.67 612.6 42.67 512 42.67 253.21 253.21 42.67 512 42.67S981.34 253.21 981.34 512 770.8 981.33 512 981.33zM166.44 896H512c211.73 0 384-172.27 384-384S723.73 128 512 128 128 300.27 128 512c0 91.29 32.83 179.9 92.46 249.46 12.58 14.69 13.73 36 2.77 51.94L166.44 896z" fill="white" p-id="5391"></path><path d="M384 448m-64 0a64 64 0 1 0 128 0 64 64 0 1 0 -128 0Z" fill="white" p-id="5392"></path><path d="M640 448m-64 0a64 64 0 1 0 128 0 64 64 0 1 0 -128 0Z" fill="white" p-id="5393"></path></svg>
|
|
207
239
|
`;
|
|
208
|
-
button.setAttribute(
|
|
209
|
-
button.title = `AI 助手 (${hotkey ||
|
|
240
|
+
button.setAttribute("aria-label", "打开 AI 助手");
|
|
241
|
+
button.title = `AI 助手 (${hotkey || "Ctrl+K"})`;
|
|
210
242
|
// 创建聊天面板
|
|
211
|
-
const chat = document.createElement(
|
|
212
|
-
chat.className =
|
|
213
|
-
chat.setAttribute(
|
|
214
|
-
chat.setAttribute(
|
|
215
|
-
chat.setAttribute(
|
|
243
|
+
const chat = document.createElement("div");
|
|
244
|
+
chat.className = "opencode-chat";
|
|
245
|
+
chat.setAttribute("role", "dialog");
|
|
246
|
+
chat.setAttribute("aria-modal", "true");
|
|
247
|
+
chat.setAttribute("aria-label", "AI 助手对话窗口");
|
|
216
248
|
// 创建面板头部操作栏
|
|
217
|
-
const chatHeader = document.createElement(
|
|
218
|
-
chatHeader.className =
|
|
249
|
+
const chatHeader = document.createElement("div");
|
|
250
|
+
chatHeader.className = "opencode-chat-header";
|
|
219
251
|
// 左侧操作区
|
|
220
|
-
const headerLeft = document.createElement(
|
|
221
|
-
headerLeft.className =
|
|
252
|
+
const headerLeft = document.createElement("div");
|
|
253
|
+
headerLeft.className = "opencode-chat-header-left";
|
|
222
254
|
// 会话列表折叠按钮
|
|
223
|
-
const toggleBtn = document.createElement(
|
|
224
|
-
toggleBtn.className =
|
|
255
|
+
const toggleBtn = document.createElement("button");
|
|
256
|
+
toggleBtn.className = "opencode-header-btn session-toggle";
|
|
225
257
|
toggleBtn.innerHTML = `
|
|
226
258
|
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2">
|
|
227
259
|
<path d="M4 6h16M4 12h16M4 18h16" stroke-linecap="round"/>
|
|
228
260
|
</svg>
|
|
229
261
|
`;
|
|
230
|
-
toggleBtn.title =
|
|
231
|
-
toggleBtn.setAttribute(
|
|
232
|
-
toggleBtn.setAttribute(
|
|
262
|
+
toggleBtn.title = "展开会话列表";
|
|
263
|
+
toggleBtn.setAttribute("aria-label", "展开会话列表");
|
|
264
|
+
toggleBtn.setAttribute("aria-expanded", "false");
|
|
233
265
|
// 选择元素按钮
|
|
234
|
-
const selectButton = document.createElement(
|
|
235
|
-
selectButton.className =
|
|
266
|
+
const selectButton = document.createElement("button");
|
|
267
|
+
selectButton.className = "opencode-header-btn select-btn";
|
|
236
268
|
selectButton.innerHTML = `
|
|
237
269
|
<svg viewBox="0 0 1024 1024" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
|
|
238
270
|
<path fill="currentColor" d="M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768m0 64a448 448 0 1 1 0-896 448 448 0 0 1 0 896"></path><path fill="currentColor" d="M512 96a32 32 0 0 1 32 32v192a32 32 0 0 1-64 0V128a32 32 0 0 1 32-32m0 576a32 32 0 0 1 32 32v192a32 32 0 1 1-64 0V704a32 32 0 0 1 32-32M96 512a32 32 0 0 1 32-32h192a32 32 0 0 1 0 64H128a32 32 0 0 1-32-32m576 0a32 32 0 0 1 32-32h192a32 32 0 1 1 0 64H704a32 32 0 0 1-32-32"></path>
|
|
239
271
|
</svg>
|
|
240
272
|
`;
|
|
241
|
-
selectButton.title =
|
|
242
|
-
selectButton.setAttribute(
|
|
243
|
-
selectButton.setAttribute(
|
|
273
|
+
selectButton.title = "选择页面元素 (Ctrl+P)";
|
|
274
|
+
selectButton.setAttribute("aria-label", "选择页面元素");
|
|
275
|
+
selectButton.setAttribute("aria-pressed", "false");
|
|
244
276
|
headerLeft.appendChild(toggleBtn);
|
|
245
277
|
headerLeft.appendChild(selectButton);
|
|
246
278
|
// 标题
|
|
247
|
-
const headerTitle = document.createElement(
|
|
248
|
-
headerTitle.className =
|
|
249
|
-
headerTitle.textContent =
|
|
279
|
+
const headerTitle = document.createElement("span");
|
|
280
|
+
headerTitle.className = "opencode-chat-header-title";
|
|
281
|
+
headerTitle.textContent = "AI 助手";
|
|
250
282
|
// 右侧操作区
|
|
251
|
-
const headerActions = document.createElement(
|
|
252
|
-
headerActions.className =
|
|
283
|
+
const headerActions = document.createElement("div");
|
|
284
|
+
headerActions.className = "opencode-chat-header-actions";
|
|
253
285
|
// 关闭按钮
|
|
254
|
-
const closeBtn = document.createElement(
|
|
255
|
-
closeBtn.className =
|
|
286
|
+
const closeBtn = document.createElement("button");
|
|
287
|
+
closeBtn.className = "opencode-header-btn close";
|
|
256
288
|
closeBtn.innerHTML = `
|
|
257
289
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2">
|
|
258
290
|
<path d="M18 6L6 18M6 6l12 12"/>
|
|
259
291
|
</svg>
|
|
260
292
|
`;
|
|
261
|
-
closeBtn.title =
|
|
262
|
-
closeBtn.setAttribute(
|
|
263
|
-
closeBtn.addEventListener(
|
|
293
|
+
closeBtn.title = "关闭";
|
|
294
|
+
closeBtn.setAttribute("aria-label", "关闭面板");
|
|
295
|
+
closeBtn.addEventListener("click", () => {
|
|
264
296
|
isOpen = false;
|
|
265
|
-
chat.classList.remove(
|
|
266
|
-
button.classList.remove(
|
|
297
|
+
chat.classList.remove("open");
|
|
298
|
+
button.classList.remove("active");
|
|
267
299
|
});
|
|
268
300
|
headerActions.appendChild(closeBtn);
|
|
269
301
|
chatHeader.appendChild(headerLeft);
|
|
270
302
|
chatHeader.appendChild(headerTitle);
|
|
271
303
|
chatHeader.appendChild(headerActions);
|
|
272
304
|
// 创建会话列表
|
|
273
|
-
const sessionList = document.createElement(
|
|
274
|
-
sessionList.className =
|
|
305
|
+
const sessionList = document.createElement("div");
|
|
306
|
+
sessionList.className = "opencode-session-list collapsed";
|
|
275
307
|
// 创建会话列表头部
|
|
276
|
-
const sessionListHeader = document.createElement(
|
|
277
|
-
sessionListHeader.className =
|
|
308
|
+
const sessionListHeader = document.createElement("div");
|
|
309
|
+
sessionListHeader.className = "opencode-session-list-header";
|
|
278
310
|
sessionListHeader.innerHTML = `
|
|
279
311
|
<span id="opencode-session-list-title">会话列表</span>
|
|
280
312
|
<button class="opencode-new-session-btn" title="新建会话" aria-label="新建会话">+</button>
|
|
281
313
|
`;
|
|
282
314
|
// 创建会话列表头部骨架屏
|
|
283
|
-
const sessionHeaderSkeleton = document.createElement(
|
|
284
|
-
sessionHeaderSkeleton.className =
|
|
315
|
+
const sessionHeaderSkeleton = document.createElement("div");
|
|
316
|
+
sessionHeaderSkeleton.className = "opencode-session-header-skeleton";
|
|
285
317
|
sessionHeaderSkeleton.innerHTML = `
|
|
286
318
|
<div class="opencode-skeleton-header-title"></div>
|
|
287
319
|
<div class="opencode-skeleton-header-btn"></div>
|
|
288
320
|
`;
|
|
289
321
|
// 创建会话列表内容
|
|
290
|
-
const sessionListContent = document.createElement(
|
|
291
|
-
sessionListContent.className =
|
|
292
|
-
sessionListContent.setAttribute(
|
|
293
|
-
sessionListContent.setAttribute(
|
|
322
|
+
const sessionListContent = document.createElement("div");
|
|
323
|
+
sessionListContent.className = "opencode-session-list-content";
|
|
324
|
+
sessionListContent.setAttribute("role", "listbox");
|
|
325
|
+
sessionListContent.setAttribute("aria-labelledby", "opencode-session-list-title");
|
|
294
326
|
// 创建会话列表内容骨架屏
|
|
295
|
-
const sessionSkeleton = document.createElement(
|
|
296
|
-
sessionSkeleton.className =
|
|
327
|
+
const sessionSkeleton = document.createElement("div");
|
|
328
|
+
sessionSkeleton.className = "opencode-session-skeleton";
|
|
297
329
|
sessionSkeleton.innerHTML = `
|
|
298
330
|
<div class="opencode-skeleton-item">
|
|
299
331
|
<div class="opencode-skeleton-title"></div>
|
|
@@ -325,96 +357,126 @@
|
|
|
325
357
|
function toggleSessionList() {
|
|
326
358
|
isSessionListCollapsed = !isSessionListCollapsed;
|
|
327
359
|
if (!isSessionListCollapsed) {
|
|
328
|
-
sessionHeaderSkeleton.classList.add(
|
|
329
|
-
sessionListHeader.style.display =
|
|
330
|
-
sessionSkeleton.classList.add(
|
|
331
|
-
sessionListContent.style.display =
|
|
360
|
+
sessionHeaderSkeleton.classList.add("visible");
|
|
361
|
+
sessionListHeader.style.display = "none";
|
|
362
|
+
sessionSkeleton.classList.add("visible");
|
|
363
|
+
sessionListContent.style.display = "none";
|
|
332
364
|
}
|
|
333
|
-
sessionList.classList.toggle(
|
|
334
|
-
toggleBtn.title = isSessionListCollapsed
|
|
335
|
-
|
|
336
|
-
|
|
365
|
+
sessionList.classList.toggle("collapsed", isSessionListCollapsed);
|
|
366
|
+
toggleBtn.title = isSessionListCollapsed
|
|
367
|
+
? "展开会话列表"
|
|
368
|
+
: "折叠会话列表";
|
|
369
|
+
toggleBtn.setAttribute("aria-label", isSessionListCollapsed ? "展开会话列表" : "折叠会话列表");
|
|
370
|
+
toggleBtn.setAttribute("aria-expanded", String(!isSessionListCollapsed));
|
|
337
371
|
if (!isSessionListCollapsed) {
|
|
338
372
|
setTimeout(() => {
|
|
339
|
-
sessionHeaderSkeleton.classList.remove(
|
|
340
|
-
sessionListHeader.style.display =
|
|
341
|
-
sessionSkeleton.classList.remove(
|
|
342
|
-
sessionListContent.style.display =
|
|
373
|
+
sessionHeaderSkeleton.classList.remove("visible");
|
|
374
|
+
sessionListHeader.style.display = "";
|
|
375
|
+
sessionSkeleton.classList.remove("visible");
|
|
376
|
+
sessionListContent.style.display = "";
|
|
343
377
|
}, 200);
|
|
344
378
|
}
|
|
345
379
|
}
|
|
346
|
-
toggleBtn.addEventListener(
|
|
380
|
+
toggleBtn.addEventListener("click", toggleSessionList);
|
|
347
381
|
// 创建 iframe 容器
|
|
348
|
-
const iframeContainer = document.createElement(
|
|
349
|
-
iframeContainer.className =
|
|
382
|
+
const iframeContainer = document.createElement("div");
|
|
383
|
+
iframeContainer.className = "opencode-iframe-container";
|
|
350
384
|
// 创建加载指示器
|
|
351
|
-
const loadingOverlay = document.createElement(
|
|
352
|
-
loadingOverlay.className =
|
|
385
|
+
const loadingOverlay = document.createElement("div");
|
|
386
|
+
loadingOverlay.className = "opencode-loading-overlay";
|
|
353
387
|
loadingOverlay.innerHTML = `
|
|
354
388
|
<div class="opencode-loading-spinner"></div>
|
|
355
389
|
<div class="opencode-loading-text">加载中...</div>
|
|
356
390
|
`;
|
|
391
|
+
// 创建空状态提示
|
|
392
|
+
const emptyStateOverlay = document.createElement("div");
|
|
393
|
+
emptyStateOverlay.className = "opencode-empty-state-overlay";
|
|
394
|
+
emptyStateOverlay.innerHTML = `
|
|
395
|
+
<div class="opencode-empty-state-icon">
|
|
396
|
+
<svg viewBox="0 0 24 24" width="48" height="48" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
397
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
|
|
398
|
+
</svg>
|
|
399
|
+
</div>
|
|
400
|
+
<div class="opencode-empty-state-text">当前项目暂无会话</div>
|
|
401
|
+
<button class="opencode-empty-state-btn">立即创建</button>
|
|
402
|
+
`;
|
|
403
|
+
emptyStateOverlay
|
|
404
|
+
.querySelector(".opencode-empty-state-btn")
|
|
405
|
+
.addEventListener("click", () => {
|
|
406
|
+
createNewSession();
|
|
407
|
+
});
|
|
357
408
|
// 创建 iframe
|
|
358
|
-
const iframe = document.createElement(
|
|
359
|
-
iframe.className =
|
|
360
|
-
iframe.src = servicesStarted ?
|
|
361
|
-
iframe.allow =
|
|
362
|
-
iframe.referrerPolicy =
|
|
409
|
+
const iframe = document.createElement("iframe");
|
|
410
|
+
iframe.className = "opencode-iframe";
|
|
411
|
+
iframe.src = servicesStarted && sessionUrl ? sessionUrl : "about:blank";
|
|
412
|
+
iframe.allow = "clipboard-write; clipboard-read";
|
|
413
|
+
iframe.referrerPolicy = "origin";
|
|
414
|
+
if (servicesStarted && !sessionUrl) {
|
|
415
|
+
showLoading();
|
|
416
|
+
}
|
|
363
417
|
iframe.onload = function () {
|
|
364
|
-
if (servicesStarted
|
|
418
|
+
if (servicesStarted &&
|
|
419
|
+
iframe.src !== "about:blank" &&
|
|
420
|
+
iframe.src !== window.location.href) {
|
|
365
421
|
updateContext();
|
|
366
422
|
loadSessions();
|
|
367
|
-
setupSSEConnection();
|
|
368
423
|
}
|
|
369
|
-
|
|
424
|
+
if (iframe.src !== "about:blank" && iframe.src !== window.location.href) {
|
|
425
|
+
hideLoading();
|
|
426
|
+
}
|
|
370
427
|
};
|
|
428
|
+
iframeContainer.appendChild(emptyStateOverlay);
|
|
371
429
|
iframeContainer.appendChild(loadingOverlay);
|
|
372
430
|
iframeContainer.appendChild(iframe);
|
|
373
431
|
// 创建右侧工具栏
|
|
374
|
-
const rightToolbar = document.createElement(
|
|
375
|
-
rightToolbar.className = `opencode-right-toolbar${selectedElements.length === 0 ?
|
|
432
|
+
const rightToolbar = document.createElement("div");
|
|
433
|
+
rightToolbar.className = `opencode-right-toolbar${selectedElements.length === 0 ? " collapsed" : ""}`;
|
|
376
434
|
// 创建已选节点标题
|
|
377
|
-
const selectedNodesHeader = document.createElement(
|
|
378
|
-
selectedNodesHeader.className =
|
|
435
|
+
const selectedNodesHeader = document.createElement("div");
|
|
436
|
+
selectedNodesHeader.className = "opencode-selected-nodes-header";
|
|
379
437
|
selectedNodesHeader.innerHTML = `
|
|
380
438
|
<div class="opencode-selected-nodes-title">已选节点</div>
|
|
381
439
|
<div class="opencode-selected-nodes-desc">选中的节点会在对话时一起发送给助手</div>
|
|
382
440
|
`;
|
|
383
441
|
// 创建已选节点容器
|
|
384
|
-
const selectedNodesContainer = document.createElement(
|
|
385
|
-
selectedNodesContainer.className =
|
|
386
|
-
selectedNodesContainer.setAttribute(
|
|
387
|
-
selectedNodesContainer.setAttribute(
|
|
442
|
+
const selectedNodesContainer = document.createElement("div");
|
|
443
|
+
selectedNodesContainer.className = "opencode-selected-nodes";
|
|
444
|
+
selectedNodesContainer.setAttribute("role", "list");
|
|
445
|
+
selectedNodesContainer.setAttribute("aria-label", "已选元素列表");
|
|
388
446
|
// 创建清空按钮
|
|
389
|
-
const clearAllButton = document.createElement(
|
|
390
|
-
clearAllButton.className =
|
|
391
|
-
clearAllButton.setAttribute(
|
|
392
|
-
clearAllButton.innerHTML =
|
|
447
|
+
const clearAllButton = document.createElement("button");
|
|
448
|
+
clearAllButton.className = "opencode-clear-all-btn";
|
|
449
|
+
clearAllButton.setAttribute("aria-label", "清空所有已选节点");
|
|
450
|
+
clearAllButton.innerHTML = "一键清空";
|
|
393
451
|
rightToolbar.appendChild(selectedNodesHeader);
|
|
394
452
|
rightToolbar.appendChild(selectedNodesContainer);
|
|
395
453
|
rightToolbar.appendChild(clearAllButton);
|
|
396
454
|
// 创建选择模式常驻提示(固定到页面顶部)
|
|
397
|
-
const selectModeHint = document.createElement(
|
|
398
|
-
selectModeHint.className =
|
|
455
|
+
const selectModeHint = document.createElement("div");
|
|
456
|
+
selectModeHint.className = "opencode-select-mode-hint";
|
|
399
457
|
selectModeHint.innerHTML = `
|
|
400
458
|
<span>🎯 选择模式已开启 - 点击元素进行选择</span>
|
|
401
459
|
<span class="opencode-hint-shortcut">按 ESC 或 Ctrl+P 退出</span>
|
|
402
460
|
`;
|
|
403
461
|
// 创建元素高亮覆盖层
|
|
404
|
-
const elementHighlight = document.createElement(
|
|
405
|
-
elementHighlight.className =
|
|
462
|
+
const elementHighlight = document.createElement("div");
|
|
463
|
+
elementHighlight.className = "opencode-element-highlight";
|
|
406
464
|
// 创建元素信息提示框
|
|
407
|
-
const elementTooltip = document.createElement(
|
|
408
|
-
elementTooltip.className =
|
|
465
|
+
const elementTooltip = document.createElement("div");
|
|
466
|
+
elementTooltip.className = "opencode-element-tooltip";
|
|
409
467
|
// 创建已选节点气泡容器(气泡按钮上方)
|
|
410
|
-
const selectedBubbles = document.createElement(
|
|
411
|
-
selectedBubbles.className =
|
|
412
|
-
selectedBubbles.setAttribute(
|
|
413
|
-
selectedBubbles.setAttribute(
|
|
414
|
-
|
|
468
|
+
const selectedBubbles = document.createElement("div");
|
|
469
|
+
selectedBubbles.className = "opencode-selected-bubbles";
|
|
470
|
+
selectedBubbles.setAttribute("role", "list");
|
|
471
|
+
selectedBubbles.setAttribute("aria-label", "已选元素列表");
|
|
472
|
+
// 创建内容容器
|
|
473
|
+
const chatContent = document.createElement("div");
|
|
474
|
+
chatContent.className = "opencode-chat-content";
|
|
415
475
|
chat.appendChild(chatHeader);
|
|
416
|
-
|
|
417
|
-
|
|
476
|
+
chatContent.appendChild(sessionList);
|
|
477
|
+
chatContent.appendChild(iframeContainer);
|
|
478
|
+
chatContent.appendChild(rightToolbar);
|
|
479
|
+
chat.appendChild(chatContent);
|
|
418
480
|
container.appendChild(button);
|
|
419
481
|
container.appendChild(selectedBubbles);
|
|
420
482
|
container.appendChild(chat);
|
|
@@ -427,81 +489,77 @@
|
|
|
427
489
|
}
|
|
428
490
|
/** @type {Array} 会话列表 */
|
|
429
491
|
let sessions = [];
|
|
430
|
-
/** @type {string|null} 当前会话 ID */
|
|
431
|
-
let currentSessionId = null;
|
|
432
|
-
/**
|
|
433
|
-
* 从 URL 中提取会话 ID
|
|
434
|
-
*/
|
|
435
|
-
function extractSessionId(url) {
|
|
436
|
-
if (!url)
|
|
437
|
-
return null;
|
|
438
|
-
const match = url.match(/\/session\/([^/?]+)/);
|
|
439
|
-
return match ? match[1] : null;
|
|
440
|
-
}
|
|
441
|
-
// 从初始 URL 中提取会话 ID
|
|
442
|
-
currentSessionId = extractSessionId(sessionUrl);
|
|
443
492
|
/**
|
|
444
493
|
* 显示加载状态
|
|
445
494
|
*/
|
|
446
495
|
function showLoading() {
|
|
447
|
-
loadingOverlay.classList.add(
|
|
496
|
+
loadingOverlay.classList.add("visible");
|
|
448
497
|
}
|
|
449
498
|
/**
|
|
450
499
|
* 隐藏加载状态
|
|
451
500
|
*/
|
|
452
501
|
function hideLoading() {
|
|
453
|
-
loadingOverlay.classList.remove(
|
|
502
|
+
loadingOverlay.classList.remove("visible");
|
|
454
503
|
}
|
|
455
504
|
/**
|
|
456
505
|
* 加载会话列表
|
|
457
506
|
*/
|
|
458
507
|
async function loadSessions() {
|
|
459
508
|
try {
|
|
460
|
-
const response = await fetch(
|
|
509
|
+
const response = await fetch("/__opencode_sessions__");
|
|
461
510
|
sessions = await response.json();
|
|
462
511
|
renderSessionList();
|
|
463
512
|
}
|
|
464
513
|
catch (e) {
|
|
465
|
-
console.error(
|
|
514
|
+
console.error("Failed to load sessions:", e);
|
|
466
515
|
}
|
|
467
516
|
}
|
|
468
517
|
/**
|
|
469
518
|
* 渲染会话列表
|
|
470
519
|
*/
|
|
471
520
|
function renderSessionList() {
|
|
472
|
-
sessionListContent.innerHTML =
|
|
473
|
-
const currentProjectSessions = sessions.filter(session => session.directory === cwd);
|
|
474
|
-
currentProjectSessions.
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
521
|
+
sessionListContent.innerHTML = "";
|
|
522
|
+
const currentProjectSessions = sessions.filter((session) => session.directory === cwd);
|
|
523
|
+
if (currentProjectSessions.length === 0) {
|
|
524
|
+
emptyStateOverlay.classList.add("visible");
|
|
525
|
+
iframe.style.display = "none";
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
emptyStateOverlay.classList.remove("visible");
|
|
529
|
+
iframe.style.display = "";
|
|
530
|
+
}
|
|
531
|
+
currentProjectSessions.forEach((session) => {
|
|
532
|
+
const item = document.createElement("div");
|
|
533
|
+
item.className = "opencode-session-item";
|
|
534
|
+
item.setAttribute("role", "option");
|
|
535
|
+
item.setAttribute("aria-selected", String(session.id === currentSessionId));
|
|
479
536
|
if (session.id === currentSessionId) {
|
|
480
|
-
item.classList.add(
|
|
537
|
+
item.classList.add("active");
|
|
481
538
|
}
|
|
482
|
-
const header = document.createElement(
|
|
483
|
-
header.className =
|
|
484
|
-
const title = document.createElement(
|
|
485
|
-
title.className =
|
|
486
|
-
title.textContent = session.title ||
|
|
487
|
-
const deleteBtn = document.createElement(
|
|
488
|
-
deleteBtn.className =
|
|
489
|
-
deleteBtn.innerHTML =
|
|
490
|
-
deleteBtn.title =
|
|
491
|
-
deleteBtn.setAttribute(
|
|
492
|
-
deleteBtn.addEventListener(
|
|
539
|
+
const header = document.createElement("div");
|
|
540
|
+
header.className = "opencode-session-header";
|
|
541
|
+
const title = document.createElement("div");
|
|
542
|
+
title.className = "opencode-session-title";
|
|
543
|
+
title.textContent = session.title || "新会话";
|
|
544
|
+
const deleteBtn = document.createElement("button");
|
|
545
|
+
deleteBtn.className = "opencode-session-delete-btn";
|
|
546
|
+
deleteBtn.innerHTML = "×";
|
|
547
|
+
deleteBtn.title = "删除会话";
|
|
548
|
+
deleteBtn.setAttribute("aria-label", `删除会话: ${session.title || "新会话"}`);
|
|
549
|
+
deleteBtn.addEventListener("click", (e) => {
|
|
493
550
|
e.stopPropagation();
|
|
494
551
|
confirmDeleteSession(session);
|
|
495
552
|
});
|
|
496
553
|
header.appendChild(title);
|
|
497
554
|
header.appendChild(deleteBtn);
|
|
498
|
-
const meta = document.createElement(
|
|
499
|
-
meta.className =
|
|
555
|
+
const meta = document.createElement("div");
|
|
556
|
+
meta.className = "opencode-session-meta";
|
|
500
557
|
const date = new Date(session.time.updated);
|
|
501
|
-
meta.textContent =
|
|
558
|
+
meta.textContent =
|
|
559
|
+
date.toLocaleDateString() + " " + date.toLocaleTimeString();
|
|
502
560
|
item.appendChild(header);
|
|
503
561
|
item.appendChild(meta);
|
|
504
|
-
item.addEventListener(
|
|
562
|
+
item.addEventListener("click", () => {
|
|
505
563
|
switchSession(session);
|
|
506
564
|
});
|
|
507
565
|
sessionListContent.appendChild(item);
|
|
@@ -516,7 +574,7 @@
|
|
|
516
574
|
}
|
|
517
575
|
currentSessionId = session.id;
|
|
518
576
|
const encodedDir = btoa(cwd);
|
|
519
|
-
const baseUrl =
|
|
577
|
+
const baseUrl = webUrl;
|
|
520
578
|
showLoading();
|
|
521
579
|
iframe.src = `${baseUrl}/${encodedDir}/session/${session.id}`;
|
|
522
580
|
renderSessionList();
|
|
@@ -526,23 +584,23 @@
|
|
|
526
584
|
*/
|
|
527
585
|
async function createNewSession() {
|
|
528
586
|
try {
|
|
529
|
-
const response = await fetch(
|
|
530
|
-
method:
|
|
587
|
+
const response = await fetch("/__opencode_sessions__", {
|
|
588
|
+
method: "POST",
|
|
531
589
|
});
|
|
532
590
|
const newSession = await response.json();
|
|
533
591
|
sessions.unshift(newSession);
|
|
534
592
|
switchSession(newSession);
|
|
535
593
|
}
|
|
536
594
|
catch (e) {
|
|
537
|
-
console.error(
|
|
538
|
-
showNotification(
|
|
595
|
+
console.error("Failed to create session:", e);
|
|
596
|
+
showNotification("创建会话失败");
|
|
539
597
|
}
|
|
540
598
|
}
|
|
541
599
|
/**
|
|
542
600
|
* 确认删除会话
|
|
543
601
|
*/
|
|
544
602
|
async function confirmDeleteSession(session) {
|
|
545
|
-
const confirmed = await showConfirmDialog(`确定要删除会话 "${session.title ||
|
|
603
|
+
const confirmed = await showConfirmDialog(`确定要删除会话 "${session.title || "新会话"}" 吗?`);
|
|
546
604
|
if (confirmed) {
|
|
547
605
|
deleteSession(session);
|
|
548
606
|
}
|
|
@@ -553,60 +611,65 @@
|
|
|
553
611
|
async function deleteSession(session) {
|
|
554
612
|
try {
|
|
555
613
|
const response = await fetch(`/__opencode_sessions__?id=${session.id}`, {
|
|
556
|
-
method:
|
|
614
|
+
method: "DELETE",
|
|
557
615
|
});
|
|
558
616
|
if (!response.ok) {
|
|
559
|
-
throw new Error(
|
|
617
|
+
throw new Error("Delete failed");
|
|
560
618
|
}
|
|
561
|
-
sessions = sessions.filter(s => s.id !== session.id);
|
|
619
|
+
sessions = sessions.filter((s) => s.id !== session.id);
|
|
562
620
|
if (session.id === currentSessionId) {
|
|
563
|
-
const remainingSessions = sessions.filter(s => s.directory === cwd);
|
|
621
|
+
const remainingSessions = sessions.filter((s) => s.directory === cwd);
|
|
564
622
|
if (remainingSessions.length > 0) {
|
|
565
623
|
switchSession(remainingSessions[0]);
|
|
566
624
|
}
|
|
567
625
|
else {
|
|
568
626
|
currentSessionId = null;
|
|
569
|
-
iframe.src =
|
|
627
|
+
iframe.src = "about:blank";
|
|
570
628
|
}
|
|
571
629
|
}
|
|
572
630
|
renderSessionList();
|
|
573
|
-
showNotification(
|
|
631
|
+
showNotification("会话已删除");
|
|
574
632
|
}
|
|
575
633
|
catch (e) {
|
|
576
|
-
console.error(
|
|
577
|
-
showNotification(
|
|
634
|
+
console.error("Failed to delete session:", e);
|
|
635
|
+
showNotification("删除会话失败");
|
|
578
636
|
}
|
|
579
637
|
}
|
|
580
638
|
// 绑定新建会话按钮
|
|
581
|
-
sessionListHeader
|
|
639
|
+
sessionListHeader
|
|
640
|
+
.querySelector(".opencode-new-session-btn")
|
|
641
|
+
.addEventListener("click", createNewSession);
|
|
582
642
|
/**
|
|
583
643
|
* 应用主题
|
|
584
644
|
*/
|
|
585
645
|
function applyTheme() {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
646
|
+
const resolvedTheme = theme === "auto"
|
|
647
|
+
? window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
648
|
+
? "dark"
|
|
649
|
+
: "light"
|
|
650
|
+
: theme;
|
|
651
|
+
container.classList.toggle("opencode-dark", resolvedTheme === "dark");
|
|
652
|
+
container.style.colorScheme = resolvedTheme;
|
|
593
653
|
}
|
|
594
654
|
applyTheme();
|
|
595
|
-
if (theme ===
|
|
596
|
-
window
|
|
655
|
+
if (theme === "auto") {
|
|
656
|
+
window
|
|
657
|
+
.matchMedia("(prefers-color-scheme: dark)")
|
|
658
|
+
.addEventListener("change", applyTheme);
|
|
597
659
|
}
|
|
598
660
|
/**
|
|
599
661
|
* 切换挂件显示状态
|
|
600
662
|
*/
|
|
601
663
|
async function toggle() {
|
|
602
664
|
if (lazy && !servicesStarted) {
|
|
603
|
-
button.classList.add(
|
|
665
|
+
button.classList.add("loading");
|
|
604
666
|
const started = await ensureServicesStarted();
|
|
605
|
-
button.classList.remove(
|
|
667
|
+
button.classList.remove("loading");
|
|
606
668
|
if (!started) {
|
|
607
|
-
showNotification(
|
|
669
|
+
showNotification("服务启动失败,请检查控制台");
|
|
608
670
|
return;
|
|
609
671
|
}
|
|
672
|
+
setupSSEConnection();
|
|
610
673
|
}
|
|
611
674
|
if (isSelectMode) {
|
|
612
675
|
exitSelectMode();
|
|
@@ -614,14 +677,14 @@
|
|
|
614
677
|
return;
|
|
615
678
|
}
|
|
616
679
|
isOpen = !isOpen;
|
|
617
|
-
chat.classList.toggle(
|
|
618
|
-
button.classList.toggle(
|
|
680
|
+
chat.classList.toggle("open", isOpen);
|
|
681
|
+
button.classList.toggle("active", isOpen);
|
|
619
682
|
if (isOpen) {
|
|
620
683
|
updateContext();
|
|
621
684
|
}
|
|
622
685
|
}
|
|
623
|
-
button.addEventListener(
|
|
624
|
-
document.addEventListener(
|
|
686
|
+
button.addEventListener("click", toggle);
|
|
687
|
+
document.addEventListener("keydown", (e) => {
|
|
625
688
|
if (matchHotkey(e, mainHotkey)) {
|
|
626
689
|
e.preventDefault();
|
|
627
690
|
toggle();
|
|
@@ -632,7 +695,7 @@
|
|
|
632
695
|
toggleSelectMode();
|
|
633
696
|
}
|
|
634
697
|
else {
|
|
635
|
-
showNotification(
|
|
698
|
+
showNotification("Vue Inspector 未加载,无法使用元素选择功能");
|
|
636
699
|
}
|
|
637
700
|
}
|
|
638
701
|
});
|
|
@@ -648,14 +711,14 @@
|
|
|
648
711
|
*/
|
|
649
712
|
function showConfirmDialog(message) {
|
|
650
713
|
return new Promise((resolve) => {
|
|
651
|
-
const overlay = document.createElement(
|
|
652
|
-
overlay.className =
|
|
653
|
-
const dialog = document.createElement(
|
|
654
|
-
dialog.className =
|
|
655
|
-
dialog.setAttribute(
|
|
656
|
-
dialog.setAttribute(
|
|
657
|
-
dialog.setAttribute(
|
|
658
|
-
dialog.setAttribute(
|
|
714
|
+
const overlay = document.createElement("div");
|
|
715
|
+
overlay.className = "opencode-dialog-overlay";
|
|
716
|
+
const dialog = document.createElement("div");
|
|
717
|
+
dialog.className = "opencode-dialog";
|
|
718
|
+
dialog.setAttribute("role", "alertdialog");
|
|
719
|
+
dialog.setAttribute("aria-modal", "true");
|
|
720
|
+
dialog.setAttribute("aria-labelledby", "opencode-dialog-title");
|
|
721
|
+
dialog.setAttribute("aria-describedby", "opencode-dialog-desc");
|
|
659
722
|
dialog.innerHTML = `
|
|
660
723
|
<div class="opencode-dialog-content">
|
|
661
724
|
<div id="opencode-dialog-desc" class="opencode-dialog-message">${message}</div>
|
|
@@ -673,22 +736,24 @@
|
|
|
673
736
|
overlay.remove();
|
|
674
737
|
resolve(false);
|
|
675
738
|
};
|
|
676
|
-
dialog
|
|
677
|
-
|
|
678
|
-
|
|
739
|
+
dialog
|
|
740
|
+
.querySelector(".confirm")
|
|
741
|
+
.addEventListener("click", handleConfirm);
|
|
742
|
+
dialog.querySelector(".cancel").addEventListener("click", handleCancel);
|
|
743
|
+
overlay.addEventListener("click", (e) => {
|
|
679
744
|
if (e.target === overlay) {
|
|
680
745
|
handleCancel();
|
|
681
746
|
}
|
|
682
747
|
});
|
|
683
|
-
document.addEventListener(
|
|
684
|
-
if (e.key ===
|
|
685
|
-
document.removeEventListener(
|
|
748
|
+
document.addEventListener("keydown", function handleEscape(e) {
|
|
749
|
+
if (e.key === "Escape") {
|
|
750
|
+
document.removeEventListener("keydown", handleEscape);
|
|
686
751
|
handleCancel();
|
|
687
752
|
}
|
|
688
753
|
});
|
|
689
754
|
overlay.appendChild(dialog);
|
|
690
|
-
|
|
691
|
-
dialog.querySelector(
|
|
755
|
+
container.appendChild(overlay);
|
|
756
|
+
dialog.querySelector(".confirm").focus();
|
|
692
757
|
});
|
|
693
758
|
}
|
|
694
759
|
/**
|
|
@@ -696,8 +761,8 @@
|
|
|
696
761
|
* @param {string} message - 通知消息
|
|
697
762
|
*/
|
|
698
763
|
function showNotification(message) {
|
|
699
|
-
const notification = document.createElement(
|
|
700
|
-
notification.className =
|
|
764
|
+
const notification = document.createElement("div");
|
|
765
|
+
notification.className = "opencode-notification";
|
|
701
766
|
notification.textContent = message;
|
|
702
767
|
chat.appendChild(notification);
|
|
703
768
|
setTimeout(() => {
|
|
@@ -712,10 +777,11 @@
|
|
|
712
777
|
const key = elementInfo.filePath && elementInfo.line
|
|
713
778
|
? `${elementInfo.filePath}:${elementInfo.line}`
|
|
714
779
|
: null;
|
|
715
|
-
const exists = key &&
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
780
|
+
const exists = key &&
|
|
781
|
+
selectedElements.some((el) => {
|
|
782
|
+
const elKey = el.filePath && el.line ? `${el.filePath}:${el.line}` : null;
|
|
783
|
+
return elKey === key;
|
|
784
|
+
});
|
|
719
785
|
if (!exists) {
|
|
720
786
|
selectedElements.push(elementInfo);
|
|
721
787
|
saveSelectedElements(selectedElements);
|
|
@@ -726,7 +792,7 @@
|
|
|
726
792
|
showNotification(`已选中元素 (${selectedElements.length}个)`);
|
|
727
793
|
}
|
|
728
794
|
else {
|
|
729
|
-
showNotification(
|
|
795
|
+
showNotification("该元素已选中");
|
|
730
796
|
}
|
|
731
797
|
}
|
|
732
798
|
/**
|
|
@@ -749,17 +815,17 @@
|
|
|
749
815
|
renderSelectedNodes();
|
|
750
816
|
updateToolbarState();
|
|
751
817
|
sendSelectedElements();
|
|
752
|
-
showNotification(
|
|
818
|
+
showNotification("已清除所有选中元素");
|
|
753
819
|
}
|
|
754
820
|
/**
|
|
755
821
|
* 更新工具栏状态
|
|
756
822
|
*/
|
|
757
823
|
function updateToolbarState() {
|
|
758
824
|
if (selectedElements.length > 0) {
|
|
759
|
-
rightToolbar.classList.remove(
|
|
825
|
+
rightToolbar.classList.remove("collapsed");
|
|
760
826
|
}
|
|
761
827
|
else {
|
|
762
|
-
rightToolbar.classList.add(
|
|
828
|
+
rightToolbar.classList.add("collapsed");
|
|
763
829
|
}
|
|
764
830
|
}
|
|
765
831
|
/** @type {boolean} 是否处于元素选择模式 */
|
|
@@ -777,14 +843,14 @@
|
|
|
777
843
|
const { targetNode, params } = inspector.getTargetNode(e);
|
|
778
844
|
if (targetNode && params) {
|
|
779
845
|
const rect = targetNode.getBoundingClientRect();
|
|
780
|
-
elementHighlight.style.display =
|
|
846
|
+
elementHighlight.style.display = "block";
|
|
781
847
|
elementHighlight.style.top = `${rect.top}px`;
|
|
782
848
|
elementHighlight.style.left = `${rect.left}px`;
|
|
783
849
|
elementHighlight.style.width = `${rect.width}px`;
|
|
784
850
|
elementHighlight.style.height = `${rect.height}px`;
|
|
785
851
|
const description = getElementDescription(targetNode);
|
|
786
|
-
const fileName = params.file ? params.file.split(
|
|
787
|
-
let lineInfo =
|
|
852
|
+
const fileName = params.file ? params.file.split("/").pop() : "";
|
|
853
|
+
let lineInfo = "";
|
|
788
854
|
if (params.line) {
|
|
789
855
|
lineInfo = `:${params.line}`;
|
|
790
856
|
if (params.column) {
|
|
@@ -793,9 +859,9 @@
|
|
|
793
859
|
}
|
|
794
860
|
elementTooltip.innerHTML = `
|
|
795
861
|
<div class="opencode-tooltip-tag">${description}</div>
|
|
796
|
-
${fileName ? `<div class="opencode-tooltip-file">${fileName}${lineInfo}</div>` :
|
|
862
|
+
${fileName ? `<div class="opencode-tooltip-file">${fileName}${lineInfo}</div>` : ""}
|
|
797
863
|
`;
|
|
798
|
-
elementTooltip.style.display =
|
|
864
|
+
elementTooltip.style.display = "block";
|
|
799
865
|
const tooltipRect = elementTooltip.getBoundingClientRect();
|
|
800
866
|
let tooltipTop = rect.top - tooltipRect.height - 8;
|
|
801
867
|
let tooltipLeft = rect.left;
|
|
@@ -809,8 +875,8 @@
|
|
|
809
875
|
elementTooltip.style.left = `${tooltipLeft}px`;
|
|
810
876
|
}
|
|
811
877
|
else {
|
|
812
|
-
elementHighlight.style.display =
|
|
813
|
-
elementTooltip.style.display =
|
|
878
|
+
elementHighlight.style.display = "none";
|
|
879
|
+
elementTooltip.style.display = "none";
|
|
814
880
|
}
|
|
815
881
|
}
|
|
816
882
|
/**
|
|
@@ -819,34 +885,34 @@
|
|
|
819
885
|
function toggleSelectMode() {
|
|
820
886
|
const inspector = window.__VUE_INSPECTOR__;
|
|
821
887
|
if (!inspector) {
|
|
822
|
-
showNotification(
|
|
888
|
+
showNotification("Vue Inspector 未加载,无法使用元素选择功能");
|
|
823
889
|
return;
|
|
824
890
|
}
|
|
825
891
|
isSelectMode = !isSelectMode;
|
|
826
|
-
selectButton.classList.toggle(
|
|
827
|
-
selectButton.setAttribute(
|
|
828
|
-
selectModeHint.classList.toggle(
|
|
829
|
-
selectedBubbles.classList.toggle(
|
|
892
|
+
selectButton.classList.toggle("active", isSelectMode);
|
|
893
|
+
selectButton.setAttribute("aria-pressed", String(isSelectMode));
|
|
894
|
+
selectModeHint.classList.toggle("visible", isSelectMode);
|
|
895
|
+
selectedBubbles.classList.toggle("visible", isSelectMode);
|
|
830
896
|
if (isSelectMode) {
|
|
831
897
|
if (isOpen) {
|
|
832
898
|
isOpen = false;
|
|
833
|
-
chat.classList.remove(
|
|
834
|
-
button.classList.remove(
|
|
899
|
+
chat.classList.remove("open");
|
|
900
|
+
button.classList.remove("active");
|
|
835
901
|
}
|
|
836
|
-
chat.style.display =
|
|
902
|
+
chat.style.display = "none";
|
|
837
903
|
inspector.enable();
|
|
838
904
|
renderSelectedBubbles();
|
|
839
|
-
document.addEventListener(
|
|
905
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
840
906
|
}
|
|
841
907
|
else {
|
|
842
|
-
chat.style.display =
|
|
908
|
+
chat.style.display = "";
|
|
843
909
|
isOpen = true;
|
|
844
|
-
chat.classList.add(
|
|
845
|
-
button.classList.add(
|
|
910
|
+
chat.classList.add("open");
|
|
911
|
+
button.classList.add("active");
|
|
846
912
|
inspector.disable();
|
|
847
|
-
document.removeEventListener(
|
|
848
|
-
elementHighlight.style.display =
|
|
849
|
-
elementTooltip.style.display =
|
|
913
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
914
|
+
elementHighlight.style.display = "none";
|
|
915
|
+
elementTooltip.style.display = "none";
|
|
850
916
|
}
|
|
851
917
|
}
|
|
852
918
|
/**
|
|
@@ -860,20 +926,20 @@
|
|
|
860
926
|
inspector.disable();
|
|
861
927
|
}
|
|
862
928
|
isSelectMode = false;
|
|
863
|
-
selectButton.classList.remove(
|
|
864
|
-
selectModeHint.classList.remove(
|
|
865
|
-
selectedBubbles.classList.remove(
|
|
866
|
-
chat.style.display =
|
|
929
|
+
selectButton.classList.remove("active");
|
|
930
|
+
selectModeHint.classList.remove("visible");
|
|
931
|
+
selectedBubbles.classList.remove("visible");
|
|
932
|
+
chat.style.display = "";
|
|
867
933
|
isOpen = true;
|
|
868
|
-
chat.classList.add(
|
|
869
|
-
button.classList.add(
|
|
870
|
-
document.removeEventListener(
|
|
871
|
-
elementHighlight.style.display =
|
|
872
|
-
elementTooltip.style.display =
|
|
934
|
+
chat.classList.add("open");
|
|
935
|
+
button.classList.add("active");
|
|
936
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
937
|
+
elementHighlight.style.display = "none";
|
|
938
|
+
elementTooltip.style.display = "none";
|
|
873
939
|
}
|
|
874
940
|
// ESC 键退出选择模式(使用捕获阶段确保优先处理)
|
|
875
|
-
document.addEventListener(
|
|
876
|
-
if (e.key ===
|
|
941
|
+
document.addEventListener("keydown", (e) => {
|
|
942
|
+
if (e.key === "Escape" && isSelectMode) {
|
|
877
943
|
e.preventDefault();
|
|
878
944
|
e.stopPropagation();
|
|
879
945
|
exitSelectMode();
|
|
@@ -883,32 +949,41 @@
|
|
|
883
949
|
* 建立 SSE 连接监听服务端事件
|
|
884
950
|
*/
|
|
885
951
|
function setupSSEConnection() {
|
|
886
|
-
if (!servicesStarted)
|
|
952
|
+
if (!servicesStarted || sseConnection)
|
|
887
953
|
return;
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
console.log(
|
|
954
|
+
sseConnection = new EventSource("/__opencode_events__");
|
|
955
|
+
sseConnection.onopen = () => {
|
|
956
|
+
console.log("[OpenCode] SSE connected");
|
|
891
957
|
};
|
|
892
|
-
|
|
958
|
+
sseConnection.onmessage = (event) => {
|
|
893
959
|
try {
|
|
894
960
|
const data = JSON.parse(event.data);
|
|
895
|
-
console.log(
|
|
896
|
-
if (data.type ===
|
|
961
|
+
console.log("[OpenCode] SSE message:", data);
|
|
962
|
+
if (data.type === "CONNECTED") {
|
|
897
963
|
// 连接成功后,主动同步当前节点到服务端
|
|
898
964
|
if (selectedElements.length > 0) {
|
|
899
|
-
fetch(
|
|
900
|
-
method:
|
|
901
|
-
headers: {
|
|
965
|
+
fetch("/__opencode_context__", {
|
|
966
|
+
method: "POST",
|
|
967
|
+
headers: { "Content-Type": "application/json" },
|
|
902
968
|
body: JSON.stringify({
|
|
903
969
|
url: window.location.href,
|
|
904
970
|
title: document.title,
|
|
905
|
-
selectedElements: selectedElements
|
|
906
|
-
})
|
|
971
|
+
selectedElements: selectedElements,
|
|
972
|
+
}),
|
|
907
973
|
}).catch(() => { });
|
|
908
974
|
}
|
|
909
975
|
}
|
|
910
|
-
if (data.type ===
|
|
911
|
-
|
|
976
|
+
if (data.type === "SESSION_READY") {
|
|
977
|
+
if (data.sessionUrl && !sessionUrl) {
|
|
978
|
+
sessionUrl = data.sessionUrl;
|
|
979
|
+
currentSessionId = extractSessionId(sessionUrl);
|
|
980
|
+
if (iframe) {
|
|
981
|
+
iframe.src = sessionUrl;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
if (data.type === "CLEAR_ELEMENTS" && selectedElements.length > 0) {
|
|
986
|
+
console.log("[OpenCode] Clearing elements");
|
|
912
987
|
selectedElements = [];
|
|
913
988
|
saveSelectedElements(selectedElements);
|
|
914
989
|
renderSelectedNodes();
|
|
@@ -919,13 +994,16 @@
|
|
|
919
994
|
}
|
|
920
995
|
}
|
|
921
996
|
catch (e) {
|
|
922
|
-
console.error(
|
|
997
|
+
console.error("[OpenCode] SSE parse error:", e);
|
|
923
998
|
}
|
|
924
999
|
};
|
|
925
|
-
|
|
926
|
-
console.error(
|
|
1000
|
+
sseConnection.onerror = (e) => {
|
|
1001
|
+
console.error("[OpenCode] SSE error:", e);
|
|
927
1002
|
// 连接失败时自动重连
|
|
928
|
-
|
|
1003
|
+
if (sseConnection) {
|
|
1004
|
+
sseConnection.close();
|
|
1005
|
+
sseConnection = null;
|
|
1006
|
+
}
|
|
929
1007
|
setTimeout(setupSSEConnection, 3000);
|
|
930
1008
|
};
|
|
931
1009
|
}
|
|
@@ -933,24 +1011,31 @@
|
|
|
933
1011
|
* 渲染已选节点气泡
|
|
934
1012
|
*/
|
|
935
1013
|
function renderSelectedBubbles() {
|
|
936
|
-
selectedBubbles.innerHTML =
|
|
1014
|
+
selectedBubbles.innerHTML = "";
|
|
937
1015
|
if (selectedElements.length === 0) {
|
|
938
|
-
selectedBubbles.innerHTML =
|
|
1016
|
+
selectedBubbles.innerHTML =
|
|
1017
|
+
'<div class="opencode-bubble-empty">暂无选中元素</div>';
|
|
939
1018
|
return;
|
|
940
1019
|
}
|
|
941
1020
|
selectedElements.forEach((element, index) => {
|
|
942
|
-
const bubble = document.createElement(
|
|
943
|
-
bubble.className =
|
|
944
|
-
bubble.setAttribute(
|
|
945
|
-
const description = element.description ||
|
|
946
|
-
const fileName = element.filePath
|
|
947
|
-
|
|
1021
|
+
const bubble = document.createElement("div");
|
|
1022
|
+
bubble.className = "opencode-selected-bubble";
|
|
1023
|
+
bubble.setAttribute("role", "listitem");
|
|
1024
|
+
const description = element.description || "未知元素";
|
|
1025
|
+
const fileName = element.filePath
|
|
1026
|
+
? element.filePath.split("/").pop()
|
|
1027
|
+
: "";
|
|
1028
|
+
const lineInfo = element.line
|
|
1029
|
+
? `:${element.line}${element.column ? `:${element.column}` : ""}`
|
|
1030
|
+
: "";
|
|
948
1031
|
bubble.innerHTML = `
|
|
949
1032
|
<span class="opencode-bubble-text">${description}</span>
|
|
950
|
-
${fileName ? `<span class="opencode-bubble-file">${fileName}${lineInfo}</span>` :
|
|
1033
|
+
${fileName ? `<span class="opencode-bubble-file">${fileName}${lineInfo}</span>` : ""}
|
|
951
1034
|
<button class="opencode-bubble-remove" data-index="${index}" aria-label="移除元素: ${description}">×</button>
|
|
952
1035
|
`;
|
|
953
|
-
bubble
|
|
1036
|
+
bubble
|
|
1037
|
+
.querySelector(".opencode-bubble-remove")
|
|
1038
|
+
.addEventListener("click", (e) => {
|
|
954
1039
|
e.stopPropagation();
|
|
955
1040
|
removeElement(index);
|
|
956
1041
|
renderSelectedBubbles();
|
|
@@ -959,42 +1044,50 @@
|
|
|
959
1044
|
});
|
|
960
1045
|
}
|
|
961
1046
|
function renderSelectedNodes() {
|
|
962
|
-
selectedNodesContainer.innerHTML =
|
|
1047
|
+
selectedNodesContainer.innerHTML = "";
|
|
963
1048
|
if (selectedElements.length === 0) {
|
|
964
|
-
rightToolbar.classList.add(
|
|
965
|
-
clearAllButton.style.display =
|
|
1049
|
+
rightToolbar.classList.add("collapsed");
|
|
1050
|
+
clearAllButton.style.display = "none";
|
|
966
1051
|
}
|
|
967
1052
|
else {
|
|
968
|
-
rightToolbar.classList.remove(
|
|
969
|
-
clearAllButton.style.display =
|
|
1053
|
+
rightToolbar.classList.remove("collapsed");
|
|
1054
|
+
clearAllButton.style.display = "block";
|
|
970
1055
|
}
|
|
971
1056
|
selectedElements.forEach((element, index) => {
|
|
972
|
-
const node = document.createElement(
|
|
973
|
-
node.className =
|
|
974
|
-
node.setAttribute(
|
|
975
|
-
const description = element.description ||
|
|
976
|
-
const textPreview = element.innerText
|
|
977
|
-
|
|
978
|
-
|
|
1057
|
+
const node = document.createElement("div");
|
|
1058
|
+
node.className = "opencode-selected-node";
|
|
1059
|
+
node.setAttribute("role", "listitem");
|
|
1060
|
+
const description = element.description || "未知元素";
|
|
1061
|
+
const textPreview = element.innerText
|
|
1062
|
+
? element.innerText.substring(0, 30)
|
|
1063
|
+
: "";
|
|
1064
|
+
const fileName = element.filePath
|
|
1065
|
+
? element.filePath.split("/").pop()
|
|
1066
|
+
: "未知文件";
|
|
1067
|
+
const lineInfo = element.line
|
|
1068
|
+
? `:${element.line}${element.column ? `:${element.column}` : ""}`
|
|
1069
|
+
: "";
|
|
979
1070
|
node.innerHTML = `
|
|
980
1071
|
<div class="opencode-node-content">
|
|
981
1072
|
<span class="opencode-node-text">${description}</span>
|
|
982
|
-
<span class="opencode-node-file">${textPreview ? textPreview +
|
|
1073
|
+
<span class="opencode-node-file">${textPreview ? textPreview + " · " : ""}${fileName}${lineInfo}</span>
|
|
983
1074
|
</div>
|
|
984
1075
|
<button class="opencode-node-remove" data-index="${index}" aria-label="移除元素: ${description}">×</button>
|
|
985
1076
|
`;
|
|
986
|
-
node
|
|
1077
|
+
node
|
|
1078
|
+
.querySelector(".opencode-node-remove")
|
|
1079
|
+
.addEventListener("click", (e) => {
|
|
987
1080
|
e.stopPropagation();
|
|
988
1081
|
removeElement(index);
|
|
989
1082
|
});
|
|
990
|
-
node.addEventListener(
|
|
1083
|
+
node.addEventListener("click", () => {
|
|
991
1084
|
highlightElement(element);
|
|
992
1085
|
});
|
|
993
1086
|
selectedNodesContainer.appendChild(node);
|
|
994
1087
|
});
|
|
995
1088
|
}
|
|
996
1089
|
// 绑定清空按钮点击事件
|
|
997
|
-
clearAllButton.addEventListener(
|
|
1090
|
+
clearAllButton.addEventListener("click", async () => {
|
|
998
1091
|
if (selectedElements.length === 0)
|
|
999
1092
|
return;
|
|
1000
1093
|
const confirmed = await showConfirmDialog(`确定要清空所有 ${selectedElements.length} 个已选节点吗?`);
|
|
@@ -1003,7 +1096,7 @@
|
|
|
1003
1096
|
}
|
|
1004
1097
|
});
|
|
1005
1098
|
// 绑定选择按钮点击事件
|
|
1006
|
-
selectButton.addEventListener(
|
|
1099
|
+
selectButton.addEventListener("click", toggleSelectMode);
|
|
1007
1100
|
/**
|
|
1008
1101
|
* 高亮页面元素
|
|
1009
1102
|
* @param {SelectedElement} element - 元素信息
|
|
@@ -1014,18 +1107,18 @@
|
|
|
1014
1107
|
if (!description)
|
|
1015
1108
|
return;
|
|
1016
1109
|
let targetElement = null;
|
|
1017
|
-
if (description.includes(
|
|
1110
|
+
if (description.includes("#")) {
|
|
1018
1111
|
const idMatch = description.match(/#([^\.\[\s]+)/);
|
|
1019
1112
|
if (idMatch) {
|
|
1020
1113
|
targetElement = document.getElementById(idMatch[1]);
|
|
1021
1114
|
}
|
|
1022
1115
|
}
|
|
1023
|
-
if (!targetElement && description.includes(
|
|
1116
|
+
if (!targetElement && description.includes(".")) {
|
|
1024
1117
|
const classMatch = description.match(/^([a-z]+)\.([^\[\s]+)/i);
|
|
1025
1118
|
if (classMatch) {
|
|
1026
1119
|
const tagName = classMatch[1];
|
|
1027
|
-
const classes = classMatch[2].split(
|
|
1028
|
-
const selector = `${tagName}.${classes.join(
|
|
1120
|
+
const classes = classMatch[2].split(".").filter(Boolean);
|
|
1121
|
+
const selector = `${tagName}.${classes.join(".")}`;
|
|
1029
1122
|
targetElement = document.querySelector(selector);
|
|
1030
1123
|
}
|
|
1031
1124
|
}
|
|
@@ -1037,9 +1130,9 @@
|
|
|
1037
1130
|
}
|
|
1038
1131
|
}
|
|
1039
1132
|
if (targetElement) {
|
|
1040
|
-
targetElement.scrollIntoView({ behavior:
|
|
1041
|
-
const highlightOverlay = document.createElement(
|
|
1042
|
-
highlightOverlay.className =
|
|
1133
|
+
targetElement.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
1134
|
+
const highlightOverlay = document.createElement("div");
|
|
1135
|
+
highlightOverlay.className = "opencode-element-highlight-temp";
|
|
1043
1136
|
const rect = targetElement.getBoundingClientRect();
|
|
1044
1137
|
highlightOverlay.style.top = `${rect.top + window.scrollY}px`;
|
|
1045
1138
|
highlightOverlay.style.left = `${rect.left + window.scrollX}px`;
|
|
@@ -1052,7 +1145,7 @@
|
|
|
1052
1145
|
}
|
|
1053
1146
|
}
|
|
1054
1147
|
catch (e) {
|
|
1055
|
-
console.error(
|
|
1148
|
+
console.error("Failed to highlight element:", e);
|
|
1056
1149
|
}
|
|
1057
1150
|
}
|
|
1058
1151
|
/**
|
|
@@ -1060,22 +1153,24 @@
|
|
|
1060
1153
|
*/
|
|
1061
1154
|
function sendSelectedElements() {
|
|
1062
1155
|
if (!servicesStarted) {
|
|
1063
|
-
console.log(
|
|
1156
|
+
console.log("[OpenCode] sendSelectedElements: services not started");
|
|
1064
1157
|
return;
|
|
1065
1158
|
}
|
|
1066
|
-
console.log(
|
|
1067
|
-
fetch(
|
|
1068
|
-
method:
|
|
1069
|
-
headers: {
|
|
1159
|
+
console.log("[OpenCode] Sending selected elements:", selectedElements);
|
|
1160
|
+
fetch("/__opencode_context__", {
|
|
1161
|
+
method: "POST",
|
|
1162
|
+
headers: { "Content-Type": "application/json" },
|
|
1070
1163
|
body: JSON.stringify({
|
|
1071
1164
|
url: currentPageUrl,
|
|
1072
1165
|
title: currentPageTitle,
|
|
1073
|
-
selectedElements: selectedElements
|
|
1074
|
-
})
|
|
1075
|
-
})
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1166
|
+
selectedElements: selectedElements,
|
|
1167
|
+
}),
|
|
1168
|
+
})
|
|
1169
|
+
.then(() => {
|
|
1170
|
+
console.log("[OpenCode] Selected elements sent successfully");
|
|
1171
|
+
})
|
|
1172
|
+
.catch((e) => {
|
|
1173
|
+
console.error("[OpenCode] Failed to send selected elements:", e);
|
|
1079
1174
|
});
|
|
1080
1175
|
}
|
|
1081
1176
|
/**
|
|
@@ -1086,8 +1181,8 @@
|
|
|
1086
1181
|
*/
|
|
1087
1182
|
function truncate(str, maxLength) {
|
|
1088
1183
|
if (!str)
|
|
1089
|
-
return
|
|
1090
|
-
return str.length > maxLength ? str.substring(0, maxLength) +
|
|
1184
|
+
return "";
|
|
1185
|
+
return str.length > maxLength ? str.substring(0, maxLength) + "..." : str;
|
|
1091
1186
|
}
|
|
1092
1187
|
/**
|
|
1093
1188
|
* 获取元素的直接文本内容
|
|
@@ -1095,10 +1190,10 @@
|
|
|
1095
1190
|
* @returns {string} 直接文本内容
|
|
1096
1191
|
*/
|
|
1097
1192
|
function getDirectText(element) {
|
|
1098
|
-
let text =
|
|
1193
|
+
let text = "";
|
|
1099
1194
|
for (const child of element.childNodes) {
|
|
1100
1195
|
if (child.nodeType === Node.TEXT_NODE) {
|
|
1101
|
-
text += child.textContent ||
|
|
1196
|
+
text += child.textContent || "";
|
|
1102
1197
|
}
|
|
1103
1198
|
}
|
|
1104
1199
|
return text.trim();
|
|
@@ -1114,24 +1209,29 @@
|
|
|
1114
1209
|
const id = element.id;
|
|
1115
1210
|
if (id)
|
|
1116
1211
|
parts.push(`#${id}`);
|
|
1117
|
-
const className = element.className && typeof element.className ===
|
|
1118
|
-
? element.className
|
|
1119
|
-
|
|
1212
|
+
const className = element.className && typeof element.className === "string"
|
|
1213
|
+
? element.className
|
|
1214
|
+
.trim()
|
|
1215
|
+
.split(/\s+/)
|
|
1216
|
+
.filter(Boolean)
|
|
1217
|
+
.slice(0, 2)
|
|
1218
|
+
.join(".")
|
|
1219
|
+
: "";
|
|
1120
1220
|
if (className)
|
|
1121
1221
|
parts.push(`.${className}`);
|
|
1122
|
-
const name = element.getAttribute(
|
|
1222
|
+
const name = element.getAttribute("name");
|
|
1123
1223
|
if (name)
|
|
1124
1224
|
parts.push(`[name="${name}"]`);
|
|
1125
|
-
const placeholder = element.getAttribute(
|
|
1225
|
+
const placeholder = element.getAttribute("placeholder");
|
|
1126
1226
|
if (placeholder)
|
|
1127
1227
|
parts.push(`[placeholder="${placeholder.substring(0, 20)}"]`);
|
|
1128
|
-
const src = element.getAttribute(
|
|
1228
|
+
const src = element.getAttribute("src");
|
|
1129
1229
|
if (src)
|
|
1130
1230
|
parts.push(`[src]`);
|
|
1131
|
-
const href = element.getAttribute(
|
|
1132
|
-
if (href && href !==
|
|
1231
|
+
const href = element.getAttribute("href");
|
|
1232
|
+
if (href && href !== "#")
|
|
1133
1233
|
parts.push(`[href]`);
|
|
1134
|
-
return parts.join(
|
|
1234
|
+
return parts.join("");
|
|
1135
1235
|
}
|
|
1136
1236
|
/**
|
|
1137
1237
|
* 设置 Vue Inspector 钩子
|
|
@@ -1151,7 +1251,7 @@
|
|
|
1151
1251
|
line: params.line,
|
|
1152
1252
|
column: params.column,
|
|
1153
1253
|
innerText: truncate(innerText, 200),
|
|
1154
|
-
description
|
|
1254
|
+
description,
|
|
1155
1255
|
};
|
|
1156
1256
|
addElement(elementInfo);
|
|
1157
1257
|
sendSelectedElements();
|
|
@@ -1175,10 +1275,14 @@
|
|
|
1175
1275
|
}
|
|
1176
1276
|
// 导出全局 API
|
|
1177
1277
|
window.OpenCodeWidget = {
|
|
1178
|
-
open: () => {
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1278
|
+
open: () => {
|
|
1279
|
+
if (!isOpen)
|
|
1280
|
+
toggle();
|
|
1281
|
+
},
|
|
1282
|
+
close: () => {
|
|
1283
|
+
if (isOpen)
|
|
1284
|
+
toggle();
|
|
1285
|
+
},
|
|
1182
1286
|
toggle,
|
|
1183
1287
|
showNotification,
|
|
1184
1288
|
updateContext,
|
|
@@ -1284,6 +1388,13 @@
|
|
|
1284
1388
|
transform: translateY(20px) scale(0.95);
|
|
1285
1389
|
transition: all 0.3s ease;
|
|
1286
1390
|
display: flex;
|
|
1391
|
+
flex-direction: column;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
.opencode-chat-content {
|
|
1395
|
+
display: flex;
|
|
1396
|
+
flex: 1;
|
|
1397
|
+
overflow: hidden;
|
|
1287
1398
|
}
|
|
1288
1399
|
|
|
1289
1400
|
.opencode-widget.bottom-right .opencode-chat {
|
|
@@ -1313,17 +1424,15 @@
|
|
|
1313
1424
|
}
|
|
1314
1425
|
|
|
1315
1426
|
.opencode-chat-header {
|
|
1316
|
-
position:
|
|
1317
|
-
|
|
1318
|
-
left: 0;
|
|
1319
|
-
right: 0;
|
|
1427
|
+
position: relative;
|
|
1428
|
+
flex-shrink: 0;
|
|
1320
1429
|
display: flex;
|
|
1321
1430
|
align-items: center;
|
|
1322
1431
|
justify-content: space-between;
|
|
1323
1432
|
padding: 0 12px;
|
|
1324
1433
|
height: 40px;
|
|
1325
1434
|
background: #f8f9fa;
|
|
1326
|
-
border-bottom: 1px solid #
|
|
1435
|
+
border-bottom: 1px solid #282828;
|
|
1327
1436
|
z-index: 5;
|
|
1328
1437
|
}
|
|
1329
1438
|
|
|
@@ -1336,7 +1445,7 @@
|
|
|
1336
1445
|
.opencode-chat-header-title {
|
|
1337
1446
|
font-size: 14px;
|
|
1338
1447
|
font-weight: 600;
|
|
1339
|
-
color: #
|
|
1448
|
+
color: #282828;
|
|
1340
1449
|
position: absolute;
|
|
1341
1450
|
left: 50%;
|
|
1342
1451
|
transform: translateX(-50%);
|
|
@@ -1362,8 +1471,8 @@
|
|
|
1362
1471
|
}
|
|
1363
1472
|
|
|
1364
1473
|
.opencode-header-btn:hover {
|
|
1365
|
-
background: #
|
|
1366
|
-
color: #
|
|
1474
|
+
background: #282828;
|
|
1475
|
+
color: #282828;
|
|
1367
1476
|
}
|
|
1368
1477
|
|
|
1369
1478
|
.opencode-header-btn.close:hover {
|
|
@@ -1377,8 +1486,8 @@
|
|
|
1377
1486
|
}
|
|
1378
1487
|
|
|
1379
1488
|
.opencode-dark .opencode-chat-header {
|
|
1380
|
-
background: #
|
|
1381
|
-
border-bottom-color: #
|
|
1489
|
+
background: #121212;
|
|
1490
|
+
border-bottom-color: #282828;
|
|
1382
1491
|
}
|
|
1383
1492
|
|
|
1384
1493
|
.opencode-dark .opencode-chat-header-title {
|
|
@@ -1390,7 +1499,7 @@
|
|
|
1390
1499
|
}
|
|
1391
1500
|
|
|
1392
1501
|
.opencode-dark .opencode-header-btn:hover {
|
|
1393
|
-
background: #
|
|
1502
|
+
background: #282828;
|
|
1394
1503
|
color: #f3f4f6;
|
|
1395
1504
|
}
|
|
1396
1505
|
|
|
@@ -1400,10 +1509,9 @@
|
|
|
1400
1509
|
}
|
|
1401
1510
|
|
|
1402
1511
|
.opencode-session-list {
|
|
1403
|
-
margin-top: 40px;
|
|
1404
1512
|
width: 240px;
|
|
1405
1513
|
background: #f8f9fa;
|
|
1406
|
-
border-right: 1px solid #
|
|
1514
|
+
border-right: 1px solid #282828;
|
|
1407
1515
|
display: flex;
|
|
1408
1516
|
flex-direction: column;
|
|
1409
1517
|
flex-shrink: 0;
|
|
@@ -1422,13 +1530,13 @@
|
|
|
1422
1530
|
|
|
1423
1531
|
.opencode-session-list-header {
|
|
1424
1532
|
padding: 16px;
|
|
1425
|
-
border-bottom: 1px solid #
|
|
1533
|
+
border-bottom: 1px solid #282828;
|
|
1426
1534
|
display: flex;
|
|
1427
1535
|
justify-content: space-between;
|
|
1428
1536
|
align-items: center;
|
|
1429
1537
|
font-weight: 600;
|
|
1430
1538
|
font-size: 14px;
|
|
1431
|
-
color: #
|
|
1539
|
+
color: #282828;
|
|
1432
1540
|
}
|
|
1433
1541
|
|
|
1434
1542
|
.opencode-new-session-btn {
|
|
@@ -1466,7 +1574,7 @@
|
|
|
1466
1574
|
}
|
|
1467
1575
|
|
|
1468
1576
|
.opencode-session-item:hover {
|
|
1469
|
-
background: #
|
|
1577
|
+
background: #282828;
|
|
1470
1578
|
}
|
|
1471
1579
|
|
|
1472
1580
|
.opencode-session-item.active {
|
|
@@ -1532,7 +1640,7 @@
|
|
|
1532
1640
|
|
|
1533
1641
|
.opencode-session-header-skeleton {
|
|
1534
1642
|
padding: 16px;
|
|
1535
|
-
border-bottom: 1px solid #
|
|
1643
|
+
border-bottom: 1px solid #282828;
|
|
1536
1644
|
display: none;
|
|
1537
1645
|
justify-content: space-between;
|
|
1538
1646
|
align-items: center;
|
|
@@ -1545,7 +1653,7 @@
|
|
|
1545
1653
|
.opencode-skeleton-header-title {
|
|
1546
1654
|
height: 18px;
|
|
1547
1655
|
width: 80px;
|
|
1548
|
-
background:
|
|
1656
|
+
background: #151515;
|
|
1549
1657
|
background-size: 200% 100%;
|
|
1550
1658
|
animation: skeleton-loading 1.5s ease-in-out infinite;
|
|
1551
1659
|
border-radius: 4px;
|
|
@@ -1554,7 +1662,7 @@
|
|
|
1554
1662
|
.opencode-skeleton-header-btn {
|
|
1555
1663
|
width: 28px;
|
|
1556
1664
|
height: 28px;
|
|
1557
|
-
background:
|
|
1665
|
+
background: #151515;
|
|
1558
1666
|
background-size: 200% 100%;
|
|
1559
1667
|
animation: skeleton-loading 1.5s ease-in-out infinite;
|
|
1560
1668
|
border-radius: 6px;
|
|
@@ -1580,7 +1688,7 @@
|
|
|
1580
1688
|
|
|
1581
1689
|
.opencode-skeleton-title {
|
|
1582
1690
|
height: 16px;
|
|
1583
|
-
background:
|
|
1691
|
+
background: #151515;
|
|
1584
1692
|
background-size: 200% 100%;
|
|
1585
1693
|
animation: skeleton-loading 1.5s ease-in-out infinite;
|
|
1586
1694
|
border-radius: 4px;
|
|
@@ -1590,7 +1698,7 @@
|
|
|
1590
1698
|
|
|
1591
1699
|
.opencode-skeleton-meta {
|
|
1592
1700
|
height: 12px;
|
|
1593
|
-
background:
|
|
1701
|
+
background: #151515;
|
|
1594
1702
|
background-size: 200% 100%;
|
|
1595
1703
|
animation: skeleton-loading 1.5s ease-in-out infinite;
|
|
1596
1704
|
border-radius: 4px;
|
|
@@ -1607,22 +1715,22 @@
|
|
|
1607
1715
|
}
|
|
1608
1716
|
|
|
1609
1717
|
.opencode-dark .opencode-skeleton-item {
|
|
1610
|
-
background: #
|
|
1718
|
+
background: #1e1e1e;
|
|
1611
1719
|
}
|
|
1612
1720
|
|
|
1613
1721
|
.opencode-dark .opencode-skeleton-title,
|
|
1614
1722
|
.opencode-dark .opencode-skeleton-meta {
|
|
1615
|
-
background: linear-gradient(90deg, #
|
|
1723
|
+
background: linear-gradient(90deg, #282828 25%, #4b5563 50%, #282828 75%);
|
|
1616
1724
|
background-size: 200% 100%;
|
|
1617
1725
|
}
|
|
1618
1726
|
|
|
1619
1727
|
.opencode-dark .opencode-session-header-skeleton {
|
|
1620
|
-
border-bottom-color: #
|
|
1728
|
+
border-bottom-color: #282828;
|
|
1621
1729
|
}
|
|
1622
1730
|
|
|
1623
1731
|
.opencode-dark .opencode-skeleton-header-title,
|
|
1624
1732
|
.opencode-dark .opencode-skeleton-header-btn {
|
|
1625
|
-
background: linear-gradient(90deg, #
|
|
1733
|
+
background: linear-gradient(90deg, #282828 25%, #4b5563 50%, #282828 75%);
|
|
1626
1734
|
background-size: 200% 100%;
|
|
1627
1735
|
}
|
|
1628
1736
|
|
|
@@ -1633,7 +1741,6 @@
|
|
|
1633
1741
|
display: flex;
|
|
1634
1742
|
flex-direction: column;
|
|
1635
1743
|
margin-top: -42px;
|
|
1636
|
-
padding-top: 40px;
|
|
1637
1744
|
}
|
|
1638
1745
|
|
|
1639
1746
|
.opencode-loading-overlay {
|
|
@@ -1658,7 +1765,7 @@
|
|
|
1658
1765
|
.opencode-loading-spinner {
|
|
1659
1766
|
width: 40px;
|
|
1660
1767
|
height: 40px;
|
|
1661
|
-
border: 3px solid #
|
|
1768
|
+
border: 3px solid #282828;
|
|
1662
1769
|
border-top-color: #3b82f6;
|
|
1663
1770
|
border-radius: 50%;
|
|
1664
1771
|
animation: spin 0.8s linear infinite;
|
|
@@ -1674,6 +1781,61 @@
|
|
|
1674
1781
|
color: #6b7280;
|
|
1675
1782
|
}
|
|
1676
1783
|
|
|
1784
|
+
.opencode-empty-state-overlay {
|
|
1785
|
+
position: absolute;
|
|
1786
|
+
top: 0;
|
|
1787
|
+
left: 0;
|
|
1788
|
+
right: 0;
|
|
1789
|
+
bottom: 0;
|
|
1790
|
+
background: #f8f9fa;
|
|
1791
|
+
display: none;
|
|
1792
|
+
flex-direction: column;
|
|
1793
|
+
align-items: center;
|
|
1794
|
+
justify-content: center;
|
|
1795
|
+
z-index: 5;
|
|
1796
|
+
transition: opacity 0.3s ease;
|
|
1797
|
+
margin-top: 42px;
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
.opencode-empty-state-overlay.visible {
|
|
1801
|
+
display: flex;
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
.opencode-empty-state-icon {
|
|
1805
|
+
color: #9ca3af;
|
|
1806
|
+
margin-bottom: 16px;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
.opencode-empty-state-text {
|
|
1810
|
+
color: #4b5563;
|
|
1811
|
+
font-size: 16px;
|
|
1812
|
+
font-weight: 500;
|
|
1813
|
+
margin-bottom: 24px;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
.opencode-empty-state-btn {
|
|
1817
|
+
padding: 10px 24px;
|
|
1818
|
+
border-radius: 8px;
|
|
1819
|
+
border: none;
|
|
1820
|
+
background: #3b82f6;
|
|
1821
|
+
color: white;
|
|
1822
|
+
font-size: 14px;
|
|
1823
|
+
font-weight: 500;
|
|
1824
|
+
cursor: pointer;
|
|
1825
|
+
transition: all 0.2s;
|
|
1826
|
+
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.2);
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
.opencode-empty-state-btn:hover {
|
|
1830
|
+
background: #2563eb;
|
|
1831
|
+
transform: translateY(-1px);
|
|
1832
|
+
box-shadow: 0 4px 6px rgba(59, 130, 246, 0.3);
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
.opencode-empty-state-btn:active {
|
|
1836
|
+
transform: translateY(0);
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1677
1839
|
.opencode-iframe {
|
|
1678
1840
|
width: 100%;
|
|
1679
1841
|
height: 100%;
|
|
@@ -1685,31 +1847,36 @@
|
|
|
1685
1847
|
}
|
|
1686
1848
|
|
|
1687
1849
|
.opencode-dark .opencode-session-list {
|
|
1688
|
-
background: #
|
|
1689
|
-
border-right-color: #
|
|
1850
|
+
background: #121212;
|
|
1851
|
+
border-right-color: #282828;
|
|
1690
1852
|
}
|
|
1691
1853
|
|
|
1692
1854
|
.opencode-dark .opencode-session-toggle {
|
|
1693
1855
|
color: #9ca3af;
|
|
1694
|
-
border-bottom-color: #
|
|
1856
|
+
border-bottom-color: #282828;
|
|
1695
1857
|
}
|
|
1696
1858
|
|
|
1697
1859
|
.opencode-dark .opencode-session-toggle:hover {
|
|
1698
|
-
background: #
|
|
1860
|
+
background: #282828;
|
|
1699
1861
|
color: #f3f4f6;
|
|
1700
1862
|
}
|
|
1701
1863
|
|
|
1702
1864
|
.opencode-dark .opencode-session-list-header {
|
|
1703
|
-
border-bottom-color: #
|
|
1865
|
+
border-bottom-color: #282828;
|
|
1704
1866
|
color: #f3f4f6;
|
|
1705
1867
|
}
|
|
1706
1868
|
|
|
1707
1869
|
.opencode-dark .opencode-session-item:hover {
|
|
1708
|
-
background: #
|
|
1870
|
+
background: #282828;
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
.opencode-dark .opencode-session-item {
|
|
1874
|
+
color: #d1d5db;
|
|
1709
1875
|
}
|
|
1710
1876
|
|
|
1711
1877
|
.opencode-dark .opencode-session-item.active {
|
|
1712
1878
|
background: #3b82f6;
|
|
1879
|
+
color: white;
|
|
1713
1880
|
}
|
|
1714
1881
|
|
|
1715
1882
|
.opencode-dark .opencode-loading-overlay {
|
|
@@ -1717,7 +1884,7 @@
|
|
|
1717
1884
|
}
|
|
1718
1885
|
|
|
1719
1886
|
.opencode-dark .opencode-loading-spinner {
|
|
1720
|
-
border-color: #
|
|
1887
|
+
border-color: #282828;
|
|
1721
1888
|
border-top-color: #3b82f6;
|
|
1722
1889
|
}
|
|
1723
1890
|
|
|
@@ -1725,15 +1892,26 @@
|
|
|
1725
1892
|
color: #9ca3af;
|
|
1726
1893
|
}
|
|
1727
1894
|
|
|
1895
|
+
.opencode-dark .opencode-empty-state-overlay {
|
|
1896
|
+
background: #1a1a1a;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
.opencode-dark .opencode-empty-state-icon {
|
|
1900
|
+
color: #4b5563;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
.opencode-dark .opencode-empty-state-text {
|
|
1904
|
+
color: #d1d5db;
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1728
1907
|
.opencode-right-toolbar {
|
|
1729
1908
|
width: 140px;
|
|
1730
1909
|
background: #f8f9fa;
|
|
1731
|
-
border-left: 1px solid #
|
|
1910
|
+
border-left: 1px solid #282828;
|
|
1732
1911
|
display: flex;
|
|
1733
1912
|
flex-direction: column;
|
|
1734
1913
|
flex-shrink: 0;
|
|
1735
1914
|
transition: width 0.2s ease;
|
|
1736
|
-
margin-top: 40px;
|
|
1737
1915
|
overflow: hidden;
|
|
1738
1916
|
}
|
|
1739
1917
|
|
|
@@ -1750,13 +1928,13 @@
|
|
|
1750
1928
|
|
|
1751
1929
|
.opencode-selected-nodes-header {
|
|
1752
1930
|
padding: 12px 8px 8px;
|
|
1753
|
-
border-bottom: 1px solid #
|
|
1931
|
+
border-bottom: 1px solid #282828;
|
|
1754
1932
|
}
|
|
1755
1933
|
|
|
1756
1934
|
.opencode-selected-nodes-title {
|
|
1757
1935
|
font-size: 14px;
|
|
1758
1936
|
font-weight: 600;
|
|
1759
|
-
color: #
|
|
1937
|
+
color: #282828;
|
|
1760
1938
|
margin-bottom: 4px;
|
|
1761
1939
|
}
|
|
1762
1940
|
|
|
@@ -1790,7 +1968,7 @@
|
|
|
1790
1968
|
gap: 8px;
|
|
1791
1969
|
padding: 8px 10px;
|
|
1792
1970
|
background: white;
|
|
1793
|
-
border: 1px solid #
|
|
1971
|
+
border: 1px solid #282828;
|
|
1794
1972
|
border-radius: 6px;
|
|
1795
1973
|
font-size: 12px;
|
|
1796
1974
|
transition: all 0.2s;
|
|
@@ -1810,7 +1988,7 @@
|
|
|
1810
1988
|
}
|
|
1811
1989
|
|
|
1812
1990
|
.opencode-node-text {
|
|
1813
|
-
color: #
|
|
1991
|
+
color: #282828;
|
|
1814
1992
|
font-weight: 500;
|
|
1815
1993
|
overflow: hidden;
|
|
1816
1994
|
text-overflow: ellipsis;
|
|
@@ -1870,12 +2048,12 @@
|
|
|
1870
2048
|
}
|
|
1871
2049
|
|
|
1872
2050
|
.opencode-dark .opencode-right-toolbar {
|
|
1873
|
-
background: #
|
|
1874
|
-
border-left-color: #
|
|
2051
|
+
background: #121212;
|
|
2052
|
+
border-left-color: #282828;
|
|
1875
2053
|
}
|
|
1876
2054
|
|
|
1877
2055
|
.opencode-dark .opencode-selected-nodes-header {
|
|
1878
|
-
border-bottom-color: #
|
|
2056
|
+
border-bottom-color: #282828;
|
|
1879
2057
|
}
|
|
1880
2058
|
|
|
1881
2059
|
.opencode-dark .opencode-selected-nodes-title {
|
|
@@ -1891,8 +2069,8 @@
|
|
|
1891
2069
|
}
|
|
1892
2070
|
|
|
1893
2071
|
.opencode-dark .opencode-selected-node {
|
|
1894
|
-
background: #
|
|
1895
|
-
border-color: #
|
|
2072
|
+
background: #1e1e1e;
|
|
2073
|
+
border-color: #282828;
|
|
1896
2074
|
}
|
|
1897
2075
|
|
|
1898
2076
|
.opencode-dark .opencode-selected-node:hover {
|
|
@@ -1985,7 +2163,7 @@
|
|
|
1985
2163
|
|
|
1986
2164
|
.opencode-dialog-message {
|
|
1987
2165
|
font-size: 15px;
|
|
1988
|
-
color: #
|
|
2166
|
+
color: #282828;
|
|
1989
2167
|
line-height: 1.5;
|
|
1990
2168
|
}
|
|
1991
2169
|
|
|
@@ -2007,11 +2185,12 @@
|
|
|
2007
2185
|
|
|
2008
2186
|
.opencode-dialog-btn.cancel {
|
|
2009
2187
|
background: #f3f4f6;
|
|
2010
|
-
color: #
|
|
2188
|
+
color: #282828;
|
|
2011
2189
|
}
|
|
2012
2190
|
|
|
2013
2191
|
.opencode-dialog-btn.cancel:hover {
|
|
2014
|
-
background: #
|
|
2192
|
+
background: #282828;
|
|
2193
|
+
color: white;
|
|
2015
2194
|
}
|
|
2016
2195
|
|
|
2017
2196
|
.opencode-dialog-btn.confirm {
|
|
@@ -2024,7 +2203,7 @@
|
|
|
2024
2203
|
}
|
|
2025
2204
|
|
|
2026
2205
|
.opencode-dark .opencode-dialog {
|
|
2027
|
-
background: #
|
|
2206
|
+
background: #1e1e1e;
|
|
2028
2207
|
}
|
|
2029
2208
|
|
|
2030
2209
|
.opencode-dark .opencode-dialog-message {
|
|
@@ -2032,7 +2211,7 @@
|
|
|
2032
2211
|
}
|
|
2033
2212
|
|
|
2034
2213
|
.opencode-dark .opencode-dialog-btn.cancel {
|
|
2035
|
-
background: #
|
|
2214
|
+
background: #282828;
|
|
2036
2215
|
color: #f3f4f6;
|
|
2037
2216
|
}
|
|
2038
2217
|
|
|
@@ -2097,7 +2276,7 @@
|
|
|
2097
2276
|
|
|
2098
2277
|
.opencode-element-tooltip {
|
|
2099
2278
|
position: fixed;
|
|
2100
|
-
background: #
|
|
2279
|
+
background: #1e1e1e;
|
|
2101
2280
|
color: white;
|
|
2102
2281
|
padding: 8px 12px;
|
|
2103
2282
|
border-radius: 6px;
|
|
@@ -2143,7 +2322,7 @@
|
|
|
2143
2322
|
gap: 2px;
|
|
2144
2323
|
padding: 8px 10px;
|
|
2145
2324
|
background: white;
|
|
2146
|
-
border: 1px solid #
|
|
2325
|
+
border: 1px solid #282828;
|
|
2147
2326
|
border-radius: 8px;
|
|
2148
2327
|
font-size: 12px;
|
|
2149
2328
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
@@ -2151,7 +2330,7 @@
|
|
|
2151
2330
|
}
|
|
2152
2331
|
|
|
2153
2332
|
.opencode-bubble-text {
|
|
2154
|
-
color: #
|
|
2333
|
+
color: #282828;
|
|
2155
2334
|
font-weight: 500;
|
|
2156
2335
|
overflow: hidden;
|
|
2157
2336
|
text-overflow: ellipsis;
|
|
@@ -2200,8 +2379,8 @@
|
|
|
2200
2379
|
}
|
|
2201
2380
|
|
|
2202
2381
|
.opencode-dark .opencode-selected-bubble {
|
|
2203
|
-
background: #
|
|
2204
|
-
border-color: #
|
|
2382
|
+
background: #1e1e1e;
|
|
2383
|
+
border-color: #282828;
|
|
2205
2384
|
}
|
|
2206
2385
|
|
|
2207
2386
|
.opencode-dark .opencode-bubble-text {
|
|
@@ -2217,8 +2396,8 @@
|
|
|
2217
2396
|
}
|
|
2218
2397
|
|
|
2219
2398
|
.opencode-dark .opencode-bubble-empty {
|
|
2220
|
-
background: #
|
|
2221
|
-
border-color: #
|
|
2399
|
+
background: #1e1e1e;
|
|
2400
|
+
border-color: #282828;
|
|
2222
2401
|
color: #6b7280;
|
|
2223
2402
|
}
|
|
2224
2403
|
|
|
@@ -2259,14 +2438,15 @@
|
|
|
2259
2438
|
* 自动初始化挂件
|
|
2260
2439
|
*/
|
|
2261
2440
|
function autoInit() {
|
|
2262
|
-
const script = document.currentScript ||
|
|
2441
|
+
const script = document.currentScript ||
|
|
2442
|
+
document.querySelector("script[data-opencode-config]");
|
|
2263
2443
|
if (script) {
|
|
2264
|
-
const configBase64 = script.getAttribute(
|
|
2444
|
+
const configBase64 = script.getAttribute("data-opencode-config");
|
|
2265
2445
|
if (configBase64) {
|
|
2266
2446
|
try {
|
|
2267
2447
|
const config = JSON.parse(atob(configBase64));
|
|
2268
|
-
if (document.readyState ===
|
|
2269
|
-
document.addEventListener(
|
|
2448
|
+
if (document.readyState === "loading") {
|
|
2449
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
2270
2450
|
initOpenCodeWidget(config);
|
|
2271
2451
|
});
|
|
2272
2452
|
}
|
|
@@ -2275,7 +2455,7 @@
|
|
|
2275
2455
|
}
|
|
2276
2456
|
}
|
|
2277
2457
|
catch (e) {
|
|
2278
|
-
console.error(
|
|
2458
|
+
console.error("[OpenCode Widget] Failed to parse config:", e);
|
|
2279
2459
|
}
|
|
2280
2460
|
}
|
|
2281
2461
|
}
|