Built-in design generation now runs as an agentic MCP tool-loop (reusing the agent-rs BuiltInProvider), gated behind OPENPENCIL_DESIGN_AGENT_LOOP / the Settings experimental toggle; the orchestrator stays the default. - design-agent system prompt + in-process design toolset (parity-locked with the MCP surface) + flag-gated Intent::Design routing - spawn_agents execution as sequential sub-loops + live creation-mode badges (per-agent glow + 'N/M designing...' header) - new MCP tools: get_guidelines, ToolSearch, get_screenshot, get_editor_state, export_nodes, spawn_agents; style-guide local audit - #27 AI panel restyle: rounded tool cards + green check-rings, gray user bubbles, model-pill bottom toolbar, header, empty-state pills, the PARALLEL AGENTS (agent_team_size) 1x-6x chip dropdown - multi-chat tabs: ChatSessions model (Deref-to-active) + tab row UI (switch / close / + / Cmd+T) with each run bound to its tab Large checkpoint commit spanning the working tree (Rust shell crates).
108 lines
3.5 KiB
TypeScript
108 lines
3.5 KiB
TypeScript
import { defineEventHandler, readBody } from 'h3';
|
|
|
|
interface ImageServiceTestRequest {
|
|
service: string;
|
|
apiKey?: string;
|
|
model?: string;
|
|
baseUrl?: string;
|
|
clientId?: string;
|
|
clientSecret?: string;
|
|
}
|
|
|
|
interface ImageServiceTestResponse {
|
|
valid: boolean;
|
|
error?: string;
|
|
}
|
|
|
|
/**
|
|
* POST /api/ai/image-service-test
|
|
*
|
|
* Validates API keys for image generation services.
|
|
* Returns { valid: boolean, error?: string }
|
|
*/
|
|
export default defineEventHandler(async (event): Promise<ImageServiceTestResponse> => {
|
|
const body = await readBody<ImageServiceTestRequest>(event);
|
|
const { service, apiKey, baseUrl, clientId, clientSecret } = body ?? {};
|
|
|
|
if (!service) {
|
|
return { valid: false, error: 'Missing required field: service' };
|
|
}
|
|
|
|
try {
|
|
switch (service) {
|
|
case 'openverse': {
|
|
if (!clientId || !clientSecret) {
|
|
return { valid: false, error: 'Openverse requires clientId and clientSecret' };
|
|
}
|
|
const formData = new URLSearchParams();
|
|
formData.set('grant_type', 'client_credentials');
|
|
formData.set('client_id', clientId);
|
|
formData.set('client_secret', clientSecret);
|
|
const res = await fetch('https://api.openverse.org/v1/auth_tokens/token/', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
body: formData.toString(),
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => '');
|
|
return { valid: false, error: `Openverse auth failed (${res.status}): ${text}` };
|
|
}
|
|
return { valid: true };
|
|
}
|
|
|
|
case 'openai':
|
|
case 'custom': {
|
|
if (!apiKey) {
|
|
return { valid: false, error: 'Missing required field: apiKey' };
|
|
}
|
|
const origin = baseUrl ?? 'https://api.openai.com';
|
|
const res = await fetch(`${origin}/v1/models`, {
|
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => '');
|
|
return { valid: false, error: `Models request failed (${res.status}): ${text}` };
|
|
}
|
|
return { valid: true };
|
|
}
|
|
|
|
case 'gemini': {
|
|
if (!apiKey) {
|
|
return { valid: false, error: 'Missing required field: apiKey' };
|
|
}
|
|
const origin = baseUrl ?? 'https://generativelanguage.googleapis.com';
|
|
const res = await fetch(`${origin}/v1beta/models?key=${encodeURIComponent(apiKey)}`);
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => '');
|
|
return { valid: false, error: `Gemini models request failed (${res.status}): ${text}` };
|
|
}
|
|
return { valid: true };
|
|
}
|
|
|
|
case 'replicate': {
|
|
if (!apiKey) {
|
|
return { valid: false, error: 'Missing required field: apiKey' };
|
|
}
|
|
const origin = baseUrl ?? 'https://api.replicate.com';
|
|
const res = await fetch(`${origin}/v1/models`, {
|
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => '');
|
|
return {
|
|
valid: false,
|
|
error: `Replicate models request failed (${res.status}): ${text}`,
|
|
};
|
|
}
|
|
return { valid: true };
|
|
}
|
|
|
|
default:
|
|
return { valid: false, error: `Unknown service: ${service}` };
|
|
}
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
return { valid: false, error: message };
|
|
}
|
|
});
|