apteva 0.2.8 → 0.2.10
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/App.44ge5b89.js +218 -0
- package/dist/index.html +2 -2
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/binary.ts +36 -36
- package/src/db.ts +130 -16
- package/src/integrations/composio.ts +437 -0
- package/src/integrations/index.ts +80 -0
- package/src/openapi.ts +1724 -0
- package/src/routes/api.ts +599 -107
- package/src/server.ts +82 -8
- package/src/web/App.tsx +3 -0
- package/src/web/components/agents/AgentPanel.tsx +84 -38
- package/src/web/components/api/ApiDocsPage.tsx +583 -0
- package/src/web/components/common/Icons.tsx +8 -0
- package/src/web/components/common/Modal.tsx +183 -0
- package/src/web/components/layout/Sidebar.tsx +7 -1
- package/src/web/components/mcp/IntegrationsPanel.tsx +743 -0
- package/src/web/components/mcp/McpPage.tsx +242 -83
- package/src/web/components/settings/SettingsPage.tsx +24 -9
- package/src/web/components/tasks/TasksPage.tsx +1 -1
- package/src/web/index.html +1 -1
- package/src/web/types.ts +4 -1
- package/dist/App.hzbfeg94.js +0 -217
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
import { useAuth } from "../../context";
|
|
3
|
+
|
|
4
|
+
interface OpenApiPath {
|
|
5
|
+
[method: string]: {
|
|
6
|
+
tags?: string[];
|
|
7
|
+
summary?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
parameters?: Array<{
|
|
10
|
+
name: string;
|
|
11
|
+
in: string;
|
|
12
|
+
required?: boolean;
|
|
13
|
+
schema?: { type: string };
|
|
14
|
+
description?: string;
|
|
15
|
+
}>;
|
|
16
|
+
requestBody?: {
|
|
17
|
+
required?: boolean;
|
|
18
|
+
content?: {
|
|
19
|
+
[mediaType: string]: {
|
|
20
|
+
schema?: any;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
responses?: {
|
|
25
|
+
[code: string]: {
|
|
26
|
+
description?: string;
|
|
27
|
+
content?: any;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface OpenApiSpec {
|
|
34
|
+
info: {
|
|
35
|
+
title: string;
|
|
36
|
+
description: string;
|
|
37
|
+
version: string;
|
|
38
|
+
};
|
|
39
|
+
tags?: Array<{ name: string; description: string }>;
|
|
40
|
+
paths: { [path: string]: OpenApiPath };
|
|
41
|
+
components?: {
|
|
42
|
+
schemas?: { [name: string]: any };
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const METHOD_COLORS: Record<string, string> = {
|
|
47
|
+
get: "#61affe",
|
|
48
|
+
post: "#49cc90",
|
|
49
|
+
put: "#fca130",
|
|
50
|
+
delete: "#f93e3e",
|
|
51
|
+
patch: "#50e3c2",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export function ApiDocsPage() {
|
|
55
|
+
const { authFetch } = useAuth();
|
|
56
|
+
const [spec, setSpec] = useState<OpenApiSpec | null>(null);
|
|
57
|
+
const [loading, setLoading] = useState(true);
|
|
58
|
+
const [expandedPaths, setExpandedPaths] = useState<Set<string>>(new Set());
|
|
59
|
+
const [selectedTag, setSelectedTag] = useState<string | null>(null);
|
|
60
|
+
const [copied, setCopied] = useState(false);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
loadSpec();
|
|
64
|
+
}, []);
|
|
65
|
+
|
|
66
|
+
async function copyToClipboard() {
|
|
67
|
+
if (!spec) return;
|
|
68
|
+
try {
|
|
69
|
+
await navigator.clipboard.writeText(JSON.stringify(spec, null, 2));
|
|
70
|
+
setCopied(true);
|
|
71
|
+
setTimeout(() => setCopied(false), 2000);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.error("Failed to copy:", err);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function downloadJson() {
|
|
78
|
+
if (!spec) return;
|
|
79
|
+
const blob = new Blob([JSON.stringify(spec, null, 2)], { type: "application/json" });
|
|
80
|
+
const url = URL.createObjectURL(blob);
|
|
81
|
+
const a = document.createElement("a");
|
|
82
|
+
a.href = url;
|
|
83
|
+
a.download = "apteva-openapi.json";
|
|
84
|
+
a.click();
|
|
85
|
+
URL.revokeObjectURL(url);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function loadSpec() {
|
|
89
|
+
try {
|
|
90
|
+
const res = await authFetch("/api/openapi");
|
|
91
|
+
if (res.ok) {
|
|
92
|
+
const data = await res.json();
|
|
93
|
+
setSpec(data);
|
|
94
|
+
}
|
|
95
|
+
} catch (err) {
|
|
96
|
+
console.error("Failed to load OpenAPI spec:", err);
|
|
97
|
+
} finally {
|
|
98
|
+
setLoading(false);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function togglePath(pathKey: string) {
|
|
103
|
+
setExpandedPaths((prev) => {
|
|
104
|
+
const next = new Set(prev);
|
|
105
|
+
if (next.has(pathKey)) {
|
|
106
|
+
next.delete(pathKey);
|
|
107
|
+
} else {
|
|
108
|
+
next.add(pathKey);
|
|
109
|
+
}
|
|
110
|
+
return next;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function getSchemaPreview(schema: any, depth = 0): string {
|
|
115
|
+
if (!schema) return "{}";
|
|
116
|
+
if (depth > 2) return "...";
|
|
117
|
+
|
|
118
|
+
if (schema.$ref) {
|
|
119
|
+
const refName = schema.$ref.split("/").pop();
|
|
120
|
+
return refName || "Object";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (schema.type === "array") {
|
|
124
|
+
const itemType = getSchemaPreview(schema.items, depth + 1);
|
|
125
|
+
return `${itemType}[]`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (schema.type === "object" && schema.properties) {
|
|
129
|
+
const props = Object.entries(schema.properties)
|
|
130
|
+
.slice(0, 3)
|
|
131
|
+
.map(([k, v]: [string, any]) => `${k}: ${v.type || "any"}`)
|
|
132
|
+
.join(", ");
|
|
133
|
+
const more = Object.keys(schema.properties).length > 3 ? ", ..." : "";
|
|
134
|
+
return `{ ${props}${more} }`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return schema.type || "any";
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (loading) {
|
|
141
|
+
return (
|
|
142
|
+
<div style={{ padding: 24 }}>
|
|
143
|
+
<p style={{ color: "#888" }}>Loading API documentation...</p>
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!spec) {
|
|
149
|
+
return (
|
|
150
|
+
<div style={{ padding: 24 }}>
|
|
151
|
+
<p style={{ color: "#f66" }}>Failed to load API documentation</p>
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const tags = spec.tags || [];
|
|
157
|
+
const paths = Object.entries(spec.paths);
|
|
158
|
+
|
|
159
|
+
// Extract schema names referenced by a method
|
|
160
|
+
function getReferencedSchemas(method: any): Set<string> {
|
|
161
|
+
const refs = new Set<string>();
|
|
162
|
+
|
|
163
|
+
function extractRefs(obj: any) {
|
|
164
|
+
if (!obj) return;
|
|
165
|
+
if (typeof obj === "object") {
|
|
166
|
+
if (obj.$ref) {
|
|
167
|
+
const name = obj.$ref.split("/").pop();
|
|
168
|
+
if (name) refs.add(name);
|
|
169
|
+
}
|
|
170
|
+
for (const value of Object.values(obj)) {
|
|
171
|
+
extractRefs(value);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
extractRefs(method.requestBody);
|
|
177
|
+
extractRefs(method.responses);
|
|
178
|
+
return refs;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Get all schemas referenced by filtered endpoints
|
|
182
|
+
function getFilteredSchemas(): string[] {
|
|
183
|
+
if (!selectedTag || !spec.components?.schemas) {
|
|
184
|
+
return Object.keys(spec.components?.schemas || {});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const usedSchemas = new Set<string>();
|
|
188
|
+
|
|
189
|
+
for (const [_, methods] of filteredPaths) {
|
|
190
|
+
for (const [method, details] of Object.entries(methods)) {
|
|
191
|
+
if (!["get", "post", "put", "delete", "patch"].includes(method)) continue;
|
|
192
|
+
if (details.tags?.includes(selectedTag)) {
|
|
193
|
+
const refs = getReferencedSchemas(details);
|
|
194
|
+
refs.forEach(r => usedSchemas.add(r));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return Array.from(usedSchemas);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Filter paths by selected tag
|
|
203
|
+
const filteredPaths = selectedTag
|
|
204
|
+
? paths.filter(([_, methods]) =>
|
|
205
|
+
Object.values(methods).some((m) => m.tags?.includes(selectedTag))
|
|
206
|
+
)
|
|
207
|
+
: paths;
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<div style={{ padding: 24, maxWidth: 1000, height: "100%", overflowY: "auto" }}>
|
|
211
|
+
<div style={{ marginBottom: 24 }}>
|
|
212
|
+
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 8 }}>
|
|
213
|
+
<h1 style={{ fontSize: 24, fontWeight: 600 }}>
|
|
214
|
+
{spec.info.title}
|
|
215
|
+
</h1>
|
|
216
|
+
<div style={{ display: "flex", gap: 8 }}>
|
|
217
|
+
<button
|
|
218
|
+
onClick={copyToClipboard}
|
|
219
|
+
style={{
|
|
220
|
+
padding: "8px 16px",
|
|
221
|
+
borderRadius: 4,
|
|
222
|
+
border: "1px solid #333",
|
|
223
|
+
background: copied ? "#49cc90" : "#1a1a2e",
|
|
224
|
+
color: copied ? "#000" : "#fff",
|
|
225
|
+
cursor: "pointer",
|
|
226
|
+
fontSize: 12,
|
|
227
|
+
fontFamily: "inherit",
|
|
228
|
+
}}
|
|
229
|
+
>
|
|
230
|
+
{copied ? "Copied!" : "Copy JSON"}
|
|
231
|
+
</button>
|
|
232
|
+
<button
|
|
233
|
+
onClick={downloadJson}
|
|
234
|
+
style={{
|
|
235
|
+
padding: "8px 16px",
|
|
236
|
+
borderRadius: 4,
|
|
237
|
+
border: "1px solid #333",
|
|
238
|
+
background: "#1a1a2e",
|
|
239
|
+
color: "#fff",
|
|
240
|
+
cursor: "pointer",
|
|
241
|
+
fontSize: 12,
|
|
242
|
+
fontFamily: "inherit",
|
|
243
|
+
}}
|
|
244
|
+
>
|
|
245
|
+
Download
|
|
246
|
+
</button>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
<p style={{ color: "#888", marginBottom: 8 }}>
|
|
250
|
+
{spec.info.description.split("\n")[0]}
|
|
251
|
+
</p>
|
|
252
|
+
<p style={{ color: "#666", fontSize: 12 }}>Version: {spec.info.version}</p>
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
{/* Base URL */}
|
|
256
|
+
<div
|
|
257
|
+
style={{
|
|
258
|
+
background: "#1a1a2e",
|
|
259
|
+
padding: 12,
|
|
260
|
+
borderRadius: 6,
|
|
261
|
+
marginBottom: 24,
|
|
262
|
+
fontFamily: "monospace",
|
|
263
|
+
}}
|
|
264
|
+
>
|
|
265
|
+
<span style={{ color: "#888" }}>Base URL: </span>
|
|
266
|
+
<span style={{ color: "#61affe" }}>
|
|
267
|
+
{window.location.origin}/api
|
|
268
|
+
</span>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
{/* Tag filters */}
|
|
272
|
+
<div style={{ marginBottom: 24, display: "flex", flexWrap: "wrap", gap: 8 }}>
|
|
273
|
+
<button
|
|
274
|
+
onClick={() => setSelectedTag(null)}
|
|
275
|
+
style={{
|
|
276
|
+
padding: "6px 12px",
|
|
277
|
+
borderRadius: 4,
|
|
278
|
+
border: "1px solid #333",
|
|
279
|
+
background: selectedTag === null ? "#333" : "transparent",
|
|
280
|
+
color: selectedTag === null ? "#fff" : "#888",
|
|
281
|
+
cursor: "pointer",
|
|
282
|
+
fontSize: 12,
|
|
283
|
+
}}
|
|
284
|
+
>
|
|
285
|
+
All
|
|
286
|
+
</button>
|
|
287
|
+
{tags.map((tag) => (
|
|
288
|
+
<button
|
|
289
|
+
key={tag.name}
|
|
290
|
+
onClick={() => setSelectedTag(tag.name)}
|
|
291
|
+
style={{
|
|
292
|
+
padding: "6px 12px",
|
|
293
|
+
borderRadius: 4,
|
|
294
|
+
border: "1px solid #333",
|
|
295
|
+
background: selectedTag === tag.name ? "#333" : "transparent",
|
|
296
|
+
color: selectedTag === tag.name ? "#fff" : "#888",
|
|
297
|
+
cursor: "pointer",
|
|
298
|
+
fontSize: 12,
|
|
299
|
+
}}
|
|
300
|
+
title={tag.description}
|
|
301
|
+
>
|
|
302
|
+
{tag.name}
|
|
303
|
+
</button>
|
|
304
|
+
))}
|
|
305
|
+
</div>
|
|
306
|
+
|
|
307
|
+
{/* Endpoints */}
|
|
308
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
|
309
|
+
{filteredPaths.map(([path, methods]) =>
|
|
310
|
+
Object.entries(methods)
|
|
311
|
+
.filter(([method]) => ["get", "post", "put", "delete", "patch"].includes(method))
|
|
312
|
+
.map(([method, details]) => {
|
|
313
|
+
const pathKey = `${method}:${path}`;
|
|
314
|
+
const isExpanded = expandedPaths.has(pathKey);
|
|
315
|
+
const methodUpper = method.toUpperCase();
|
|
316
|
+
const color = METHOD_COLORS[method] || "#888";
|
|
317
|
+
|
|
318
|
+
return (
|
|
319
|
+
<div
|
|
320
|
+
key={pathKey}
|
|
321
|
+
style={{
|
|
322
|
+
border: "1px solid #333",
|
|
323
|
+
borderRadius: 6,
|
|
324
|
+
overflow: "hidden",
|
|
325
|
+
}}
|
|
326
|
+
>
|
|
327
|
+
{/* Header */}
|
|
328
|
+
<div
|
|
329
|
+
onClick={() => togglePath(pathKey)}
|
|
330
|
+
style={{
|
|
331
|
+
display: "flex",
|
|
332
|
+
alignItems: "center",
|
|
333
|
+
gap: 12,
|
|
334
|
+
padding: "12px 16px",
|
|
335
|
+
background: isExpanded ? "#1a1a2e" : "transparent",
|
|
336
|
+
cursor: "pointer",
|
|
337
|
+
}}
|
|
338
|
+
>
|
|
339
|
+
<span
|
|
340
|
+
style={{
|
|
341
|
+
background: color,
|
|
342
|
+
color: "#000",
|
|
343
|
+
padding: "4px 8px",
|
|
344
|
+
borderRadius: 4,
|
|
345
|
+
fontSize: 11,
|
|
346
|
+
fontWeight: 600,
|
|
347
|
+
minWidth: 60,
|
|
348
|
+
textAlign: "center",
|
|
349
|
+
}}
|
|
350
|
+
>
|
|
351
|
+
{methodUpper}
|
|
352
|
+
</span>
|
|
353
|
+
<span style={{ fontFamily: "monospace", color: "#fff" }}>
|
|
354
|
+
{path}
|
|
355
|
+
</span>
|
|
356
|
+
<span style={{ color: "#888", flex: 1, fontSize: 13 }}>
|
|
357
|
+
{details.summary}
|
|
358
|
+
</span>
|
|
359
|
+
<span style={{ color: "#666", fontSize: 12 }}>
|
|
360
|
+
{isExpanded ? "[-]" : "[+]"}
|
|
361
|
+
</span>
|
|
362
|
+
</div>
|
|
363
|
+
|
|
364
|
+
{/* Expanded details */}
|
|
365
|
+
{isExpanded && (
|
|
366
|
+
<div
|
|
367
|
+
style={{
|
|
368
|
+
padding: 16,
|
|
369
|
+
background: "#0d0d1a",
|
|
370
|
+
borderTop: "1px solid #333",
|
|
371
|
+
}}
|
|
372
|
+
>
|
|
373
|
+
{details.description && (
|
|
374
|
+
<p style={{ color: "#888", marginBottom: 16, fontSize: 13 }}>
|
|
375
|
+
{details.description}
|
|
376
|
+
</p>
|
|
377
|
+
)}
|
|
378
|
+
|
|
379
|
+
{/* Parameters */}
|
|
380
|
+
{details.parameters && details.parameters.length > 0 && (
|
|
381
|
+
<div style={{ marginBottom: 16 }}>
|
|
382
|
+
<h4 style={{ fontSize: 13, color: "#888", marginBottom: 8 }}>
|
|
383
|
+
Parameters
|
|
384
|
+
</h4>
|
|
385
|
+
<div
|
|
386
|
+
style={{
|
|
387
|
+
background: "#1a1a2e",
|
|
388
|
+
borderRadius: 4,
|
|
389
|
+
padding: 12,
|
|
390
|
+
}}
|
|
391
|
+
>
|
|
392
|
+
{details.parameters.map((param) => (
|
|
393
|
+
<div
|
|
394
|
+
key={param.name}
|
|
395
|
+
style={{
|
|
396
|
+
display: "flex",
|
|
397
|
+
gap: 12,
|
|
398
|
+
marginBottom: 8,
|
|
399
|
+
fontSize: 12,
|
|
400
|
+
}}
|
|
401
|
+
>
|
|
402
|
+
<span style={{ color: "#61affe", minWidth: 100 }}>
|
|
403
|
+
{param.name}
|
|
404
|
+
{param.required && (
|
|
405
|
+
<span style={{ color: "#f66" }}>*</span>
|
|
406
|
+
)}
|
|
407
|
+
</span>
|
|
408
|
+
<span style={{ color: "#666" }}>({param.in})</span>
|
|
409
|
+
<span style={{ color: "#888" }}>
|
|
410
|
+
{param.schema?.type || "string"}
|
|
411
|
+
</span>
|
|
412
|
+
{param.description && (
|
|
413
|
+
<span style={{ color: "#666" }}>
|
|
414
|
+
- {param.description}
|
|
415
|
+
</span>
|
|
416
|
+
)}
|
|
417
|
+
</div>
|
|
418
|
+
))}
|
|
419
|
+
</div>
|
|
420
|
+
</div>
|
|
421
|
+
)}
|
|
422
|
+
|
|
423
|
+
{/* Request Body */}
|
|
424
|
+
{details.requestBody && (
|
|
425
|
+
<div style={{ marginBottom: 16 }}>
|
|
426
|
+
<h4 style={{ fontSize: 13, color: "#888", marginBottom: 8 }}>
|
|
427
|
+
Request Body
|
|
428
|
+
{details.requestBody.required && (
|
|
429
|
+
<span style={{ color: "#f66" }}> (required)</span>
|
|
430
|
+
)}
|
|
431
|
+
</h4>
|
|
432
|
+
<div
|
|
433
|
+
style={{
|
|
434
|
+
background: "#1a1a2e",
|
|
435
|
+
borderRadius: 4,
|
|
436
|
+
padding: 12,
|
|
437
|
+
fontFamily: "monospace",
|
|
438
|
+
fontSize: 12,
|
|
439
|
+
color: "#49cc90",
|
|
440
|
+
}}
|
|
441
|
+
>
|
|
442
|
+
{Object.entries(details.requestBody.content || {}).map(
|
|
443
|
+
([mediaType, content]) => (
|
|
444
|
+
<div key={mediaType}>
|
|
445
|
+
<span style={{ color: "#666" }}>{mediaType}: </span>
|
|
446
|
+
{getSchemaPreview(content.schema)}
|
|
447
|
+
</div>
|
|
448
|
+
)
|
|
449
|
+
)}
|
|
450
|
+
</div>
|
|
451
|
+
</div>
|
|
452
|
+
)}
|
|
453
|
+
|
|
454
|
+
{/* Responses */}
|
|
455
|
+
{details.responses && (
|
|
456
|
+
<div>
|
|
457
|
+
<h4 style={{ fontSize: 13, color: "#888", marginBottom: 8 }}>
|
|
458
|
+
Responses
|
|
459
|
+
</h4>
|
|
460
|
+
<div
|
|
461
|
+
style={{
|
|
462
|
+
background: "#1a1a2e",
|
|
463
|
+
borderRadius: 4,
|
|
464
|
+
padding: 12,
|
|
465
|
+
}}
|
|
466
|
+
>
|
|
467
|
+
{Object.entries(details.responses).map(([code, resp]) => {
|
|
468
|
+
const respContent = resp.content?.["application/json"]?.schema;
|
|
469
|
+
const schemaRef = respContent?.$ref?.split("/").pop();
|
|
470
|
+
const schemaType = respContent?.type;
|
|
471
|
+
const schemaItems = respContent?.items?.$ref?.split("/").pop();
|
|
472
|
+
|
|
473
|
+
return (
|
|
474
|
+
<div
|
|
475
|
+
key={code}
|
|
476
|
+
style={{
|
|
477
|
+
marginBottom: 12,
|
|
478
|
+
fontSize: 12,
|
|
479
|
+
}}
|
|
480
|
+
>
|
|
481
|
+
<div style={{ display: "flex", gap: 12, marginBottom: 4 }}>
|
|
482
|
+
<span
|
|
483
|
+
style={{
|
|
484
|
+
color: code.startsWith("2") ? "#49cc90" : "#f66",
|
|
485
|
+
minWidth: 40,
|
|
486
|
+
}}
|
|
487
|
+
>
|
|
488
|
+
{code}
|
|
489
|
+
</span>
|
|
490
|
+
<span style={{ color: "#888" }}>{resp.description}</span>
|
|
491
|
+
</div>
|
|
492
|
+
{respContent && (
|
|
493
|
+
<div
|
|
494
|
+
style={{
|
|
495
|
+
marginLeft: 52,
|
|
496
|
+
padding: "8px 12px",
|
|
497
|
+
background: "#0d0d1a",
|
|
498
|
+
borderRadius: 4,
|
|
499
|
+
fontFamily: "monospace",
|
|
500
|
+
}}
|
|
501
|
+
>
|
|
502
|
+
{schemaRef ? (
|
|
503
|
+
<span style={{ color: "#61affe" }}>{schemaRef}</span>
|
|
504
|
+
) : schemaType === "array" && schemaItems ? (
|
|
505
|
+
<span style={{ color: "#61affe" }}>{schemaItems}[]</span>
|
|
506
|
+
) : schemaType === "array" ? (
|
|
507
|
+
<span style={{ color: "#888" }}>array</span>
|
|
508
|
+
) : (
|
|
509
|
+
<span style={{ color: "#888" }}>{getSchemaPreview(respContent)}</span>
|
|
510
|
+
)}
|
|
511
|
+
</div>
|
|
512
|
+
)}
|
|
513
|
+
</div>
|
|
514
|
+
);
|
|
515
|
+
})}
|
|
516
|
+
</div>
|
|
517
|
+
</div>
|
|
518
|
+
)}
|
|
519
|
+
</div>
|
|
520
|
+
)}
|
|
521
|
+
</div>
|
|
522
|
+
);
|
|
523
|
+
})
|
|
524
|
+
)}
|
|
525
|
+
</div>
|
|
526
|
+
|
|
527
|
+
{/* Schemas section */}
|
|
528
|
+
{spec.components?.schemas && getFilteredSchemas().length > 0 && (
|
|
529
|
+
<div style={{ marginTop: 32 }}>
|
|
530
|
+
<h2 style={{ fontSize: 18, fontWeight: 600, marginBottom: 16 }}>
|
|
531
|
+
Schemas {selectedTag && <span style={{ color: "#666", fontSize: 14 }}>({selectedTag})</span>}
|
|
532
|
+
</h2>
|
|
533
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
|
534
|
+
{getFilteredSchemas().map((name) => {
|
|
535
|
+
const schema = spec.components!.schemas![name];
|
|
536
|
+
if (!schema) return null;
|
|
537
|
+
return (
|
|
538
|
+
<div
|
|
539
|
+
key={name}
|
|
540
|
+
style={{
|
|
541
|
+
border: "1px solid #333",
|
|
542
|
+
borderRadius: 6,
|
|
543
|
+
padding: 12,
|
|
544
|
+
}}
|
|
545
|
+
>
|
|
546
|
+
<h3 style={{ fontSize: 14, color: "#61affe", marginBottom: 8 }}>
|
|
547
|
+
{name}
|
|
548
|
+
</h3>
|
|
549
|
+
{schema.properties && (
|
|
550
|
+
<div style={{ fontSize: 12 }}>
|
|
551
|
+
{Object.entries(schema.properties).map(([prop, propSchema]: [string, any]) => (
|
|
552
|
+
<div
|
|
553
|
+
key={prop}
|
|
554
|
+
style={{
|
|
555
|
+
display: "flex",
|
|
556
|
+
gap: 8,
|
|
557
|
+
marginBottom: 4,
|
|
558
|
+
fontFamily: "monospace",
|
|
559
|
+
}}
|
|
560
|
+
>
|
|
561
|
+
<span style={{ color: "#fff", minWidth: 120 }}>{prop}</span>
|
|
562
|
+
<span style={{ color: "#888" }}>
|
|
563
|
+
{propSchema.type || (propSchema.$ref ? propSchema.$ref.split("/").pop() : "any")}
|
|
564
|
+
{propSchema.nullable && " | null"}
|
|
565
|
+
</span>
|
|
566
|
+
{propSchema.enum && (
|
|
567
|
+
<span style={{ color: "#666" }}>
|
|
568
|
+
[{propSchema.enum.join(" | ")}]
|
|
569
|
+
</span>
|
|
570
|
+
)}
|
|
571
|
+
</div>
|
|
572
|
+
))}
|
|
573
|
+
</div>
|
|
574
|
+
)}
|
|
575
|
+
</div>
|
|
576
|
+
);
|
|
577
|
+
})}
|
|
578
|
+
</div>
|
|
579
|
+
</div>
|
|
580
|
+
)}
|
|
581
|
+
</div>
|
|
582
|
+
);
|
|
583
|
+
}
|
|
@@ -118,6 +118,14 @@ export function TelemetryIcon({ className = "w-4 h-4" }: IconProps) {
|
|
|
118
118
|
);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
export function ApiIcon({ className = "w-4 h-4" }: IconProps) {
|
|
122
|
+
return (
|
|
123
|
+
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
124
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
|
125
|
+
</svg>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
121
129
|
export function FilesIcon({ className = "w-4 h-4" }: IconProps) {
|
|
122
130
|
return (
|
|
123
131
|
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|