apiskill 0.1.2 → 0.1.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/MCP.md +5 -5
- package/README.ja.md +8 -8
- package/README.ko.md +8 -8
- package/README.md +113 -12
- package/README.zh.md +113 -12
- package/dist/assets/{index-CPxVZ06Y.js → index-CV6e3HAP.js} +55 -55
- package/dist/assets/index-slFOragU.css +1 -0
- package/dist/index.html +2 -2
- package/docs/cli.ja.md +1 -1
- package/docs/cli.ko.md +1 -1
- package/docs/cli.md +30 -19
- package/docs/cli.zh.md +30 -19
- package/docs/mcp.ja.md +1 -1
- package/docs/mcp.ko.md +1 -1
- package/docs/mcp.md +3 -1
- package/docs/mcp.zh.md +3 -1
- package/docs/web.ja.md +1 -1
- package/docs/web.ko.md +1 -1
- package/docs/web.md +1 -1
- package/docs/web.zh.md +1 -1
- package/package.json +4 -3
- package/scripts/apiskill-cli.mjs +3 -3
- package/scripts/lib/apiskill-core.mjs +5 -4
- package/scripts/lib/openapi-store.mjs +142 -51
- package/scripts/mcp-server.mjs +2 -2
- package/scripts/test-cli-concurrency.mjs +110 -0
- package/src/App.tsx +12 -0
- package/src/styles.css +12 -3
- package/tsconfig.json +3 -3
- package/dist/assets/index-BeAJ1G-n.css +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
-
import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { mkdir, open, readFile, readdir, stat, unlink, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { basename, dirname, resolve } from 'node:path';
|
|
4
4
|
import { parse as parseYaml } from 'yaml';
|
|
5
5
|
import { getDefaultCacheDir } from './cache-paths.mjs';
|
|
@@ -11,6 +11,9 @@ const versionsDir = resolve(cacheDir, 'versions');
|
|
|
11
11
|
const latestMetaPath = resolve(cacheDir, 'latest-import.json');
|
|
12
12
|
const legacyCachePath = resolve(cacheDir, 'openapi-cache.json');
|
|
13
13
|
const legacyCacheMetaPath = resolve(cacheDir, 'import-meta.json');
|
|
14
|
+
const writeLockPath = resolve(cacheDir, '.write.lock');
|
|
15
|
+
const writeLockTimeoutMs = 15_000;
|
|
16
|
+
const malformedLockStaleMs = 30_000;
|
|
14
17
|
|
|
15
18
|
export function parseOpenApiText(text) {
|
|
16
19
|
const cleaned = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
@@ -94,20 +97,22 @@ export async function listCachedVersions() {
|
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
export async function saveImportedDocument({ document, mode, inputUrl, resolvedUrl, versionId }) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
100
|
+
return withCacheWriteLock(async () => {
|
|
101
|
+
if (!isOpenApiDocument(document)) throw new Error('不是有效的 OpenAPI/Swagger 文档');
|
|
102
|
+
const existing = versionId ? await readCachedDocument(versionId) : undefined;
|
|
103
|
+
const savedAt = new Date();
|
|
104
|
+
const nextVersionId = existing?.meta?.versionId || `${formatVersionDate(savedAt)}-${mode}-${slugify(sourceName(resolvedUrl || inputUrl))}`;
|
|
105
|
+
return writeVersion(document, {
|
|
106
|
+
...(existing?.meta ?? {}),
|
|
107
|
+
savedPath: existing?.meta?.savedPath,
|
|
108
|
+
versionId: nextVersionId,
|
|
109
|
+
mode,
|
|
110
|
+
inputUrl,
|
|
111
|
+
resolvedUrl: resolvedUrl || inputUrl,
|
|
112
|
+
title: document.info?.title || '',
|
|
113
|
+
version: document.info?.version || '',
|
|
114
|
+
savedAt: savedAt.toISOString(),
|
|
115
|
+
});
|
|
111
116
|
});
|
|
112
117
|
}
|
|
113
118
|
|
|
@@ -118,50 +123,61 @@ export async function createBlankDocument({
|
|
|
118
123
|
environmentName = '',
|
|
119
124
|
environmentBaseUrl = '',
|
|
120
125
|
} = {}) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
126
|
+
return withCacheWriteLock(async () => {
|
|
127
|
+
const document = createManualDocument(text(title) || 'API Skill Document', text(version) || '1.0.0', text(description));
|
|
128
|
+
const savedAt = new Date();
|
|
129
|
+
const versionId = `${formatVersionDate(savedAt)}-document-${slugify(document.info?.title || 'api-skill-document')}`;
|
|
130
|
+
return writeVersion(document, {
|
|
131
|
+
versionId,
|
|
132
|
+
mode: 'document',
|
|
133
|
+
inputUrl: 'manual-document',
|
|
134
|
+
resolvedUrl: 'manual-document',
|
|
135
|
+
title: document.info?.title || '',
|
|
136
|
+
version: document.info?.version || '',
|
|
137
|
+
environmentName: text(environmentName).slice(0, 80),
|
|
138
|
+
environmentBaseUrl: text(environmentBaseUrl),
|
|
139
|
+
savedAt: savedAt.toISOString(),
|
|
140
|
+
});
|
|
134
141
|
});
|
|
135
142
|
}
|
|
136
143
|
|
|
137
144
|
export async function saveManualOperation({ versionId, config, replaceTarget }) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
145
|
+
return withCacheWriteLock(async () => {
|
|
146
|
+
const normalized = normalizeManualConfig(config);
|
|
147
|
+
const existing = versionId ? await readCachedDocument(versionId) : await readLatestCachedDocumentIfExists();
|
|
148
|
+
let document = existing?.document ?? createManualDocument();
|
|
149
|
+
if (replaceTarget?.method && replaceTarget?.path) {
|
|
150
|
+
document = deleteOperation(document, replaceTarget.method, replaceTarget.path);
|
|
151
|
+
}
|
|
152
|
+
document = applyOperation(document, normalized);
|
|
153
|
+
const savedAt = new Date();
|
|
154
|
+
const nextVersionId = existing?.meta?.versionId || `${formatVersionDate(savedAt)}-manual-${slugify(normalized.operationId || normalized.summary || normalized.path)}`;
|
|
155
|
+
return writeVersion(document, {
|
|
156
|
+
...(existing?.meta ?? {}),
|
|
157
|
+
versionId: nextVersionId,
|
|
158
|
+
mode: existing?.meta?.mode || 'manual',
|
|
159
|
+
inputUrl: existing?.meta?.inputUrl || 'manual-api-config',
|
|
160
|
+
resolvedUrl: existing?.meta?.resolvedUrl || 'manual-api-config',
|
|
161
|
+
title: document.info?.title || 'Manual API Config',
|
|
162
|
+
version: document.info?.version || 'manual',
|
|
163
|
+
savedAt: savedAt.toISOString(),
|
|
164
|
+
});
|
|
156
165
|
});
|
|
157
166
|
}
|
|
158
167
|
|
|
168
|
+
async function readLatestCachedDocumentIfExists() {
|
|
169
|
+
if (!existsSync(latestMetaPath) && !existsSync(legacyCachePath)) return undefined;
|
|
170
|
+
return readCachedDocument();
|
|
171
|
+
}
|
|
172
|
+
|
|
159
173
|
export async function deleteManualOperation({ versionId, method, path }) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
174
|
+
return withCacheWriteLock(async () => {
|
|
175
|
+
const existing = await readCachedDocument(required(versionId, '必须指定 --version'));
|
|
176
|
+
const document = deleteOperation(existing.document, method, path);
|
|
177
|
+
return writeVersion(document, {
|
|
178
|
+
...existing.meta,
|
|
179
|
+
savedAt: new Date().toISOString(),
|
|
180
|
+
});
|
|
165
181
|
});
|
|
166
182
|
}
|
|
167
183
|
|
|
@@ -494,6 +510,81 @@ async function writeVersion(document, metaInput) {
|
|
|
494
510
|
return { document, meta };
|
|
495
511
|
}
|
|
496
512
|
|
|
513
|
+
async function withCacheWriteLock(task) {
|
|
514
|
+
await mkdir(cacheDir, { recursive: true });
|
|
515
|
+
const startedAt = Date.now();
|
|
516
|
+
const token = `${process.pid}-${startedAt}-${Math.random().toString(36).slice(2)}`;
|
|
517
|
+
|
|
518
|
+
while (true) {
|
|
519
|
+
try {
|
|
520
|
+
const handle = await open(writeLockPath, 'wx');
|
|
521
|
+
let initialized = false;
|
|
522
|
+
try {
|
|
523
|
+
await handle.writeFile(JSON.stringify({ pid: process.pid, token, createdAt: new Date().toISOString() }));
|
|
524
|
+
initialized = true;
|
|
525
|
+
} finally {
|
|
526
|
+
await handle.close().catch(() => {});
|
|
527
|
+
if (!initialized) await unlink(writeLockPath).catch(() => {});
|
|
528
|
+
}
|
|
529
|
+
break;
|
|
530
|
+
} catch (error) {
|
|
531
|
+
if (error?.code !== 'EEXIST') throw error;
|
|
532
|
+
await removeAbandonedWriteLock();
|
|
533
|
+
if (Date.now() - startedAt >= writeLockTimeoutMs) {
|
|
534
|
+
throw new Error('等待缓存写入锁超时,请确认没有卡住的 API Skill 进程后重试');
|
|
535
|
+
}
|
|
536
|
+
await sleep(15 + Math.floor(Math.random() * 25));
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
try {
|
|
541
|
+
return await task();
|
|
542
|
+
} finally {
|
|
543
|
+
await releaseWriteLock(token);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async function removeAbandonedWriteLock() {
|
|
548
|
+
let lock;
|
|
549
|
+
try {
|
|
550
|
+
lock = JSON.parse(await readFile(writeLockPath, 'utf8'));
|
|
551
|
+
} catch (error) {
|
|
552
|
+
if (error?.code === 'ENOENT') return;
|
|
553
|
+
try {
|
|
554
|
+
const lockStat = await stat(writeLockPath);
|
|
555
|
+
if (Date.now() - lockStat.mtimeMs >= malformedLockStaleMs) await unlink(writeLockPath);
|
|
556
|
+
} catch {}
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (isProcessAlive(lock?.pid)) return;
|
|
561
|
+
try {
|
|
562
|
+
const current = JSON.parse(await readFile(writeLockPath, 'utf8'));
|
|
563
|
+
if (current?.token === lock?.token) await unlink(writeLockPath);
|
|
564
|
+
} catch {}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
async function releaseWriteLock(token) {
|
|
568
|
+
try {
|
|
569
|
+
const current = JSON.parse(await readFile(writeLockPath, 'utf8'));
|
|
570
|
+
if (current?.token === token) await unlink(writeLockPath);
|
|
571
|
+
} catch {}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function isProcessAlive(pid) {
|
|
575
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
576
|
+
try {
|
|
577
|
+
process.kill(pid, 0);
|
|
578
|
+
return true;
|
|
579
|
+
} catch (error) {
|
|
580
|
+
return error?.code === 'EPERM';
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function sleep(milliseconds) {
|
|
585
|
+
return new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
|
|
586
|
+
}
|
|
587
|
+
|
|
497
588
|
function assertSafeVersionId(versionId) {
|
|
498
589
|
if (!/^[a-zA-Z0-9_.-]+$/.test(versionId)) throw new Error('versionId 不合法');
|
|
499
590
|
}
|
package/scripts/mcp-server.mjs
CHANGED
|
@@ -178,11 +178,11 @@ const tools = [
|
|
|
178
178
|
},
|
|
179
179
|
{
|
|
180
180
|
name: 'apiskill_create_api',
|
|
181
|
-
description: 'Create a manual API operation. If versionId is omitted, this
|
|
181
|
+
description: 'Create a manual API operation. If versionId is omitted, this appends to the latest version or creates a manual version when the cache is empty.',
|
|
182
182
|
inputSchema: {
|
|
183
183
|
type: 'object',
|
|
184
184
|
properties: {
|
|
185
|
-
versionId: { type: 'string', description: 'Optional version id
|
|
185
|
+
versionId: { type: 'string', description: 'Optional version id. Defaults to latest, or a new manual version when the cache is empty.' },
|
|
186
186
|
configText: { type: 'string', description: 'JSON/YAML/CLI config text. Use root key api, config, or operation.' },
|
|
187
187
|
config: { type: 'object', description: 'Manual API config object.' },
|
|
188
188
|
},
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
|
|
8
|
+
const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
9
|
+
const cliPath = resolve(projectRoot, 'scripts/apiskill-cli.mjs');
|
|
10
|
+
const cacheDir = await mkdtemp(join(tmpdir(), 'apiskill-cli-concurrency-'));
|
|
11
|
+
const endpointCount = 16;
|
|
12
|
+
const implicitVersionEndpointCount = 8;
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const created = JSON.parse(
|
|
16
|
+
await runCli(['document', 'create', '--title', 'Concurrent CLI Test', '--doc-version', '1.0.0', '--json']),
|
|
17
|
+
);
|
|
18
|
+
const versionId = created.meta.versionId;
|
|
19
|
+
|
|
20
|
+
await Promise.all(
|
|
21
|
+
Array.from({ length: endpointCount }, (_, index) => {
|
|
22
|
+
const number = index + 1;
|
|
23
|
+
return runCli([
|
|
24
|
+
'api',
|
|
25
|
+
'create',
|
|
26
|
+
'--version',
|
|
27
|
+
versionId,
|
|
28
|
+
'--config',
|
|
29
|
+
JSON.stringify({
|
|
30
|
+
api: {
|
|
31
|
+
method: 'get',
|
|
32
|
+
path: `/api/v1/concurrent/${number}`,
|
|
33
|
+
summary: `Concurrent endpoint ${number}`,
|
|
34
|
+
responses: [{ status: '200', description: 'Success' }],
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
'--json',
|
|
38
|
+
]);
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const endpoints = JSON.parse(await runCli(['api', 'list', '--version', versionId, '--json']));
|
|
43
|
+
assert.equal(endpoints.length, endpointCount, `expected ${endpointCount} endpoints, received ${endpoints.length}`);
|
|
44
|
+
assert.deepEqual(
|
|
45
|
+
new Set(endpoints.map((endpoint) => endpoint.path)),
|
|
46
|
+
new Set(Array.from({ length: endpointCount }, (_, index) => `/api/v1/concurrent/${index + 1}`)),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const implicitCreated = JSON.parse(
|
|
50
|
+
await runCli(['document', 'create', '--title', 'Implicit Latest Version Test', '--doc-version', '1.0.0', '--json']),
|
|
51
|
+
);
|
|
52
|
+
const implicitVersionId = implicitCreated.meta.versionId;
|
|
53
|
+
await Promise.all(
|
|
54
|
+
Array.from({ length: implicitVersionEndpointCount }, (_, index) => {
|
|
55
|
+
const number = index + 1;
|
|
56
|
+
return runCli([
|
|
57
|
+
'api',
|
|
58
|
+
'create',
|
|
59
|
+
'--config',
|
|
60
|
+
JSON.stringify({
|
|
61
|
+
api: {
|
|
62
|
+
method: 'post',
|
|
63
|
+
path: `/api/v1/implicit/${number}`,
|
|
64
|
+
summary: `Implicit latest endpoint ${number}`,
|
|
65
|
+
responses: [{ status: '201', description: 'Created' }],
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
'--json',
|
|
69
|
+
]);
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
const implicitEndpoints = JSON.parse(await runCli(['api', 'list', '--version', implicitVersionId, '--json']));
|
|
73
|
+
assert.equal(
|
|
74
|
+
implicitEndpoints.length,
|
|
75
|
+
implicitVersionEndpointCount,
|
|
76
|
+
`expected ${implicitVersionEndpointCount} implicit-version endpoints, received ${implicitEndpoints.length}`,
|
|
77
|
+
);
|
|
78
|
+
assert.ok(implicitEndpoints.every((endpoint) => endpoint.path.startsWith('/api/v1/implicit/')));
|
|
79
|
+
|
|
80
|
+
console.log(
|
|
81
|
+
`CLI concurrency test passed: retained ${endpointCount}/${endpointCount} explicit-version and ${implicitVersionEndpointCount}/${implicitVersionEndpointCount} latest-version parallel writes.`,
|
|
82
|
+
);
|
|
83
|
+
} finally {
|
|
84
|
+
await rm(cacheDir, { recursive: true, force: true });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function runCli(args) {
|
|
88
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
89
|
+
const child = spawn(process.execPath, [cliPath, ...args], {
|
|
90
|
+
cwd: projectRoot,
|
|
91
|
+
env: { ...process.env, APISKILL_CACHE_DIR: cacheDir },
|
|
92
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
93
|
+
});
|
|
94
|
+
let stdout = '';
|
|
95
|
+
let stderr = '';
|
|
96
|
+
child.stdout.setEncoding('utf8');
|
|
97
|
+
child.stderr.setEncoding('utf8');
|
|
98
|
+
child.stdout.on('data', (chunk) => {
|
|
99
|
+
stdout += chunk;
|
|
100
|
+
});
|
|
101
|
+
child.stderr.on('data', (chunk) => {
|
|
102
|
+
stderr += chunk;
|
|
103
|
+
});
|
|
104
|
+
child.on('error', rejectPromise);
|
|
105
|
+
child.on('close', (code) => {
|
|
106
|
+
if (code === 0) resolvePromise(stdout);
|
|
107
|
+
else rejectPromise(new Error(`apiskill ${args.join(' ')} failed (${code}): ${stderr || stdout}`));
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
package/src/App.tsx
CHANGED
|
@@ -967,6 +967,18 @@ export function App() {
|
|
|
967
967
|
{mockStatus?.running && mockStatus.mock ? <small>MOCK {mockStatus.mock.url} · {mockStatus.mock.routesCount} routes</small> : null}
|
|
968
968
|
</div>
|
|
969
969
|
<div className="source-panel-actions">
|
|
970
|
+
<button
|
|
971
|
+
className="primary-button"
|
|
972
|
+
onClick={() => {
|
|
973
|
+
if (!selectedVersion) return;
|
|
974
|
+
void prepareVersionUpdate(selectedVersion);
|
|
975
|
+
}}
|
|
976
|
+
disabled={Boolean(syncingMode) || !selectedVersion}
|
|
977
|
+
title={selectedVersion ? '按当前文档最初的生成方式更新并覆盖原版本' : '请先新建或导入文档'}
|
|
978
|
+
>
|
|
979
|
+
<RefreshCcw size={16} />
|
|
980
|
+
更新文档
|
|
981
|
+
</button>
|
|
970
982
|
<button className="secondary-button" onClick={startMockService} disabled={startingMock}>
|
|
971
983
|
<Server size={16} className={startingMock ? 'spin' : ''} />
|
|
972
984
|
{startingMock ? '启动中' : '启动MOCK服务'}
|
package/src/styles.css
CHANGED
|
@@ -30,8 +30,12 @@ button {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
.app-shell {
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
height: 100svh;
|
|
33
36
|
min-height: 100vh;
|
|
34
|
-
padding: 18px;
|
|
37
|
+
padding: 18px 18px 0;
|
|
38
|
+
overflow: hidden;
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
.topbar {
|
|
@@ -686,10 +690,10 @@ p {
|
|
|
686
690
|
|
|
687
691
|
.workspace {
|
|
688
692
|
display: grid;
|
|
693
|
+
flex: 1 1 auto;
|
|
689
694
|
grid-template-columns: minmax(320px, 380px) minmax(0, 1fr);
|
|
690
695
|
gap: 16px;
|
|
691
|
-
min-height:
|
|
692
|
-
height: calc(100vh - 270px);
|
|
696
|
+
min-height: 0;
|
|
693
697
|
margin-top: 16px;
|
|
694
698
|
}
|
|
695
699
|
|
|
@@ -811,6 +815,8 @@ p {
|
|
|
811
815
|
}
|
|
812
816
|
|
|
813
817
|
.endpoint-list {
|
|
818
|
+
flex: 1 1 auto;
|
|
819
|
+
min-height: 0;
|
|
814
820
|
overflow: auto;
|
|
815
821
|
display: flex;
|
|
816
822
|
flex-direction: column;
|
|
@@ -2060,7 +2066,9 @@ td code {
|
|
|
2060
2066
|
|
|
2061
2067
|
@media (max-width: 980px) {
|
|
2062
2068
|
.app-shell {
|
|
2069
|
+
height: auto;
|
|
2063
2070
|
padding: 12px;
|
|
2071
|
+
overflow: visible;
|
|
2064
2072
|
}
|
|
2065
2073
|
|
|
2066
2074
|
.topbar {
|
|
@@ -2149,6 +2157,7 @@ td code {
|
|
|
2149
2157
|
|
|
2150
2158
|
.workspace {
|
|
2151
2159
|
grid-template-columns: 1fr;
|
|
2160
|
+
flex: none;
|
|
2152
2161
|
height: auto;
|
|
2153
2162
|
}
|
|
2154
2163
|
|
package/tsconfig.json
CHANGED
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
"strict": true,
|
|
11
11
|
"forceConsistentCasingInFileNames": true,
|
|
12
12
|
"module": "ESNext",
|
|
13
|
-
"moduleResolution": "
|
|
13
|
+
"moduleResolution": "bundler", // ✅ 改为 bundler
|
|
14
14
|
"resolveJsonModule": true,
|
|
15
15
|
"isolatedModules": true,
|
|
16
16
|
"noEmit": true,
|
|
17
17
|
"jsx": "react-jsx"
|
|
18
18
|
},
|
|
19
|
-
"include": ["src"],
|
|
19
|
+
"include": ["src", "vite-env.d.ts"],
|
|
20
20
|
"references": []
|
|
21
|
-
}
|
|
21
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{color-scheme:light;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;background:#eef1f4;color:#18202a;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased}*{box-sizing:border-box}body{margin:0;min-width:320px;min-height:100vh}button,input,select{font:inherit}button{cursor:pointer}.app-shell{min-height:100vh;padding:18px}.topbar{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:16px 20px;border:1px solid #d7dde4;border-radius:8px;background:#fff}.eyebrow{margin:0 0 4px;color:#667282;font-size:12px;font-weight:700;letter-spacing:0;text-transform:uppercase}h1,h2,h3,h4,p{margin-top:0}.topbar h1{margin:0;font-size:24px;line-height:1.2}.topbar-actions{display:flex;align-items:center;gap:14px}.version-switcher{display:inline-flex;align-items:center;gap:8px;color:#647182;font-size:12px;font-weight:800}.version-switcher select{width:min(320px,32vw);min-height:36px;padding:0 10px;border:1px solid #cfd7e1;border-radius:6px;outline:0;color:#15202c;background:#fff}.version-manager{min-width:min(420px,38vw)}.version-manager-trigger{display:grid;grid-template-columns:auto minmax(0,1fr);align-items:center;gap:9px;width:100%;min-height:44px;padding:7px 10px;border:1px solid #cfd7e1;border-radius:7px;color:#23303e;background:#fff;text-align:left}.version-manager-trigger:disabled{cursor:not-allowed;opacity:.72}.version-manager-trigger span{display:grid;gap:2px;min-width:0}.version-manager-trigger strong,.version-manager-trigger small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.version-manager-trigger strong{font-size:13px}.version-manager-trigger small{color:#657181;font-size:11px;font-weight:800}.version-manager-dialog{width:min(900px,100%);max-height:calc(100vh - 40px);overflow:auto;border-radius:8px;border:1px solid #d7dde4;background:#fff;box-shadow:0 18px 60px #12192338}.version-manager-body{display:grid;gap:12px;padding:16px 20px 20px;background:#f7f9fb}.version-message{padding:9px 11px;border:1px solid #cbe5df;border-radius:7px;color:#176b4f;background:#eef9f5;font-size:13px;font-weight:800}.version-message.error{border-color:#f1c1bd;color:#9a2d27;background:#fff4f3}.version-item{display:grid;gap:12px;padding:13px;border:1px solid #dfe5eb;border-radius:8px;background:#fff}.version-item.active{border-color:#7ab6ad;box-shadow:inset 3px 0 #1b6b65}.version-item-main{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px;align-items:start}.version-item-title{display:flex;align-items:center;gap:8px;min-width:0}.version-item-title strong{overflow:hidden;color:#202a36;text-overflow:ellipsis;white-space:nowrap}.version-item-title span{padding:3px 7px;border-radius:999px;color:#176b4f;background:#e5f6ef;font-size:11px;font-weight:900}.version-item-main p{margin:4px 0 0;overflow-wrap:anywhere;color:#657181;font-family:SFMono-Regular,Consolas,monospace;font-size:12px}.version-item-stats{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:6px}.version-item-stats span,.version-env-summary span{padding:5px 7px;border-radius:6px;color:#4d5b6c;background:#eef2f6;font-size:12px;font-weight:800}.version-env-summary{display:grid;grid-template-columns:auto minmax(0,1fr);align-items:center;gap:8px}.version-env-summary code{overflow:hidden;padding:7px 9px;border:1px solid #dfe5eb;border-radius:6px;color:#2f3d4d;background:#fbfcfd;text-overflow:ellipsis;white-space:nowrap}.version-env-editor{display:grid;grid-template-columns:minmax(160px,.5fr) minmax(0,1fr);gap:10px}.version-env-editor label{display:grid;gap:5px;min-width:0;color:#647182;font-size:12px;font-weight:800}.version-env-editor input{width:100%;min-height:36px;padding:0 10px;border:1px solid #cfd7e1;border-radius:6px;outline:0;color:#15202c;background:#fff}.version-item-actions{display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end}.sync-meta{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px;color:#5c6877;font-size:13px}.sync-meta span{padding:5px 8px;border:1px solid #dce2e9;border-radius:6px;background:#f7f9fb}.primary-button,.copy-inline-button{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:36px;border:0;border-radius:6px;color:#fff;background:#1b6b65;font-weight:700;white-space:nowrap}.primary-button{padding:0 14px}.primary-button:disabled{cursor:wait;opacity:.72}.copy-inline-button{padding:0 12px;background:#202a36}.secondary-button{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:36px;padding:0 14px;border:1px solid #ccd5df;border-radius:6px;color:#22303d;background:#fff;font-weight:700;white-space:nowrap}.secondary-button:disabled{cursor:wait;opacity:.72}.danger-button{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:36px;padding:0 14px;border:1px solid #efcbc8;border-radius:6px;color:#b63831;background:#fff7f6;font-weight:700;white-space:nowrap}.icon-secondary-button{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;padding:0;border:1px solid #ccd5df;border-radius:6px;color:#22303d;background:#fff}.source-panel{display:grid;gap:10px;margin-top:12px;padding:12px;border:1px solid #d7dde4;border-radius:8px;background:#fff}.source-panel-header{display:flex;align-items:center;justify-content:space-between;gap:10px}.source-panel-actions{display:inline-flex;flex-wrap:wrap;gap:8px;justify-content:flex-end}.document-toolbar{grid-template-columns:minmax(0,1fr) auto;align-items:center}.document-source-summary{display:grid;min-width:0;gap:3px}.document-source-summary>span{color:#6a7685;font-size:12px;font-weight:700}.document-source-summary strong,.document-source-summary small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.document-source-summary strong{color:#1d2936;font-size:13px}.document-source-summary small{color:#176b4f;font-size:12px}.document-toolbar>.version-message{grid-column:1 / -1}.source-tabs{display:inline-flex;width:fit-content;gap:4px;padding:3px;border:1px solid #d4dce5;border-radius:7px;background:#fff}.source-tabs button{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:32px;padding:0 12px;border:0;border-radius:5px;color:#566373;background:transparent;font-size:13px;font-weight:800}.source-tabs button.active{color:#fff;background:#1b6b65}.source-form{display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:10px}.document-dialog{width:min(760px,100%);max-height:calc(100vh - 40px);overflow:auto;border:1px solid #d7dde4;border-radius:8px;background:#fff;box-shadow:0 18px 60px #12192338;animation:dialog-enter .16s ease-out}.document-dialog-tabs{display:flex;gap:2px;overflow-x:auto;padding:10px 20px 0;border-bottom:1px solid #e5eaf0}.document-dialog-tabs button{display:inline-flex;flex:0 0 auto;align-items:center;justify-content:center;gap:6px;min-height:38px;padding:0 12px;border:0;border-bottom:2px solid transparent;color:#5c6877;background:transparent;font-size:13px;font-weight:800}.document-dialog-tabs button.active{border-bottom-color:#1b6b65;color:#155a55}.document-dialog-body{min-height:174px;padding:20px;background:#f7f9fb}.blank-document-fields{display:grid;gap:12px}.document-source-input{min-height:46px;background:#fff}.document-file-picker{display:grid;grid-template-columns:auto minmax(0,1fr);align-content:center;min-height:134px;padding:22px;background:#fff}.document-file-picker>svg{grid-row:1 / span 2}.document-file-picker small{color:#748091}.document-dialog-actions{gap:8px;padding-top:18px}.document-dialog-actions .clear-document-source{margin-right:auto}.source-input,.source-file-picker{display:flex;align-items:center;gap:8px;min-width:0;min-height:38px;padding:0 12px;border:1px solid #cfd7e1;border-radius:7px;background:#f9fafb}.source-file-picker{cursor:pointer}.source-file-picker input{display:none}.source-file-picker span{overflow:hidden;min-width:0;color:#15202c;text-overflow:ellipsis;white-space:nowrap}.source-input input{width:100%;min-width:0;border:0;outline:0;background:transparent;color:#15202c}.source-input textarea{width:100%;min-width:0;min-height:118px;resize:vertical;border:0;outline:0;background:transparent;color:#15202c;font-family:SFMono-Regular,Consolas,monospace;font-size:12px;line-height:1.5}.source-input-multiline{align-items:flex-start;padding-top:10px;padding-bottom:10px}.source-input-multiline svg{margin-top:2px}.source-auth{grid-column:1 / -1;display:grid;grid-template-columns:auto minmax(0,1fr);align-items:center;gap:10px}.source-auth-fields{display:grid;grid-template-columns:repeat(2,minmax(0,220px));gap:8px}.source-auth-fields label{display:flex;align-items:center;gap:8px;min-width:0;min-height:36px;padding:0 10px;border:1px solid #cfd7e1;border-radius:7px;background:#f9fafb}.source-auth-fields input{width:100%;min-width:0;border:0;outline:0;background:transparent;color:#15202c}.source-meta{grid-column:1 / -1;overflow:hidden;color:#657181;font-size:12px;text-overflow:ellipsis;white-space:nowrap}.source-meta span{color:#2f3d4d;font-family:SFMono-Regular,Consolas,monospace}.source-meta small{margin-left:8px;color:#728094;font-weight:800}.workspace{display:grid;grid-template-columns:minmax(320px,380px) minmax(0,1fr);gap:16px;min-height:420px;height:calc(100vh - 270px);margin-top:16px}.sidebar,.detail-pane{min-height:0;border:1px solid #d7dde4;border-radius:8px;background:#fff}.sidebar{display:flex;flex-direction:column;padding:14px}.search-box{display:flex;align-items:center;gap:8px;min-height:42px;padding:0 12px;border:1px solid #cfd7e1;border-radius:7px;background:#f9fafb}.search-box input{width:100%;min-width:0;border:0;outline:0;background:transparent;color:#15202c}.search-clear-button{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:24px;height:24px;padding:0;border:0;border-radius:5px;color:#647182;background:transparent}.search-clear-button:hover{color:#b63831;background:#fff0ef}.filters{display:grid;grid-template-columns:1fr auto;gap:10px;margin-top:12px}.filters label:first-child{display:flex;align-items:center;gap:8px;min-width:0;padding:0 10px;border:1px solid #d5dce5;border-radius:7px;background:#fff}.filters select{width:100%;min-height:38px;border:0;outline:0;background:transparent}.checkbox-line{display:inline-flex;align-items:center;gap:6px;color:#566373;font-size:13px;white-space:nowrap}.method-tabs{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:6px;margin-top:12px}.method-tabs button{min-height:32px;border:1px solid #d5dce5;border-radius:6px;color:#536171;background:#fff;font-size:12px;font-weight:800}.method-tabs button.active{color:#fff;border-color:#1b6b65;background:#1b6b65}.endpoint-count{margin:12px 2px 8px;color:#6b7582;font-size:13px}.endpoint-list{overflow:auto;display:flex;flex-direction:column;gap:6px;padding-right:4px}.endpoint-item{display:grid;grid-template-columns:64px minmax(0,1fr);align-items:start;gap:10px;width:100%;min-height:66px;padding:10px;border:1px solid transparent;border-radius:7px;text-align:left;background:#fff}.endpoint-item:hover{background:#f6f8fa}.endpoint-item.selected{border-color:#1b6b65;background:#edf7f5}.endpoint-main{min-width:0}.endpoint-main strong,.endpoint-main small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.endpoint-main strong{margin-bottom:4px;color:#1d2732;font-size:14px}.endpoint-main small{color:#647182;font-family:SFMono-Regular,Consolas,monospace;font-size:12px}.method{display:inline-flex;align-items:center;justify-content:center;width:58px;height:24px;border-radius:5px;color:#fff;font-size:11px;font-weight:900}.method-get{background:#246bce}.method-post{background:#168456}.method-put{background:#9a6200}.method-delete{background:#c23131}.method-patch{background:#7953b8}.method-options,.method-head{background:#52606f}.detail-pane{overflow:auto}.opened-api-tabs{position:sticky;top:0;z-index:5;display:flex;align-items:flex-end;gap:4px;overflow-x:auto;overflow-y:hidden;min-height:47px;padding:10px 12px 0;border-bottom:1px solid #dfe5eb;background:#f7f9fb}.opened-api-tab{--tab-accent: #52606f;--tab-soft: #eef2f6;display:inline-grid;grid-template-columns:auto minmax(100px,210px) 20px;align-items:center;gap:7px;min-width:180px;max-width:280px;height:36px;padding:7px 8px 8px;border:1px solid color-mix(in srgb,var(--tab-accent) 45%,#d5dce5);border-bottom-color:#dfe5eb;border-radius:7px 7px 0 0;text-align:left;background:var(--tab-soft);cursor:pointer}.opened-api-tab-get{--tab-accent: #246bce;--tab-soft: #eef5ff}.opened-api-tab-post{--tab-accent: #168456;--tab-soft: #edf8f2}.opened-api-tab-put{--tab-accent: #9a6200;--tab-soft: #fff5e4}.opened-api-tab-delete{--tab-accent: #c23131;--tab-soft: #fff0f0}.opened-api-tab-patch{--tab-accent: #7953b8;--tab-soft: #f4efff}.opened-api-tab-options,.opened-api-tab-head{--tab-accent: #52606f;--tab-soft: #f0f3f6}.opened-api-tab.active{position:relative;margin-bottom:-1px;border-color:var(--tab-accent);border-bottom-color:#fff;background:#fff;box-shadow:inset 0 3px 0 var(--tab-accent)}.opened-api-tab-text{min-width:0}.opened-api-tab-text strong{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opened-api-tab-text strong{color:#1d2732;font-size:12px;font-weight:800}.opened-api-tab-method{display:inline-flex;align-items:center;justify-content:center;min-width:34px;height:18px;padding:0 5px;border-radius:4px;color:#fff;font-size:9px;font-weight:900;line-height:1}.opened-api-tab-close{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:0;border-radius:5px;color:var(--tab-accent);background:transparent;cursor:pointer}.opened-api-tab-close:hover{color:#b63831;background:#fff0ef}.endpoint-detail{padding:22px}.detail-header{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;padding-bottom:18px;border-bottom:1px solid #e1e6ec}.detail-actions{display:flex;align-items:center;gap:8px;margin-left:auto}.endpoint-title-line{display:flex;align-items:center;gap:10px}.endpoint-title-line h2{margin:0;color:#121923;font-size:22px;line-height:1.25}.path-row{display:flex;align-items:center;gap:8px;min-width:0;margin-top:10px}.path-line{display:block;min-width:0;color:#354253;font-size:13px;white-space:normal;word-break:break-all}.path-copy-button{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;gap:4px;min-height:28px;padding:0 8px;border:1px solid #d4dce5;border-radius:6px;color:#415166;background:#fff;font-size:12px;font-weight:800}.path-copy-button:hover{border-color:#9fb0c1;background:#f7f9fb}.tag-row{display:flex;flex-wrap:wrap;gap:6px;margin-top:12px}.tag-row span{padding:4px 8px;border-radius:5px;color:#475364;background:#edf1f5;font-size:12px;font-weight:700}.description{margin:16px 0 0;color:#4f5c6a;line-height:1.7}.copy-preview{margin-top:18px;border:1px solid #d9e0e7;border-radius:8px;background:#f8fafb}.copy-preview-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px;border-bottom:1px solid #dfe5eb}.copy-tabs{display:inline-flex;gap:4px;padding:3px;border:1px solid #d4dce5;border-radius:7px;background:#fff}.copy-tabs button{display:inline-flex;align-items:center;gap:6px;min-height:30px;padding:0 10px;border:0;border-radius:5px;color:#566373;background:transparent;font-size:13px;font-weight:800}.copy-tabs button.active{color:#fff;background:#1b6b65}.copy-preview-content{overflow:auto;min-height:calc(3 * 1.6em + 28px);max-height:calc(25.6em + 28px);margin:0;padding:14px;color:#e5edf6;background:#111827;font-family:SFMono-Regular,Consolas,monospace;font-size:12px;line-height:1.6;white-space:pre-wrap;overflow-wrap:anywhere}.request-tester{display:grid;gap:14px;padding:14px;background:#fff}.endpoint-links-config{display:grid;gap:12px;padding:14px;background:#fff}.links-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.links-actions,.link-editor-actions{display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end}.link-editor-panel{min-width:0}.link-button{text-decoration:none}.request-line{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:end;gap:10px}.request-actions{display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end}.request-line label,.tester-form-grid label,.param-editor label{display:grid;gap:5px;min-width:0;color:#647182;font-size:12px;font-weight:800}.request-line input,.tester-form-grid input,.tester-form-grid select,.param-editor input{width:100%;min-height:36px;padding:0 10px;border:1px solid #cfd7e1;border-radius:6px;outline:0;color:#15202c;background:#fff}.request-preview{display:block;overflow:hidden;padding:9px 10px;border:1px solid #dfe5eb;border-radius:6px;color:#334254;background:#f7f9fb;font-size:12px;text-overflow:ellipsis;white-space:nowrap}.tester-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.tester-panel{min-width:0;padding:12px;border:1px solid #dfe5eb;border-radius:7px;background:#fbfcfd}.tester-panel-title{display:flex;align-items:center;gap:6px;margin-bottom:10px;color:#243242;font-size:13px;font-weight:900}.tester-form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.param-editor+.param-editor{margin-top:12px}.param-editor h4{margin:0 0 8px;color:#516070;font-size:12px}.param-editor label+label{margin-top:8px}.param-editor span{display:inline-flex;align-items:center;gap:3px}.param-editor b{color:#c23131}.tester-panel textarea{width:100%;min-height:168px;resize:vertical;padding:10px;border:1px solid #cfd7e1;border-radius:6px;outline:0;color:#1f2b38;background:#fff;font-family:SFMono-Regular,Consolas,monospace;font-size:12px;line-height:1.5}.tester-panel textarea:disabled{color:#8792a1;background:#f2f5f8}.response-panel{background:#fff}.response-meta-line{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:10px}.response-meta-line span{padding:5px 8px;border-radius:5px;color:#536171;background:#eef2f6;font-size:12px;font-weight:800}.response-meta-line .status-ok{color:#176b4f;background:#e5f6ef}.response-meta-line .status-error{color:#a8322b;background:#fff0ef}.response-panel pre{overflow:auto;max-height:360px;margin:0;padding:12px;border-radius:6px;color:#202a36;background:#f7f9fb;font-family:SFMono-Regular,Consolas,monospace;font-size:12px;line-height:1.55}.detail-section{padding:20px 0;border-bottom:1px solid #e8edf2}.section-title{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}.section-title h3{margin:0;font-size:16px}.section-title span{min-width:28px;padding:3px 8px;border-radius:999px;color:#1b6b65;background:#e7f4f2;text-align:center;font-size:12px;font-weight:800}.media-block+.media-block,.response-block+.response-block{margin-top:18px}.media-block h4{margin:0 0 8px;color:#516070;font-family:SFMono-Regular,Consolas,monospace;font-size:13px}.response-heading{display:flex;align-items:center;gap:10px;margin-bottom:10px}.response-heading strong{padding:4px 8px;border-radius:5px;color:#fff;background:#202a36;font-size:12px}.response-heading span{color:#657181;font-size:13px}.table-wrap{overflow:auto;border:1px solid #dfe5eb;border-radius:7px}table{width:100%;min-width:920px;border-collapse:collapse;font-size:13px}th,td{padding:10px 12px;border-bottom:1px solid #edf1f4;text-align:left;vertical-align:top}th{position:sticky;top:0;z-index:1;color:#526171;background:#f7f9fb;font-size:12px;font-weight:800}tr:last-child td{border-bottom:0}td code{color:#17202b;font-family:SFMono-Regular,Consolas,monospace;font-size:12px}.muted{margin:0;color:#798596}.inline-error{display:flex;align-items:center;gap:8px;margin-top:12px;padding:10px 12px;border:1px solid #f1c1bd;border-radius:7px;color:#9a2d27;background:#fff4f3}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:30;display:grid;place-items:center;padding:20px;background:#1219237a;animation:backdrop-enter .14s ease-out}.import-dialog{width:min(720px,100%);max-height:calc(100vh - 40px);overflow:auto;border-radius:8px;border:1px solid #d7dde4;background:#fff;box-shadow:0 18px 60px #12192338}.auth-dialog{width:min(560px,100%);max-height:calc(100vh - 40px);overflow:auto;border-radius:8px;border:1px solid #d7dde4;background:#fff;box-shadow:0 18px 60px #12192338}.api-config-dialog{width:80vw;min-width:1000px;max-width:80vw;max-height:calc(100vh - 40px);overflow:auto;border-radius:8px;border:1px solid #d7dde4;background:#fff;box-shadow:0 18px 60px #12192338}.import-dialog-header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:18px 20px 14px;border-bottom:1px solid #e5eaf0}.import-dialog-header h2{margin:0;font-size:20px}.import-badge{display:inline-flex;align-items:center;min-height:28px;padding:0 10px;border-radius:999px;font-size:12px;font-weight:800;white-space:nowrap}.import-badge.running{color:#8a5b00;background:#fff4d8}.import-badge.success{color:#176b4f;background:#e5f6ef}.import-badge.error{color:#a8322b;background:#fff0ef}.progress-track{height:8px;margin:16px 20px 0;overflow:hidden;border-radius:999px;background:#e9eef3}.progress-bar{height:100%;border-radius:inherit;background:#1b6b65;transition:width .24s ease}.progress-bar.running{background:linear-gradient(90deg,#1b6b65,#4f8fcb)}.progress-bar.success{background:#168456}.progress-bar.error{background:#c23131}.import-summary{display:grid;gap:12px;padding:18px 20px}.auth-dialog-body{display:grid;gap:14px;padding:18px 20px}.api-config-body{display:grid;gap:14px;padding:18px 20px;background:#f7f9fb}.api-config-tabs{display:inline-flex;width:fit-content;gap:4px;padding:3px;border:1px solid #d4dce5;border-radius:7px;background:#fff}.api-config-tabs button{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:32px;padding:0 12px;border:0;border-radius:5px;color:#566373;background:transparent;font-size:13px;font-weight:800}.api-config-tabs button.active{color:#fff;background:#1b6b65}.api-config-errors{display:flex;align-items:flex-start;gap:10px;padding:10px 12px;border:1px solid #efcbc8;border-radius:7px;color:#8f2f29;background:#fff7f6}.api-config-errors strong{display:block;margin-bottom:4px;font-size:13px}.api-config-errors p{margin:0;font-size:12px;line-height:1.45}.api-cli-editor{width:100%;min-height:calc(4.65em + 24px);max-height:calc(24.8em + 24px);overflow-y:auto;resize:vertical;padding:12px;border:1px solid #334155;border-radius:7px;outline:0;color:#e5edf6;background:#111827;font-family:SFMono-Regular,Consolas,monospace;font-size:12px;line-height:1.55}.api-cli-editor::placeholder{color:#94a3b8}.api-cli-editor:focus{border-color:#5aa69d;box-shadow:0 0 0 3px #5aa69d38}.api-config-section{display:grid;gap:12px;padding:14px;border:1px solid #dfe5eb;border-radius:8px;background:#fff}.section-title-row{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.section-title-row h3{margin:0;color:#243242;font-size:15px}.section-title-row p{margin:4px 0 0;color:#657181;font-size:12px}.section-title-row code{max-width:420px;overflow:hidden;padding:5px 7px;border:1px solid #dce2e9;border-radius:6px;color:#415166;background:#f7f9fb;text-overflow:ellipsis;white-space:nowrap}.api-config-grid{display:grid;grid-template-columns:140px repeat(2,minmax(0,1fr));gap:10px}.api-config-grid.compact{grid-template-columns:repeat(3,minmax(0,1fr)) auto}.api-config-grid label,.field-config-row label{display:grid;gap:5px;min-width:0;color:#647182;font-size:12px;font-weight:800}.api-config-grid input,.api-config-grid select,.api-config-grid textarea,.field-config-row input,.field-config-row select{width:100%;min-height:36px;padding:0 10px;border:1px solid #cfd7e1;border-radius:6px;outline:0;color:#15202c;background:#fff}.api-config-grid textarea{min-height:82px;padding:10px;resize:vertical;line-height:1.45}.span-2{grid-column:span 2}.span-all{grid-column:1 / -1}.inline-check{display:inline-flex;align-items:center;gap:6px;min-height:32px;color:#415166;font-size:13px;font-weight:800}.field-config-table{display:grid;gap:8px;overflow-x:auto}.field-config-item{display:grid;gap:8px}.field-config-head,.field-config-row{display:grid;grid-template-columns:minmax(150px,1.1fr) 120px 58px minmax(180px,1.2fr) minmax(120px,.8fr) 38px;gap:8px;align-items:center;min-width:760px}.field-config-head.with-location,.field-config-row.with-location{grid-template-columns:98px minmax(150px,1.1fr) 120px 58px minmax(180px,1.2fr) minmax(120px,.8fr) 38px}.field-config-head{color:#657181;font-size:12px;font-weight:900}.field-check{place-items:center}.field-check input,.inline-check input{width:16px;min-height:16px}.icon-danger-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;border:1px solid #efcbc8;border-radius:6px;color:#b63831;background:#fff7f6}.field-add-row{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:34px;border:1px dashed #b8c4d0;border-radius:6px;color:#415166;background:#fbfcfd;font-weight:800}.child-field-config{display:grid;gap:8px;margin-left:18px;padding:10px;border-left:3px solid #b9d7d3;border-radius:0 7px 7px 0;background:#f7fbfa}.child-field-title{color:#415166;font-size:12px;font-weight:900}.response-config{display:grid;gap:10px;padding:12px;border:1px solid #e3e8ee;border-radius:7px;background:#fbfcfd}.auth-message{display:flex;align-items:flex-start;gap:8px;padding:10px 12px;border:1px solid #d9e0e7;border-radius:7px;color:#2f3d4d;background:#f7f9fb}.auth-target{display:grid;gap:4px}.auth-target span,.auth-fields label{color:#657181;font-size:12px;font-weight:800}.auth-target strong{overflow-wrap:anywhere;color:#1f2b38;font-family:SFMono-Regular,Consolas,monospace;font-size:13px;line-height:1.5}.auth-fields{display:grid;gap:10px}.auth-fields label{display:grid;gap:6px}.auth-fields input{width:100%;min-height:38px;padding:0 10px;border:1px solid #cfd7e1;border-radius:6px;outline:0;color:#15202c;background:#fff}.import-summary div:not(.import-counts):not(.import-error){display:grid;gap:4px}.import-summary span{color:#657181;font-size:12px;font-weight:700}.import-summary strong{overflow-wrap:anywhere;color:#1f2b38;font-family:SFMono-Regular,Consolas,monospace;font-size:13px;line-height:1.5}.import-counts{display:flex;flex-wrap:wrap;gap:8px}.import-counts span{padding:6px 9px;border-radius:6px;color:#1b6b65;background:#e7f4f2}.import-error{display:flex;align-items:flex-start;gap:8px;padding:10px 12px;border:1px solid #f1c1bd;border-radius:7px;color:#9a2d27;background:#fff4f3}.import-dialog-actions{display:flex;justify-content:flex-end;padding:0 20px 18px}.state-screen,.empty-state{display:grid;place-items:center;align-content:center;min-height:100vh;padding:24px;text-align:center}.empty-state{min-height:60vh}.state-icon{width:42px;height:42px;margin-bottom:14px;color:#1b6b65}.state-icon.error{color:#b8312d}.state-screen h1,.empty-state h2{margin-bottom:8px}.spin{animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@keyframes backdrop-enter{0%{opacity:0}}@keyframes dialog-enter{0%{opacity:0;transform:translateY(8px)}}@media(max-width:980px){.app-shell{padding:12px}.topbar{align-items:flex-start;flex-direction:column}.topbar-actions{width:100%;justify-content:space-between}.version-switcher,.version-switcher select{width:100%}.version-manager{width:100%;min-width:0}.version-item-main,.version-env-editor,.version-env-summary{grid-template-columns:1fr}.version-item-stats,.version-item-actions{justify-content:flex-start}.source-panel{grid-template-columns:1fr}.source-panel-header{align-items:stretch;flex-direction:column}.source-panel-header>.secondary-button,.source-panel-actions,.source-tabs{width:100%}.source-panel-actions{justify-content:stretch}.source-panel-actions .secondary-button{flex:1 1 180px}.source-tabs{flex-wrap:wrap}.document-dialog-tabs{width:100%}.source-form,.source-auth,.source-auth-fields,.api-config-grid,.api-config-grid.compact{grid-template-columns:1fr}.span-2,.span-all{grid-column:auto}.workspace{grid-template-columns:1fr;height:auto}.sidebar{max-height:46vh}.detail-pane{min-height:60vh}.detail-header{flex-direction:column}.copy-preview-toolbar{align-items:stretch;flex-direction:column}.copy-tabs,.copy-inline-button{width:100%}.copy-tabs button{flex:1;justify-content:center}.request-line,.tester-grid,.links-grid,.tester-form-grid{grid-template-columns:1fr}}@media(max-width:640px){.filters{grid-template-columns:1fr}.method-tabs{grid-template-columns:repeat(3,minmax(0,1fr))}.topbar-actions,.sync-meta{align-items:stretch;flex-direction:column;width:100%}.primary-button,.secondary-button,.copy-inline-button,.source-tabs{width:100%}.source-tabs button{flex:1}.document-dialog{max-height:calc(100vh - 20px)}.document-dialog-tabs{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));overflow:visible;padding-inline:12px}.document-dialog-tabs button{min-width:0;padding-inline:6px}.document-dialog-body{min-height:210px;padding:16px}.document-dialog-actions .clear-document-source{margin-right:0}.import-dialog-actions .secondary-button{width:100%}.import-dialog-actions{flex-direction:column-reverse;gap:8px}.endpoint-title-line{align-items:flex-start;flex-direction:column}}
|