vite-plugin-opencode-assistant 1.0.12 → 1.0.14
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/es/client/index.js +424 -84
- package/es/core/api.d.ts +1 -0
- package/es/core/api.js +97 -15
- package/es/core/service.d.ts +8 -1
- package/es/core/service.js +84 -16
- package/es/endpoints/index.js +2 -0
- package/es/endpoints/sse.js +14 -5
- package/es/endpoints/types.d.ts +7 -1
- package/es/endpoints/warmup.d.ts +3 -0
- package/es/endpoints/warmup.js +45 -0
- package/es/index.js +12 -3
- package/lib/client/index.js +420 -83
- package/lib/client.js +3005 -2707
- package/lib/core/api.d.ts +1 -0
- package/lib/core/api.js +97 -15
- package/lib/core/service.d.ts +8 -1
- package/lib/core/service.js +83 -16
- package/lib/endpoints/index.js +2 -0
- package/lib/endpoints/sse.js +14 -5
- package/lib/endpoints/types.d.ts +7 -1
- package/lib/endpoints/warmup.d.ts +3 -0
- package/lib/endpoints/warmup.js +68 -0
- package/lib/index.js +12 -3
- package/lib/style.css +1 -1
- package/package.json +5 -5
package/lib/core/api.d.ts
CHANGED
package/lib/core/api.js
CHANGED
|
@@ -61,13 +61,36 @@ function sleep(ms) {
|
|
|
61
61
|
function base64Encode(str) {
|
|
62
62
|
return Buffer.from(str).toString("base64");
|
|
63
63
|
}
|
|
64
|
+
function extractTextFromResponse(data) {
|
|
65
|
+
if (!data || typeof data !== "object") return null;
|
|
66
|
+
const obj = data;
|
|
67
|
+
if (obj.parts && Array.isArray(obj.parts)) {
|
|
68
|
+
const textParts = obj.parts.filter(
|
|
69
|
+
(p) => p && typeof p === "object" && p.type === "text"
|
|
70
|
+
).map((p) => p.text).filter(Boolean);
|
|
71
|
+
if (textParts.length > 0) return textParts.join("");
|
|
72
|
+
}
|
|
73
|
+
if (obj.text && typeof obj.text === "string") {
|
|
74
|
+
return obj.text;
|
|
75
|
+
}
|
|
76
|
+
if (obj.content && typeof obj.content === "string") {
|
|
77
|
+
return obj.content;
|
|
78
|
+
}
|
|
79
|
+
if (obj.message && typeof obj.message === "string") {
|
|
80
|
+
return obj.message;
|
|
81
|
+
}
|
|
82
|
+
if (typeof data === "string") {
|
|
83
|
+
return data;
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
64
87
|
class OpenCodeAPI {
|
|
65
88
|
constructor(hostname, getPort, warmupChromeMcpConfig = false) {
|
|
66
89
|
__publicField(this, "hostname", hostname);
|
|
67
90
|
__publicField(this, "getPort", getPort);
|
|
68
91
|
__publicField(this, "warmupChromeMcpConfig", warmupChromeMcpConfig);
|
|
69
92
|
}
|
|
70
|
-
createHttpRequest(options, body) {
|
|
93
|
+
createHttpRequest(options, body, timeout) {
|
|
71
94
|
const timer = new import_shared.PerformanceTimer("HTTP Request", {
|
|
72
95
|
operation: `${options.method || "GET"} ${options.path}`
|
|
73
96
|
});
|
|
@@ -90,6 +113,13 @@ class OpenCodeAPI {
|
|
|
90
113
|
timer.end("\u274C Request failed");
|
|
91
114
|
reject(e);
|
|
92
115
|
});
|
|
116
|
+
if (timeout) {
|
|
117
|
+
req.setTimeout(timeout, () => {
|
|
118
|
+
timer.end("\u274C Request timeout");
|
|
119
|
+
req.destroy();
|
|
120
|
+
reject(new Error(`Request timeout after ${timeout}ms`));
|
|
121
|
+
});
|
|
122
|
+
}
|
|
93
123
|
if (body) req.write(body);
|
|
94
124
|
req.end();
|
|
95
125
|
});
|
|
@@ -243,24 +273,16 @@ class OpenCodeAPI {
|
|
|
243
273
|
try {
|
|
244
274
|
const warmupSession = yield this.createSession(import_shared.DEFAULT_RETRIES, "__chrome_mcp_warmup__");
|
|
245
275
|
warmupSessionId = warmupSession.id;
|
|
246
|
-
let chromeToolIds;
|
|
247
|
-
try {
|
|
248
|
-
const toolIds = yield this.getToolIds();
|
|
249
|
-
chromeToolIds = toolIds.filter((toolId) => /chrome[-_]?devtools/i.test(toolId));
|
|
250
|
-
log.debug("Resolved Chrome MCP tool ids", {
|
|
251
|
-
chromeToolIds
|
|
252
|
-
});
|
|
253
|
-
} catch (e) {
|
|
254
|
-
log.debug("Failed to resolve Chrome MCP tool ids", { error: e });
|
|
255
|
-
}
|
|
256
276
|
const prompt = [
|
|
257
277
|
"Call the browser tool list_pages immediately to establish the Chrome DevTools MCP connection.",
|
|
258
278
|
viteOrigin ? `If there are no pages, call new_page with ${viteOrigin}.` : "If there are no pages, call new_page with about:blank.",
|
|
259
279
|
"Do not read or modify project files.",
|
|
260
280
|
"Do not use any non-browser tools.",
|
|
261
|
-
"After the tool call is complete, reply with exactly: ready"
|
|
281
|
+
"After the tool call is complete, reply with exactly: ready",
|
|
282
|
+
"If the tool call fails, reply with exactly: fail"
|
|
262
283
|
].join(" ");
|
|
263
|
-
|
|
284
|
+
const WARMUP_TIMEOUT = 3e4;
|
|
285
|
+
const data = yield this.createHttpRequest(
|
|
264
286
|
{
|
|
265
287
|
hostname: this.hostname,
|
|
266
288
|
port: this.getPort(),
|
|
@@ -270,14 +292,19 @@ class OpenCodeAPI {
|
|
|
270
292
|
},
|
|
271
293
|
JSON.stringify({
|
|
272
294
|
system: "You are warming up Chrome DevTools MCP during startup. You must use the available browser tools immediately before replying.",
|
|
273
|
-
tools: (chromeToolIds == null ? void 0 : chromeToolIds.length) ? chromeToolIds : void 0,
|
|
274
295
|
parts: [{ type: "text", text: prompt }]
|
|
275
|
-
})
|
|
296
|
+
}),
|
|
297
|
+
WARMUP_TIMEOUT
|
|
276
298
|
);
|
|
299
|
+
const responseText = extractTextFromResponse(data);
|
|
300
|
+
if (!(responseText == null ? void 0 : responseText.toLowerCase().includes("ready"))) {
|
|
301
|
+
throw new Error(`Chrome MCP warmup failed: ${responseText || "No response"}`);
|
|
302
|
+
}
|
|
277
303
|
timer.end("Chrome MCP warmed up");
|
|
278
304
|
} catch (e) {
|
|
279
305
|
log.warn("Failed to warm up Chrome MCP", { error: e });
|
|
280
306
|
timer.end("Chrome MCP warmup skipped");
|
|
307
|
+
throw e;
|
|
281
308
|
} finally {
|
|
282
309
|
if (warmupSessionId) {
|
|
283
310
|
try {
|
|
@@ -314,6 +341,61 @@ class OpenCodeAPI {
|
|
|
314
341
|
return url;
|
|
315
342
|
});
|
|
316
343
|
}
|
|
344
|
+
retryWarmupChromeMcp(viteOrigin) {
|
|
345
|
+
return __async(this, null, function* () {
|
|
346
|
+
const timer = log.timer("retryWarmupChromeMcp", { viteOrigin });
|
|
347
|
+
let warmupSessionId = null;
|
|
348
|
+
try {
|
|
349
|
+
const warmupSession = yield this.createSession(import_shared.DEFAULT_RETRIES, "__chrome_mcp_warmup__");
|
|
350
|
+
warmupSessionId = warmupSession.id;
|
|
351
|
+
const prompt = [
|
|
352
|
+
"Call the browser tool list_pages immediately to establish the Chrome DevTools MCP connection.",
|
|
353
|
+
viteOrigin ? `If there are no pages, call new_page with ${viteOrigin}.` : "If there are no pages, call new_page with about:blank.",
|
|
354
|
+
"Do not read or modify project files.",
|
|
355
|
+
"Do not use any non-browser tools.",
|
|
356
|
+
"After the tool call is complete, reply with exactly: ready",
|
|
357
|
+
"If the tool call fails, reply with exactly: fail"
|
|
358
|
+
].join(" ");
|
|
359
|
+
const WARMUP_TIMEOUT = 6e4;
|
|
360
|
+
const data = yield this.createHttpRequest(
|
|
361
|
+
{
|
|
362
|
+
hostname: this.hostname,
|
|
363
|
+
port: this.getPort(),
|
|
364
|
+
path: `/session/${warmupSessionId}/message`,
|
|
365
|
+
method: "POST",
|
|
366
|
+
headers: { "Content-Type": "application/json" }
|
|
367
|
+
},
|
|
368
|
+
JSON.stringify({
|
|
369
|
+
system: "You are warming up Chrome DevTools MCP during startup. You must use the available browser tools immediately before replying.",
|
|
370
|
+
parts: [{ type: "text", text: prompt }]
|
|
371
|
+
}),
|
|
372
|
+
WARMUP_TIMEOUT
|
|
373
|
+
);
|
|
374
|
+
log.debug("Chrome MCP warmup response:", { data });
|
|
375
|
+
const responseText = extractTextFromResponse(data);
|
|
376
|
+
if (!(responseText == null ? void 0 : responseText.toLowerCase().includes("ready"))) {
|
|
377
|
+
throw new Error(`Chrome MCP warmup failed: ${responseText || "No response"}`);
|
|
378
|
+
}
|
|
379
|
+
timer.end("Chrome MCP warmed up successfully");
|
|
380
|
+
return true;
|
|
381
|
+
} catch (e) {
|
|
382
|
+
log.warn("Failed to retry warm up Chrome MCP", { error: e });
|
|
383
|
+
timer.end("Chrome MCP warmup retry failed");
|
|
384
|
+
return false;
|
|
385
|
+
} finally {
|
|
386
|
+
if (warmupSessionId) {
|
|
387
|
+
try {
|
|
388
|
+
yield this.deleteSession(warmupSessionId, 5);
|
|
389
|
+
} catch (e) {
|
|
390
|
+
log.warn("Failed to delete warmup session after retries", {
|
|
391
|
+
error: e,
|
|
392
|
+
warmupSessionId
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
}
|
|
317
399
|
}
|
|
318
400
|
// Annotate the CommonJS export names for ESM import in node:
|
|
319
401
|
0 && (module.exports = {
|
package/lib/core/service.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ResultPromise } from "execa";
|
|
2
2
|
import type http from "http";
|
|
3
|
-
import type { OpenCodeOptions } from "@vite-plugin-opencode-assistant/shared";
|
|
3
|
+
import type { OpenCodeOptions, ServiceStartupTask } from "@vite-plugin-opencode-assistant/shared";
|
|
4
4
|
import type { OpenCodeAPI } from "./api.js";
|
|
5
5
|
export declare class OpenCodeService {
|
|
6
6
|
private config;
|
|
@@ -15,7 +15,14 @@ export declare class OpenCodeService {
|
|
|
15
15
|
private startPromise;
|
|
16
16
|
sessionUrl: string | null;
|
|
17
17
|
private proxyServer;
|
|
18
|
+
chromeMcpWarmupFailed: boolean;
|
|
19
|
+
currentTask: {
|
|
20
|
+
task: ServiceStartupTask;
|
|
21
|
+
data?: Record<string, unknown>;
|
|
22
|
+
} | null;
|
|
18
23
|
constructor(config: Required<OpenCodeOptions>, api: OpenCodeAPI, sseClients: Set<http.ServerResponse>, onPortAllocated: (port: number) => void, onProxyPortAllocated: (port: number) => void);
|
|
24
|
+
private sendTaskUpdate;
|
|
19
25
|
start(corsOrigins?: string[], contextApiUrl?: string, viteOrigin?: string): Promise<void>;
|
|
26
|
+
retryWarmupChromeMcp(viteOrigin?: string): Promise<boolean>;
|
|
20
27
|
stop(): Promise<void>;
|
|
21
28
|
}
|
package/lib/core/service.js
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
4
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
5
7
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __spreadValues = (a, b) => {
|
|
9
|
+
for (var prop in b || (b = {}))
|
|
10
|
+
if (__hasOwnProp.call(b, prop))
|
|
11
|
+
__defNormalProp(a, prop, b[prop]);
|
|
12
|
+
if (__getOwnPropSymbols)
|
|
13
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
+
if (__propIsEnum.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
}
|
|
17
|
+
return a;
|
|
18
|
+
};
|
|
6
19
|
var __export = (target, all) => {
|
|
7
20
|
for (var name in all)
|
|
8
21
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -61,10 +74,24 @@ class OpenCodeService {
|
|
|
61
74
|
__publicField(this, "startPromise", null);
|
|
62
75
|
__publicField(this, "sessionUrl", null);
|
|
63
76
|
__publicField(this, "proxyServer", null);
|
|
77
|
+
__publicField(this, "chromeMcpWarmupFailed", false);
|
|
78
|
+
__publicField(this, "currentTask", null);
|
|
64
79
|
var _a;
|
|
65
80
|
this.actualWebPort = config.webPort;
|
|
66
81
|
this.actualProxyPort = (_a = config.proxyPort) != null ? _a : import_shared.DEFAULT_PROXY_PORT;
|
|
67
82
|
}
|
|
83
|
+
sendTaskUpdate(task, data) {
|
|
84
|
+
this.currentTask = __spreadValues({ task }, data);
|
|
85
|
+
this.sseClients.forEach((client) => {
|
|
86
|
+
try {
|
|
87
|
+
client.write(`data: ${JSON.stringify(__spreadValues({ type: "TASK_UPDATE", task }, data))}
|
|
88
|
+
|
|
89
|
+
`);
|
|
90
|
+
} catch (e) {
|
|
91
|
+
log.debug("Failed to send TASK_UPDATE event", { error: e });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
68
95
|
start(corsOrigins, contextApiUrl, viteOrigin) {
|
|
69
96
|
return __async(this, null, function* () {
|
|
70
97
|
if (this.isStarted && this.webProcess) {
|
|
@@ -87,6 +114,7 @@ class OpenCodeService {
|
|
|
87
114
|
if (orphanCount > 0) {
|
|
88
115
|
log.debug(`Killed ${orphanCount} orphan OpenCode process(es)`);
|
|
89
116
|
}
|
|
117
|
+
this.sendTaskUpdate("checking_opencode");
|
|
90
118
|
if (!(yield (0, import_system.checkOpenCodeInstalled)())) {
|
|
91
119
|
log.error(`OpenCode is not installed!
|
|
92
120
|
|
|
@@ -106,10 +134,13 @@ Please install OpenCode first:
|
|
|
106
134
|
mise use -g opencode # Any OS
|
|
107
135
|
nix run nixpkgs#opencode # or github:anomalyco/opencode for latest dev branch
|
|
108
136
|
`);
|
|
137
|
+
this.sendTaskUpdate("opencode_not_installed");
|
|
138
|
+
this.startPromise = null;
|
|
109
139
|
timer.end("\u274C OpenCode not installed");
|
|
110
140
|
return;
|
|
111
141
|
}
|
|
112
142
|
timer.checkpoint("OpenCode installation verified");
|
|
143
|
+
this.sendTaskUpdate("allocating_port");
|
|
113
144
|
this.actualWebPort = yield (0, import_system.findAvailablePort)(this.config.webPort, this.config.hostname);
|
|
114
145
|
this.onPortAllocated(this.actualWebPort);
|
|
115
146
|
if (this.actualWebPort !== this.config.webPort) {
|
|
@@ -118,8 +149,10 @@ Please install OpenCode first:
|
|
|
118
149
|
log.debug(`Using port ${this.actualWebPort}`);
|
|
119
150
|
}
|
|
120
151
|
timer.checkpoint("Port allocated");
|
|
152
|
+
this.sendTaskUpdate("preparing_runtime");
|
|
121
153
|
const configDir = (0, import_opencode.prepareOpenCodeRuntime)(process.cwd());
|
|
122
154
|
timer.checkpoint("Plugin setup complete");
|
|
155
|
+
this.sendTaskUpdate("starting_web");
|
|
123
156
|
log.debug("Starting OpenCode Web process...", {
|
|
124
157
|
port: this.actualWebPort,
|
|
125
158
|
hostname: this.config.hostname,
|
|
@@ -137,8 +170,18 @@ Please install OpenCode first:
|
|
|
137
170
|
timer.checkpoint("Web process started");
|
|
138
171
|
const webUrl = `http://${this.config.hostname}:${this.actualWebPort}`;
|
|
139
172
|
log.info(`Waiting for OpenCode Web to become ready at ${webUrl}...`);
|
|
140
|
-
|
|
141
|
-
|
|
173
|
+
this.sendTaskUpdate("waiting_web_ready");
|
|
174
|
+
try {
|
|
175
|
+
yield (0, import_system.waitForServer)(webUrl, import_shared.SERVER_START_TIMEOUT);
|
|
176
|
+
log.info(`OpenCode Web started at ${webUrl}`);
|
|
177
|
+
} catch (e) {
|
|
178
|
+
log.error("OpenCode Web failed to start", { error: e });
|
|
179
|
+
this.sendTaskUpdate("web_start_timeout");
|
|
180
|
+
this.startPromise = null;
|
|
181
|
+
timer.end("\u274C Web start timeout");
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
this.sendTaskUpdate("starting_proxy");
|
|
142
185
|
this.actualProxyPort = yield (0, import_system.findAvailablePort)(
|
|
143
186
|
(_a = this.config.proxyPort) != null ? _a : import_shared.DEFAULT_PROXY_PORT,
|
|
144
187
|
this.config.hostname
|
|
@@ -157,33 +200,57 @@ Please install OpenCode first:
|
|
|
157
200
|
settings: this.config.settings
|
|
158
201
|
});
|
|
159
202
|
timer.checkpoint("Proxy server started");
|
|
160
|
-
|
|
161
|
-
|
|
203
|
+
this.sendTaskUpdate("warming_up_chrome");
|
|
204
|
+
let warmupFailed = false;
|
|
205
|
+
try {
|
|
206
|
+
yield this.api.warmupChromeMcp(viteOrigin);
|
|
207
|
+
timer.checkpoint("Chrome MCP warmup complete");
|
|
208
|
+
} catch (e) {
|
|
209
|
+
log.warn("Chrome MCP warmup failed", { error: e });
|
|
210
|
+
this.chromeMcpWarmupFailed = true;
|
|
211
|
+
warmupFailed = true;
|
|
212
|
+
}
|
|
213
|
+
this.sendTaskUpdate("creating_session");
|
|
214
|
+
let sessionFailed = false;
|
|
162
215
|
try {
|
|
163
216
|
this.sessionUrl = yield this.api.getOrCreateSession();
|
|
164
217
|
timer.checkpoint("Session created");
|
|
165
218
|
log.debug(`Session URL: ${this.sessionUrl}`);
|
|
166
|
-
this.sseClients.forEach((client) => {
|
|
167
|
-
try {
|
|
168
|
-
client.write(
|
|
169
|
-
`data: ${JSON.stringify({ type: "SESSION_READY", sessionUrl: this.sessionUrl })}
|
|
170
|
-
|
|
171
|
-
`
|
|
172
|
-
);
|
|
173
|
-
} catch (e) {
|
|
174
|
-
log.debug("Failed to send SESSION_READY event", { error: e });
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
219
|
} catch (e) {
|
|
178
220
|
log.warn("Failed to get/create session", { error: e });
|
|
221
|
+
sessionFailed = true;
|
|
222
|
+
}
|
|
223
|
+
if (sessionFailed) {
|
|
224
|
+
this.sendTaskUpdate("session_creation_failed");
|
|
225
|
+
this.isStarted = false;
|
|
226
|
+
this.startPromise = null;
|
|
227
|
+
} else if (warmupFailed) {
|
|
228
|
+
this.sendTaskUpdate("chrome_mcp_failed", { sessionUrl: this.sessionUrl });
|
|
229
|
+
this.isStarted = true;
|
|
230
|
+
} else {
|
|
231
|
+
this.sendTaskUpdate("ready", { sessionUrl: this.sessionUrl });
|
|
232
|
+
}
|
|
233
|
+
if (!sessionFailed) {
|
|
234
|
+
this.isStarted = true;
|
|
235
|
+
} else {
|
|
236
|
+
this.sessionUrl = null;
|
|
179
237
|
}
|
|
180
|
-
this.isStarted = true;
|
|
181
238
|
log.debug(`OpenCode services started successfully: ${this.sessionUrl || webUrl}`);
|
|
182
239
|
timer.end("\u2713 Services started successfully");
|
|
183
240
|
}))();
|
|
184
241
|
return this.startPromise;
|
|
185
242
|
});
|
|
186
243
|
}
|
|
244
|
+
retryWarmupChromeMcp(viteOrigin) {
|
|
245
|
+
return __async(this, null, function* () {
|
|
246
|
+
const success = yield this.api.retryWarmupChromeMcp(viteOrigin);
|
|
247
|
+
if (success) {
|
|
248
|
+
this.chromeMcpWarmupFailed = false;
|
|
249
|
+
this.sendTaskUpdate("ready", { sessionUrl: this.sessionUrl });
|
|
250
|
+
}
|
|
251
|
+
return success;
|
|
252
|
+
});
|
|
253
|
+
}
|
|
187
254
|
stop() {
|
|
188
255
|
return __async(this, null, function* () {
|
|
189
256
|
const timer = log.timer("stopServices");
|
package/lib/endpoints/index.js
CHANGED
|
@@ -26,6 +26,7 @@ var import_context = require("./context.js");
|
|
|
26
26
|
var import_start = require("./start.js");
|
|
27
27
|
var import_sse = require("./sse.js");
|
|
28
28
|
var import_sessions = require("./sessions.js");
|
|
29
|
+
var import_warmup = require("./warmup.js");
|
|
29
30
|
__reExport(endpoints_exports, require("./types.js"), module.exports);
|
|
30
31
|
function setupMiddlewares(server, ctx) {
|
|
31
32
|
(0, import_widget.setupWidgetEndpoints)(server, ctx);
|
|
@@ -33,6 +34,7 @@ function setupMiddlewares(server, ctx) {
|
|
|
33
34
|
(0, import_start.setupStartEndpoint)(server, ctx);
|
|
34
35
|
(0, import_sse.setupSseEndpoint)(server, ctx);
|
|
35
36
|
(0, import_sessions.setupSessionsEndpoint)(server, ctx);
|
|
37
|
+
(0, import_warmup.setupWarmupEndpoint)(server, ctx);
|
|
36
38
|
}
|
|
37
39
|
// Annotate the CommonJS export names for ESM import in node:
|
|
38
40
|
0 && (module.exports = {
|
package/lib/endpoints/sse.js
CHANGED
|
@@ -57,13 +57,22 @@ function setupSseEndpoint(server, ctx) {
|
|
|
57
57
|
res.write(`data: ${JSON.stringify({ type: "CONNECTED" })}
|
|
58
58
|
|
|
59
59
|
`);
|
|
60
|
+
const statusPayload = { type: "STATUS_SYNC" };
|
|
61
|
+
if (ctx.isServiceStarted !== void 0) {
|
|
62
|
+
statusPayload.isStarted = ctx.isServiceStarted;
|
|
63
|
+
}
|
|
64
|
+
if (ctx.currentTask) {
|
|
65
|
+
statusPayload.task = ctx.currentTask.task;
|
|
66
|
+
if (ctx.currentTask.data) {
|
|
67
|
+
Object.assign(statusPayload, ctx.currentTask.data);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
60
70
|
if (ctx.sessionUrl) {
|
|
61
|
-
|
|
62
|
-
`data: ${JSON.stringify({ type: "SESSION_READY", sessionUrl: ctx.sessionUrl })}
|
|
63
|
-
|
|
64
|
-
`
|
|
65
|
-
);
|
|
71
|
+
statusPayload.sessionUrl = ctx.sessionUrl;
|
|
66
72
|
}
|
|
73
|
+
res.write(`data: ${JSON.stringify(statusPayload)}
|
|
74
|
+
|
|
75
|
+
`);
|
|
67
76
|
req.on("close", () => {
|
|
68
77
|
ctx.sseClients.delete(res);
|
|
69
78
|
log.debug("SSE client disconnected", {
|
package/lib/endpoints/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PageContext, SessionInfo } from "@vite-plugin-opencode-assistant/shared";
|
|
1
|
+
import type { PageContext, SessionInfo, ServiceStartupTask } from "@vite-plugin-opencode-assistant/shared";
|
|
2
2
|
import type http from "http";
|
|
3
3
|
export interface EndpointContext {
|
|
4
4
|
get sessionUrl(): string | null;
|
|
@@ -6,9 +6,15 @@ export interface EndpointContext {
|
|
|
6
6
|
get sseClients(): Set<http.ServerResponse>;
|
|
7
7
|
get pageContext(): PageContext;
|
|
8
8
|
set pageContext(ctx: PageContext);
|
|
9
|
+
get isServiceStarted(): boolean;
|
|
10
|
+
get currentTask(): {
|
|
11
|
+
task: ServiceStartupTask;
|
|
12
|
+
data?: Record<string, unknown>;
|
|
13
|
+
} | null;
|
|
9
14
|
getSessions: () => Promise<SessionInfo[]>;
|
|
10
15
|
createSession: () => Promise<SessionInfo>;
|
|
11
16
|
deleteSession: (id: string) => Promise<void>;
|
|
12
17
|
resolveWidgetPath: () => string;
|
|
13
18
|
resolveWidgetStylePath: () => string;
|
|
19
|
+
retryWarmupChromeMcp: () => Promise<boolean>;
|
|
14
20
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
var __async = (__this, __arguments, generator) => {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
var fulfilled = (value) => {
|
|
21
|
+
try {
|
|
22
|
+
step(generator.next(value));
|
|
23
|
+
} catch (e) {
|
|
24
|
+
reject(e);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var rejected = (value) => {
|
|
28
|
+
try {
|
|
29
|
+
step(generator.throw(value));
|
|
30
|
+
} catch (e) {
|
|
31
|
+
reject(e);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
35
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
var warmup_exports = {};
|
|
39
|
+
__export(warmup_exports, {
|
|
40
|
+
setupWarmupEndpoint: () => setupWarmupEndpoint
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(warmup_exports);
|
|
43
|
+
var import_shared = require("@vite-plugin-opencode-assistant/shared");
|
|
44
|
+
const log = (0, import_shared.createLogger)("Endpoints:Warmup");
|
|
45
|
+
function setupWarmupEndpoint(server, ctx) {
|
|
46
|
+
server.middlewares.use("/__opencode_warmup__", (req, res) => __async(null, null, function* () {
|
|
47
|
+
if (req.method !== "POST") {
|
|
48
|
+
res.writeHead(405);
|
|
49
|
+
res.end("Method not allowed");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const success = yield ctx.retryWarmupChromeMcp();
|
|
54
|
+
res.setHeader("Content-Type", "application/json");
|
|
55
|
+
res.writeHead(200);
|
|
56
|
+
res.end(JSON.stringify({ success }));
|
|
57
|
+
} catch (e) {
|
|
58
|
+
log.error("Failed to retry warmup", { error: e });
|
|
59
|
+
res.setHeader("Content-Type", "application/json");
|
|
60
|
+
res.writeHead(500);
|
|
61
|
+
res.end(JSON.stringify({ success: false, error: String(e) }));
|
|
62
|
+
}
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
66
|
+
0 && (module.exports = {
|
|
67
|
+
setupWarmupEndpoint
|
|
68
|
+
});
|
package/lib/index.js
CHANGED
|
@@ -114,6 +114,8 @@ function createOpenCodePlugin(options = {}) {
|
|
|
114
114
|
return __async(this, null, function* () {
|
|
115
115
|
var _a2, _b2;
|
|
116
116
|
const timer = log.timer("configureServer");
|
|
117
|
+
let viteOrigin = "";
|
|
118
|
+
const getViteOrigin = () => viteOrigin;
|
|
117
119
|
(0, import_endpoints.setupMiddlewares)(server, {
|
|
118
120
|
get sessionUrl() {
|
|
119
121
|
return service.sessionUrl;
|
|
@@ -130,11 +132,18 @@ function createOpenCodePlugin(options = {}) {
|
|
|
130
132
|
set pageContext(ctx) {
|
|
131
133
|
pageContext = ctx;
|
|
132
134
|
},
|
|
135
|
+
get isServiceStarted() {
|
|
136
|
+
return service.isStarted;
|
|
137
|
+
},
|
|
138
|
+
get currentTask() {
|
|
139
|
+
return service.currentTask;
|
|
140
|
+
},
|
|
133
141
|
getSessions: () => api.getSessions(),
|
|
134
142
|
createSession: () => api.createSession(),
|
|
135
143
|
deleteSession: (id) => api.deleteSession(id),
|
|
136
144
|
resolveWidgetPath: import_paths.resolveWidgetPath,
|
|
137
|
-
resolveWidgetStylePath: import_paths.resolveWidgetStylePath
|
|
145
|
+
resolveWidgetStylePath: import_paths.resolveWidgetStylePath,
|
|
146
|
+
retryWarmupChromeMcp: () => service.retryWarmupChromeMcp(getViteOrigin())
|
|
138
147
|
});
|
|
139
148
|
(_a2 = server.httpServer) == null ? void 0 : _a2.on("listening", () => __async(null, null, function* () {
|
|
140
149
|
var _a3;
|
|
@@ -155,7 +164,7 @@ function createOpenCodePlugin(options = {}) {
|
|
|
155
164
|
vitePort = server.config.server.port || 5173;
|
|
156
165
|
viteHost = typeof host === "string" && host !== "0.0.0.0" && host !== "::" && host !== "::1" ? host : "localhost";
|
|
157
166
|
}
|
|
158
|
-
|
|
167
|
+
viteOrigin = `http://${viteHost}:${vitePort}`;
|
|
159
168
|
const contextApiUrl = `http://${viteHost}:${vitePort}${import_shared.CONTEXT_API_PATH}`;
|
|
160
169
|
log.debug("Vite server ready", {
|
|
161
170
|
vitePort,
|
|
@@ -194,7 +203,7 @@ function createOpenCodePlugin(options = {}) {
|
|
|
194
203
|
open: config.open,
|
|
195
204
|
autoReload: config.autoReload,
|
|
196
205
|
cwd: process.cwd(),
|
|
197
|
-
|
|
206
|
+
// 不再注入 sessionUrl,客户端完全依赖 SSE 状态同步
|
|
198
207
|
hotkey: config.hotkey
|
|
199
208
|
});
|
|
200
209
|
timer.end();
|
package/lib/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.opencode-widget{--oc-bg-main: #ffffff;--oc-bg-secondary: #f8f9fa;--oc-bg-tertiary: #f3f4f6;--oc-overlay-bg: rgba(255, 255, 255, .9);--oc-bg-inverse: #1e1e1e;--oc-text-primary: #282828;--oc-text-secondary: #4b5563;--oc-text-tertiary: #6b7280;--oc-text-placeholder: #9ca3af;--oc-text-inverse: #ffffff;--oc-border-primary: #e5e7eb;--oc-border-secondary: #d1d5db;--oc-primary: #3b82f6;--oc-primary-hover: #2563eb;--oc-primary-bg: rgba(59, 130, 246, .1);--oc-danger: #ef4444;--oc-danger-hover: #dc2626;--oc-danger-active: #b91c1c;--oc-success: #10b981;--oc-overlay: rgba(0, 0, 0, .5);--oc-tooltip-bg: #1e1e1e;--oc-dialog-overlay: rgba(0, 0, 0, .5);--oc-skeleton-bg: #e5e7eb;--oc-skeleton-gradient: linear-gradient(90deg, #e5e7eb 25%, #f3f4f6 50%, #e5e7eb 75%);--oc-shadow-sm: 0 2px 4px rgba(0, 0, 0, .1);--oc-shadow-md: 0 4px 12px rgba(0, 0, 0, .15);--oc-shadow-lg: 0 8px 32px rgba(0, 0, 0, .12);--oc-shadow-xl: 0 20px 60px rgba(0, 0, 0, .3);--oc-shadow-primary: 0 2px 4px rgba(59, 130, 246, .2);--oc-shadow-primary-hover: 0 4px 6px rgba(59, 130, 246, .3);--oc-shadow-danger: 0 4px 12px rgba(239, 68, 68, .3);--oc-trigger-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);--oc-trigger-bg-hover: linear-gradient(135deg, #764ba2 0%, #667eea 100%);--oc-trigger-bg-active: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);--oc-trigger-shadow: 0 4px 15px rgba(102, 126, 234, .4);--oc-trigger-shadow-hover: 0 6px 20px rgba(102, 126, 234, .6);--oc-trigger-shadow-active: 0 6px 20px rgba(240, 147, 251, .4);position:fixed;z-index:999999;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.opencode-widget.opencode-theme-dark{--oc-bg-main: #1a1a1a;--oc-bg-secondary: #1e1e1e;--oc-bg-tertiary: #282828;--oc-overlay-bg: rgba(26, 26, 26, .9);--oc-bg-inverse: #ffffff;--oc-text-primary: #f3f4f6;--oc-text-secondary: #d1d5db;--oc-text-tertiary: #9ca3af;--oc-text-placeholder: #6b7280;--oc-text-inverse: #282828;--oc-border-primary: #282828;--oc-border-secondary: #4b5563;--oc-primary: #3b82f6;--oc-primary-hover: #2563eb;--oc-primary-bg: rgba(59, 130, 246, .15);--oc-danger: #ef4444;--oc-danger-hover: #dc2626;--oc-danger-active: #b91c1c;--oc-success: #10b981;--oc-overlay: rgba(26, 26, 26, .9);--oc-tooltip-bg: #282828;--oc-dialog-overlay: rgba(0, 0, 0, .7);--oc-skeleton-bg: #151515;--oc-skeleton-gradient: linear-gradient(90deg, #282828 25%, #4b5563 50%, #282828 75%);--oc-shadow-sm: 0 2px 4px rgba(0, 0, 0, .3);--oc-shadow-md: 0 4px 12px rgba(0, 0, 0, .4);--oc-shadow-lg: 0 8px 32px rgba(0, 0, 0, .4);--oc-shadow-xl: 0 20px 60px rgba(0, 0, 0, .6);--oc-shadow-primary: 0 2px 4px rgba(59, 130, 246, .3);--oc-shadow-primary-hover: 0 4px 6px rgba(59, 130, 246, .4);--oc-shadow-danger: 0 4px 12px rgba(239, 68, 68, .4);--oc-trigger-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);--oc-trigger-bg-hover: linear-gradient(135deg, #764ba2 0%, #667eea 100%);--oc-trigger-bg-active: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);--oc-trigger-shadow: 0 4px 15px rgba(102, 126, 234, .5);--oc-trigger-shadow-hover: 0 6px 20px rgba(102, 126, 234, .7);--oc-trigger-shadow-active: 0 6px 20px rgba(240, 147, 251, .5)}.opencode-widget.bottom-right{bottom:20px;right:20px}.opencode-widget.bottom-left{bottom:20px;left:20px}.opencode-widget.top-right{top:20px;right:20px}.opencode-widget.top-left{top:20px;left:20px}.opencode-chat{position:absolute;width:700px;height:86vh;background:var(--oc-bg-main);border-radius:16px;box-shadow:var(--oc-shadow-lg);overflow:hidden;opacity:0;visibility:hidden;transform:translateY(20px) scale(.95);transition:all .3s ease;display:flex;flex-direction:column}.opencode-chat-content{display:flex;flex:1;overflow:hidden}.opencode-widget.bottom-right .opencode-chat{bottom:56px;right:0}.opencode-widget.bottom-left .opencode-chat{bottom:56px;left:0}.opencode-widget.top-right .opencode-chat{top:56px;right:0}.opencode-widget.top-left .opencode-chat{top:56px;left:0}.opencode-widget.bottom-right .opencode-selected-bubbles{bottom:56px;right:0}.opencode-widget.bottom-left .opencode-selected-bubbles{bottom:56px;left:0}.opencode-widget.top-right .opencode-selected-bubbles{top:56px;bottom:auto;right:0}.opencode-widget.top-left .opencode-selected-bubbles{top:56px;bottom:auto;left:0}.opencode-chat.open{opacity:1;visibility:visible;transform:translateY(0) scale(1)}.opencode-notification{position:absolute;top:20px;left:50%;transform:translate(-50%);padding:12px 24px;background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff;border-radius:10px;font-size:14px;font-weight:500;box-shadow:0 4px 16px #3b82f666,0 0 0 2px #3b82f633;animation:slideDown .3s ease;z-index:10000000;display:flex;align-items:center;gap:10px}.opencode-notification:before{content:"💡";font-size:16px}.opencode-dialog-overlay{position:fixed;inset:0;background:var(--oc-dialog-overlay);display:flex;align-items:center;justify-content:center;z-index:9999999;animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.opencode-dialog{background:var(--oc-bg-main);border-radius:12px;padding:24px;min-width:320px;max-width:400px;box-shadow:var(--oc-shadow-xl);animation:scaleIn .2s ease}@keyframes scaleIn{0%{transform:scale(.9);opacity:0}to{transform:scale(1);opacity:1}}.opencode-dialog-content{margin-bottom:20px}.opencode-dialog-message{font-size:15px;color:var(--oc-text-primary);line-height:1.5}.opencode-dialog-actions{display:flex;gap:12px;justify-content:flex-end}.opencode-dialog-btn{padding:10px 20px;border-radius:8px;border:none;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s}.opencode-dialog-btn.cancel{background:var(--oc-bg-tertiary);color:var(--oc-text-primary)}.opencode-dialog-btn.cancel:hover{background:var(--oc-text-primary);color:var(--oc-bg-main)}.opencode-dialog-btn.confirm{background:var(--oc-danger);color:#fff}.opencode-dialog-btn.confirm:hover{background:var(--oc-danger-hover)}@keyframes slideDown{0%{transform:translate(-50%) translateY(-100%);opacity:0}to{transform:translate(-50%) translateY(0);opacity:1}}.opencode-element-highlight{position:fixed;pointer-events:none;z-index:999998;display:none;transition:all .1s ease;border-radius:4px}#vue-inspector-container{display:none!important}.opencode-element-tooltip{position:fixed;background:var(--oc-tooltip-bg);color:#fff;padding:8px 12px;border-radius:6px;font-size:12px;z-index:9999998;display:none;box-shadow:var(--oc-shadow-md);max-width:300px;pointer-events:none}.opencode-tooltip-tag{font-weight:500;margin-bottom:4px;word-break:break-all}.opencode-tooltip-file{font-size:11px;color:var(--oc-text-placeholder);word-break:break-all}.opencode-element-highlight-temp{position:absolute;pointer-events:none;z-index:999998;border-radius:4px;animation:highlight-pulse 2s ease-out forwards}@keyframes highlight-pulse{0%{opacity:1;transform:scale(1)}50%{opacity:.8;transform:scale(1.02)}to{opacity:0;transform:scale(1)}}@media (max-width:768px){.opencode-chat{width:calc(100vw - 40px);height:calc(100vh - 100px)}}.opencode-iframe-container{flex:1;position:relative;overflow:hidden;display:flex;flex-direction:column;margin-top:-42px}.opencode-loading-overlay{position:absolute;inset:0;background:var(--oc-overlay-bg);display:none;flex-direction:column;align-items:center;justify-content:center;z-index:10;transition:opacity .3s ease}.opencode-loading-overlay.visible{display:flex}.opencode-loading-spinner{width:40px;height:40px;border:3px solid var(--oc-border-primary);border-top-color:var(--oc-primary);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.opencode-loading-text{margin-top:12px;font-size:14px;color:var(--oc-text-placeholder)}.opencode-empty-state-overlay{position:absolute;inset:0;background:var(--oc-bg-secondary);display:none;flex-direction:column;align-items:center;justify-content:center;z-index:5;transition:opacity .3s ease;margin-top:42px}.opencode-empty-state-overlay.visible{display:flex}.opencode-empty-state-icon{color:var(--oc-text-placeholder);margin-bottom:16px}.opencode-empty-state-text{color:var(--oc-text-primary);font-size:16px;font-weight:500;margin-bottom:24px}.opencode-empty-state-btn{padding:10px 24px;border-radius:8px;border:none;background:var(--oc-primary);color:#fff;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s;box-shadow:var(--oc-shadow-primary)}.opencode-empty-state-btn:hover{background:var(--oc-primary-hover);transform:translateY(-1px);box-shadow:var(--oc-shadow-primary-hover)}.opencode-empty-state-btn:active{transform:translateY(0)}.opencode-iframe{width:100%;height:100%;border:none}.opencode-chat-header{position:relative;flex-shrink:0;display:flex;align-items:center;justify-content:space-between;padding:0 12px;height:40px;background:var(--oc-bg-secondary);border-bottom:1px solid var(--oc-border-primary);z-index:5}.opencode-chat-header-left{display:flex;align-items:center;gap:4px}.opencode-chat-header-title{font-size:14px;font-weight:600;color:var(--oc-text-primary);position:absolute;left:50%;transform:translate(-50%)}.opencode-chat-header-actions{display:flex;gap:4px}.opencode-header-btn{width:28px;height:28px;border-radius:6px;border:none;background:transparent;color:var(--oc-text-placeholder);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s}.opencode-header-btn:hover{background:var(--oc-bg-tertiary);color:var(--oc-text-primary)}.opencode-header-btn.close:hover{background:var(--oc-danger);color:#fff}.opencode-header-btn.select-btn.active,.opencode-header-btn.session-toggle.active{background:var(--oc-primary);color:#fff}.opencode-select-mode-hint{position:fixed;top:20px;left:50%;transform:translate(-50%);padding:14px 20px;background:linear-gradient(135deg,#ef4444,#dc2626);color:#fff;border-radius:12px;font-size:15px;font-weight:500;box-shadow:0 6px 20px #ef444480,0 0 0 3px #ef44444d;z-index:9999999;display:none;align-items:center;gap:12px;border:2px solid rgba(255,255,255,.3)}.opencode-select-mode-hint.visible{display:flex;animation:slideDown .3s ease,pulseHint 2s ease-in-out infinite}.opencode-hint-shortcut{padding:4px 10px;background:#ffffff40;border-radius:6px;font-size:13px;font-weight:600;border:1px solid rgba(255,255,255,.4)}@keyframes pulseHint{0%,to{box-shadow:0 6px 20px #ef444480,0 0 0 3px #ef44444d}50%{box-shadow:0 6px 20px #ef444499,0 0 0 6px #ef444466}}.opencode-selected-bubbles{position:absolute;display:none;flex-direction:column;gap:6px;max-width:220px;max-height:300px;overflow-y:auto}.opencode-selected-bubbles.visible{display:flex}.opencode-selected-bubble{display:flex;flex-direction:column;gap:2px;padding:8px 24px 8px 10px;background:var(--oc-bg-main);border:1px solid var(--oc-border-primary);border-radius:8px;font-size:12px;box-shadow:var(--oc-shadow-sm);position:relative;cursor:pointer;transition:all .2s}.opencode-selected-bubble:hover{border-color:var(--oc-primary);box-shadow:var(--oc-shadow-primary)}.opencode-bubble-text{color:var(--oc-text-primary);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opencode-bubble-file{color:var(--oc-text-placeholder);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opencode-bubble-remove{position:absolute;top:8px;right:6px;width:16px;height:16px;border-radius:50%;border:none;background:transparent;color:var(--oc-text-placeholder);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px;transition:all .2s}.opencode-bubble-remove:hover{background:var(--oc-danger);color:#fff}.opencode-bubble-empty{padding:8px 12px;background:var(--oc-bg-main);border:1px dashed var(--oc-border-secondary);border-radius:8px;color:var(--oc-text-placeholder);font-size:12px;text-align:center}.opencode-right-toolbar{width:140px;background:var(--oc-bg-secondary);border-left:1px solid var(--oc-border-primary);display:flex;flex-direction:column;flex-shrink:0;transition:width .2s ease;overflow:hidden}.opencode-right-toolbar.collapsed{width:0;overflow:hidden}.opencode-right-toolbar.collapsed .opencode-selected-nodes-header,.opencode-right-toolbar.collapsed .opencode-selected-nodes,.opencode-right-toolbar.collapsed .opencode-clear-all-btn{display:none}.opencode-selected-nodes-header{padding:12px 8px 8px;border-bottom:1px solid var(--oc-border-primary)}.opencode-selected-nodes-title{font-size:14px;font-weight:600;color:var(--oc-text-primary);margin-bottom:4px}.opencode-selected-nodes-desc{font-size:11px;color:var(--oc-text-placeholder);line-height:1.4}.opencode-selected-nodes{flex:1;display:flex;flex-direction:column;padding:8px;gap:6px;overflow-y:auto;overflow-x:hidden}.opencode-selected-nodes:empty:before{content:"暂无选中元素";color:var(--oc-text-placeholder);font-size:12px;text-align:center;padding:20px 10px}.opencode-selected-node{display:flex;align-items:center;gap:8px;padding:8px 10px;background:var(--oc-bg-main);border:1px solid var(--oc-border-primary);border-radius:6px;font-size:12px;transition:all .2s}.opencode-selected-node:hover{border-color:var(--oc-primary);box-shadow:var(--oc-shadow-primary)}.opencode-node-content{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.opencode-node-text{color:var(--oc-text-primary);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opencode-node-file{color:var(--oc-text-placeholder);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opencode-node-remove{width:18px;height:18px;border-radius:4px;border:none;background:transparent;color:var(--oc-text-placeholder);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px;transition:all .2s;flex-shrink:0}.opencode-node-remove:hover{background:var(--oc-danger);color:#fff}.opencode-clear-all-btn{width:calc(100% - 16px);margin:8px;padding:8px 12px;border-radius:6px;border:none;background:var(--oc-danger);color:#fff;font-size:12px;font-weight:500;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:4px;transition:all .2s}.opencode-clear-all-btn:hover{background:var(--oc-danger-hover);transform:scale(1.02)}.opencode-session-list{width:240px;background:var(--oc-bg-secondary);border-right:1px solid var(--oc-border-primary);display:flex;flex-direction:column;flex-shrink:0;transition:width .2s ease}.opencode-session-list.collapsed{width:0;overflow:hidden}.opencode-session-list.collapsed .opencode-session-list-header,.opencode-session-list.collapsed .opencode-session-list-content{display:none}.opencode-session-list-header{padding:16px;border-bottom:1px solid var(--oc-border-primary);display:flex;justify-content:space-between;align-items:center;font-weight:600;font-size:14px;color:var(--oc-text-primary)}.opencode-new-session-btn{width:28px;height:28px;border-radius:6px;border:none;background:var(--oc-primary);color:#fff;font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s}.opencode-new-session-btn:hover{background:var(--oc-primary-hover);transform:scale(1.05)}.opencode-session-list-content{flex:1;overflow-y:auto;padding:8px;position:relative}.opencode-session-list-loading-overlay{position:absolute;inset:0;background:var(--oc-overlay-bg);display:flex;align-items:center;justify-content:center;z-index:10;border-radius:8px}.opencode-loading-spinner.small{width:24px;height:24px;border-width:2px}.opencode-session-item{padding:12px;border-radius:8px;cursor:pointer;transition:transform .2s;margin-bottom:4px;color:var(--oc-text-primary)}.opencode-session-item:hover{background:var(--oc-bg-tertiary)}.opencode-session-item.active{background:var(--oc-primary);color:#fff;transition:none}.opencode-session-title{font-size:14px;font-weight:500;margin-bottom:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opencode-session-meta{font-size:12px;opacity:.6}.opencode-session-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}.opencode-session-delete-btn{width:20px;height:20px;border-radius:4px;border:none;background:transparent;color:var(--oc-text-placeholder);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s;opacity:0;flex-shrink:0}.opencode-session-item:hover .opencode-session-delete-btn{opacity:1}.opencode-session-delete-btn:hover{background:var(--oc-danger);color:#fff}.opencode-session-item.active .opencode-session-delete-btn{color:#ffffffb3}.opencode-session-item.active .opencode-session-delete-btn:hover{background:#fff3;color:#fff}.opencode-session-header-skeleton{padding:16px;border-bottom:1px solid var(--oc-border-primary);display:none;justify-content:space-between;align-items:center}.opencode-session-header-skeleton.visible{display:flex}.opencode-skeleton-header-title{height:18px;width:80px;background:var(--oc-skeleton-gradient);background-size:200% 100%;animation:skeleton-loading 1.5s ease-in-out infinite;border-radius:4px}.opencode-skeleton-header-btn{width:28px;height:28px;background:var(--oc-skeleton-gradient);background-size:200% 100%;animation:skeleton-loading 1.5s ease-in-out infinite;border-radius:6px}.opencode-session-skeleton{flex:1;overflow-y:auto;padding:8px;display:none}.opencode-session-skeleton.visible{display:block}.opencode-skeleton-item{padding:12px;border-radius:8px;margin-bottom:4px;background:var(--oc-skeleton-bg)}.opencode-skeleton-title{height:16px;background:var(--oc-skeleton-gradient);background-size:200% 100%;animation:skeleton-loading 1.5s ease-in-out infinite;border-radius:4px;margin-bottom:8px;width:70%}.opencode-skeleton-meta{height:12px;background:var(--oc-skeleton-gradient);background-size:200% 100%;animation:skeleton-loading 1.5s ease-in-out infinite;border-radius:4px;width:50%}.opencode-session-empty{padding:32px 16px;text-align:center;color:var(--oc-text-placeholder);font-size:13px}@keyframes skeleton-loading{0%{background-position:200% 0}to{background-position:-200% 0}}.opencode-button{width:44px;height:44px;border-radius:50%;background:var(--oc-trigger-bg);border:none;cursor:pointer;box-shadow:var(--oc-trigger-shadow);transition:all .3s ease;display:flex;align-items:center;justify-content:center;color:#fff;padding:0;position:relative}.opencode-button:before{content:"";position:absolute;inset:-8px;border-radius:50%}.opencode-button:hover{transform:scale(1.1);box-shadow:var(--oc-trigger-shadow-hover);background:var(--oc-trigger-bg-hover)}.opencode-button.active{background:var(--oc-trigger-bg-active);box-shadow:var(--oc-trigger-shadow-active)}.opencode-button.active svg{transform:rotate(180deg)}.opencode-button svg{transition:transform .3s ease}.opencode-button.loading{animation:pulse 1s infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}
|
|
1
|
+
.opencode-widget{--oc-bg-main: #ffffff;--oc-bg-secondary: #f8f9fa;--oc-bg-tertiary: #f3f4f6;--oc-overlay-bg: rgba(255, 255, 255, .9);--oc-bg-inverse: #1e1e1e;--oc-text-primary: #282828;--oc-text-secondary: #4b5563;--oc-text-tertiary: #6b7280;--oc-text-placeholder: #9ca3af;--oc-text-inverse: #ffffff;--oc-border-primary: #e5e7eb;--oc-border-secondary: #d1d5db;--oc-primary: #3b82f6;--oc-primary-hover: #2563eb;--oc-primary-bg: rgba(59, 130, 246, .1);--oc-danger: #ef4444;--oc-danger-hover: #dc2626;--oc-danger-active: #b91c1c;--oc-success: #10b981;--oc-overlay: rgba(0, 0, 0, .5);--oc-tooltip-bg: #1e1e1e;--oc-dialog-overlay: rgba(0, 0, 0, .5);--oc-skeleton-bg: #e5e7eb;--oc-skeleton-gradient: linear-gradient(90deg, #e5e7eb 25%, #f3f4f6 50%, #e5e7eb 75%);--oc-shadow-sm: 0 2px 4px rgba(0, 0, 0, .1);--oc-shadow-md: 0 4px 12px rgba(0, 0, 0, .15);--oc-shadow-lg: 0 8px 32px rgba(0, 0, 0, .12);--oc-shadow-xl: 0 20px 60px rgba(0, 0, 0, .3);--oc-shadow-primary: 0 2px 4px rgba(59, 130, 246, .2);--oc-shadow-primary-hover: 0 4px 6px rgba(59, 130, 246, .3);--oc-shadow-danger: 0 4px 12px rgba(239, 68, 68, .3);--oc-trigger-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);--oc-trigger-bg-hover: linear-gradient(135deg, #764ba2 0%, #667eea 100%);--oc-trigger-bg-active: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);--oc-trigger-shadow: 0 4px 15px rgba(102, 126, 234, .4);--oc-trigger-shadow-hover: 0 6px 20px rgba(102, 126, 234, .6);--oc-trigger-shadow-active: 0 6px 20px rgba(240, 147, 251, .4);position:fixed;z-index:999999;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.opencode-widget.opencode-theme-dark{--oc-bg-main: #1a1a1a;--oc-bg-secondary: #1e1e1e;--oc-bg-tertiary: #282828;--oc-overlay-bg: rgba(26, 26, 26, .9);--oc-bg-inverse: #ffffff;--oc-text-primary: #f3f4f6;--oc-text-secondary: #d1d5db;--oc-text-tertiary: #9ca3af;--oc-text-placeholder: #6b7280;--oc-text-inverse: #282828;--oc-border-primary: #282828;--oc-border-secondary: #4b5563;--oc-primary: #3b82f6;--oc-primary-hover: #2563eb;--oc-primary-bg: rgba(59, 130, 246, .15);--oc-danger: #ef4444;--oc-danger-hover: #dc2626;--oc-danger-active: #b91c1c;--oc-success: #10b981;--oc-overlay: rgba(26, 26, 26, .9);--oc-tooltip-bg: #282828;--oc-dialog-overlay: rgba(0, 0, 0, .7);--oc-skeleton-bg: #151515;--oc-skeleton-gradient: linear-gradient(90deg, #282828 25%, #4b5563 50%, #282828 75%);--oc-shadow-sm: 0 2px 4px rgba(0, 0, 0, .3);--oc-shadow-md: 0 4px 12px rgba(0, 0, 0, .4);--oc-shadow-lg: 0 8px 32px rgba(0, 0, 0, .4);--oc-shadow-xl: 0 20px 60px rgba(0, 0, 0, .6);--oc-shadow-primary: 0 2px 4px rgba(59, 130, 246, .3);--oc-shadow-primary-hover: 0 4px 6px rgba(59, 130, 246, .4);--oc-shadow-danger: 0 4px 12px rgba(239, 68, 68, .4);--oc-trigger-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);--oc-trigger-bg-hover: linear-gradient(135deg, #764ba2 0%, #667eea 100%);--oc-trigger-bg-active: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);--oc-trigger-shadow: 0 4px 15px rgba(102, 126, 234, .5);--oc-trigger-shadow-hover: 0 6px 20px rgba(102, 126, 234, .7);--oc-trigger-shadow-active: 0 6px 20px rgba(240, 147, 251, .5)}.opencode-widget.bottom-right{bottom:20px;right:20px}.opencode-widget.bottom-left{bottom:20px;left:20px}.opencode-widget.top-right{top:20px;right:20px}.opencode-widget.top-left{top:20px;left:20px}.opencode-chat{position:absolute;width:700px;height:86vh;background:var(--oc-bg-main);border-radius:16px;box-shadow:var(--oc-shadow-lg);overflow:hidden;opacity:0;visibility:hidden;transform:translateY(20px) scale(.95);transition:all .3s ease;display:flex;flex-direction:column}.opencode-chat-content{display:flex;flex:1;overflow:hidden}.opencode-widget.bottom-right .opencode-chat{bottom:56px;right:0}.opencode-widget.bottom-left .opencode-chat{bottom:56px;left:0}.opencode-widget.top-right .opencode-chat{top:56px;right:0}.opencode-widget.top-left .opencode-chat{top:56px;left:0}.opencode-widget.bottom-right .opencode-selected-bubbles{bottom:56px;right:0}.opencode-widget.bottom-left .opencode-selected-bubbles{bottom:56px;left:0}.opencode-widget.top-right .opencode-selected-bubbles{top:56px;bottom:auto;right:0}.opencode-widget.top-left .opencode-selected-bubbles{top:56px;bottom:auto;left:0}.opencode-chat.open{opacity:1;visibility:visible;transform:translateY(0) scale(1)}.opencode-notification{position:absolute;top:20px;left:50%;transform:translate(-50%);padding:12px 24px;background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff;border-radius:10px;font-size:14px;font-weight:500;box-shadow:0 4px 16px #3b82f666,0 0 0 2px #3b82f633;animation:slideDown .3s ease;z-index:10000000;display:flex;align-items:center;gap:10px}.opencode-notification:before{content:"💡";font-size:16px}.opencode-dialog-overlay{position:fixed;inset:0;background:var(--oc-dialog-overlay);display:flex;align-items:center;justify-content:center;z-index:9999999;animation:fadeIn .2s ease}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.opencode-dialog{background:var(--oc-bg-main);border-radius:12px;padding:24px;min-width:320px;max-width:400px;box-shadow:var(--oc-shadow-xl);animation:scaleIn .2s ease}@keyframes scaleIn{0%{transform:scale(.9);opacity:0}to{transform:scale(1);opacity:1}}.opencode-dialog-content{margin-bottom:20px}.opencode-dialog-message{font-size:15px;color:var(--oc-text-primary);line-height:1.5}.opencode-dialog-actions{display:flex;gap:12px;justify-content:flex-end}.opencode-dialog-btn{padding:10px 20px;border-radius:8px;border:none;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s}.opencode-dialog-btn.cancel{background:var(--oc-bg-tertiary);color:var(--oc-text-primary)}.opencode-dialog-btn.cancel:hover{background:var(--oc-text-primary);color:var(--oc-bg-main)}.opencode-dialog-btn.confirm{background:var(--oc-danger);color:#fff}.opencode-dialog-btn.confirm:hover{background:var(--oc-danger-hover)}@keyframes slideDown{0%{transform:translate(-50%) translateY(-100%);opacity:0}to{transform:translate(-50%) translateY(0);opacity:1}}.opencode-element-highlight{position:fixed;pointer-events:none;z-index:999998;display:none;transition:all .1s ease;border-radius:4px}#vue-inspector-container{display:none!important}.opencode-element-tooltip{position:fixed;background:var(--oc-tooltip-bg);color:#fff;padding:8px 12px;border-radius:6px;font-size:12px;z-index:9999998;display:none;box-shadow:var(--oc-shadow-md);max-width:300px;pointer-events:none}.opencode-tooltip-tag{font-weight:500;margin-bottom:4px;word-break:break-all}.opencode-tooltip-file{font-size:11px;color:var(--oc-text-placeholder);word-break:break-all}.opencode-element-highlight-temp{position:absolute;pointer-events:none;z-index:999998;border-radius:4px;animation:highlight-pulse 2s ease-out forwards}@keyframes highlight-pulse{0%{opacity:1;transform:scale(1)}50%{opacity:.8;transform:scale(1.02)}to{opacity:0;transform:scale(1)}}@media (max-width:768px){.opencode-chat{width:calc(100vw - 40px);height:calc(100vh - 100px)}}.opencode-iframe-container{flex:1;position:relative;overflow:hidden;display:flex;flex-direction:column;margin-top:-42px}.opencode-loading-overlay{position:absolute;inset:0;background:var(--oc-overlay-bg);display:none;flex-direction:column;align-items:center;justify-content:center;z-index:10;transition:opacity .3s ease}.opencode-loading-overlay.visible{display:flex}.opencode-loading-spinner{width:40px;height:40px;border:3px solid var(--oc-border-primary);border-top-color:var(--oc-primary);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.opencode-loading-text{margin-top:12px;font-size:14px;color:var(--oc-text-placeholder)}.opencode-error-overlay{position:absolute;inset:0;z-index:15;margin-top:42px;display:none}.opencode-error-overlay.visible{display:flex}.opencode-empty-state-overlay{position:absolute;inset:0;background:var(--oc-bg-secondary);display:none;flex-direction:column;align-items:center;justify-content:center;z-index:5;transition:opacity .3s ease;margin-top:42px}.opencode-empty-state-overlay.visible{display:flex}.opencode-empty-state-icon{color:var(--oc-text-placeholder);margin-bottom:16px}.opencode-empty-state-text{color:var(--oc-text-primary);font-size:16px;font-weight:500;margin-bottom:24px}.opencode-empty-state-btn{padding:10px 24px;border-radius:8px;border:none;background:var(--oc-primary);color:#fff;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s;box-shadow:var(--oc-shadow-primary)}.opencode-empty-state-btn:hover{background:var(--oc-primary-hover);transform:translateY(-1px);box-shadow:var(--oc-shadow-primary-hover)}.opencode-empty-state-btn:active{transform:translateY(0)}.opencode-iframe{width:100%;height:100%;border:none}.opencode-chat-header{position:relative;flex-shrink:0;display:flex;align-items:center;justify-content:space-between;padding:0 12px;height:40px;background:var(--oc-bg-secondary);border-bottom:1px solid var(--oc-border-primary);z-index:5}.opencode-chat-header-left{display:flex;align-items:center;gap:4px}.opencode-chat-header-title{font-size:14px;font-weight:600;color:var(--oc-text-primary);position:absolute;left:50%;transform:translate(-50%)}.opencode-chat-header-actions{display:flex;gap:4px}.opencode-header-btn{width:28px;height:28px;border-radius:6px;border:none;background:transparent;color:var(--oc-text-placeholder);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s}.opencode-header-btn:hover{background:var(--oc-bg-tertiary);color:var(--oc-text-primary)}.opencode-header-btn.close:hover{background:var(--oc-danger);color:#fff}.opencode-header-btn.select-btn.active,.opencode-header-btn.session-toggle.active{background:var(--oc-primary);color:#fff}.opencode-select-mode-hint{position:fixed;top:20px;left:50%;transform:translate(-50%);padding:10px;background:linear-gradient(135deg,#ef4444,#dc2626);color:#fff;border-radius:12px;font-size:14px;font-weight:500;box-shadow:0 6px 20px #ef444480,0 0 0 3px #ef44444d;z-index:9999999;display:none;align-items:center;gap:12px;border:1px solid rgba(255,255,255,.3)}.opencode-select-mode-hint.visible{display:flex;animation:slideDown .3s ease,pulseHint 2s ease-in-out infinite}.opencode-hint-shortcut{padding:4px 10px;background:#ffffff40;border-radius:6px;font-size:13px;font-weight:600;border:1px solid rgba(255,255,255,.4)}@keyframes pulseHint{0%,to{box-shadow:0 6px 20px #ef444480,0 0 0 3px #ef44444d}50%{box-shadow:0 6px 20px #ef444499,0 0 0 6px #ef444466}}.opencode-selected-bubbles{position:absolute;display:none;flex-direction:column;gap:6px;max-width:220px;max-height:300px;overflow-y:auto}.opencode-selected-bubbles.visible{display:flex}.opencode-selected-bubble{display:flex;flex-direction:column;gap:2px;padding:8px 24px 8px 10px;background:var(--oc-bg-main);border:1px solid var(--oc-border-primary);border-radius:8px;font-size:12px;box-shadow:var(--oc-shadow-sm);position:relative;cursor:pointer;transition:all .2s}.opencode-selected-bubble:hover{border-color:var(--oc-primary);box-shadow:var(--oc-shadow-primary)}.opencode-bubble-text{color:var(--oc-text-primary);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opencode-bubble-file{color:var(--oc-text-placeholder);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opencode-bubble-remove{position:absolute;top:8px;right:6px;width:16px;height:16px;border-radius:50%;border:none;background:transparent;color:var(--oc-text-placeholder);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px;transition:all .2s}.opencode-bubble-remove:hover{background:var(--oc-danger);color:#fff}.opencode-bubble-empty{padding:8px 12px;background:var(--oc-bg-main);border:1px dashed var(--oc-border-secondary);border-radius:8px;color:var(--oc-text-placeholder);font-size:12px;text-align:center}.opencode-right-toolbar{width:140px;background:var(--oc-bg-secondary);border-left:1px solid var(--oc-border-primary);display:flex;flex-direction:column;flex-shrink:0;transition:width .2s ease;overflow:hidden}.opencode-right-toolbar.collapsed{width:0;overflow:hidden}.opencode-right-toolbar.collapsed .opencode-selected-nodes-header,.opencode-right-toolbar.collapsed .opencode-selected-nodes,.opencode-right-toolbar.collapsed .opencode-clear-all-btn{display:none}.opencode-selected-nodes-header{padding:12px 8px 8px;border-bottom:1px solid var(--oc-border-primary)}.opencode-selected-nodes-title{font-size:14px;font-weight:600;color:var(--oc-text-primary);margin-bottom:4px}.opencode-selected-nodes-desc{font-size:11px;color:var(--oc-text-placeholder);line-height:1.4}.opencode-selected-nodes{flex:1;display:flex;flex-direction:column;padding:8px;gap:6px;overflow-y:auto;overflow-x:hidden}.opencode-selected-nodes:empty:before{content:"暂无选中元素";color:var(--oc-text-placeholder);font-size:12px;text-align:center;padding:20px 10px}.opencode-selected-node{display:flex;align-items:center;gap:8px;padding:8px 10px;background:var(--oc-bg-main);border:1px solid var(--oc-border-primary);border-radius:6px;font-size:12px;transition:all .2s}.opencode-selected-node:hover{border-color:var(--oc-primary);box-shadow:var(--oc-shadow-primary)}.opencode-node-content{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.opencode-node-text{color:var(--oc-text-primary);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opencode-node-file{color:var(--oc-text-placeholder);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opencode-node-remove{width:18px;height:18px;border-radius:4px;border:none;background:transparent;color:var(--oc-text-placeholder);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px;transition:all .2s;flex-shrink:0}.opencode-node-remove:hover{background:var(--oc-danger);color:#fff}.opencode-clear-all-btn{width:calc(100% - 16px);margin:8px;padding:8px 12px;border-radius:6px;border:none;background:var(--oc-danger);color:#fff;font-size:12px;font-weight:500;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:4px;transition:all .2s}.opencode-clear-all-btn:hover{background:var(--oc-danger-hover);transform:scale(1.02)}.opencode-session-list{width:240px;background:var(--oc-bg-secondary);border-right:1px solid var(--oc-border-primary);display:flex;flex-direction:column;flex-shrink:0;transition:width .2s ease}.opencode-session-list.collapsed{width:0;overflow:hidden}.opencode-session-list.collapsed .opencode-session-list-header,.opencode-session-list.collapsed .opencode-session-list-content{display:none}.opencode-session-list-header{padding:16px;border-bottom:1px solid var(--oc-border-primary);display:flex;justify-content:space-between;align-items:center;font-weight:600;font-size:14px;color:var(--oc-text-primary)}.opencode-new-session-btn{width:28px;height:28px;border-radius:6px;border:none;background:var(--oc-primary);color:#fff;font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s}.opencode-new-session-btn:hover{background:var(--oc-primary-hover);transform:scale(1.05)}.opencode-session-list-content{flex:1;overflow-y:auto;padding:8px;position:relative}.opencode-session-list-loading-overlay{position:absolute;inset:0;background:var(--oc-overlay-bg);display:flex;align-items:center;justify-content:center;z-index:10;border-radius:8px}.opencode-loading-spinner.small{width:24px;height:24px;border-width:2px}.opencode-session-item{padding:12px;border-radius:8px;cursor:pointer;transition:transform .2s;margin-bottom:4px;color:var(--oc-text-primary)}.opencode-session-item:hover{background:var(--oc-bg-tertiary)}.opencode-session-item.active{background:var(--oc-primary);color:#fff;transition:none}.opencode-session-title{font-size:14px;font-weight:500;margin-bottom:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opencode-session-meta{font-size:12px;opacity:.6}.opencode-session-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}.opencode-session-delete-btn{width:20px;height:20px;border-radius:4px;border:none;background:transparent;color:var(--oc-text-placeholder);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s;opacity:0;flex-shrink:0}.opencode-session-item:hover .opencode-session-delete-btn{opacity:1}.opencode-session-delete-btn:hover{background:var(--oc-danger);color:#fff}.opencode-session-item.active .opencode-session-delete-btn{color:#ffffffb3}.opencode-session-item.active .opencode-session-delete-btn:hover{background:#fff3;color:#fff}.opencode-session-header-skeleton{padding:16px;border-bottom:1px solid var(--oc-border-primary);display:none;justify-content:space-between;align-items:center}.opencode-session-header-skeleton.visible{display:flex}.opencode-skeleton-header-title{height:18px;width:80px;background:var(--oc-skeleton-gradient);background-size:200% 100%;animation:skeleton-loading 1.5s ease-in-out infinite;border-radius:4px}.opencode-skeleton-header-btn{width:28px;height:28px;background:var(--oc-skeleton-gradient);background-size:200% 100%;animation:skeleton-loading 1.5s ease-in-out infinite;border-radius:6px}.opencode-session-skeleton{flex:1;overflow-y:auto;padding:8px;display:none}.opencode-session-skeleton.visible{display:block}.opencode-skeleton-item{padding:12px;border-radius:8px;margin-bottom:4px;background:var(--oc-skeleton-bg)}.opencode-skeleton-title{height:16px;background:var(--oc-skeleton-gradient);background-size:200% 100%;animation:skeleton-loading 1.5s ease-in-out infinite;border-radius:4px;margin-bottom:8px;width:70%}.opencode-skeleton-meta{height:12px;background:var(--oc-skeleton-gradient);background-size:200% 100%;animation:skeleton-loading 1.5s ease-in-out infinite;border-radius:4px;width:50%}.opencode-session-empty{padding:32px 16px;text-align:center;color:var(--oc-text-placeholder);font-size:13px}@keyframes skeleton-loading{0%{background-position:200% 0}to{background-position:-200% 0}}.opencode-button{width:44px;height:44px;border-radius:50%;background:var(--oc-trigger-bg);border:none;cursor:pointer;box-shadow:var(--oc-trigger-shadow);transition:all .3s ease;display:flex;align-items:center;justify-content:center;color:#fff;padding:0;position:relative}.opencode-button:before{content:"";position:absolute;inset:-8px;border-radius:50%}.opencode-button:hover{transform:scale(1.1);box-shadow:var(--oc-trigger-shadow-hover);background:var(--oc-trigger-bg-hover)}.opencode-button.active{background:var(--oc-trigger-bg-active);box-shadow:var(--oc-trigger-shadow-active)}.opencode-button.active svg{transform:rotate(180deg)}.opencode-button svg{transition:transform .3s ease}.opencode-button.loading{animation:pulse 1s infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}
|