@winspan/claude-forge 8.35.0 → 8.36.0
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/core/ai/provider.d.ts +23 -1
- package/dist/core/ai/provider.d.ts.map +1 -1
- package/dist/core/ai/provider.js +67 -1
- package/dist/core/ai/provider.js.map +1 -1
- package/dist/core/ai/types.d.ts +28 -0
- package/dist/core/ai/types.d.ts.map +1 -1
- package/dist/daemon/handlers/user-prompt.d.ts.map +1 -1
- package/dist/daemon/handlers/user-prompt.js +25 -3
- package/dist/daemon/handlers/user-prompt.js.map +1 -1
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +6 -2
- package/dist/daemon/index.js.map +1 -1
- package/dist/engine/agent-router.d.ts +37 -0
- package/dist/engine/agent-router.d.ts.map +1 -1
- package/dist/engine/agent-router.js +58 -0
- package/dist/engine/agent-router.js.map +1 -1
- package/dist/engine/conventions/routing.yaml +31 -2
- package/dist/intelligence/classifier.d.ts +63 -43
- package/dist/intelligence/classifier.d.ts.map +1 -1
- package/dist/intelligence/classifier.js +256 -191
- package/dist/intelligence/classifier.js.map +1 -1
- package/dist/intelligence/context-gatherer.d.ts +101 -0
- package/dist/intelligence/context-gatherer.d.ts.map +1 -0
- package/dist/intelligence/context-gatherer.js +417 -0
- package/dist/intelligence/context-gatherer.js.map +1 -0
- package/dist/intelligence/cot-classifier.d.ts +95 -0
- package/dist/intelligence/cot-classifier.d.ts.map +1 -0
- package/dist/intelligence/cot-classifier.js +391 -0
- package/dist/intelligence/cot-classifier.js.map +1 -0
- package/dist/intelligence/execution-doc-builder.d.ts +90 -0
- package/dist/intelligence/execution-doc-builder.d.ts.map +1 -1
- package/dist/intelligence/execution-doc-builder.js +459 -42
- package/dist/intelligence/execution-doc-builder.js.map +1 -1
- package/dist/intelligence/intent-types.d.ts +13 -0
- package/dist/intelligence/intent-types.d.ts.map +1 -0
- package/dist/intelligence/intent-types.js +19 -0
- package/dist/intelligence/intent-types.js.map +1 -0
- package/dist/intelligence/multimodal-parser.d.ts +105 -0
- package/dist/intelligence/multimodal-parser.d.ts.map +1 -0
- package/dist/intelligence/multimodal-parser.js +425 -0
- package/dist/intelligence/multimodal-parser.js.map +1 -0
- package/dist/web/static/assets/{AIConfig-DiUFET_Q.js → AIConfig-D4VglzCl.js} +2 -2
- package/dist/web/static/assets/{AIConfig-DiUFET_Q.js.map → AIConfig-D4VglzCl.js.map} +1 -1
- package/dist/web/static/assets/{Agents-bNNGbQnL.js → Agents-ne5lXc7V.js} +2 -2
- package/dist/web/static/assets/{Agents-bNNGbQnL.js.map → Agents-ne5lXc7V.js.map} +1 -1
- package/dist/web/static/assets/Dashboard-D4j0Zmek.js +2 -0
- package/dist/web/static/assets/Dashboard-D4j0Zmek.js.map +1 -0
- package/dist/web/static/assets/{Drawer-DOUcx6m1.js → Drawer-Lo5ihVP-.js} +2 -2
- package/dist/web/static/assets/{Drawer-DOUcx6m1.js.map → Drawer-Lo5ihVP-.js.map} +1 -1
- package/dist/web/static/assets/{Events-DQHP6Uaq.js → Events-DBJ1B7OW.js} +2 -2
- package/dist/web/static/assets/{Events-DQHP6Uaq.js.map → Events-DBJ1B7OW.js.map} +1 -1
- package/dist/web/static/assets/{ExecutionTrace-Co8ARdg-.js → ExecutionTrace-Du9XADc1.js} +2 -2
- package/dist/web/static/assets/{ExecutionTrace-Co8ARdg-.js.map → ExecutionTrace-Du9XADc1.js.map} +1 -1
- package/dist/web/static/assets/{Routing-BW3eGD-8.js → Routing-BNQ09OlH.js} +2 -2
- package/dist/web/static/assets/{Routing-BW3eGD-8.js.map → Routing-BNQ09OlH.js.map} +1 -1
- package/dist/web/static/assets/{SessionDetail-Cbd7Jwox.js → SessionDetail-BPrPyMNa.js} +2 -2
- package/dist/web/static/assets/{SessionDetail-Cbd7Jwox.js.map → SessionDetail-BPrPyMNa.js.map} +1 -1
- package/dist/web/static/assets/{Sessions-ZQSCgXyy.js → Sessions-o3EXsXz9.js} +2 -2
- package/dist/web/static/assets/{Sessions-ZQSCgXyy.js.map → Sessions-o3EXsXz9.js.map} +1 -1
- package/dist/web/static/assets/{Skills-C5-5zOSH.js → Skills-Czt5mkyc.js} +2 -2
- package/dist/web/static/assets/{Skills-C5-5zOSH.js.map → Skills-Czt5mkyc.js.map} +1 -1
- package/dist/web/static/assets/{export-CbQTOt71.js → export-C0mlC4AT.js} +2 -2
- package/dist/web/static/assets/{export-CbQTOt71.js.map → export-C0mlC4AT.js.map} +1 -1
- package/dist/web/static/assets/index-B1J7nBu0.js +3 -0
- package/dist/web/static/assets/index-B1J7nBu0.js.map +1 -0
- package/dist/web/static/assets/index-BVqk4bSO.css +1 -0
- package/dist/web/static/assets/{lucide-BanPULT1.js → lucide-Bu44HVAM.js} +33 -73
- package/dist/web/static/assets/lucide-Bu44HVAM.js.map +1 -0
- package/dist/web/static/index.html +3 -3
- package/package.json +1 -1
- package/dist/web/static/assets/Dashboard-Ciyyw6ph.js +0 -2
- package/dist/web/static/assets/Dashboard-Ciyyw6ph.js.map +0 -1
- package/dist/web/static/assets/Methodologies-CXNrDXwG.js +0 -5
- package/dist/web/static/assets/Methodologies-CXNrDXwG.js.map +0 -1
- package/dist/web/static/assets/MethodologyDetail-rV3W1utf.js +0 -2
- package/dist/web/static/assets/MethodologyDetail-rV3W1utf.js.map +0 -1
- package/dist/web/static/assets/index-DJK5beK6.js +0 -3
- package/dist/web/static/assets/index-DJK5beK6.js.map +0 -1
- package/dist/web/static/assets/index-phpuytMI.css +0 -1
- package/dist/web/static/assets/lucide-BanPULT1.js.map +0 -1
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MultimodalParser — turns a raw user prompt into a structured multimodal input.
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* 1. Detect `[Image #N]` markers and markdown image references in the prompt.
|
|
6
|
+
* 2. Resolve those references to absolute file paths (defaulting to the
|
|
7
|
+
* Claude Code image cache at `~/.claude/image-cache/<session>/<n>.<ext>`).
|
|
8
|
+
* 3. Sniff the file header to determine a supported media type
|
|
9
|
+
* (PNG / JPEG / GIF / WebP).
|
|
10
|
+
* 4. Call `AIProvider.completeWithImage` in parallel for every resolved image
|
|
11
|
+
* and parse the JSON response into a typed `ImageAnalysis`.
|
|
12
|
+
* 5. Extract fenced code blocks (```lang...```), preserving language and
|
|
13
|
+
* their 1-based line position within the original prompt.
|
|
14
|
+
* 6. Produce a plain-text view of the prompt with markers and code fences
|
|
15
|
+
* stripped, suitable for downstream classification.
|
|
16
|
+
*
|
|
17
|
+
* Failure model: this module never throws on missing files, unsupported media
|
|
18
|
+
* types, network errors, or malformed Vision JSON. Each failure is recorded in
|
|
19
|
+
* `meta.errors` and the analysis falls back to a neutral default
|
|
20
|
+
* (`screenshotType: 'unknown'`, empty fields) so the classifier can still run
|
|
21
|
+
* on text alone.
|
|
22
|
+
*/
|
|
23
|
+
import { promises as fs } from 'node:fs';
|
|
24
|
+
import * as os from 'node:os';
|
|
25
|
+
import * as path from 'node:path';
|
|
26
|
+
import { logger } from '../core/utils/logger.js';
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Constants & regexes
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
const DEFAULT_VISION_TIMEOUT_MS = 10_000;
|
|
31
|
+
const DEFAULT_VISION_MAX_TOKENS = 1024;
|
|
32
|
+
/** Order matters in the cache lookup: most likely first to short-circuit. */
|
|
33
|
+
const CACHE_IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif', 'webp'];
|
|
34
|
+
/** Magic-number prefix table used by `detectMediaType`. */
|
|
35
|
+
const MAGIC_PREFIXES = [
|
|
36
|
+
{ media: 'image/png', bytes: [0x89, 0x50, 0x4e, 0x47] },
|
|
37
|
+
{ media: 'image/jpeg', bytes: [0xff, 0xd8, 0xff] },
|
|
38
|
+
{ media: 'image/gif', bytes: [0x47, 0x49, 0x46, 0x38] },
|
|
39
|
+
];
|
|
40
|
+
const SCREENSHOT_TYPES = new Set([
|
|
41
|
+
'ui', 'terminal', 'code', 'error', 'diagram', 'unknown',
|
|
42
|
+
]);
|
|
43
|
+
const IMAGE_MARKER_RE = /\[Image\s+#(\d+)\]/gi;
|
|
44
|
+
const MARKDOWN_IMAGE_RE = /!\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
45
|
+
/**
|
|
46
|
+
* Fenced code block: opening ``` plus optional language + newline, body
|
|
47
|
+
* (non-greedy), then closing ```. Tolerates CRLF and missing trailing newline.
|
|
48
|
+
*/
|
|
49
|
+
const FENCED_BLOCK_RE = /```([a-zA-Z0-9_+\-#.]*)[ \t]*\r?\n([\s\S]*?)\r?\n?```/g;
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Vision prompt — kept module-level so it's easy to inspect / snapshot in tests.
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
export const VISION_ANALYSIS_PROMPT = `Analyze this screenshot and respond with ONLY a JSON object:
|
|
54
|
+
|
|
55
|
+
{
|
|
56
|
+
"screenshotType": "ui|terminal|code|error|diagram|unknown",
|
|
57
|
+
"ocrText": "all visible text",
|
|
58
|
+
"visibleElements": ["element1", "element2"],
|
|
59
|
+
"errorMessages": ["error1"],
|
|
60
|
+
"semanticDescription": "one sentence describing what this screenshot shows"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
Guidelines:
|
|
64
|
+
- screenshotType: "ui" = web/desktop UI; "terminal" = CLI output; "code" = code editor; "error" = error dialog/page; "diagram" = chart/diagram
|
|
65
|
+
- ocrText: extract ALL visible text, line by line
|
|
66
|
+
- visibleElements: identify UI components (buttons, menus, tabs, etc.), for UI screenshots
|
|
67
|
+
- errorMessages: extract error messages, stack traces, warnings if visible
|
|
68
|
+
- semanticDescription: explain WHAT this screenshot tells us (e.g., "shows methodology execution page that should have been removed")
|
|
69
|
+
|
|
70
|
+
Respond with the JSON only — no prose, no markdown fences.`;
|
|
71
|
+
/** Per-call clone of the magic-prefix table so we can also handle WebP separately. */
|
|
72
|
+
function matchesPrefix(buf, prefix) {
|
|
73
|
+
if (buf.length < prefix.length)
|
|
74
|
+
return false;
|
|
75
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
76
|
+
if (buf[i] !== prefix[i])
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
/** Read the first 12 bytes of a file and map them to a supported media type. */
|
|
82
|
+
export async function detectMediaType(filePath) {
|
|
83
|
+
let handle;
|
|
84
|
+
try {
|
|
85
|
+
handle = await fs.open(filePath, 'r');
|
|
86
|
+
const buffer = Buffer.alloc(12);
|
|
87
|
+
const { bytesRead } = await handle.read(buffer, 0, 12, 0);
|
|
88
|
+
if (bytesRead < 3)
|
|
89
|
+
return undefined;
|
|
90
|
+
const head = buffer.subarray(0, bytesRead);
|
|
91
|
+
for (const { media, bytes } of MAGIC_PREFIXES) {
|
|
92
|
+
if (matchesPrefix(head, bytes))
|
|
93
|
+
return media;
|
|
94
|
+
}
|
|
95
|
+
// WebP: RIFF....WEBP — needs the 12-byte window.
|
|
96
|
+
if (bytesRead >= 12
|
|
97
|
+
&& matchesPrefix(head, [0x52, 0x49, 0x46, 0x46])
|
|
98
|
+
&& head[8] === 0x57 && head[9] === 0x45 && head[10] === 0x42 && head[11] === 0x50) {
|
|
99
|
+
return 'image/webp';
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
await handle?.close().catch(() => {
|
|
108
|
+
/* best-effort close */
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** Look in `<cacheRoot>/<session>/<n>.<ext>` for the most recent match. */
|
|
113
|
+
export async function defaultResolveCachedImage(marker, cacheRoot) {
|
|
114
|
+
const numMatch = /#(\d+)/.exec(marker);
|
|
115
|
+
if (!numMatch)
|
|
116
|
+
return undefined;
|
|
117
|
+
const n = numMatch[1];
|
|
118
|
+
const root = cacheRoot ?? path.join(os.homedir(), '.claude', 'image-cache');
|
|
119
|
+
let sessionDirs;
|
|
120
|
+
try {
|
|
121
|
+
sessionDirs = await fs.readdir(root);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
const candidates = [];
|
|
127
|
+
for (const entry of sessionDirs) {
|
|
128
|
+
const sessionPath = path.join(root, entry);
|
|
129
|
+
try {
|
|
130
|
+
const sessionStat = await fs.stat(sessionPath);
|
|
131
|
+
if (!sessionStat.isDirectory())
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
for (const ext of CACHE_IMAGE_EXTENSIONS) {
|
|
138
|
+
const candidate = path.join(sessionPath, `${n}.${ext}`);
|
|
139
|
+
try {
|
|
140
|
+
const fileStat = await fs.stat(candidate);
|
|
141
|
+
if (fileStat.isFile()) {
|
|
142
|
+
candidates.push({ filePath: candidate, mtimeMs: fileStat.mtimeMs });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// file absent — keep searching
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (candidates.length === 0)
|
|
151
|
+
return undefined;
|
|
152
|
+
candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
153
|
+
return candidates[0].filePath;
|
|
154
|
+
}
|
|
155
|
+
/** Compute 1-based line number for a byte offset within `text`. */
|
|
156
|
+
function lineNumberAt(text, offset) {
|
|
157
|
+
if (offset <= 0)
|
|
158
|
+
return 1;
|
|
159
|
+
let line = 1;
|
|
160
|
+
const end = Math.min(offset, text.length);
|
|
161
|
+
for (let i = 0; i < end; i++) {
|
|
162
|
+
if (text.charCodeAt(i) === 0x0a /* \n */)
|
|
163
|
+
line++;
|
|
164
|
+
}
|
|
165
|
+
return line;
|
|
166
|
+
}
|
|
167
|
+
/** Extract fenced code blocks. Indented-style blocks are intentionally ignored. */
|
|
168
|
+
export function extractCodeBlocks(prompt) {
|
|
169
|
+
const blocks = [];
|
|
170
|
+
const re = new RegExp(FENCED_BLOCK_RE.source, FENCED_BLOCK_RE.flags);
|
|
171
|
+
let match;
|
|
172
|
+
while ((match = re.exec(prompt)) !== null) {
|
|
173
|
+
const rawLang = match[1] ?? '';
|
|
174
|
+
const content = (match[2] ?? '').replace(/\r?\n$/, '');
|
|
175
|
+
blocks.push({
|
|
176
|
+
language: rawLang.length > 0 ? rawLang.toLowerCase() : undefined,
|
|
177
|
+
content,
|
|
178
|
+
startLine: lineNumberAt(prompt, match.index),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return blocks;
|
|
182
|
+
}
|
|
183
|
+
/** Extract `[Image #N]` markers preserving prompt order and duplicates. */
|
|
184
|
+
function extractImageMarkers(prompt) {
|
|
185
|
+
const found = [];
|
|
186
|
+
const re = new RegExp(IMAGE_MARKER_RE.source, IMAGE_MARKER_RE.flags);
|
|
187
|
+
let match;
|
|
188
|
+
while ((match = re.exec(prompt)) !== null) {
|
|
189
|
+
found.push(match[0]);
|
|
190
|
+
}
|
|
191
|
+
return found;
|
|
192
|
+
}
|
|
193
|
+
/** Extract markdown image references (``), preserving order. */
|
|
194
|
+
function extractMarkdownImages(prompt) {
|
|
195
|
+
const found = [];
|
|
196
|
+
const re = new RegExp(MARKDOWN_IMAGE_RE.source, MARKDOWN_IMAGE_RE.flags);
|
|
197
|
+
let match;
|
|
198
|
+
while ((match = re.exec(prompt)) !== null) {
|
|
199
|
+
found.push({ alt: match[1] ?? '', path: match[2] });
|
|
200
|
+
}
|
|
201
|
+
return found;
|
|
202
|
+
}
|
|
203
|
+
/** Build the prompt with image references and fenced code blocks removed. */
|
|
204
|
+
function stripMarkersAndBlocks(prompt) {
|
|
205
|
+
return prompt
|
|
206
|
+
.replace(new RegExp(FENCED_BLOCK_RE.source, FENCED_BLOCK_RE.flags), '')
|
|
207
|
+
.replace(new RegExp(MARKDOWN_IMAGE_RE.source, MARKDOWN_IMAGE_RE.flags), '')
|
|
208
|
+
.replace(new RegExp(IMAGE_MARKER_RE.source, IMAGE_MARKER_RE.flags), '')
|
|
209
|
+
.replace(/[ \t]+\n/g, '\n')
|
|
210
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
211
|
+
.trim();
|
|
212
|
+
}
|
|
213
|
+
function isScreenshotType(val) {
|
|
214
|
+
return typeof val === 'string' && SCREENSHOT_TYPES.has(val);
|
|
215
|
+
}
|
|
216
|
+
function asStringArray(val) {
|
|
217
|
+
if (!Array.isArray(val))
|
|
218
|
+
return [];
|
|
219
|
+
return val.filter((x) => typeof x === 'string');
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Parse the JSON object returned by the Vision call. Tolerates surrounding
|
|
223
|
+
* prose / markdown fences. Returns `null` if no JSON object can be recovered.
|
|
224
|
+
*/
|
|
225
|
+
export function parseVisionResponse(text) {
|
|
226
|
+
if (!text)
|
|
227
|
+
return null;
|
|
228
|
+
let candidate = text.trim();
|
|
229
|
+
// Strip ```json ... ``` fences if present.
|
|
230
|
+
const fenceMatch = /```(?:json)?\s*\r?\n?([\s\S]*?)\r?\n?```/i.exec(candidate);
|
|
231
|
+
if (fenceMatch)
|
|
232
|
+
candidate = fenceMatch[1].trim();
|
|
233
|
+
// If still not pure JSON, try to recover the first {...} block.
|
|
234
|
+
if (!candidate.startsWith('{')) {
|
|
235
|
+
const firstBrace = candidate.indexOf('{');
|
|
236
|
+
const lastBrace = candidate.lastIndexOf('}');
|
|
237
|
+
if (firstBrace === -1 || lastBrace === -1 || lastBrace <= firstBrace)
|
|
238
|
+
return null;
|
|
239
|
+
candidate = candidate.slice(firstBrace, lastBrace + 1);
|
|
240
|
+
}
|
|
241
|
+
let parsed;
|
|
242
|
+
try {
|
|
243
|
+
parsed = JSON.parse(candidate);
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
249
|
+
return null;
|
|
250
|
+
const obj = parsed;
|
|
251
|
+
return {
|
|
252
|
+
screenshotType: isScreenshotType(obj.screenshotType) ? obj.screenshotType : 'unknown',
|
|
253
|
+
ocrText: typeof obj.ocrText === 'string' ? obj.ocrText : '',
|
|
254
|
+
visibleElements: asStringArray(obj.visibleElements),
|
|
255
|
+
errorMessages: asStringArray(obj.errorMessages),
|
|
256
|
+
semanticDescription: typeof obj.semanticDescription === 'string' ? obj.semanticDescription : '',
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function defaultAnalysis(imagePath, analysisMs) {
|
|
260
|
+
return {
|
|
261
|
+
imagePath,
|
|
262
|
+
screenshotType: 'unknown',
|
|
263
|
+
ocrText: '',
|
|
264
|
+
visibleElements: [],
|
|
265
|
+
errorMessages: [],
|
|
266
|
+
semanticDescription: '',
|
|
267
|
+
analysisMs,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
// MultimodalParser
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
export class MultimodalParser {
|
|
274
|
+
ai;
|
|
275
|
+
visionTimeoutMs;
|
|
276
|
+
visionModel;
|
|
277
|
+
customResolver;
|
|
278
|
+
imageCacheDir;
|
|
279
|
+
constructor(ai, options) {
|
|
280
|
+
this.ai = ai;
|
|
281
|
+
this.visionTimeoutMs = options?.visionTimeoutMs ?? DEFAULT_VISION_TIMEOUT_MS;
|
|
282
|
+
this.visionModel = options?.visionModel;
|
|
283
|
+
this.customResolver = options?.imagePathResolver;
|
|
284
|
+
this.imageCacheDir = options?.imageCacheDir;
|
|
285
|
+
}
|
|
286
|
+
/** Main entry point. See module header for the failure model. */
|
|
287
|
+
async parse(prompt) {
|
|
288
|
+
const start = Date.now();
|
|
289
|
+
const errors = [];
|
|
290
|
+
// --- 1. Pure-text artifacts (code blocks + stripped text) -------------
|
|
291
|
+
const codeBlocks = extractCodeBlocks(prompt);
|
|
292
|
+
const textWithoutMarkers = stripMarkersAndBlocks(prompt);
|
|
293
|
+
// --- 2. Collect image references --------------------------------------
|
|
294
|
+
const markerTokens = extractImageMarkers(prompt);
|
|
295
|
+
const markdownImages = extractMarkdownImages(prompt);
|
|
296
|
+
const imagesFound = markerTokens.length + markdownImages.length;
|
|
297
|
+
if (imagesFound === 0) {
|
|
298
|
+
return {
|
|
299
|
+
originalText: prompt,
|
|
300
|
+
textWithoutMarkers,
|
|
301
|
+
images: [],
|
|
302
|
+
codeBlocks,
|
|
303
|
+
meta: {
|
|
304
|
+
parseMs: Date.now() - start,
|
|
305
|
+
imagesFound: 0,
|
|
306
|
+
imagesAnalyzed: 0,
|
|
307
|
+
codeBlocksFound: codeBlocks.length,
|
|
308
|
+
errors,
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
// --- 3. Resolve marker -> path (custom resolver, then default cache) --
|
|
313
|
+
const references = [];
|
|
314
|
+
for (const marker of markerTokens) {
|
|
315
|
+
const custom = this.customResolver?.(marker);
|
|
316
|
+
if (custom) {
|
|
317
|
+
references.push({ marker, resolvedPath: custom });
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
const fromCache = await defaultResolveCachedImage(marker, this.imageCacheDir);
|
|
321
|
+
if (fromCache) {
|
|
322
|
+
references.push({ marker, resolvedPath: fromCache });
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
const reason = `Could not resolve ${marker} to a file path`;
|
|
326
|
+
references.push({ marker, resolutionError: reason });
|
|
327
|
+
errors.push(reason);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
for (const md of markdownImages) {
|
|
331
|
+
// Markdown image paths are already explicit; expand `~` for convenience.
|
|
332
|
+
const expanded = md.path.startsWith('~/')
|
|
333
|
+
? path.join(os.homedir(), md.path.slice(2))
|
|
334
|
+
: md.path;
|
|
335
|
+
references.push({ marker: ``, resolvedPath: expanded });
|
|
336
|
+
}
|
|
337
|
+
// --- 4. Vision analysis (parallel) ------------------------------------
|
|
338
|
+
let images = [];
|
|
339
|
+
let imagesAnalyzed = 0;
|
|
340
|
+
const visionFn = this.ai?.completeWithImage?.bind(this.ai);
|
|
341
|
+
if (!visionFn) {
|
|
342
|
+
if (references.some((r) => r.resolvedPath)) {
|
|
343
|
+
errors.push('AI provider not configured for vision; skipped image analysis');
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
const tasks = references.map((ref) => this.analyzeImage(ref, visionFn, errors));
|
|
348
|
+
const settled = await Promise.all(tasks);
|
|
349
|
+
const successes = [];
|
|
350
|
+
for (const result of settled) {
|
|
351
|
+
if (!result)
|
|
352
|
+
continue;
|
|
353
|
+
successes.push(result.analysis);
|
|
354
|
+
if (result.success)
|
|
355
|
+
imagesAnalyzed++;
|
|
356
|
+
}
|
|
357
|
+
images = successes;
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
originalText: prompt,
|
|
361
|
+
textWithoutMarkers,
|
|
362
|
+
images,
|
|
363
|
+
codeBlocks,
|
|
364
|
+
meta: {
|
|
365
|
+
parseMs: Date.now() - start,
|
|
366
|
+
imagesFound,
|
|
367
|
+
imagesAnalyzed,
|
|
368
|
+
codeBlocksFound: codeBlocks.length,
|
|
369
|
+
errors,
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Analyze a single image. Returns `null` only when path resolution failed
|
|
375
|
+
* upstream (we already pushed an error for that case). Otherwise returns
|
|
376
|
+
* an analysis plus a `success` flag indicating whether Vision actually
|
|
377
|
+
* returned a usable JSON payload.
|
|
378
|
+
*/
|
|
379
|
+
async analyzeImage(ref, completeWithImage, errors) {
|
|
380
|
+
if (!ref.resolvedPath)
|
|
381
|
+
return null;
|
|
382
|
+
const imagePath = ref.resolvedPath;
|
|
383
|
+
const started = Date.now();
|
|
384
|
+
// Make sure the file actually exists & is a supported format before we
|
|
385
|
+
// burn an API call on it.
|
|
386
|
+
const mediaType = await detectMediaType(imagePath);
|
|
387
|
+
if (!mediaType) {
|
|
388
|
+
errors.push(`Skipped ${ref.marker}: file missing or unsupported media type (${imagePath})`);
|
|
389
|
+
return { analysis: defaultAnalysis(imagePath, Date.now() - started), success: false };
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
const response = await completeWithImage(VISION_ANALYSIS_PROMPT, {
|
|
393
|
+
images: [{ path: imagePath, mediaType }],
|
|
394
|
+
timeoutMs: this.visionTimeoutMs,
|
|
395
|
+
maxTokens: DEFAULT_VISION_MAX_TOKENS,
|
|
396
|
+
...(this.visionModel ? { model: this.visionModel } : {}),
|
|
397
|
+
});
|
|
398
|
+
const parsed = parseVisionResponse(response);
|
|
399
|
+
const analysisMs = Date.now() - started;
|
|
400
|
+
if (!parsed) {
|
|
401
|
+
errors.push(`Vision response for ${ref.marker} was not valid JSON; using defaults`);
|
|
402
|
+
return { analysis: defaultAnalysis(imagePath, analysisMs), success: false };
|
|
403
|
+
}
|
|
404
|
+
return {
|
|
405
|
+
analysis: {
|
|
406
|
+
imagePath,
|
|
407
|
+
screenshotType: parsed.screenshotType,
|
|
408
|
+
ocrText: parsed.ocrText,
|
|
409
|
+
visibleElements: parsed.visibleElements,
|
|
410
|
+
errorMessages: parsed.errorMessages,
|
|
411
|
+
semanticDescription: parsed.semanticDescription,
|
|
412
|
+
analysisMs,
|
|
413
|
+
},
|
|
414
|
+
success: true,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
catch (err) {
|
|
418
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
419
|
+
logger.warn(`[MultimodalParser] vision call failed for ${imagePath}: ${reason}`);
|
|
420
|
+
errors.push(`Vision call failed for ${ref.marker}: ${reason}`);
|
|
421
|
+
return { analysis: defaultAnalysis(imagePath, Date.now() - started), success: false };
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
//# sourceMappingURL=multimodal-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multimodal-parser.js","sourceRoot":"","sources":["../../src/intelligence/multimodal-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAOlC,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAyDjD,8EAA8E;AAC9E,sBAAsB;AACtB,8EAA8E;AAE9E,MAAM,yBAAyB,GAAG,MAAM,CAAC;AACzC,MAAM,yBAAyB,GAAG,IAAI,CAAC;AAEvC,6EAA6E;AAC7E,MAAM,sBAAsB,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAU,CAAC;AAE9E,2DAA2D;AAC3D,MAAM,cAAc,GAA+D;IACjF,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE;IACvD,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE;IAClD,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE;CACxD,CAAC;AAEF,MAAM,gBAAgB,GAAgC,IAAI,GAAG,CAAC;IAC5D,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;CACxD,CAAC,CAAC;AAEH,MAAM,eAAe,GAAG,sBAAsB,CAAC;AAC/C,MAAM,iBAAiB,GAAG,4CAA4C,CAAC;AACvE;;;GAGG;AACH,MAAM,eAAe,GAAG,wDAAwD,CAAC;AAEjF,8EAA8E;AAC9E,iFAAiF;AACjF,8EAA8E;AAE9E,MAAM,CAAC,MAAM,sBAAsB,GAAG;;;;;;;;;;;;;;;;;2DAiBqB,CAAC;AAe5D,sFAAsF;AACtF,SAAS,aAAa,CAAC,GAAW,EAAE,MAAyB;IAC3D,IAAI,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC7C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;IACzC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,gFAAgF;AAChF,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAgB;IACpD,IAAI,MAA8B,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QACtC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAChC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QAC1D,IAAI,SAAS,GAAG,CAAC;YAAE,OAAO,SAAS,CAAC;QACpC,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;QAE3C,KAAK,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,cAAc,EAAE,CAAC;YAC9C,IAAI,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAC;QAC/C,CAAC;QACD,iDAAiD;QACjD,IAAI,SAAS,IAAI,EAAE;eACd,aAAa,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;eAC7C,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC,KAAK,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC;YACpF,OAAO,YAAY,CAAC;QACtB,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;YAC/B,uBAAuB;QACzB,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,2EAA2E;AAC3E,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,MAAc,EACd,SAAkB;IAElB,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChC,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAEtB,MAAM,IAAI,GAAG,SAAS,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC;IAC5E,IAAI,WAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,WAAW,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IAGD,MAAM,UAAU,GAAgB,EAAE,CAAC;IAEnC,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;QAChC,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC3C,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC/C,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE;gBAAE,SAAS;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QAED,KAAK,MAAM,GAAG,IAAI,sBAAsB,EAAE,CAAC;YACzC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;YACxD,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAC1C,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;oBACtB,UAAU,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;gBACtE,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,+BAA+B;YACjC,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC9C,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC;IACjD,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;AAChC,CAAC;AAED,mEAAmE;AACnE,SAAS,YAAY,CAAC,IAAY,EAAE,MAAc;IAChD,IAAI,MAAM,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAC1B,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,QAAQ;YAAE,IAAI,EAAE,CAAC;IACnD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,MAAM,MAAM,GAAgB,EAAE,CAAC;IAC/B,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC;IACrE,IAAI,KAA6B,CAAC;IAClC,OAAO,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC1C,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACvD,MAAM,CAAC,IAAI,CAAC;YACV,QAAQ,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS;YAChE,OAAO;YACP,SAAS,EAAE,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC;SAC7C,CAAC,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,2EAA2E;AAC3E,SAAS,mBAAmB,CAAC,MAAc;IACzC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC;IACrE,IAAI,KAA6B,CAAC;IAClC,OAAO,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC1C,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,4EAA4E;AAC5E,SAAS,qBAAqB,CAAC,MAAc;IAC3C,MAAM,KAAK,GAAyC,EAAE,CAAC;IACvD,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACzE,IAAI,KAA6B,CAAC;IAClC,OAAO,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC1C,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,6EAA6E;AAC7E,SAAS,qBAAqB,CAAC,MAAc;IAC3C,OAAO,MAAM;SACV,OAAO,CAAC,IAAI,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;SACtE,OAAO,CAAC,IAAI,MAAM,CAAC,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;SAC1E,OAAO,CAAC,IAAI,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;SACtE,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC;SAC1B,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC;SAC1B,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAY;IACpC,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,gBAAgB,CAAC,GAAG,CAAC,GAAqB,CAAC,CAAC;AAChF,CAAC;AAED,SAAS,aAAa,CAAC,GAAY;IACjC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IACnC,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;AAC/D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAO9C,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,IAAI,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAE5B,2CAA2C;IAC3C,MAAM,UAAU,GAAG,2CAA2C,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC/E,IAAI,UAAU;QAAE,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAEjD,gEAAgE;IAChE,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/B,MAAM,UAAU,GAAG,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,SAAS,GAAG,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QAC7C,IAAI,UAAU,KAAK,CAAC,CAAC,IAAI,SAAS,KAAK,CAAC,CAAC,IAAI,SAAS,IAAI,UAAU;YAAE,OAAO,IAAI,CAAC;QAClF,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,UAAU,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAE/D,MAAM,GAAG,GAAG,MAAiC,CAAC;IAC9C,OAAO;QACL,cAAc,EAAE,gBAAgB,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS;QACrF,OAAO,EAAE,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;QAC3D,eAAe,EAAE,aAAa,CAAC,GAAG,CAAC,eAAe,CAAC;QACnD,aAAa,EAAE,aAAa,CAAC,GAAG,CAAC,aAAa,CAAC;QAC/C,mBAAmB,EACjB,OAAO,GAAG,CAAC,mBAAmB,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE;KAC7E,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,SAAiB,EAAE,UAAkB;IAC5D,OAAO;QACL,SAAS;QACT,cAAc,EAAE,SAAS;QACzB,OAAO,EAAE,EAAE;QACX,eAAe,EAAE,EAAE;QACnB,aAAa,EAAE,EAAE;QACjB,mBAAmB,EAAE,EAAE;QACvB,UAAU;KACX,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,MAAM,OAAO,gBAAgB;IAOR;IANF,eAAe,CAAS;IACxB,WAAW,CAAU;IACrB,cAAc,CAA0C;IACxD,aAAa,CAAU;IAExC,YACmB,EAAqB,EACtC,OAAiC;QADhB,OAAE,GAAF,EAAE,CAAmB;QAGtC,IAAI,CAAC,eAAe,GAAG,OAAO,EAAE,eAAe,IAAI,yBAAyB,CAAC;QAC7E,IAAI,CAAC,WAAW,GAAG,OAAO,EAAE,WAAW,CAAC;QACxC,IAAI,CAAC,cAAc,GAAG,OAAO,EAAE,iBAAiB,CAAC;QACjD,IAAI,CAAC,aAAa,GAAG,OAAO,EAAE,aAAa,CAAC;IAC9C,CAAC;IAED,iEAAiE;IACjE,KAAK,CAAC,KAAK,CAAC,MAAc;QACxB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,MAAM,MAAM,GAAa,EAAE,CAAC;QAE5B,yEAAyE;QACzE,MAAM,UAAU,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAC7C,MAAM,kBAAkB,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;QAEzD,yEAAyE;QACzE,MAAM,YAAY,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;QACjD,MAAM,cAAc,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;QACrD,MAAM,WAAW,GAAG,YAAY,CAAC,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC;QAEhE,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO;gBACL,YAAY,EAAE,MAAM;gBACpB,kBAAkB;gBAClB,MAAM,EAAE,EAAE;gBACV,UAAU;gBACV,IAAI,EAAE;oBACJ,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;oBAC3B,WAAW,EAAE,CAAC;oBACd,cAAc,EAAE,CAAC;oBACjB,eAAe,EAAE,UAAU,CAAC,MAAM;oBAClC,MAAM;iBACP;aACF,CAAC;QACJ,CAAC;QAED,yEAAyE;QACzE,MAAM,UAAU,GAAqB,EAAE,CAAC;QAExC,KAAK,MAAM,MAAM,IAAI,YAAY,EAAE,CAAC;YAClC,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC,MAAM,CAAC,CAAC;YAC7C,IAAI,MAAM,EAAE,CAAC;gBACX,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,CAAC;gBAClD,SAAS;YACX,CAAC;YACD,MAAM,SAAS,GAAG,MAAM,yBAAyB,CAAC,MAAM,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;YAC9E,IAAI,SAAS,EAAE,CAAC;gBACd,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC,CAAC;YACvD,CAAC;iBAAM,CAAC;gBACN,MAAM,MAAM,GAAG,qBAAqB,MAAM,iBAAiB,CAAC;gBAC5D,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,CAAC,CAAC;gBACrD,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;QAED,KAAK,MAAM,EAAE,IAAI,cAAc,EAAE,CAAC;YAChC,yEAAyE;YACzE,MAAM,QAAQ,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;gBACvC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAC3C,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC;YACZ,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI,GAAG,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC,CAAC;QAClF,CAAC;QAED,yEAAyE;QACzE,IAAI,MAAM,GAAoB,EAAE,CAAC;QACjC,IAAI,cAAc,GAAG,CAAC,CAAC;QACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,EAAE,iBAAiB,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3D,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC3C,MAAM,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAC;YAC/E,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;YAChF,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACzC,MAAM,SAAS,GAAoB,EAAE,CAAC;YACtC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,IAAI,CAAC,MAAM;oBAAE,SAAS;gBACtB,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBAChC,IAAI,MAAM,CAAC,OAAO;oBAAE,cAAc,EAAE,CAAC;YACvC,CAAC;YACD,MAAM,GAAG,SAAS,CAAC;QACrB,CAAC;QAED,OAAO;YACL,YAAY,EAAE,MAAM;YACpB,kBAAkB;YAClB,MAAM;YACN,UAAU;YACV,IAAI,EAAE;gBACJ,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;gBAC3B,WAAW;gBACX,cAAc;gBACd,eAAe,EAAE,UAAU,CAAC,MAAM;gBAClC,MAAM;aACP;SACF,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACK,KAAK,CAAC,YAAY,CACxB,GAAmB,EACnB,iBAAyF,EACzF,MAAgB;QAEhB,IAAI,CAAC,GAAG,CAAC,YAAY;YAAE,OAAO,IAAI,CAAC;QACnC,MAAM,SAAS,GAAG,GAAG,CAAC,YAAY,CAAC;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE3B,uEAAuE;QACvE,0BAA0B;QAC1B,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,SAAS,CAAC,CAAC;QACnD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,CAAC,IAAI,CAAC,WAAW,GAAG,CAAC,MAAM,6CAA6C,SAAS,GAAG,CAAC,CAAC;YAC5F,OAAO,EAAE,QAAQ,EAAE,eAAe,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QACxF,CAAC;QAED,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,sBAAsB,EAAE;gBAC/D,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;gBACxC,SAAS,EAAE,IAAI,CAAC,eAAe;gBAC/B,SAAS,EAAE,yBAAyB;gBACpC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACzD,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;YAC7C,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;YAExC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,CAAC,IAAI,CAAC,uBAAuB,GAAG,CAAC,MAAM,qCAAqC,CAAC,CAAC;gBACpF,OAAO,EAAE,QAAQ,EAAE,eAAe,CAAC,SAAS,EAAE,UAAU,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;YAC9E,CAAC;YAED,OAAO;gBACL,QAAQ,EAAE;oBACR,SAAS;oBACT,cAAc,EAAE,MAAM,CAAC,cAAc;oBACrC,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,eAAe,EAAE,MAAM,CAAC,eAAe;oBACvC,aAAa,EAAE,MAAM,CAAC,aAAa;oBACnC,mBAAmB,EAAE,MAAM,CAAC,mBAAmB;oBAC/C,UAAU;iBACX;gBACD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAChE,MAAM,CAAC,IAAI,CAAC,6CAA6C,SAAS,KAAK,MAAM,EAAE,CAAC,CAAC;YACjF,MAAM,CAAC,IAAI,CAAC,0BAA0B,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC,CAAC;YAC/D,OAAO,EAAE,QAAQ,EAAE,eAAe,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QACxF,CAAC;IACH,CAAC;CACF"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as m,j as e}from"./react-vendor-CSp-GLFF.js";import{b as w,u as p,c as y}from"./query-C99w429o.js";import{u as N}from"./index-
|
|
2
|
-
//# sourceMappingURL=AIConfig-
|
|
1
|
+
import{r as m,j as e}from"./react-vendor-CSp-GLFF.js";import{b as w,u as p,c as y}from"./query-C99w429o.js";import{u as N}from"./index-B1J7nBu0.js";import{a as f}from"./auth-Bnf8ZcqN.js";import{C as k,E as _,n as C,e as P,d as S,o as E,Z as A,m as F}from"./lucide-Bu44HVAM.js";import"./react-router-I-HqunH7.js";import"./vendor-CMMjVdZs.js";import"./syntax-highlighter-44FakypI.js";async function I(){const a=await fetch("/api/config/ai");if(!a.ok)throw new Error("Failed to fetch config");return a.json()}async function K(a,r){const i=new URLSearchParams;a&&i.set("api_key",a),r&&i.set("base_url",r);const o=await fetch(`/api/ai/models?${i.toString()}`);return o.ok?(await o.json()).data||[]:[]}async function T(a){if(!(await f("/api/config/ai",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)})).ok)throw new Error("Failed to save config")}async function q(a){const r=await f("/api/ai/test",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)}),i=await r.json().catch(()=>({}));if(!r.ok)throw new Error(i.error||"Test failed");return i}function J(){const a=w(),r=N(),{data:i,isLoading:o}=p({queryKey:["ai-config"],queryFn:I}),[t,n]=m.useState({api_key:"",base_url:"",model:""}),[u,b]=m.useState(!1),[d,h]=m.useState(null);m.useEffect(()=>{i&&n(i)},[i]);const{data:l,refetch:j,isFetching:g}=p({queryKey:["ai-models",t.api_key,t.base_url],queryFn:()=>K(t.api_key,t.base_url),enabled:!!t.api_key&&!!t.base_url}),x=y({mutationFn:T,onSuccess:()=>{r.success("AI 配置已保存"),a.invalidateQueries({queryKey:["ai-config"]})},onError:s=>r.error(`保存失败: ${s.message}`)}),c=y({mutationFn:q,onSuccess:s=>{const v=(s==null?void 0:s.model)||t.model;h({ok:!0,message:`连接成功!模型: ${v}`}),r.success("API 连接测试通过")},onError:s=>{h({ok:!1,message:s.message}),r.error("API 连接测试失败")}});return o?e.jsx("div",{className:"bg-white rounded-lg shadow p-6",children:"加载中..."}):e.jsxs("div",{children:[e.jsx("h1",{className:"text-2xl font-bold text-gray-900 mb-6",children:"AI 配置"}),e.jsxs("div",{className:"bg-white rounded-lg shadow p-6 max-w-2xl",children:[e.jsxs("div",{className:"flex items-center gap-2 mb-6",children:[e.jsx(k,{className:"h-5 w-5 text-indigo-600"}),e.jsx("h3",{className:"text-lg font-semibold text-gray-900",children:"Anthropic API 配置"})]}),e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium text-gray-700 mb-1",children:"API Key"}),e.jsxs("div",{className:"relative",children:[e.jsx("input",{type:u?"text":"password",value:t.api_key||"",onChange:s=>n({...t,api_key:s.target.value}),placeholder:"sk-...",className:"w-full px-3 py-2 pr-10 border border-gray-300 rounded-md text-sm font-mono"}),e.jsx("button",{type:"button",onClick:()=>b(!u),className:"absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600",children:u?e.jsx(_,{className:"h-4 w-4"}):e.jsx(C,{className:"h-4 w-4"})})]})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium text-gray-700 mb-1",children:"Base URL"}),e.jsx("input",{type:"text",value:t.base_url||"",onChange:s=>n({...t,base_url:s.target.value}),placeholder:"https://api.anthropic.com",className:"w-full px-3 py-2 border border-gray-300 rounded-md text-sm"})]}),e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between mb-1",children:[e.jsx("label",{className:"block text-sm font-medium text-gray-700",children:"主模型 (Distill / QualityGate)"}),e.jsx("button",{type:"button",onClick:()=>j(),disabled:!t.api_key||!t.base_url||g,className:"text-xs text-indigo-600 hover:text-indigo-700 disabled:text-gray-400",children:g?"加载中...":"🔄 刷新模型列表"})]}),l&&l.length>0?e.jsxs("select",{value:t.model||"",onChange:s=>n({...t,model:s.target.value}),className:"w-full px-3 py-2 border border-gray-300 rounded-md text-sm bg-white",children:[e.jsx("option",{value:"",children:"-- 选择模型 --"}),l.map(s=>e.jsxs("option",{value:s.id,children:[s.id," ",s.owned_by&&`(${s.owned_by})`]},s.id))]}):e.jsx("input",{type:"text",value:t.model||"",onChange:s=>n({...t,model:s.target.value}),placeholder:"claude-sonnet-4-6",className:"w-full px-3 py-2 border border-gray-300 rounded-md text-sm"}),e.jsx("p",{className:"text-xs text-gray-500 mt-1",children:l&&l.length>0?`发现 ${l.length} 个可用模型`:"填入 API Key 和 Base URL 后可自动发现模型"})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium text-gray-700 mb-1",children:"分类器模型(可选,便宜+快的模型)"}),l&&l.length>0?e.jsxs("select",{value:t.classifier_model||"",onChange:s=>n({...t,classifier_model:s.target.value}),className:"w-full px-3 py-2 border border-gray-300 rounded-md text-sm bg-white",children:[e.jsx("option",{value:"",children:"-- 不指定 (使用主模型) --"}),l.map(s=>e.jsx("option",{value:s.id,children:s.id},s.id))]}):e.jsx("input",{type:"text",value:t.classifier_model||"",onChange:s=>n({...t,classifier_model:s.target.value}),placeholder:"claude-haiku-4-5-20251001",className:"w-full px-3 py-2 border border-gray-300 rounded-md text-sm"})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium text-gray-700 mb-1",children:"分类器超时 (ms)"}),e.jsx("input",{type:"number",value:t.classifier_timeout||1e4,onChange:s=>n({...t,classifier_timeout:parseInt(s.target.value)}),className:"w-full px-3 py-2 border border-gray-300 rounded-md text-sm"})]}),d&&e.jsxs("div",{className:`flex items-start gap-2 p-3 rounded text-sm ${d.ok?"bg-green-50 text-green-800":"bg-red-50 text-red-800"}`,children:[d.ok?e.jsx(P,{className:"h-4 w-4 flex-shrink-0 mt-0.5"}):e.jsx(S,{className:"h-4 w-4 flex-shrink-0 mt-0.5"}),e.jsx("div",{className:"flex-1 break-all",children:d.message})]}),e.jsx("div",{className:"flex items-center justify-end pt-4 border-t",children:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs("button",{onClick:()=>c.mutate({api_key:t.api_key,base_url:t.base_url,model:t.model}),disabled:c.isPending||!t.api_key||!t.model,className:"inline-flex items-center gap-2 px-4 py-2 bg-gray-100 text-gray-700 text-sm font-medium rounded-md hover:bg-gray-200 disabled:bg-gray-50 disabled:text-gray-400",children:[c.isPending?e.jsx(E,{className:"h-4 w-4 animate-spin"}):e.jsx(A,{className:"h-4 w-4"}),c.isPending?"测试中...":"测试连接"]}),e.jsxs("button",{onClick:()=>x.mutate(t),disabled:x.isPending,className:"inline-flex items-center gap-2 px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-md hover:bg-indigo-700 disabled:bg-gray-400",children:[e.jsx(F,{className:"h-4 w-4"}),x.isPending?"保存中...":"保存配置"]})]})})]})]})]})}export{J as default};
|
|
2
|
+
//# sourceMappingURL=AIConfig-D4VglzCl.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AIConfig-DiUFET_Q.js","sources":["../../src/pages/AIConfig.tsx"],"sourcesContent":["import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'\nimport { useState, useEffect } from 'react'\nimport { Save, Eye, EyeOff, Cpu, Zap, CheckCircle, XCircle, Loader2 } from 'lucide-react'\nimport { useToast } from '../components/Toast'\nimport { authFetch } from '../utils/auth'\n\ninterface AIConfig {\n api_key: string\n base_url: string\n model: string\n provider?: string\n classifier_model?: string\n classifier_timeout?: number\n}\n\ninterface ModelOption {\n id: string\n owned_by?: string\n}\n\nasync function fetchConfig(): Promise<AIConfig> {\n const res = await fetch('/api/config/ai')\n if (!res.ok) throw new Error('Failed to fetch config')\n return res.json()\n}\n\nasync function fetchModels(api_key?: string, base_url?: string): Promise<ModelOption[]> {\n const params = new URLSearchParams()\n if (api_key) params.set('api_key', api_key)\n if (base_url) params.set('base_url', base_url)\n const res = await fetch(`/api/ai/models?${params.toString()}`)\n if (!res.ok) return []\n const data = await res.json()\n return data.data || []\n}\n\nasync function saveConfig(config: AIConfig): Promise<void> {\n const res = await authFetch('/api/config/ai', {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(config),\n })\n if (!res.ok) throw new Error('Failed to save config')\n}\n\nasync function testConnection(config: { api_key: string; base_url: string; model: string }): Promise<any> {\n const res = await authFetch('/api/ai/test', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(config),\n })\n const data = await res.json().catch(() => ({}))\n if (!res.ok) throw new Error(data.error || 'Test failed')\n return data\n}\n\nexport default function AIConfig() {\n const queryClient = useQueryClient()\n const toast = useToast()\n const { data: config, isLoading } = useQuery({\n queryKey: ['ai-config'],\n queryFn: fetchConfig,\n })\n\n const [form, setForm] = useState<AIConfig>({\n api_key: '',\n base_url: '',\n model: '',\n })\n const [showKey, setShowKey] = useState(false)\n const [testResult, setTestResult] = useState<{ ok: boolean; message: string } | null>(null)\n\n useEffect(() => {\n if (config) setForm(config)\n }, [config])\n\n const { data: models, refetch: refetchModels, isFetching: loadingModels } = useQuery({\n queryKey: ['ai-models', form.api_key, form.base_url],\n queryFn: () => fetchModels(form.api_key, form.base_url),\n enabled: !!form.api_key && !!form.base_url,\n })\n\n const saveMutation = useMutation({\n mutationFn: saveConfig,\n onSuccess: () => {\n toast.success('AI 配置已保存')\n queryClient.invalidateQueries({ queryKey: ['ai-config'] })\n },\n onError: (e: Error) => toast.error(`保存失败: ${e.message}`),\n })\n\n const testMutation = useMutation({\n mutationFn: testConnection,\n onSuccess: (data) => {\n const modelName = data?.model || form.model\n setTestResult({ ok: true, message: `连接成功!模型: ${modelName}` })\n toast.success('API 连接测试通过')\n },\n onError: (err: Error) => {\n setTestResult({ ok: false, message: err.message })\n toast.error('API 连接测试失败')\n },\n })\n\n if (isLoading) {\n return <div className=\"bg-white rounded-lg shadow p-6\">加载中...</div>\n }\n\n return (\n <div>\n <h1 className=\"text-2xl font-bold text-gray-900 mb-6\">AI 配置</h1>\n\n <div className=\"bg-white rounded-lg shadow p-6 max-w-2xl\">\n <div className=\"flex items-center gap-2 mb-6\">\n <Cpu className=\"h-5 w-5 text-indigo-600\" />\n <h3 className=\"text-lg font-semibold text-gray-900\">Anthropic API 配置</h3>\n </div>\n\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-1\">API Key</label>\n <div className=\"relative\">\n <input\n type={showKey ? 'text' : 'password'}\n value={form.api_key || ''}\n onChange={(e) => setForm({ ...form, api_key: e.target.value })}\n placeholder=\"sk-...\"\n className=\"w-full px-3 py-2 pr-10 border border-gray-300 rounded-md text-sm font-mono\"\n />\n <button\n type=\"button\"\n onClick={() => setShowKey(!showKey)}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600\"\n >\n {showKey ? <EyeOff className=\"h-4 w-4\" /> : <Eye className=\"h-4 w-4\" />}\n </button>\n </div>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-1\">Base URL</label>\n <input\n type=\"text\"\n value={form.base_url || ''}\n onChange={(e) => setForm({ ...form, base_url: e.target.value })}\n placeholder=\"https://api.anthropic.com\"\n className=\"w-full px-3 py-2 border border-gray-300 rounded-md text-sm\"\n />\n </div>\n\n <div>\n <div className=\"flex items-center justify-between mb-1\">\n <label className=\"block text-sm font-medium text-gray-700\">主模型 (Distill / QualityGate)</label>\n <button\n type=\"button\"\n onClick={() => refetchModels()}\n disabled={!form.api_key || !form.base_url || loadingModels}\n className=\"text-xs text-indigo-600 hover:text-indigo-700 disabled:text-gray-400\"\n >\n {loadingModels ? '加载中...' : '🔄 刷新模型列表'}\n </button>\n </div>\n {models && models.length > 0 ? (\n <select\n value={form.model || ''}\n onChange={(e) => setForm({ ...form, model: e.target.value })}\n className=\"w-full px-3 py-2 border border-gray-300 rounded-md text-sm bg-white\"\n >\n <option value=\"\">-- 选择模型 --</option>\n {models.map((m) => (\n <option key={m.id} value={m.id}>\n {m.id} {m.owned_by && `(${m.owned_by})`}\n </option>\n ))}\n </select>\n ) : (\n <input\n type=\"text\"\n value={form.model || ''}\n onChange={(e) => setForm({ ...form, model: e.target.value })}\n placeholder=\"claude-sonnet-4-6\"\n className=\"w-full px-3 py-2 border border-gray-300 rounded-md text-sm\"\n />\n )}\n <p className=\"text-xs text-gray-500 mt-1\">\n {models && models.length > 0\n ? `发现 ${models.length} 个可用模型`\n : '填入 API Key 和 Base URL 后可自动发现模型'}\n </p>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-1\">分类器模型(可选,便宜+快的模型)</label>\n {models && models.length > 0 ? (\n <select\n value={form.classifier_model || ''}\n onChange={(e) => setForm({ ...form, classifier_model: e.target.value })}\n className=\"w-full px-3 py-2 border border-gray-300 rounded-md text-sm bg-white\"\n >\n <option value=\"\">-- 不指定 (使用主模型) --</option>\n {models.map((m) => (\n <option key={m.id} value={m.id}>{m.id}</option>\n ))}\n </select>\n ) : (\n <input\n type=\"text\"\n value={form.classifier_model || ''}\n onChange={(e) => setForm({ ...form, classifier_model: e.target.value })}\n placeholder=\"claude-haiku-4-5-20251001\"\n className=\"w-full px-3 py-2 border border-gray-300 rounded-md text-sm\"\n />\n )}\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-1\">分类器超时 (ms)</label>\n <input\n type=\"number\"\n value={form.classifier_timeout || 10000}\n onChange={(e) => setForm({ ...form, classifier_timeout: parseInt(e.target.value) })}\n className=\"w-full px-3 py-2 border border-gray-300 rounded-md text-sm\"\n />\n </div>\n\n {/* Test result */}\n {testResult && (\n <div className={`flex items-start gap-2 p-3 rounded text-sm ${\n testResult.ok ? 'bg-green-50 text-green-800' : 'bg-red-50 text-red-800'\n }`}>\n {testResult.ok ? (\n <CheckCircle className=\"h-4 w-4 flex-shrink-0 mt-0.5\" />\n ) : (\n <XCircle className=\"h-4 w-4 flex-shrink-0 mt-0.5\" />\n )}\n <div className=\"flex-1 break-all\">{testResult.message}</div>\n </div>\n )}\n\n <div className=\"flex items-center justify-end pt-4 border-t\">\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => testMutation.mutate({ api_key: form.api_key, base_url: form.base_url, model: form.model })}\n disabled={testMutation.isPending || !form.api_key || !form.model}\n className=\"inline-flex items-center gap-2 px-4 py-2 bg-gray-100 text-gray-700 text-sm font-medium rounded-md hover:bg-gray-200 disabled:bg-gray-50 disabled:text-gray-400\"\n >\n {testMutation.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Zap className=\"h-4 w-4\" />\n )}\n {testMutation.isPending ? '测试中...' : '测试连接'}\n </button>\n <button\n onClick={() => saveMutation.mutate(form)}\n disabled={saveMutation.isPending}\n className=\"inline-flex items-center gap-2 px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-md hover:bg-indigo-700 disabled:bg-gray-400\"\n >\n <Save className=\"h-4 w-4\" />\n {saveMutation.isPending ? '保存中...' : '保存配置'}\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n )\n}\n"],"names":["fetchConfig","res","fetchModels","api_key","base_url","params","saveConfig","config","authFetch","testConnection","data","AIConfig","queryClient","useQueryClient","toast","useToast","isLoading","useQuery","form","setForm","useState","showKey","setShowKey","testResult","setTestResult","useEffect","models","refetchModels","loadingModels","saveMutation","useMutation","e","testMutation","modelName","err","jsx","jsxs","Cpu","EyeOff","Eye","m","CheckCircle","XCircle","Loader2","Zap","Save"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AIConfig-D4VglzCl.js","sources":["../../src/pages/AIConfig.tsx"],"sourcesContent":["import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'\nimport { useState, useEffect } from 'react'\nimport { Save, Eye, EyeOff, Cpu, Zap, CheckCircle, XCircle, Loader2 } from 'lucide-react'\nimport { useToast } from '../components/Toast'\nimport { authFetch } from '../utils/auth'\n\ninterface AIConfig {\n api_key: string\n base_url: string\n model: string\n provider?: string\n classifier_model?: string\n classifier_timeout?: number\n}\n\ninterface ModelOption {\n id: string\n owned_by?: string\n}\n\nasync function fetchConfig(): Promise<AIConfig> {\n const res = await fetch('/api/config/ai')\n if (!res.ok) throw new Error('Failed to fetch config')\n return res.json()\n}\n\nasync function fetchModels(api_key?: string, base_url?: string): Promise<ModelOption[]> {\n const params = new URLSearchParams()\n if (api_key) params.set('api_key', api_key)\n if (base_url) params.set('base_url', base_url)\n const res = await fetch(`/api/ai/models?${params.toString()}`)\n if (!res.ok) return []\n const data = await res.json()\n return data.data || []\n}\n\nasync function saveConfig(config: AIConfig): Promise<void> {\n const res = await authFetch('/api/config/ai', {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(config),\n })\n if (!res.ok) throw new Error('Failed to save config')\n}\n\nasync function testConnection(config: { api_key: string; base_url: string; model: string }): Promise<any> {\n const res = await authFetch('/api/ai/test', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(config),\n })\n const data = await res.json().catch(() => ({}))\n if (!res.ok) throw new Error(data.error || 'Test failed')\n return data\n}\n\nexport default function AIConfig() {\n const queryClient = useQueryClient()\n const toast = useToast()\n const { data: config, isLoading } = useQuery({\n queryKey: ['ai-config'],\n queryFn: fetchConfig,\n })\n\n const [form, setForm] = useState<AIConfig>({\n api_key: '',\n base_url: '',\n model: '',\n })\n const [showKey, setShowKey] = useState(false)\n const [testResult, setTestResult] = useState<{ ok: boolean; message: string } | null>(null)\n\n useEffect(() => {\n if (config) setForm(config)\n }, [config])\n\n const { data: models, refetch: refetchModels, isFetching: loadingModels } = useQuery({\n queryKey: ['ai-models', form.api_key, form.base_url],\n queryFn: () => fetchModels(form.api_key, form.base_url),\n enabled: !!form.api_key && !!form.base_url,\n })\n\n const saveMutation = useMutation({\n mutationFn: saveConfig,\n onSuccess: () => {\n toast.success('AI 配置已保存')\n queryClient.invalidateQueries({ queryKey: ['ai-config'] })\n },\n onError: (e: Error) => toast.error(`保存失败: ${e.message}`),\n })\n\n const testMutation = useMutation({\n mutationFn: testConnection,\n onSuccess: (data) => {\n const modelName = data?.model || form.model\n setTestResult({ ok: true, message: `连接成功!模型: ${modelName}` })\n toast.success('API 连接测试通过')\n },\n onError: (err: Error) => {\n setTestResult({ ok: false, message: err.message })\n toast.error('API 连接测试失败')\n },\n })\n\n if (isLoading) {\n return <div className=\"bg-white rounded-lg shadow p-6\">加载中...</div>\n }\n\n return (\n <div>\n <h1 className=\"text-2xl font-bold text-gray-900 mb-6\">AI 配置</h1>\n\n <div className=\"bg-white rounded-lg shadow p-6 max-w-2xl\">\n <div className=\"flex items-center gap-2 mb-6\">\n <Cpu className=\"h-5 w-5 text-indigo-600\" />\n <h3 className=\"text-lg font-semibold text-gray-900\">Anthropic API 配置</h3>\n </div>\n\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-1\">API Key</label>\n <div className=\"relative\">\n <input\n type={showKey ? 'text' : 'password'}\n value={form.api_key || ''}\n onChange={(e) => setForm({ ...form, api_key: e.target.value })}\n placeholder=\"sk-...\"\n className=\"w-full px-3 py-2 pr-10 border border-gray-300 rounded-md text-sm font-mono\"\n />\n <button\n type=\"button\"\n onClick={() => setShowKey(!showKey)}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600\"\n >\n {showKey ? <EyeOff className=\"h-4 w-4\" /> : <Eye className=\"h-4 w-4\" />}\n </button>\n </div>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-1\">Base URL</label>\n <input\n type=\"text\"\n value={form.base_url || ''}\n onChange={(e) => setForm({ ...form, base_url: e.target.value })}\n placeholder=\"https://api.anthropic.com\"\n className=\"w-full px-3 py-2 border border-gray-300 rounded-md text-sm\"\n />\n </div>\n\n <div>\n <div className=\"flex items-center justify-between mb-1\">\n <label className=\"block text-sm font-medium text-gray-700\">主模型 (Distill / QualityGate)</label>\n <button\n type=\"button\"\n onClick={() => refetchModels()}\n disabled={!form.api_key || !form.base_url || loadingModels}\n className=\"text-xs text-indigo-600 hover:text-indigo-700 disabled:text-gray-400\"\n >\n {loadingModels ? '加载中...' : '🔄 刷新模型列表'}\n </button>\n </div>\n {models && models.length > 0 ? (\n <select\n value={form.model || ''}\n onChange={(e) => setForm({ ...form, model: e.target.value })}\n className=\"w-full px-3 py-2 border border-gray-300 rounded-md text-sm bg-white\"\n >\n <option value=\"\">-- 选择模型 --</option>\n {models.map((m) => (\n <option key={m.id} value={m.id}>\n {m.id} {m.owned_by && `(${m.owned_by})`}\n </option>\n ))}\n </select>\n ) : (\n <input\n type=\"text\"\n value={form.model || ''}\n onChange={(e) => setForm({ ...form, model: e.target.value })}\n placeholder=\"claude-sonnet-4-6\"\n className=\"w-full px-3 py-2 border border-gray-300 rounded-md text-sm\"\n />\n )}\n <p className=\"text-xs text-gray-500 mt-1\">\n {models && models.length > 0\n ? `发现 ${models.length} 个可用模型`\n : '填入 API Key 和 Base URL 后可自动发现模型'}\n </p>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-1\">分类器模型(可选,便宜+快的模型)</label>\n {models && models.length > 0 ? (\n <select\n value={form.classifier_model || ''}\n onChange={(e) => setForm({ ...form, classifier_model: e.target.value })}\n className=\"w-full px-3 py-2 border border-gray-300 rounded-md text-sm bg-white\"\n >\n <option value=\"\">-- 不指定 (使用主模型) --</option>\n {models.map((m) => (\n <option key={m.id} value={m.id}>{m.id}</option>\n ))}\n </select>\n ) : (\n <input\n type=\"text\"\n value={form.classifier_model || ''}\n onChange={(e) => setForm({ ...form, classifier_model: e.target.value })}\n placeholder=\"claude-haiku-4-5-20251001\"\n className=\"w-full px-3 py-2 border border-gray-300 rounded-md text-sm\"\n />\n )}\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-1\">分类器超时 (ms)</label>\n <input\n type=\"number\"\n value={form.classifier_timeout || 10000}\n onChange={(e) => setForm({ ...form, classifier_timeout: parseInt(e.target.value) })}\n className=\"w-full px-3 py-2 border border-gray-300 rounded-md text-sm\"\n />\n </div>\n\n {/* Test result */}\n {testResult && (\n <div className={`flex items-start gap-2 p-3 rounded text-sm ${\n testResult.ok ? 'bg-green-50 text-green-800' : 'bg-red-50 text-red-800'\n }`}>\n {testResult.ok ? (\n <CheckCircle className=\"h-4 w-4 flex-shrink-0 mt-0.5\" />\n ) : (\n <XCircle className=\"h-4 w-4 flex-shrink-0 mt-0.5\" />\n )}\n <div className=\"flex-1 break-all\">{testResult.message}</div>\n </div>\n )}\n\n <div className=\"flex items-center justify-end pt-4 border-t\">\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => testMutation.mutate({ api_key: form.api_key, base_url: form.base_url, model: form.model })}\n disabled={testMutation.isPending || !form.api_key || !form.model}\n className=\"inline-flex items-center gap-2 px-4 py-2 bg-gray-100 text-gray-700 text-sm font-medium rounded-md hover:bg-gray-200 disabled:bg-gray-50 disabled:text-gray-400\"\n >\n {testMutation.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Zap className=\"h-4 w-4\" />\n )}\n {testMutation.isPending ? '测试中...' : '测试连接'}\n </button>\n <button\n onClick={() => saveMutation.mutate(form)}\n disabled={saveMutation.isPending}\n className=\"inline-flex items-center gap-2 px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-md hover:bg-indigo-700 disabled:bg-gray-400\"\n >\n <Save className=\"h-4 w-4\" />\n {saveMutation.isPending ? '保存中...' : '保存配置'}\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n )\n}\n"],"names":["fetchConfig","res","fetchModels","api_key","base_url","params","saveConfig","config","authFetch","testConnection","data","AIConfig","queryClient","useQueryClient","toast","useToast","isLoading","useQuery","form","setForm","useState","showKey","setShowKey","testResult","setTestResult","useEffect","models","refetchModels","loadingModels","saveMutation","useMutation","e","testMutation","modelName","err","jsx","jsxs","Cpu","EyeOff","Eye","m","CheckCircle","XCircle","Loader2","Zap","Save"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as d,j as e}from"./react-vendor-CSp-GLFF.js";import{b as N,u as p,c as v}from"./query-C99w429o.js";import{D as w}from"./Drawer-
|
|
2
|
-
//# sourceMappingURL=Agents-
|
|
1
|
+
import{r as d,j as e}from"./react-vendor-CSp-GLFF.js";import{b as N,u as p,c as v}from"./query-C99w429o.js";import{D as w}from"./Drawer-Lo5ihVP-.js";import{M as b}from"./MarkdownRenderer-CCIz1MOz.js";import{u as A}from"./index-B1J7nBu0.js";import{a as C}from"./auth-Bnf8ZcqN.js";import{B as j,l as k,P as E,X as F,m as q}from"./lucide-Bu44HVAM.js";import"./vendor-CMMjVdZs.js";import"./syntax-highlighter-44FakypI.js";import"./react-router-I-HqunH7.js";async function S(){const n=await fetch("/api/agents");if(!n.ok)throw new Error("Failed to fetch agents");const a=await n.json();return Array.isArray(a)?a:[...a.official||[],...a.user||[]]}async function P(n){const a=await fetch(`/api/agents/${n}`);if(!a.ok)throw new Error("Failed to fetch");return a.json()}async function K(n,a){if(!(await C(`/api/agents/${n}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({content:a})})).ok)throw new Error("Failed to save")}function U(){const n=N(),a=A(),{data:r,isLoading:y}=p({queryKey:["agents"],queryFn:S}),[l,m]=d.useState(null),[g,o]=d.useState(!1),[h,x]=d.useState(""),{data:t}=p({queryKey:["agent-detail",l],queryFn:()=>P(l),enabled:!!l});d.useEffect(()=>{t!=null&&t.content&&x(t.content),o(!1)},[t]);const c=v({mutationFn:s=>K(s.name,s.content),onSuccess:()=>{a.success("Agent 已保存"),o(!1),n.invalidateQueries({queryKey:["agent-detail",l]}),n.invalidateQueries({queryKey:["agents"]})},onError:s=>a.error(`保存失败: ${s.message}`)}),u=s=>s?typeof s=="string"?s.split(",").map(i=>i.trim()):s:[];return y?e.jsx("div",{className:"bg-white rounded-lg shadow p-6",children:"加载中..."}):e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between mb-6",children:[e.jsx("h1",{className:"text-2xl font-bold text-gray-900",children:"Agent 管理"}),e.jsxs("div",{className:"text-sm text-gray-500",children:["共 ",(r==null?void 0:r.length)||0," 个 Agent"]})]}),e.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4",children:r==null?void 0:r.map(s=>{const i=u(s.tools);return e.jsxs("div",{className:"bg-white rounded-lg shadow p-5 hover:shadow-md transition-shadow cursor-pointer",onClick:()=>m(s.name),children:[e.jsxs("div",{className:"flex items-start gap-3 mb-3",children:[e.jsx("div",{className:"w-10 h-10 rounded-lg bg-indigo-100 flex items-center justify-center flex-shrink-0",children:e.jsx(j,{className:"h-5 w-5 text-indigo-600"})}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx("h3",{className:"font-semibold text-gray-900 truncate",children:s.name}),e.jsx("p",{className:"text-xs text-gray-500 mt-1 line-clamp-2",children:s.description||"(无描述)"})]})]}),i.length>0&&e.jsxs("div",{className:"mt-3 pt-3 border-t border-gray-100",children:[e.jsxs("div",{className:"flex items-center gap-1 text-xs text-gray-500 mb-2",children:[e.jsx(k,{className:"h-3 w-3"}),"可用工具 (",i.length,")"]}),e.jsxs("div",{className:"flex flex-wrap gap-1",children:[i.slice(0,5).map(f=>e.jsx("span",{className:"px-2 py-0.5 text-xs bg-gray-100 text-gray-700 rounded",children:f},f)),i.length>5&&e.jsxs("span",{className:"text-xs text-gray-400",children:["+",i.length-5]})]})]})]},s.name)})}),e.jsx(w,{open:l!==null,onClose:()=>{m(null),o(!1)},title:`Agent: ${l}`,width:"w-[800px]",children:t&&e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{className:"flex items-center gap-2 mb-2",children:[e.jsx(j,{className:"h-5 w-5 text-indigo-600"}),e.jsx("h3",{className:"text-lg font-semibold text-gray-900",children:t.name}),t.version&&e.jsxs("span",{className:"text-xs text-gray-500",children:["v",t.version]})]}),e.jsxs("div",{children:[e.jsx("div",{className:"text-xs text-gray-500 mb-1",children:"描述"}),e.jsx("div",{className:"text-sm text-gray-800",children:t.description})]}),t.tools&&e.jsxs("div",{children:[e.jsx("div",{className:"text-xs text-gray-500 mb-1",children:"可用工具"}),e.jsx("div",{className:"flex flex-wrap gap-1",children:u(t.tools).map(s=>e.jsx("span",{className:"px-2 py-0.5 text-xs bg-gray-100 text-gray-700 rounded",children:s},s))})]}),e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between mb-2",children:[e.jsx("div",{className:"text-xs text-gray-500",children:"完整内容 (Markdown)"}),e.jsx("div",{className:"flex items-center gap-2",children:g?e.jsxs(e.Fragment,{children:[e.jsxs("button",{onClick:()=>{o(!1),x(t.content||"")},className:"inline-flex items-center gap-1 text-xs text-gray-600 hover:text-gray-700",children:[e.jsx(F,{className:"h-3 w-3"}),"取消"]}),e.jsxs("button",{onClick:()=>c.mutate({name:t.name,content:h}),disabled:c.isPending,className:"inline-flex items-center gap-1 text-xs bg-indigo-600 text-white px-2 py-1 rounded hover:bg-indigo-700 disabled:bg-gray-400",children:[e.jsx(q,{className:"h-3 w-3"}),c.isPending?"保存中...":"保存"]})]}):e.jsxs("button",{onClick:()=>o(!0),className:"inline-flex items-center gap-1 text-xs text-indigo-600 hover:text-indigo-700",children:[e.jsx(E,{className:"h-3 w-3"}),"编辑"]})})]}),g?e.jsx("textarea",{value:h,onChange:s=>x(s.target.value),className:"w-full font-mono text-xs bg-gray-50 rounded p-3 border border-gray-300 focus:border-indigo-500 focus:outline-none",rows:20}):e.jsx("div",{className:"bg-white rounded p-3 border border-gray-200 max-h-[500px] overflow-y-auto",children:e.jsx(b,{content:t.content||""})}),c.isError&&e.jsxs("div",{className:"text-xs text-red-600 mt-1",children:["保存失败:",c.error.message]})]})]})})]})}export{U as default};
|
|
2
|
+
//# sourceMappingURL=Agents-ne5lXc7V.js.map
|