@wu529778790/open-im 1.11.2-beta.2 → 1.11.2-beta.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/dist/index.js +3 -10
- package/dist/logger.d.ts +0 -3
- package/dist/logger.js +5 -36
- package/dist/shared/ai-task.js +3 -49
- package/dist/shared/task-cleanup.js +1 -11
- package/package.json +1 -1
- package/web/dist/assets/{index-B1c1xlbx.css → index-3rbPJRxR.css} +1 -1
- package/web/dist/assets/index-CrzDYo2W.js +57 -0
- package/web/dist/index.html +2 -2
- package/dist/telemetry/hash-user.d.ts +0 -2
- package/dist/telemetry/hash-user.js +0 -5
- package/dist/telemetry/telemetry-sanitize.d.ts +0 -2
- package/dist/telemetry/telemetry-sanitize.js +0 -30
- package/web/dist/assets/index-Dng7B2mE.js +0 -57
package/dist/index.js
CHANGED
|
@@ -32,7 +32,7 @@ import { initAdapters, cleanupAdapters } from "./adapters/registry.js";
|
|
|
32
32
|
import { SessionManager } from "./session/session-manager.js";
|
|
33
33
|
import { loadActiveChats, getActiveChatId, flushActiveChats, } from "./shared/active-chats.js";
|
|
34
34
|
import { destroyAllLiveChildren } from "./shared/process-kill.js";
|
|
35
|
-
import { initLogger, createLogger, closeLogger,
|
|
35
|
+
import { initLogger, createLogger, closeLogger, } from "./logger.js";
|
|
36
36
|
import { APP_HOME, SHUTDOWN_PORT } from "./constants.js";
|
|
37
37
|
import { createRequire } from "node:module";
|
|
38
38
|
import { escapePathForMarkdown, getAIToolDisplayName } from "./shared/utils.js";
|
|
@@ -267,10 +267,6 @@ export async function main() {
|
|
|
267
267
|
}
|
|
268
268
|
log.info("Service is running. Press Ctrl+C to stop.");
|
|
269
269
|
log.info(`Successfully initialized platforms: ${successfulPlatforms.join(", ")}`);
|
|
270
|
-
emitStructuredEvent("Main", "service.platform.init", {
|
|
271
|
-
platforms: successfulPlatforms,
|
|
272
|
-
version: APP_VERSION,
|
|
273
|
-
});
|
|
274
270
|
// Send notification only to successfully initialized platforms
|
|
275
271
|
for (const platform of successfulPlatforms) {
|
|
276
272
|
const startupMsg = buildStartupMessage(platform, APP_VERSION, resolvePlatformAiCommand(config, platform), startupCwd, sessionManager);
|
|
@@ -328,7 +324,6 @@ export async function main() {
|
|
|
328
324
|
sessionManager.destroy();
|
|
329
325
|
cleanupAdapters();
|
|
330
326
|
flushActiveChats();
|
|
331
|
-
await shutdownLoggerTelemetry();
|
|
332
327
|
await flushSentry();
|
|
333
328
|
await closeLogger();
|
|
334
329
|
process.exit(0);
|
|
@@ -381,8 +376,7 @@ export async function main() {
|
|
|
381
376
|
}
|
|
382
377
|
}
|
|
383
378
|
}
|
|
384
|
-
void
|
|
385
|
-
.then(() => closeLogger())
|
|
379
|
+
void closeLogger()
|
|
386
380
|
.finally(() => process.exit(1));
|
|
387
381
|
});
|
|
388
382
|
}
|
|
@@ -391,8 +385,7 @@ const isEntry = process.argv[1]?.replace(/\\/g, "/").endsWith("/index.js") ||
|
|
|
391
385
|
if (isEntry) {
|
|
392
386
|
main().catch((err) => {
|
|
393
387
|
log.error("Fatal error:", err);
|
|
394
|
-
void
|
|
395
|
-
.then(() => closeLogger())
|
|
388
|
+
void closeLogger()
|
|
396
389
|
.finally(() => process.exit(1));
|
|
397
390
|
});
|
|
398
391
|
}
|
package/dist/logger.d.ts
CHANGED
|
@@ -21,14 +21,11 @@ export declare function createLogger(tag: string): {
|
|
|
21
21
|
warn: (msg: string, ...args: unknown[]) => void;
|
|
22
22
|
error: (msg: string, ...args: unknown[]) => void;
|
|
23
23
|
debug: (msg: string, ...args: unknown[]) => void;
|
|
24
|
-
infoEvent: (event: string, data?: Record<string, unknown>, msg?: string) => void;
|
|
25
24
|
};
|
|
26
25
|
/**
|
|
27
26
|
* Audit log — records user interactions for debugging and compliance.
|
|
28
27
|
* Always enabled, writes to audit.log.
|
|
29
28
|
*/
|
|
30
29
|
export declare function auditLog(platform: string, userId: string, action: string, detail?: Record<string, unknown>): void;
|
|
31
|
-
export declare function emitStructuredEvent(tag: string, event: string, data?: Record<string, unknown>, level?: LogLevel, msg?: string): void;
|
|
32
|
-
export declare function shutdownLoggerTelemetry(): Promise<void>;
|
|
33
30
|
export declare function closeLogger(): Promise<void>;
|
|
34
31
|
export {};
|
package/dist/logger.js
CHANGED
|
@@ -3,16 +3,13 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { finished } from 'node:stream/promises';
|
|
4
4
|
import { sanitize } from './sanitize.js';
|
|
5
5
|
import { APP_HOME } from './constants.js';
|
|
6
|
-
import { sanitizeTelemetryData } from './telemetry/telemetry-sanitize.js';
|
|
7
6
|
const DEFAULT_LOG_DIR = join(APP_HOME, 'logs');
|
|
8
7
|
const MAX_LOG_FILES = 10;
|
|
9
8
|
const LOG_LEVELS = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 };
|
|
10
9
|
let logDir = DEFAULT_LOG_DIR;
|
|
11
10
|
let minLevel = LOG_LEVELS.DEBUG;
|
|
12
11
|
let logStream;
|
|
13
|
-
let eventsStream;
|
|
14
12
|
let auditStream;
|
|
15
|
-
let telemetryEnabled = false;
|
|
16
13
|
function pad(n) {
|
|
17
14
|
return String(n).padStart(2, '0');
|
|
18
15
|
}
|
|
@@ -80,15 +77,6 @@ export function initLogger(dirOrOpts, level, telemetry) {
|
|
|
80
77
|
auditStream = undefined;
|
|
81
78
|
}
|
|
82
79
|
auditStream = createWriteStream(join(logDir, 'audit.log'), { flags: 'a' });
|
|
83
|
-
telemetryEnabled = !!tel?.enabled;
|
|
84
|
-
if (eventsStream) {
|
|
85
|
-
eventsStream.end();
|
|
86
|
-
eventsStream = undefined;
|
|
87
|
-
}
|
|
88
|
-
if (telemetryEnabled) {
|
|
89
|
-
rotateOldJsonl();
|
|
90
|
-
eventsStream = createWriteStream(join(logDir, getEventsFileName()), { flags: 'a' });
|
|
91
|
-
}
|
|
92
80
|
}
|
|
93
81
|
function write(level, tag, msg, ...args) {
|
|
94
82
|
if (LOG_LEVELS[level] < minLevel)
|
|
@@ -109,7 +97,6 @@ export function createLogger(tag) {
|
|
|
109
97
|
warn: (msg, ...args) => write('WARN', tag, msg, ...args),
|
|
110
98
|
error: (msg, ...args) => write('ERROR', tag, msg, ...args),
|
|
111
99
|
debug: (msg, ...args) => write('DEBUG', tag, msg, ...args),
|
|
112
|
-
infoEvent: (event, data, msg) => emitStructuredEvent(tag, event, data, 'INFO', msg),
|
|
113
100
|
};
|
|
114
101
|
}
|
|
115
102
|
/**
|
|
@@ -126,31 +113,13 @@ export function auditLog(platform, userId, action, detail) {
|
|
|
126
113
|
};
|
|
127
114
|
auditStream?.write(JSON.stringify(entry) + '\n');
|
|
128
115
|
}
|
|
129
|
-
export function emitStructuredEvent(tag, event, data, level = 'INFO', msg = '') {
|
|
130
|
-
if (!telemetryEnabled)
|
|
131
|
-
return;
|
|
132
|
-
const payload = {
|
|
133
|
-
v: 1,
|
|
134
|
-
ts: new Date().toISOString(),
|
|
135
|
-
level,
|
|
136
|
-
tag,
|
|
137
|
-
event,
|
|
138
|
-
msg,
|
|
139
|
-
data: sanitizeTelemetryData(data),
|
|
140
|
-
};
|
|
141
|
-
const line = `${JSON.stringify(payload)}\n`;
|
|
142
|
-
eventsStream?.write(line);
|
|
143
|
-
}
|
|
144
|
-
export async function shutdownLoggerTelemetry() {
|
|
145
|
-
// Local event logging only — no upload to clean up
|
|
146
|
-
}
|
|
147
116
|
export async function closeLogger() {
|
|
148
|
-
if (
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
117
|
+
if (auditStream) {
|
|
118
|
+
const as = auditStream;
|
|
119
|
+
auditStream = undefined;
|
|
120
|
+
as.end();
|
|
152
121
|
try {
|
|
153
|
-
await finished(
|
|
122
|
+
await finished(as);
|
|
154
123
|
}
|
|
155
124
|
catch {
|
|
156
125
|
/* ignore */
|
package/dist/shared/ai-task.js
CHANGED
|
@@ -4,9 +4,7 @@
|
|
|
4
4
|
import { resolvePlatformAiCommand } from '../config.js';
|
|
5
5
|
import { captureError } from './sentry.js';
|
|
6
6
|
import { formatToolStats, formatToolCallNotification, getContextWarning, getAIToolDisplayName, toReplyPlainText, } from './utils.js';
|
|
7
|
-
import { createLogger
|
|
8
|
-
import { hashUserId } from '../telemetry/hash-user.js';
|
|
9
|
-
import { sanitize } from '../sanitize.js';
|
|
7
|
+
import { createLogger } from '../logger.js';
|
|
10
8
|
const log = createLogger('AITask');
|
|
11
9
|
function isUsageLimitError(error) {
|
|
12
10
|
return /usage limit/i.test(error) || /try again at\s+\d{1,2}:\d{2}\s*(AM|PM)/i.test(error);
|
|
@@ -154,12 +152,6 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
|
|
|
154
152
|
const aiCommand = resolvePlatformAiCommand(config, ctx.platform);
|
|
155
153
|
const startRun = () => {
|
|
156
154
|
log.info(`[AITask] Starting: userId=${ctx.userId}, initialSessionId=${currentSessionId ?? 'new'}, prompt="${prompt.slice(0, 50)}..."`);
|
|
157
|
-
emitStructuredEvent('AITask', 'ai.task.start', {
|
|
158
|
-
platform: ctx.platform,
|
|
159
|
-
taskKey: hashUserId(ctx.taskKey),
|
|
160
|
-
userKey: hashUserId(ctx.userId),
|
|
161
|
-
toolId: aiCommand,
|
|
162
|
-
});
|
|
163
155
|
activeHandle = toolAdapter.run(prompt, currentSessionId, ctx.workDir, {
|
|
164
156
|
onSessionId: (id) => {
|
|
165
157
|
log.info(`[AITask] SessionId callback: old=${currentSessionId ?? 'none'}, new=${id}, aiCommand=${aiCommand}, userId=${ctx.userId}`);
|
|
@@ -218,17 +210,6 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
|
|
|
218
210
|
log.info(`[AITask] onComplete fired: settled=${settled}, success=${result.success}, platform=${ctx.platform}, taskKey=${ctx.taskKey}`);
|
|
219
211
|
if (settled)
|
|
220
212
|
return;
|
|
221
|
-
emitStructuredEvent('AITask', 'ai.task.complete', {
|
|
222
|
-
platform: ctx.platform,
|
|
223
|
-
taskKey: hashUserId(ctx.taskKey),
|
|
224
|
-
userKey: hashUserId(ctx.userId),
|
|
225
|
-
toolId: aiCommand,
|
|
226
|
-
durationMs: result.durationMs,
|
|
227
|
-
success: result.success,
|
|
228
|
-
numTurns: result.numTurns,
|
|
229
|
-
model: result.model,
|
|
230
|
-
toolStats: result.toolStats,
|
|
231
|
-
});
|
|
232
213
|
settled = true;
|
|
233
214
|
if (pendingUpdate) {
|
|
234
215
|
clearTimeout(pendingUpdate);
|
|
@@ -282,15 +263,6 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
|
|
|
282
263
|
}
|
|
283
264
|
settled = true;
|
|
284
265
|
log.error(`Task error for user ${ctx.userId}: ${error}`);
|
|
285
|
-
emitStructuredEvent('AITask', 'ai.task.error', {
|
|
286
|
-
platform: ctx.platform,
|
|
287
|
-
taskKey: hashUserId(ctx.taskKey),
|
|
288
|
-
userKey: hashUserId(ctx.userId),
|
|
289
|
-
toolId: aiCommand,
|
|
290
|
-
durationMs: Date.now() - taskState.startedAt,
|
|
291
|
-
errorSnippet: sanitize(String(error).slice(0, 400)),
|
|
292
|
-
errorType: classifyErrorType(String(error)),
|
|
293
|
-
});
|
|
294
266
|
if (isUsageLimitError(error)) {
|
|
295
267
|
// Usage limit errors: keep session for all tools (user can retry later)
|
|
296
268
|
log.warn(`Keeping ${aiCommand} session for user ${ctx.userId} after usage limit error`);
|
|
@@ -330,15 +302,6 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
|
|
|
330
302
|
handle: {
|
|
331
303
|
abort: () => {
|
|
332
304
|
if (!settled) {
|
|
333
|
-
emitStructuredEvent('AITask', 'ai.task.error', {
|
|
334
|
-
platform: ctx.platform,
|
|
335
|
-
taskKey: hashUserId(ctx.taskKey),
|
|
336
|
-
userKey: hashUserId(ctx.userId),
|
|
337
|
-
toolId: aiCommand,
|
|
338
|
-
durationMs: Date.now() - taskState.startedAt,
|
|
339
|
-
errorSnippet: 'aborted',
|
|
340
|
-
errorType: 'aborted',
|
|
341
|
-
});
|
|
342
305
|
// 用户取消(/new、/resume、队列超时、stale 清理):把「思考中…」占位卡片编辑为终态,
|
|
343
306
|
// 避免卡片卡在转圈。停按钮路径会先 settle() 再 abort,settled=true 时此处跳过,不会双发。
|
|
344
307
|
void platformAdapter.sendError('⏹️ 已取消').catch(() => {
|
|
@@ -354,9 +317,9 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
|
|
|
354
317
|
settle,
|
|
355
318
|
startedAt: Date.now(),
|
|
356
319
|
toolId: aiCommand,
|
|
357
|
-
taskKey:
|
|
320
|
+
taskKey: ctx.taskKey,
|
|
358
321
|
platform: ctx.platform,
|
|
359
|
-
userKey:
|
|
322
|
+
userKey: ctx.userId,
|
|
360
323
|
};
|
|
361
324
|
try {
|
|
362
325
|
startRun();
|
|
@@ -371,15 +334,6 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
|
|
|
371
334
|
userId: ctx.userId,
|
|
372
335
|
aiCommand,
|
|
373
336
|
});
|
|
374
|
-
emitStructuredEvent('AITask', 'ai.task.error', {
|
|
375
|
-
platform: ctx.platform,
|
|
376
|
-
taskKey: hashUserId(ctx.taskKey),
|
|
377
|
-
userKey: hashUserId(ctx.userId),
|
|
378
|
-
toolId: aiCommand,
|
|
379
|
-
durationMs: 0,
|
|
380
|
-
errorSnippet: sanitize(String(err).slice(0, 400)),
|
|
381
|
-
errorType: classifyErrorType(String(err)),
|
|
382
|
-
});
|
|
383
337
|
platformAdapter
|
|
384
338
|
.sendError(`内部错误:${err instanceof Error ? err.message : String(err)}`)
|
|
385
339
|
.catch(() => {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Tasks older than 30 minutes are aborted and removed from the running-tasks
|
|
5
5
|
* map so they never accumulate indefinitely.
|
|
6
6
|
*/
|
|
7
|
-
import { createLogger
|
|
7
|
+
import { createLogger } from '../logger.js';
|
|
8
8
|
const log = createLogger('TaskCleanup');
|
|
9
9
|
const CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
|
10
10
|
const STALE_THRESHOLD_MS = 30 * 60 * 1000; // 30 minutes
|
|
@@ -46,17 +46,7 @@ export function startTaskCleanup(runningTasks) {
|
|
|
46
46
|
export function emitInterruptedTerminals(runningTasks) {
|
|
47
47
|
if (runningTasks.size === 0)
|
|
48
48
|
return;
|
|
49
|
-
const now = Date.now();
|
|
50
49
|
for (const state of runningTasks.values()) {
|
|
51
|
-
emitStructuredEvent('AITask', 'ai.task.error', {
|
|
52
|
-
platform: state.platform,
|
|
53
|
-
taskKey: state.taskKey,
|
|
54
|
-
userKey: state.userKey,
|
|
55
|
-
toolId: state.toolId,
|
|
56
|
-
durationMs: now - state.startedAt,
|
|
57
|
-
errorSnippet: 'interrupted',
|
|
58
|
-
errorType: 'interrupted',
|
|
59
|
-
});
|
|
60
50
|
// 标记已结算,使随后 shutdown 的 abort() 跳过重复的 aborted 事件
|
|
61
51
|
state.settle();
|
|
62
52
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wu529778790/open-im",
|
|
3
|
-
"version": "1.11.2-beta.
|
|
3
|
+
"version": "1.11.2-beta.4",
|
|
4
4
|
"description": "Your AI coding assistant, in every chat app. Multi-platform IM bridge for Claude Code, Codex, and CodeBuddy.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
:root{--c-bg: #f4f5f7;--c-surface: #ffffff;--c-surface-alt: #f9fafb;--c-border: #e4e7ec;--c-border-foc: #c0c7d1;--c-text: #101828;--c-text-2: #475467;--c-text-3: #98a2b3;--c-accent: #2970ff;--c-accent-h: #155eef;--c-accent-bg: #eff4ff;--c-accent-bdr: #b2ddff;--c-ok: #12b76a;--c-ok-bg: #ecfdf3;--c-ok-bdr: #a6f4c5;--c-warn: #f79009;--c-warn-bg: #fffaeb;--c-warn-bdr: #fedf89;--c-err: #f04438;--c-err-bg: #fef3f2;--c-err-bdr: #fecdca;--c-sb-bg: #101828;--c-sb-text: #98a2b3;--c-sb-text-a: #f4f5f7;--c-sb-hover: rgba(255,255,255,.06);--c-sb-active: rgba(255,255,255,.1);--shadow-xs: 0 1px 2px rgba(16,24,40,.05);--shadow-s: 0 1px 3px rgba(16,24,40,.1), 0 1px 2px rgba(16,24,40,.06);--shadow-m: 0 4px 8px -2px rgba(16,24,40,.1), 0 2px 4px -2px rgba(16,24,40,.06);--shadow-l: 0 12px 16px -4px rgba(16,24,40,.08), 0 4px 6px -2px rgba(16,24,40,.03);--r-s: 6px;--r-m: 8px;--r-l: 12px;--r-xl: 16px;--font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--mono: "SF Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;--fast: .12s ease;--norm: .2s ease;--sidebar-w: 240px;--header-h: 56px}:root.dark{--c-bg: #0c0d0f;--c-surface: #16171a;--c-surface-alt: #1c1d21;--c-border: #2a2b30;--c-border-foc: #3e4047;--c-text: #f0f1f4;--c-text-2: #98a2b3;--c-text-3: #5d6573;--c-accent: #528bff;--c-accent-h: #79a6ff;--c-accent-bg: rgba(42,114,255,.12);--c-accent-bdr: rgba(42,114,255,.24);--c-ok-bg: rgba(18,183,108,.1);--c-ok-bdr: rgba(18,183,108,.2);--c-warn-bg: rgba(247,144,9,.1);--c-warn-bdr: rgba(247,144,9,.2);--c-err-bg: rgba(240,68,56,.1);--c-err-bdr: rgba(240,68,56,.2);--c-sb-bg: #08090b;--c-sb-hover: rgba(255,255,255,.04);--c-sb-active: rgba(255,255,255,.08)}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}body{font-family:var(--font);font-size:14px;line-height:1.5;color:var(--c-text);background:var(--c-bg);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.app{display:flex;min-height:100vh}.main{flex:1;margin-left:var(--sidebar-w);display:flex;flex-direction:column;min-height:100vh;transition:margin-left var(--norm)}.content{flex:1;padding:32px 40px 64px;max-width:960px;width:100%;margin:0 auto}.sidebar{position:fixed;top:0;left:0;bottom:0;width:var(--sidebar-w);background:var(--c-sb-bg);color:var(--c-sb-text);display:flex;flex-direction:column;z-index:20;border-right:1px solid rgba(255,255,255,.06);overflow:hidden}.sidebar-header{padding:20px 16px 16px;display:flex;align-items:center;gap:10px}.sidebar-logo{width:32px;height:32px;border-radius:var(--r-m);background:var(--c-accent);display:flex;align-items:center;justify-content:center;flex-shrink:0}.sidebar-logo svg{width:18px;height:18px;color:#fff}.sidebar-brand{font-size:15px;font-weight:700;color:var(--c-sb-text-a);letter-spacing:-.01em;white-space:nowrap;overflow:hidden}.sidebar-nav{flex:1;padding:4px 8px;display:flex;flex-direction:column;gap:2px;overflow-y:auto}.sidebar-section-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--c-text-3);padding:16px 12px 6px}.nav-item{display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:var(--r-m);color:var(--c-sb-text);font-size:13px;font-weight:500;cursor:pointer;border:none;background:transparent;width:100%;text-align:left;transition:background var(--fast),color var(--fast);white-space:nowrap;overflow:hidden}.nav-item:hover{background:var(--c-sb-hover);color:var(--c-sb-text-a)}.nav-item.active{background:var(--c-sb-active);color:var(--c-sb-text-a)}.nav-item svg{width:18px;height:18px;flex-shrink:0;opacity:.7}.nav-item.active svg,.nav-item:hover svg{opacity:1}.header{position:sticky;top:0;z-index:15;background:var(--c-surface);border-bottom:1px solid var(--c-border);padding:0 40px;height:var(--header-h);display:flex;align-items:center;justify-content:space-between;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.header-title{font-size:15px;font-weight:600;color:var(--c-text)}.header-actions{display:flex;align-items:center;gap:8px}.header-divider{height:1px;background:var(--c-border);margin:0}.header-toolbar{background:var(--c-surface-alt);border-bottom:1px solid var(--c-border);padding:10px 40px;display:flex;gap:8px;flex-wrap:wrap;align-items:center}.header-toolbar-spacer{flex:1}.header-toolbar-status{font-size:12px;font-weight:500;display:inline-flex;align-items:center;gap:6px;color:var(--c-text-2)}.header-toolbar-status .dot{width:6px;height:6px;border-radius:50%;display:inline-block}.header-toolbar-status .dot.running{background:var(--c-ok)}.header-toolbar-status .dot.idle{background:var(--c-text-3)}.stats-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-bottom:32px}.stat-card{background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r-l);padding:20px}.stat-label{font-size:12px;font-weight:500;color:var(--c-text-3);text-transform:uppercase;letter-spacing:.04em;margin-bottom:8px}.stat-value{font-size:28px;font-weight:700;color:var(--c-text);line-height:1}.stat-meta{font-size:12px;color:var(--c-text-3);margin-top:4px}.section{margin-bottom:40px}.section-head{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:20px;flex-wrap:wrap;gap:8px}.section-title{font-size:16px;font-weight:600;color:var(--c-text)}.section-desc{font-size:13px;color:var(--c-text-3)}.card{background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r-l);overflow:hidden}.card-head{padding:16px 20px;border-bottom:1px solid var(--c-border);display:flex;justify-content:space-between;align-items:center;gap:12px}.card-title{font-size:14px;font-weight:600;color:var(--c-text)}.card-body{padding:20px}.card-foot{padding:12px 20px;border-top:1px solid var(--c-border);background:var(--c-surface-alt)}.platform-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px}.platform-card{background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r-l);overflow:hidden;transition:border-color var(--fast),box-shadow var(--fast)}.platform-card:hover{box-shadow:var(--shadow-s)}.platform-card.disabled{opacity:.55}.platform-card-head{padding:14px 20px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--c-border);gap:12px}.platform-card-name{font-size:14px;font-weight:600;color:var(--c-text);display:flex;align-items:center;gap:8px}.platform-card-chevron{font-size:12px;color:var(--c-text-3);margin-left:4px;transition:transform var(--fast)}.platform-card-name .dot{width:6px;height:6px;border-radius:50%;background:var(--c-text-3)}.platform-card.enabled .platform-card-name .dot{background:var(--c-ok)}.platform-card-body{padding:16px 20px 20px}.platform-card-hint{font-size:12px;color:var(--c-text-3);line-height:1.5;margin-bottom:16px}.platform-card-help{margin-top:16px;padding:10px 14px;background:var(--c-surface-alt);border-radius:var(--r-m);font-size:12px;color:var(--c-text-2);line-height:1.6}.platform-card-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:16px}.ai-grid{display:grid;grid-template-columns:280px 1fr;gap:16px}.config-stack{display:flex;flex-direction:column;gap:16px}.config-card .card-title{font-family:var(--mono);font-size:13px}.config-card textarea{width:100%}.config-card-toolbar{display:flex;justify-content:flex-end;gap:6px;margin-bottom:8px}.form-group{margin-bottom:14px}.form-group:last-child{margin-bottom:0}.form-label{display:block;font-size:12px;font-weight:500;color:var(--c-text-2);margin-bottom:5px}.form-input,.form-select,.form-textarea{width:100%;padding:8px 10px;font-family:inherit;font-size:13px;color:var(--c-text);background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r-s);transition:border-color var(--fast),box-shadow var(--fast);outline:none}.form-input:focus,.form-select:focus,.form-textarea:focus{border-color:var(--c-accent);box-shadow:0 0 0 3px var(--c-accent-bg)}.form-input.mono,.form-textarea.mono{font-family:var(--mono);font-size:12px}.form-textarea{min-height:80px;resize:vertical}.form-hint{font-size:11px;color:var(--c-text-3);margin-top:4px;line-height:1.4}.form-hint a{color:var(--c-accent)}.form-hint code{font-family:var(--mono);font-size:10px;padding:1px 4px;background:var(--c-surface-alt);border-radius:3px}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:7px 14px;font-family:inherit;font-size:13px;font-weight:500;border-radius:var(--r-s);border:1px solid transparent;cursor:pointer;text-decoration:none;transition:all var(--fast);white-space:nowrap}.btn:disabled{opacity:.5;cursor:not-allowed}.btn-p{background:var(--c-accent);color:#fff}.btn-p:hover:not(:disabled){background:var(--c-accent-h)}.btn-s{background:var(--c-surface);color:var(--c-text);border-color:var(--c-border)}.btn-s:hover:not(:disabled){background:var(--c-surface-alt);border-color:var(--c-border-foc)}.btn-g{background:transparent;color:var(--c-text-2)}.btn-g:hover:not(:disabled){background:var(--c-surface-alt);color:var(--c-text)}.btn-d{background:var(--c-err-bg);color:var(--c-err);border-color:var(--c-err-bdr)}.btn-d:hover:not(:disabled){background:var(--c-err);color:#fff}.btn-w{background:var(--c-warn-bg);color:var(--c-warn);border-color:var(--c-warn-bdr)}.btn-w:hover:not(:disabled){background:var(--c-warn);color:#fff}.btn-sm{padding:5px 10px;font-size:12px}.btn-lg{padding:10px 20px;font-size:14px}.toggle{display:inline-flex;align-items:center;gap:8px;cursor:pointer;flex-shrink:0}.toggle-input{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.toggle-track{position:relative;width:36px;height:20px;background:var(--c-border-foc);border-radius:999px;transition:background var(--fast);flex-shrink:0}.toggle-track:after{content:"";position:absolute;top:2px;left:2px;width:16px;height:16px;background:#fff;border-radius:50%;transition:transform var(--fast);box-shadow:var(--shadow-xs)}.toggle-input:checked+.toggle-track{background:var(--c-accent)}.toggle-input:checked+.toggle-track:after{transform:translate(16px)}.tabs{display:inline-flex;gap:2px;padding:3px;background:var(--c-surface-alt);border:1px solid var(--c-border);border-radius:var(--r-m)}.tab{padding:6px 12px;font-size:12px;font-weight:500;color:var(--c-text-2);background:transparent;border:none;border-radius:var(--r-s);cursor:pointer;transition:all var(--fast)}.tab:hover{color:var(--c-text)}.tab.active{background:var(--c-surface);color:var(--c-text);box-shadow:var(--shadow-xs)}.msg{padding:10px 14px;border-radius:var(--r-m);font-size:13px;line-height:1.5}.msg-ok{background:var(--c-ok-bg);color:var(--c-ok)}.msg-err{background:var(--c-err-bg);color:var(--c-err)}.msg-w{background:var(--c-warn-bg);color:var(--c-warn)}.badge{display:inline-flex;align-items:center;padding:2px 8px;font-size:11px;font-weight:500;border-radius:999px}.badge-ok{background:var(--c-ok-bg);color:var(--c-ok)}.badge-err{background:var(--c-err-bg);color:var(--c-err)}.badge-w{background:var(--c-warn-bg);color:var(--c-warn)}.badge-info{background:var(--c-accent-bg);color:var(--c-accent)}.field-tip{font-size:11px;color:var(--c-text-3);line-height:1.45;margin-top:5px}.field-tip a{color:var(--c-accent)}.field-tip code{font-family:var(--mono);font-size:10px;padding:1px 4px;background:var(--c-surface-alt);border-radius:3px}.flash{margin-bottom:20px}.mt-4{margin-top:16px}.mt-6{margin-top:24px}.gap-2{gap:8px}.gap-3{gap:12px}.text-center{text-align:center}.mono{font-family:var(--mono)}.hidden{display:none!important}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.wizard-wrap{display:flex;justify-content:center;padding-top:16px}.wizard{width:100%;max-width:580px;background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r-xl);box-shadow:var(--shadow-m);overflow:hidden}.wizard-bar{height:3px;background:var(--c-border)}.wizard-bar-fill{height:100%;background:var(--c-accent);transition:width .4s cubic-bezier(.4,0,.2,1)}.wizard-steps{display:flex;padding:20px 24px 0;gap:0}.wizard-step-ind{flex:1;display:flex;flex-direction:column;align-items:center;gap:6px;position:relative}.wizard-step-ind+.wizard-step-ind:before{content:"";position:absolute;top:12px;right:calc(50% + 16px);width:calc(100% - 32px);height:1px;background:var(--c-border)}.wizard-step-ind.done+.wizard-step-ind:before{background:var(--c-accent)}.wizard-step-dot{width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:600;background:var(--c-surface-alt);color:var(--c-text-3);border:2px solid var(--c-border);position:relative;z-index:1;transition:all var(--norm)}.wizard-step-ind.active .wizard-step-dot{background:var(--c-accent);color:#fff;border-color:var(--c-accent)}.wizard-step-ind.done .wizard-step-dot{background:var(--c-ok);color:#fff;border-color:var(--c-ok)}.wizard-step-label{font-size:11px;font-weight:500;color:var(--c-text-3);white-space:nowrap}.wizard-step-ind.active .wizard-step-label{color:var(--c-accent);font-weight:600}.wizard-step-ind.done .wizard-step-label{color:var(--c-ok)}.wizard-body{padding:28px 32px;min-height:260px}.wizard-icon{font-size:36px;margin-bottom:12px}.wizard-title{font-size:18px;font-weight:700;color:var(--c-text);margin-bottom:6px;letter-spacing:-.01em}.wizard-desc{font-size:13px;color:var(--c-text-2);line-height:1.6;margin-bottom:24px}.wizard-checklist{display:flex;flex-direction:column;gap:10px}.wizard-check{display:flex;align-items:center;gap:10px;font-size:13px;color:var(--c-text)}.wizard-check-num{width:24px;height:24px;border-radius:50%;background:var(--c-accent-bg);color:var(--c-accent);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:600;flex-shrink:0}.wizard-chips{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px}.wizard-chip{display:flex;flex-direction:column;gap:4px;padding:14px;background:var(--c-surface);border:2px solid var(--c-border);border-radius:var(--r-l);cursor:pointer;text-align:left;transition:all var(--fast);color:var(--c-text);font-family:inherit;font-size:inherit}.wizard-chip:hover{border-color:var(--c-accent-bdr)}.wizard-chip.on{border-color:var(--c-accent);background:var(--c-accent-bg)}.wizard-chip-name{font-weight:600;font-size:13px}.wizard-chip-hint{font-size:11px;color:var(--c-text-3);line-height:1.3}.wizard-cred{background:var(--c-surface-alt);border:1px solid var(--c-border);border-radius:var(--r-l);padding:20px;margin-bottom:14px}.wizard-cred:last-child{margin-bottom:0}.wizard-cred-name{font-size:14px;font-weight:600;margin-bottom:14px;color:var(--c-text)}.wizard-review-block{margin-bottom:20px}.wizard-review-head{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--c-text-3);margin-bottom:10px}.wizard-kv{display:flex;justify-content:space-between;align-items:center;padding:7px 0;border-bottom:1px solid var(--c-border);font-size:12px}.wizard-kv span{color:var(--c-text-2)}.wizard-kv code{font-family:var(--mono);font-size:11px;color:var(--c-text);background:var(--c-surface-alt);padding:2px 6px;border-radius:4px}.wizard-platforms{display:flex;flex-wrap:wrap;gap:6px}.wizard-platform-pill{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:var(--c-surface-alt);border-radius:999px;font-size:12px;font-weight:500}.wizard-platform-pill .cmd{font-size:10px;padding:1px 6px;border-radius:999px;background:var(--c-accent-bg);color:var(--c-accent);font-weight:500}.wizard-done{text-align:center;padding:20px 0}.wizard-pulse{animation:wiz-pulse 1.2s ease-in-out infinite}@keyframes wiz-pulse{0%,to{opacity:1}50%{opacity:.4}}.wizard-radio-group{display:flex;flex-direction:column;gap:8px}.wizard-radio{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;border:2px solid var(--c-border);border-radius:var(--r-m);cursor:pointer;transition:all var(--fast)}.wizard-radio:hover{border-color:var(--c-accent-bdr)}.wizard-radio.on{border-color:var(--c-accent);background:var(--c-accent-bg)}.wizard-radio input[type=radio]{margin-top:2px;accent-color:var(--c-accent)}.wizard-radio-body{display:flex;flex-direction:column;gap:2px}.wizard-radio-label{font-size:13px;font-weight:600;color:var(--c-text)}.wizard-radio-desc{font-size:12px;color:var(--c-text-3)}.wizard-nav{display:flex;align-items:center;padding:16px 32px 24px;gap:10px}.wizard-nav-spacer{flex:1}.conn{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:32px}.conn-card{width:100%;max-width:420px;background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r-xl);box-shadow:var(--shadow-l);padding:32px}.conn-logo{width:40px;height:40px;border-radius:var(--r-m);background:var(--c-accent);display:flex;align-items:center;justify-content:center;margin-bottom:20px}.conn-logo svg{width:22px;height:22px;color:#fff}.conn-title{font-size:18px;font-weight:700;color:var(--c-text);margin-bottom:6px}.conn-desc{font-size:13px;color:var(--c-text-2);margin-bottom:24px;line-height:1.5}.conn-row{display:flex;gap:8px}.conn-input{flex:1;padding:8px 10px;font-family:var(--mono);font-size:13px;color:var(--c-text);background:var(--c-surface-alt);border:1px solid var(--c-border);border-radius:var(--r-s);outline:none;transition:border-color var(--fast)}.conn-input:focus{border-color:var(--c-accent);box-shadow:0 0 0 3px var(--c-accent-bg)}.conn-status{font-size:12px;margin-top:10px;color:var(--c-text-3)}.conn-status.ok{color:var(--c-ok)}.conn-status.err{color:var(--c-err)}.lang-btn{padding:4px 10px;font-size:12px;font-weight:500;border-radius:var(--r-s);border:1px solid var(--c-border);background:var(--c-surface);color:var(--c-text-2);cursor:pointer;transition:all var(--fast)}.lang-btn:hover{border-color:var(--c-border-foc);color:var(--c-text)}.dark-btn{padding:4px 8px;border-radius:var(--r-s);border:1px solid var(--c-border);background:var(--c-surface);color:var(--c-text-2);cursor:pointer;font-size:14px;line-height:1;transition:all var(--fast);display:inline-flex;align-items:center;justify-content:center}.dark-btn:hover{border-color:var(--c-border-foc);color:var(--c-text)}@media(max-width:1024px){.ai-grid,.stats-grid{grid-template-columns:1fr}}@media(max-width:768px){.sidebar{display:none}.main{margin-left:0}.content{padding:20px 16px 40px}.header{padding:0 16px}.header-toolbar{padding:10px 16px}.platform-grid{grid-template-columns:1fr}.wizard-chips{grid-template-columns:1fr 1fr}}
|
|
1
|
+
:root{--c-bg: #f4f5f7;--c-surface: #ffffff;--c-surface-alt: #f9fafb;--c-border: #e4e7ec;--c-border-foc: #c0c7d1;--c-text: #101828;--c-text-2: #475467;--c-text-3: #98a2b3;--c-accent: #2970ff;--c-accent-h: #155eef;--c-accent-bg: #eff4ff;--c-accent-bdr: #b2ddff;--c-ok: #12b76a;--c-ok-bg: #ecfdf3;--c-ok-bdr: #a6f4c5;--c-warn: #f79009;--c-warn-bg: #fffaeb;--c-warn-bdr: #fedf89;--c-err: #f04438;--c-err-bg: #fef3f2;--c-err-bdr: #fecdca;--c-sb-bg: #101828;--c-sb-text: #98a2b3;--c-sb-text-a: #f4f5f7;--c-sb-hover: rgba(255,255,255,.06);--c-sb-active: rgba(255,255,255,.1);--shadow-xs: 0 1px 2px rgba(16,24,40,.05);--shadow-s: 0 1px 3px rgba(16,24,40,.1), 0 1px 2px rgba(16,24,40,.06);--shadow-m: 0 4px 8px -2px rgba(16,24,40,.1), 0 2px 4px -2px rgba(16,24,40,.06);--shadow-l: 0 12px 16px -4px rgba(16,24,40,.08), 0 4px 6px -2px rgba(16,24,40,.03);--r-s: 6px;--r-m: 8px;--r-l: 12px;--r-xl: 16px;--font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--mono: "SF Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;--fast: .12s ease;--norm: .2s ease;--sidebar-w: 240px;--header-h: 56px}:root.dark{--c-bg: #0c0d0f;--c-surface: #16171a;--c-surface-alt: #1c1d21;--c-border: #2a2b30;--c-border-foc: #3e4047;--c-text: #f0f1f4;--c-text-2: #98a2b3;--c-text-3: #5d6573;--c-accent: #528bff;--c-accent-h: #79a6ff;--c-accent-bg: rgba(42,114,255,.12);--c-accent-bdr: rgba(42,114,255,.24);--c-ok-bg: rgba(18,183,108,.1);--c-ok-bdr: rgba(18,183,108,.2);--c-warn-bg: rgba(247,144,9,.1);--c-warn-bdr: rgba(247,144,9,.2);--c-err-bg: rgba(240,68,56,.1);--c-err-bdr: rgba(240,68,56,.2);--c-sb-bg: #08090b;--c-sb-hover: rgba(255,255,255,.04);--c-sb-active: rgba(255,255,255,.08)}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}body{font-family:var(--font);font-size:14px;line-height:1.5;color:var(--c-text);background:var(--c-bg);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.app{display:flex;min-height:100vh}.main{flex:1;margin-left:var(--sidebar-w);display:flex;flex-direction:column;min-height:100vh;transition:margin-left var(--norm)}.content{flex:1;padding:32px 40px 64px;max-width:960px;width:100%;margin:0 auto}.sidebar{position:fixed;top:0;left:0;bottom:0;width:var(--sidebar-w);background:var(--c-sb-bg);color:var(--c-sb-text);display:flex;flex-direction:column;z-index:20;border-right:1px solid rgba(255,255,255,.06);overflow:hidden}.sidebar-header{padding:20px 16px 16px;display:flex;align-items:center;gap:10px}.sidebar-logo{width:32px;height:32px;border-radius:var(--r-m);background:var(--c-accent);display:flex;align-items:center;justify-content:center;flex-shrink:0}.sidebar-logo svg{width:18px;height:18px;color:#fff}.sidebar-brand{font-size:15px;font-weight:700;color:var(--c-sb-text-a);letter-spacing:-.01em;white-space:nowrap;overflow:hidden}.sidebar-nav{flex:1;padding:4px 8px;display:flex;flex-direction:column;gap:2px;overflow-y:auto}.sidebar-section-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--c-text-3);padding:16px 12px 6px}.nav-item{display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:var(--r-m);color:var(--c-sb-text);font-size:13px;font-weight:500;cursor:pointer;border:none;background:transparent;width:100%;text-align:left;transition:background var(--fast),color var(--fast);white-space:nowrap;overflow:hidden}.nav-item:hover{background:var(--c-sb-hover);color:var(--c-sb-text-a)}.nav-item.active{background:var(--c-sb-active);color:var(--c-sb-text-a)}.nav-item svg{width:18px;height:18px;flex-shrink:0;opacity:.7}.nav-item.active svg,.nav-item:hover svg{opacity:1}.header{position:sticky;top:0;z-index:15;background:var(--c-surface);border-bottom:1px solid var(--c-border);padding:0 40px;height:var(--header-h);display:flex;align-items:center;justify-content:space-between;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.header-title{font-size:15px;font-weight:600;color:var(--c-text)}.header-actions{display:flex;align-items:center;gap:8px}.header-divider{height:1px;background:var(--c-border);margin:0}.header-toolbar{background:var(--c-surface-alt);border-bottom:1px solid var(--c-border);padding:10px 40px;display:flex;gap:8px;flex-wrap:wrap;align-items:center}.header-toolbar-spacer{flex:1}.header-toolbar-status{font-size:12px;font-weight:500;display:inline-flex;align-items:center;gap:6px;color:var(--c-text-2)}.header-toolbar-status .dot{width:6px;height:6px;border-radius:50%;display:inline-block}.header-toolbar-status .dot.running{background:var(--c-ok)}.header-toolbar-status .dot.idle{background:var(--c-text-3)}.stats-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-bottom:32px}.stat-card{background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r-l);padding:20px}.stat-label{font-size:12px;font-weight:500;color:var(--c-text-3);text-transform:uppercase;letter-spacing:.04em;margin-bottom:8px}.stat-value{font-size:28px;font-weight:700;color:var(--c-text);line-height:1}.stat-meta{font-size:12px;color:var(--c-text-3);margin-top:4px}.section{margin-bottom:40px}.section-head{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:20px;flex-wrap:wrap;gap:8px}.section-title{font-size:16px;font-weight:600;color:var(--c-text)}.section-desc{font-size:13px;color:var(--c-text-3)}.card{background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r-l);overflow:hidden}.card-head{padding:16px 20px;border-bottom:1px solid var(--c-border);display:flex;justify-content:space-between;align-items:center;gap:12px}.card-title{font-size:14px;font-weight:600;color:var(--c-text)}.card-body{padding:20px}.card-foot{padding:12px 20px;border-top:1px solid var(--c-border);background:var(--c-surface-alt)}.platform-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px}.platform-card{background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r-l);overflow:hidden;transition:border-color var(--fast),box-shadow var(--fast)}.platform-card:hover{box-shadow:var(--shadow-s)}.platform-card.disabled{opacity:.55}.platform-card-head{padding:14px 20px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--c-border);gap:12px}.platform-card-name{font-size:14px;font-weight:600;color:var(--c-text);display:flex;align-items:center;gap:8px}.platform-card-chevron{font-size:12px;color:var(--c-text-3);margin-left:4px;transition:transform var(--fast)}.platform-card-name .dot{width:6px;height:6px;border-radius:50%;background:var(--c-text-3)}.platform-card.enabled .platform-card-name .dot{background:var(--c-ok)}.platform-card-body{padding:16px 20px 20px}.platform-card-hint{font-size:12px;color:var(--c-text-3);line-height:1.5;margin-bottom:16px}.platform-card-help{margin-top:16px;padding:10px 14px;background:var(--c-surface-alt);border-radius:var(--r-m);font-size:12px;color:var(--c-text-2);line-height:1.6}.platform-card-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:16px}.ai-grid{display:grid;grid-template-columns:280px 1fr;gap:16px}.config-stack{display:flex;flex-direction:column;gap:16px}.config-card .card-title{font-family:var(--mono);font-size:13px}.config-card textarea{width:100%}.config-card-toolbar{display:flex;justify-content:flex-end;gap:6px;margin-bottom:8px}.form-group{margin-bottom:14px}.form-group:last-child{margin-bottom:0}.form-label{display:block;font-size:12px;font-weight:500;color:var(--c-text-2);margin-bottom:5px}.form-input,.form-select,.form-textarea{width:100%;padding:8px 10px;font-family:inherit;font-size:13px;color:var(--c-text);background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r-s);transition:border-color var(--fast),box-shadow var(--fast);outline:none}.form-input:focus,.form-select:focus,.form-textarea:focus{border-color:var(--c-accent);box-shadow:0 0 0 3px var(--c-accent-bg)}.form-input.mono,.form-textarea.mono{font-family:var(--mono);font-size:12px}.form-textarea{min-height:80px;resize:vertical}.form-hint{font-size:11px;color:var(--c-text-3);margin-top:4px;line-height:1.4}.form-hint a{color:var(--c-accent)}.form-hint code{font-family:var(--mono);font-size:10px;padding:1px 4px;background:var(--c-surface-alt);border-radius:3px}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:7px 14px;font-family:inherit;font-size:13px;font-weight:500;border-radius:var(--r-s);border:1px solid transparent;cursor:pointer;text-decoration:none;transition:all var(--fast);white-space:nowrap}.btn:disabled{opacity:.5;cursor:not-allowed}.btn-p{background:var(--c-accent);color:#fff}.btn-p:hover:not(:disabled){background:var(--c-accent-h)}.btn-s{background:var(--c-surface);color:var(--c-text);border-color:var(--c-border)}.btn-s:hover:not(:disabled){background:var(--c-surface-alt);border-color:var(--c-border-foc)}.btn-g{background:transparent;color:var(--c-text-2)}.btn-g:hover:not(:disabled){background:var(--c-surface-alt);color:var(--c-text)}.btn-d{background:var(--c-err-bg);color:var(--c-err);border-color:var(--c-err-bdr)}.btn-d:hover:not(:disabled){background:var(--c-err);color:#fff}.btn-w{background:var(--c-warn-bg);color:var(--c-warn);border-color:var(--c-warn-bdr)}.btn-w:hover:not(:disabled){background:var(--c-warn);color:#fff}.btn-sm{padding:5px 10px;font-size:12px}.btn-lg{padding:10px 20px;font-size:14px}.toggle{display:inline-flex;align-items:center;gap:8px;cursor:pointer;flex-shrink:0}.toggle-input{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.toggle-track{position:relative;width:36px;height:20px;background:var(--c-border-foc);border-radius:999px;transition:background var(--fast);flex-shrink:0}.toggle-track:after{content:"";position:absolute;top:2px;left:2px;width:16px;height:16px;background:#fff;border-radius:50%;transition:transform var(--fast);box-shadow:var(--shadow-xs)}.toggle-input:checked+.toggle-track{background:var(--c-accent)}.toggle-input:checked+.toggle-track:after{transform:translate(16px)}.tabs{display:inline-flex;gap:2px;padding:3px;background:var(--c-surface-alt);border:1px solid var(--c-border);border-radius:var(--r-m)}.tab{padding:6px 12px;font-size:12px;font-weight:500;color:var(--c-text-2);background:transparent;border:none;border-radius:var(--r-s);cursor:pointer;transition:all var(--fast)}.tab:hover{color:var(--c-text)}.tab.active{background:var(--c-surface);color:var(--c-text);box-shadow:var(--shadow-xs)}.msg{padding:10px 14px;border-radius:var(--r-m);font-size:13px;line-height:1.5}.msg-ok{background:var(--c-ok-bg);color:var(--c-ok)}.msg-err{background:var(--c-err-bg);color:var(--c-err)}.msg-w{background:var(--c-warn-bg);color:var(--c-warn)}.badge{display:inline-flex;align-items:center;padding:2px 8px;font-size:11px;font-weight:500;border-radius:999px}.badge-ok{background:var(--c-ok-bg);color:var(--c-ok)}.badge-err{background:var(--c-err-bg);color:var(--c-err)}.badge-w{background:var(--c-warn-bg);color:var(--c-warn)}.badge-info{background:var(--c-accent-bg);color:var(--c-accent)}.field-tip{font-size:11px;color:var(--c-text-3);line-height:1.45;margin-top:5px}.field-tip a{color:var(--c-accent)}.field-tip code{font-family:var(--mono);font-size:10px;padding:1px 4px;background:var(--c-surface-alt);border-radius:3px}.flash{margin-bottom:20px}.mt-4{margin-top:16px}.mt-6{margin-top:24px}.gap-2{gap:8px}.gap-3{gap:12px}.text-center{text-align:center}.mono{font-family:var(--mono)}.hidden{display:none!important}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.wizard-wrap{display:flex;justify-content:center;padding-top:16px}.wizard{width:100%;max-width:580px;background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r-xl);box-shadow:var(--shadow-m);overflow:hidden}.wizard-bar{height:3px;background:var(--c-border)}.wizard-bar-fill{height:100%;background:var(--c-accent);transition:width .4s cubic-bezier(.4,0,.2,1)}.wizard-steps{display:flex;padding:20px 24px 0;gap:0}.wizard-step-ind{flex:1;display:flex;flex-direction:column;align-items:center;gap:6px;position:relative}.wizard-step-ind+.wizard-step-ind:before{content:"";position:absolute;top:12px;right:calc(50% + 16px);width:calc(100% - 32px);height:1px;background:var(--c-border)}.wizard-step-ind.done+.wizard-step-ind:before{background:var(--c-accent)}.wizard-step-dot{width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:600;background:var(--c-surface-alt);color:var(--c-text-3);border:2px solid var(--c-border);position:relative;z-index:1;transition:all var(--norm)}.wizard-step-ind.active .wizard-step-dot{background:var(--c-accent);color:#fff;border-color:var(--c-accent)}.wizard-step-ind.done .wizard-step-dot{background:var(--c-ok);color:#fff;border-color:var(--c-ok)}.wizard-step-label{font-size:11px;font-weight:500;color:var(--c-text-3);white-space:nowrap}.wizard-step-ind.active .wizard-step-label{color:var(--c-accent);font-weight:600}.wizard-step-ind.done .wizard-step-label{color:var(--c-ok)}.wizard-body{padding:28px 32px;min-height:260px}.wizard-icon{font-size:36px;margin-bottom:12px}.wizard-title{font-size:18px;font-weight:700;color:var(--c-text);margin-bottom:6px;letter-spacing:-.01em}.wizard-desc{font-size:13px;color:var(--c-text-2);line-height:1.6;margin-bottom:24px}.wizard-checklist{display:flex;flex-direction:column;gap:10px}.wizard-check{display:flex;align-items:center;gap:10px;font-size:13px;color:var(--c-text)}.wizard-check-num{width:24px;height:24px;border-radius:50%;background:var(--c-accent-bg);color:var(--c-accent);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:600;flex-shrink:0}.wizard-chips{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:12px}.wizard-platform-card{background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r-l);overflow:hidden;transition:border-color var(--fast)}.wizard-platform-card.on{border-color:var(--c-accent)}.wizard-platform-head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;cursor:pointer;gap:8px}.wizard-platform-head:hover{background:var(--c-surface-alt)}.wizard-platform-name{font-size:14px;font-weight:600;display:flex;align-items:center;gap:8px}.wizard-platform-name .dot{width:6px;height:6px;border-radius:50%;background:var(--c-text-3)}.wizard-platform-card.on .wizard-platform-name .dot{background:var(--c-ok)}.wizard-platform-chevron{font-size:12px;color:var(--c-text-3)}.wizard-platform-body{padding:0 16px 16px;border-top:1px solid var(--c-border)}.wizard-chip{display:flex;flex-direction:column;gap:4px;padding:14px;background:var(--c-surface);border:2px solid var(--c-border);border-radius:var(--r-l);cursor:pointer;text-align:left;transition:all var(--fast);color:var(--c-text);font-family:inherit;font-size:inherit}.wizard-chip:hover{border-color:var(--c-accent-bdr)}.wizard-chip.on{border-color:var(--c-accent);background:var(--c-accent-bg)}.wizard-chip-name{font-weight:600;font-size:13px}.wizard-chip-hint{font-size:11px;color:var(--c-text-3);line-height:1.3}.wizard-cred{background:var(--c-surface-alt);border:1px solid var(--c-border);border-radius:var(--r-l);padding:20px;margin-bottom:14px}.wizard-cred:last-child{margin-bottom:0}.wizard-cred-name{font-size:14px;font-weight:600;margin-bottom:14px;color:var(--c-text)}.wizard-review-block{margin-bottom:20px}.wizard-review-head{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--c-text-3);margin-bottom:10px}.wizard-kv{display:flex;justify-content:space-between;align-items:center;padding:7px 0;border-bottom:1px solid var(--c-border);font-size:12px}.wizard-kv span{color:var(--c-text-2)}.wizard-kv code{font-family:var(--mono);font-size:11px;color:var(--c-text);background:var(--c-surface-alt);padding:2px 6px;border-radius:4px}.wizard-platforms{display:flex;flex-wrap:wrap;gap:6px}.wizard-platform-pill{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:var(--c-surface-alt);border-radius:999px;font-size:12px;font-weight:500}.wizard-platform-pill .cmd{font-size:10px;padding:1px 6px;border-radius:999px;background:var(--c-accent-bg);color:var(--c-accent);font-weight:500}.wizard-done{text-align:center;padding:20px 0}.wizard-pulse{animation:wiz-pulse 1.2s ease-in-out infinite}@keyframes wiz-pulse{0%,to{opacity:1}50%{opacity:.4}}.wizard-radio-group{display:flex;flex-direction:column;gap:8px}.wizard-radio{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;border:2px solid var(--c-border);border-radius:var(--r-m);cursor:pointer;transition:all var(--fast)}.wizard-radio:hover{border-color:var(--c-accent-bdr)}.wizard-radio.on{border-color:var(--c-accent);background:var(--c-accent-bg)}.wizard-radio input[type=radio]{margin-top:2px;accent-color:var(--c-accent)}.wizard-radio-body{display:flex;flex-direction:column;gap:2px}.wizard-radio-label{font-size:13px;font-weight:600;color:var(--c-text)}.wizard-radio-desc{font-size:12px;color:var(--c-text-3)}.wizard-nav{display:flex;align-items:center;padding:16px 32px 24px;gap:10px}.wizard-nav-spacer{flex:1}.conn{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:32px}.conn-card{width:100%;max-width:420px;background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--r-xl);box-shadow:var(--shadow-l);padding:32px}.conn-logo{width:40px;height:40px;border-radius:var(--r-m);background:var(--c-accent);display:flex;align-items:center;justify-content:center;margin-bottom:20px}.conn-logo svg{width:22px;height:22px;color:#fff}.conn-title{font-size:18px;font-weight:700;color:var(--c-text);margin-bottom:6px}.conn-desc{font-size:13px;color:var(--c-text-2);margin-bottom:24px;line-height:1.5}.conn-row{display:flex;gap:8px}.conn-input{flex:1;padding:8px 10px;font-family:var(--mono);font-size:13px;color:var(--c-text);background:var(--c-surface-alt);border:1px solid var(--c-border);border-radius:var(--r-s);outline:none;transition:border-color var(--fast)}.conn-input:focus{border-color:var(--c-accent);box-shadow:0 0 0 3px var(--c-accent-bg)}.conn-status{font-size:12px;margin-top:10px;color:var(--c-text-3)}.conn-status.ok{color:var(--c-ok)}.conn-status.err{color:var(--c-err)}.lang-btn{padding:4px 10px;font-size:12px;font-weight:500;border-radius:var(--r-s);border:1px solid var(--c-border);background:var(--c-surface);color:var(--c-text-2);cursor:pointer;transition:all var(--fast)}.lang-btn:hover{border-color:var(--c-border-foc);color:var(--c-text)}.dark-btn{padding:4px 8px;border-radius:var(--r-s);border:1px solid var(--c-border);background:var(--c-surface);color:var(--c-text-2);cursor:pointer;font-size:14px;line-height:1;transition:all var(--fast);display:inline-flex;align-items:center;justify-content:center}.dark-btn:hover{border-color:var(--c-border-foc);color:var(--c-text)}@media(max-width:1024px){.ai-grid,.stats-grid{grid-template-columns:1fr}}@media(max-width:768px){.sidebar{display:none}.main{margin-left:0}.content{padding:20px 16px 40px}.header{padding:0 16px}.header-toolbar{padding:10px 16px}.platform-grid{grid-template-columns:1fr}.wizard-chips{grid-template-columns:1fr 1fr}}
|