mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-06 07:12:12 +03:00
feat: ACP Agents dashboard + themeCoral i18n fix
- Add Dashboard > Debug > Agents page: grid of 14 built-in CLI agents with installation status, version detection, protocol badges - Add custom agent support: users can register any CLI tool via form - Expand registry from 5 to 14 built-in agents (aider, opencode, cline, qwen-code, forge, amazon-q, interpreter, cursor-cli, warp) - Add 60-second detection cache to avoid repeated execSync calls - API: GET lists all agents, POST adds custom/refreshes, DELETE removes - Settings schema: add customAgents array field - Fix missing themeCoral in settings namespace for all 30 languages - Add agents sidebar key in all 30 languages - Add agents page i18n namespace (en + pt-BR)
This commit is contained in:
286
src/app/(dashboard)/dashboard/agents/page.tsx
Normal file
286
src/app/(dashboard)/dashboard/agents/page.tsx
Normal file
@@ -0,0 +1,286 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { Card, Button, Input } from "@/shared/components";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface AgentInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
binary: string;
|
||||
version: string | null;
|
||||
installed: boolean;
|
||||
protocol: string;
|
||||
isCustom?: boolean;
|
||||
}
|
||||
|
||||
interface AgentSummary {
|
||||
total: number;
|
||||
installed: number;
|
||||
notFound: number;
|
||||
builtIn: number;
|
||||
custom: number;
|
||||
}
|
||||
|
||||
export default function AgentsPage() {
|
||||
const [agents, setAgents] = useState<AgentInfo[]>([]);
|
||||
const [summary, setSummary] = useState<AgentSummary | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [showAddForm, setShowAddForm] = useState(false);
|
||||
const [addLoading, setAddLoading] = useState(false);
|
||||
const [newAgent, setNewAgent] = useState({
|
||||
name: "",
|
||||
binary: "",
|
||||
versionCommand: "",
|
||||
spawnArgs: "",
|
||||
});
|
||||
const t = useTranslations("agents");
|
||||
|
||||
const fetchAgents = useCallback(async () => {
|
||||
try {
|
||||
const res = await fetch("/api/acp/agents");
|
||||
const data = await res.json();
|
||||
setAgents(data.agents || []);
|
||||
setSummary(data.summary || null);
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch agents:", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchAgents();
|
||||
}, [fetchAgents]);
|
||||
|
||||
const handleRefresh = async () => {
|
||||
setRefreshing(true);
|
||||
try {
|
||||
const res = await fetch("/api/acp/agents", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ action: "refresh" }),
|
||||
});
|
||||
const data = await res.json();
|
||||
setAgents(data.agents || []);
|
||||
await fetchAgents(); // Re-fetch for summary
|
||||
} catch (err) {
|
||||
console.error("Failed to refresh:", err);
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddAgent = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setAddLoading(true);
|
||||
try {
|
||||
const id = newAgent.name.toLowerCase().replace(/[^a-z0-9]/g, "-");
|
||||
const res = await fetch("/api/acp/agents", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
id,
|
||||
name: newAgent.name,
|
||||
binary: newAgent.binary,
|
||||
versionCommand: newAgent.versionCommand || `${newAgent.binary} --version`,
|
||||
spawnArgs: newAgent.spawnArgs ? newAgent.spawnArgs.split(",").map((s) => s.trim()) : [],
|
||||
protocol: "stdio",
|
||||
}),
|
||||
});
|
||||
if (res.ok) {
|
||||
setNewAgent({ name: "", binary: "", versionCommand: "", spawnArgs: "" });
|
||||
setShowAddForm(false);
|
||||
await fetchAgents();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to add agent:", err);
|
||||
} finally {
|
||||
setAddLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveAgent = async (agentId: string) => {
|
||||
try {
|
||||
const res = await fetch(`/api/acp/agents?id=${agentId}`, { method: "DELETE" });
|
||||
if (res.ok) {
|
||||
await fetchAgents();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to remove agent:", err);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-2 border-primary border-t-transparent" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 p-6 max-w-5xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">{t("title")}</h1>
|
||||
<p className="text-text-muted mt-1">{t("description")}</p>
|
||||
</div>
|
||||
<Button variant="secondary" onClick={handleRefresh} loading={refreshing}>
|
||||
<span className="material-symbols-outlined text-[16px] mr-1">refresh</span>
|
||||
{t("refresh")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Summary Cards */}
|
||||
{summary && (
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
<div className="rounded-xl border border-border/50 bg-card p-4 text-center">
|
||||
<div className="text-2xl font-bold text-primary">{summary.installed}</div>
|
||||
<div className="text-xs text-text-muted mt-1">{t("installed")}</div>
|
||||
</div>
|
||||
<div className="rounded-xl border border-border/50 bg-card p-4 text-center">
|
||||
<div className="text-2xl font-bold text-text-muted">{summary.notFound}</div>
|
||||
<div className="text-xs text-text-muted mt-1">{t("notFound")}</div>
|
||||
</div>
|
||||
<div className="rounded-xl border border-border/50 bg-card p-4 text-center">
|
||||
<div className="text-2xl font-bold">{summary.builtIn}</div>
|
||||
<div className="text-xs text-text-muted mt-1">{t("builtIn")}</div>
|
||||
</div>
|
||||
<div className="rounded-xl border border-border/50 bg-card p-4 text-center">
|
||||
<div className="text-2xl font-bold text-amber-500">{summary.custom}</div>
|
||||
<div className="text-xs text-text-muted mt-1">{t("custom")}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Agent Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{agents.map((agent) => (
|
||||
<Card key={agent.id}>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className={`p-2 rounded-lg ${
|
||||
agent.installed
|
||||
? "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"
|
||||
: "bg-zinc-500/10 text-zinc-400"
|
||||
}`}
|
||||
>
|
||||
<span className="material-symbols-outlined text-[20px]">
|
||||
{agent.installed ? "smart_toy" : "block"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold text-sm flex items-center gap-1.5">
|
||||
{agent.name}
|
||||
{agent.isCustom && (
|
||||
<span className="text-[10px] px-1.5 py-0.5 rounded-full bg-amber-500/10 text-amber-600 dark:text-amber-400 font-medium">
|
||||
{t("custom")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<code className="text-xs text-text-muted">{agent.binary}</code>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{agent.installed ? (
|
||||
<span className="inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 font-medium">
|
||||
<span className="material-symbols-outlined text-[12px]">check_circle</span>
|
||||
{agent.version || t("installed")}
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-zinc-500/10 text-zinc-500 font-medium">
|
||||
<span className="material-symbols-outlined text-[12px]">cancel</span>
|
||||
{t("notFound")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-3 pt-3 border-t border-border/30">
|
||||
<span className="inline-flex items-center gap-1 text-[10px] px-2 py-0.5 rounded-full bg-blue-500/10 text-blue-500 font-mono">
|
||||
{agent.protocol}
|
||||
</span>
|
||||
{agent.isCustom && (
|
||||
<button
|
||||
onClick={() => handleRemoveAgent(agent.id)}
|
||||
className="text-xs text-red-500 hover:text-red-400 transition-colors flex items-center gap-0.5"
|
||||
title={t("remove")}
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px]">delete</span>
|
||||
{t("remove")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Add Custom Agent */}
|
||||
<Card>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 rounded-lg bg-amber-500/10 text-amber-600 dark:text-amber-400">
|
||||
<span className="material-symbols-outlined text-[20px]">add_circle</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">{t("addCustomAgent")}</h3>
|
||||
<p className="text-sm text-text-muted">{t("addCustomAgentDesc")}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="secondary" onClick={() => setShowAddForm(!showAddForm)}>
|
||||
<span className="material-symbols-outlined text-[16px]">
|
||||
{showAddForm ? "expand_less" : "expand_more"}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{showAddForm && (
|
||||
<form
|
||||
onSubmit={handleAddAgent}
|
||||
className="flex flex-col gap-4 pt-4 border-t border-border/50"
|
||||
>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Input
|
||||
label={t("agentName")}
|
||||
placeholder="e.g. My Custom CLI"
|
||||
value={newAgent.name}
|
||||
onChange={(e) => setNewAgent({ ...newAgent, name: e.target.value })}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label={t("binaryName")}
|
||||
placeholder="e.g. mycli"
|
||||
value={newAgent.binary}
|
||||
onChange={(e) => setNewAgent({ ...newAgent, binary: e.target.value })}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Input
|
||||
label={t("versionCommand")}
|
||||
placeholder="e.g. mycli --version"
|
||||
value={newAgent.versionCommand}
|
||||
onChange={(e) => setNewAgent({ ...newAgent, versionCommand: e.target.value })}
|
||||
/>
|
||||
<Input
|
||||
label={t("spawnArgs")}
|
||||
placeholder="e.g. --quiet, --json"
|
||||
value={newAgent.spawnArgs}
|
||||
onChange={(e) => setNewAgent({ ...newAgent, spawnArgs: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" variant="primary" loading={addLoading}>
|
||||
<span className="material-symbols-outlined text-[16px] mr-1">add</span>
|
||||
{t("addAgent")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,35 +1,121 @@
|
||||
/**
|
||||
* API Route: /api/acp/agents
|
||||
*
|
||||
* Returns the list of detected CLI agents and their availability status.
|
||||
* Used by the dashboard to show ACP transport options.
|
||||
*/
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
import { detectInstalledAgents } from "@/lib/acp";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
import {
|
||||
detectInstalledAgents,
|
||||
refreshAgentCache,
|
||||
setCustomAgents,
|
||||
getCustomAgentDefs,
|
||||
type CustomAgentDef,
|
||||
} from "@/lib/acp/registry";
|
||||
import { getSettings, updateSettings } from "@/lib/localDb";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
// Load custom agents from settings on each GET to stay in sync
|
||||
const settings = await getSettings();
|
||||
if (settings.customAgents) {
|
||||
setCustomAgents(settings.customAgents as CustomAgentDef[]);
|
||||
}
|
||||
|
||||
const agents = detectInstalledAgents();
|
||||
const installed = agents.filter((a) => a.installed).length;
|
||||
const total = agents.length;
|
||||
|
||||
return NextResponse.json({
|
||||
agents: agents.map((a) => ({
|
||||
id: a.id,
|
||||
name: a.name,
|
||||
binary: a.binary,
|
||||
version: a.version,
|
||||
installed: a.installed,
|
||||
providerAlias: a.providerAlias,
|
||||
protocol: a.protocol,
|
||||
})),
|
||||
available: agents.filter((a) => a.installed).length,
|
||||
total: agents.length,
|
||||
agents,
|
||||
summary: {
|
||||
total,
|
||||
installed,
|
||||
notFound: total - installed,
|
||||
builtIn: agents.filter((a) => !a.isCustom).length,
|
||||
custom: agents.filter((a) => a.isCustom).length,
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
return NextResponse.json(
|
||||
{ error: error.message || "Failed to detect agents" },
|
||||
{ status: 500 }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error detecting agents:", error);
|
||||
return NextResponse.json({ error: "Failed to detect agents" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
|
||||
if (body.action === "refresh") {
|
||||
const agents = refreshAgentCache();
|
||||
return NextResponse.json({ agents, refreshed: true });
|
||||
}
|
||||
|
||||
// Add custom agent
|
||||
const { id, name, binary, versionCommand, providerAlias, spawnArgs, protocol } = body;
|
||||
if (!id || !name || !binary || !versionCommand) {
|
||||
return NextResponse.json(
|
||||
{ error: "Missing required fields: id, name, binary, versionCommand" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const newAgent: CustomAgentDef = {
|
||||
id: id.toLowerCase().replace(/[^a-z0-9-]/g, "-"),
|
||||
name,
|
||||
binary,
|
||||
versionCommand,
|
||||
providerAlias: providerAlias || id,
|
||||
spawnArgs: spawnArgs || [],
|
||||
protocol: protocol || "stdio",
|
||||
};
|
||||
|
||||
// Load current, append, save
|
||||
const settings = await getSettings();
|
||||
const current: CustomAgentDef[] = (settings.customAgents as CustomAgentDef[]) || [];
|
||||
|
||||
// Avoid duplicates
|
||||
if (current.some((a) => a.id === newAgent.id)) {
|
||||
return NextResponse.json(
|
||||
{ error: `Agent with id '${newAgent.id}' already exists` },
|
||||
{ status: 409 }
|
||||
);
|
||||
}
|
||||
|
||||
const updated = [...current, newAgent];
|
||||
await updateSettings({ customAgents: updated });
|
||||
setCustomAgents(updated);
|
||||
|
||||
// Refresh cache to detect the new agent
|
||||
const agents = refreshAgentCache();
|
||||
return NextResponse.json({ agents, added: newAgent });
|
||||
} catch (error) {
|
||||
console.error("Error adding custom agent:", error);
|
||||
return NextResponse.json({ error: "Failed to add agent" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(request: Request) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const agentId = searchParams.get("id");
|
||||
|
||||
if (!agentId) {
|
||||
return NextResponse.json({ error: "Missing agent id" }, { status: 400 });
|
||||
}
|
||||
|
||||
const settings = await getSettings();
|
||||
const current: CustomAgentDef[] = (settings.customAgents as CustomAgentDef[]) || [];
|
||||
const updated = current.filter((a) => a.id !== agentId);
|
||||
|
||||
if (updated.length === current.length) {
|
||||
return NextResponse.json(
|
||||
{ error: `Agent '${agentId}' not found in custom agents` },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
await updateSettings({ customAgents: updated });
|
||||
setCustomAgents(updated);
|
||||
const agents = refreshAgentCache();
|
||||
|
||||
return NextResponse.json({ agents, removed: agentId });
|
||||
} catch (error) {
|
||||
console.error("Error removing custom agent:", error);
|
||||
return NextResponse.json({ error: "Failed to remove agent" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "أورانج (Orange)",
|
||||
"themeCyan": "كيان",
|
||||
"endpoints": "نقاط النهاية",
|
||||
"playground": "ملعب النماذج"
|
||||
"playground": "ملعب النماذج",
|
||||
"agents": "وكلاء"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "المواضيع",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "الرموز المميزة المستخدمة لإنشاء إدخالات ذاكرة التخزين المؤقت (الرجوع إلى معدل الإدخال)",
|
||||
"customPricingNote": "يمكنك تجاوز التسعير الافتراضي لنماذج محددة. تحظى التجاوزات المخصصة بالأولوية على الأسعار التي يتم اكتشافها تلقائيًا.",
|
||||
"editPricing": "تحرير التسعير",
|
||||
"viewFullDetails": "عرض التفاصيل الكاملة"
|
||||
"viewFullDetails": "عرض التفاصيل الكاملة",
|
||||
"themeCoral": "مرجاني"
|
||||
},
|
||||
"translator": {
|
||||
"title": "مترجم",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Оранжево",
|
||||
"themeCyan": "Циан",
|
||||
"endpoints": "Крайни точки",
|
||||
"playground": "Площадка"
|
||||
"playground": "Площадка",
|
||||
"agents": "Агенти"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Теми",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "Токени, използвани за създаване на записи в кеша (резервен към скоростта на въвеждане)",
|
||||
"customPricingNote": "Можете да замените цените по подразбиране за конкретни модели. Персонализираните замени имат приоритет пред автоматично разпознатото ценообразуване.",
|
||||
"editPricing": "Редактиране на цените",
|
||||
"viewFullDetails": "Вижте пълните подробности"
|
||||
"viewFullDetails": "Вижте пълните подробности",
|
||||
"themeCoral": "Корал"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Преводач",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Turkis",
|
||||
"endpoints": "Endpoints",
|
||||
"playground": "Legeplads"
|
||||
"playground": "Legeplads",
|
||||
"agents": "Agenter"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Temaer",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "Tokens, der bruges til at oprette cacheposter (tilbage til inputhastighed)",
|
||||
"customPricingNote": "Du kan tilsidesætte standardpriser for specifikke modeller. Tilpassede tilsidesættelser har prioritet frem for automatisk registrerede priser.",
|
||||
"editPricing": "Rediger prissætning",
|
||||
"viewFullDetails": "Se alle detaljer"
|
||||
"viewFullDetails": "Se alle detaljer",
|
||||
"themeCoral": "Koral"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Oversætter",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Endpunkte",
|
||||
"playground": "Spielwiese"
|
||||
"playground": "Spielwiese",
|
||||
"agents": "Agenten"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themen",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "Token, die zum Erstellen von Cache-Einträgen verwendet werden (Fallback auf Eingaberate)",
|
||||
"customPricingNote": "Sie können die Standardpreise für bestimmte Modelle überschreiben. Benutzerdefinierte Überschreibungen haben Vorrang vor automatisch erkannten Preisen.",
|
||||
"editPricing": "Preise bearbeiten",
|
||||
"viewFullDetails": "Vollständige Details anzeigen"
|
||||
"viewFullDetails": "Vollständige Details anzeigen",
|
||||
"themeCoral": "Koralle"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Übersetzer",
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
"settings": "Settings",
|
||||
"translator": "Translator",
|
||||
"playground": "Playground",
|
||||
"agents": "Agents",
|
||||
"docs": "Docs",
|
||||
"issues": "Issues",
|
||||
"endpoints": "Endpoints",
|
||||
@@ -1748,7 +1749,8 @@
|
||||
"cacheCreationTokenDesc": "Tokens used to create cache entries (fallback to input rate)",
|
||||
"customPricingNote": "You can override default pricing for specific models. Custom overrides take priority over auto-detected pricing.",
|
||||
"editPricing": "Edit Pricing",
|
||||
"viewFullDetails": "View Full Details"
|
||||
"viewFullDetails": "View Full Details",
|
||||
"themeCoral": "Coral"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Translator",
|
||||
@@ -2421,5 +2423,22 @@
|
||||
"termsSection5Text": "OmniRoute is provided \"as is\" without warranty of any kind. We are not responsible for any costs incurred through API usage, service disruptions, or data loss. Always maintain backups of your configuration.",
|
||||
"termsSection6Title": "6. Open Source",
|
||||
"termsSection6Text": "OmniRoute is open-source software. You are free to inspect, modify, and distribute it under the terms of its license."
|
||||
},
|
||||
"agents": {
|
||||
"title": "CLI Agents",
|
||||
"description": "Discover installed CLI agents on your system. Add custom agents for auto-detection.",
|
||||
"refresh": "Refresh",
|
||||
"installed": "Installed",
|
||||
"notFound": "Not Found",
|
||||
"builtIn": "Built-in",
|
||||
"custom": "Custom",
|
||||
"remove": "Remove",
|
||||
"addCustomAgent": "Add Custom Agent",
|
||||
"addCustomAgentDesc": "Register any CLI tool for detection. It will be scanned automatically on refresh.",
|
||||
"agentName": "Agent Name",
|
||||
"binaryName": "Binary Name",
|
||||
"versionCommand": "Version Command",
|
||||
"spawnArgs": "Spawn Args",
|
||||
"addAgent": "Add Agent"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Naranja",
|
||||
"themeCyan": "cian",
|
||||
"endpoints": "Endpoints",
|
||||
"playground": "Playground"
|
||||
"playground": "Playground",
|
||||
"agents": "Agentes"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Temas",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "Tokens utilizados para crear entradas de caché (retroceso a la tasa de entrada)",
|
||||
"customPricingNote": "Puede anular los precios predeterminados para modelos específicos. Las anulaciones personalizadas tienen prioridad sobre los precios detectados automáticamente.",
|
||||
"editPricing": "Editar precios",
|
||||
"viewFullDetails": "Ver todos los detalles"
|
||||
"viewFullDetails": "Ver todos los detalles",
|
||||
"themeCoral": "Coral"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Traductor",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Oranssi",
|
||||
"themeCyan": "Syaani",
|
||||
"endpoints": "Päätepisteet",
|
||||
"playground": "Leikkipaikka"
|
||||
"playground": "Leikkipaikka",
|
||||
"agents": "Agentit"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Teemat",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "Välimuistimerkintöjen luomiseen käytetyt tunnukset (varausarvo syöttönopeuteen)",
|
||||
"customPricingNote": "Voit ohittaa tiettyjen mallien oletushinnoittelun. Mukautetut ohitukset ovat etusijalla automaattisesti tunnistettuihin hinnoitteluun nähden.",
|
||||
"editPricing": "Muokkaa hinnoittelua",
|
||||
"viewFullDetails": "Näytä täydelliset tiedot"
|
||||
"viewFullDetails": "Näytä täydelliset tiedot",
|
||||
"themeCoral": "Koralli"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Kääntäjä",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Points d'accès",
|
||||
"playground": "Playground"
|
||||
"playground": "Playground",
|
||||
"agents": "Agents"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Thèmes",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "Jetons utilisés pour créer des entrées de cache (repli sur le débit d'entrée)",
|
||||
"customPricingNote": "Vous pouvez remplacer le prix par défaut pour des modèles spécifiques. Les remplacements personnalisés ont la priorité sur les prix détectés automatiquement.",
|
||||
"editPricing": "Modifier le prix",
|
||||
"viewFullDetails": "Afficher tous les détails"
|
||||
"viewFullDetails": "Afficher tous les détails",
|
||||
"themeCoral": "Corail"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Traducteur",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "נקודות קצה",
|
||||
"playground": "Playground"
|
||||
"playground": "Playground",
|
||||
"agents": "סוכנים"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "אסימונים המשמשים ליצירת ערכי מטמון (חזרה לקצב קלט)",
|
||||
"customPricingNote": "אתה יכול לעקוף את תמחור ברירת המחדל עבור דגמים ספציפיים. עקיפות מותאמות אישית מקבלות עדיפות על פני תמחור שזוהה אוטומטית.",
|
||||
"editPricing": "ערוך תמחור",
|
||||
"viewFullDetails": "צפה בפרטים המלאים"
|
||||
"viewFullDetails": "צפה בפרטים המלאים",
|
||||
"themeCoral": "אלמוג"
|
||||
},
|
||||
"translator": {
|
||||
"title": "מתרגם",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "narancssárga",
|
||||
"themeCyan": "Cián",
|
||||
"endpoints": "Végpontok",
|
||||
"playground": "Játszótér"
|
||||
"playground": "Játszótér",
|
||||
"agents": "Ügynökök"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Témák",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "A gyorsítótár bejegyzéseinek létrehozására használt tokenek (vissza a beviteli sebességre)",
|
||||
"customPricingNote": "Egyes modelleknél felülbírálhatja az alapértelmezett árazást. Az egyéni felülbírálások elsőbbséget élveznek az automatikusan észlelt árképzéssel szemben.",
|
||||
"editPricing": "Árak szerkesztése",
|
||||
"viewFullDetails": "Teljes részletek megtekintése"
|
||||
"viewFullDetails": "Teljes részletek megtekintése",
|
||||
"themeCoral": "Korall"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Fordító",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Endpoint",
|
||||
"playground": "Playground"
|
||||
"playground": "Playground",
|
||||
"agents": "Agen"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "Token yang digunakan untuk membuat entri cache (pengembalian ke tingkat input)",
|
||||
"customPricingNote": "Anda dapat mengganti harga default untuk model tertentu. Penggantian khusus lebih diprioritaskan dibandingkan harga yang terdeteksi otomatis.",
|
||||
"editPricing": "Sunting Harga",
|
||||
"viewFullDetails": "Lihat Detail Lengkap"
|
||||
"viewFullDetails": "Lihat Detail Lengkap",
|
||||
"themeCoral": "Koral"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Penerjemah",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Endpoint",
|
||||
"playground": "Playground"
|
||||
"playground": "Playground",
|
||||
"agents": "एजेंट"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "कैश प्रविष्टियाँ बनाने के लिए उपयोग किए जाने वाले टोकन (इनपुट दर पर फ़ॉलबैक)",
|
||||
"customPricingNote": "आप विशिष्ट मॉडलों के लिए डिफ़ॉल्ट मूल्य निर्धारण को ओवरराइड कर सकते हैं। कस्टम ओवरराइड्स को स्वतः-पता लगाए गए मूल्य-निर्धारण पर प्राथमिकता दी जाती है।",
|
||||
"editPricing": "मूल्य निर्धारण संपादित करें",
|
||||
"viewFullDetails": "पूर्ण विवरण देखें"
|
||||
"viewFullDetails": "पूर्ण विवरण देखें",
|
||||
"themeCoral": "कोरल"
|
||||
},
|
||||
"translator": {
|
||||
"title": "अनुवादक",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Endpoint",
|
||||
"playground": "Playground"
|
||||
"playground": "Playground",
|
||||
"agents": "Agenti"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "Token utilizzati per creare voci nella cache (fallback alla velocità di input)",
|
||||
"customPricingNote": "Puoi sostituire i prezzi predefiniti per modelli specifici. Le sostituzioni personalizzate hanno la priorità sui prezzi rilevati automaticamente.",
|
||||
"editPricing": "Modifica prezzi",
|
||||
"viewFullDetails": "Visualizza i dettagli completi"
|
||||
"viewFullDetails": "Visualizza i dettagli completi",
|
||||
"themeCoral": "Corallo"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Traduttore",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "エンドポイント",
|
||||
"playground": "プレイグラウンド"
|
||||
"playground": "プレイグラウンド",
|
||||
"agents": "エージェント"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "キャッシュ エントリの作成に使用されるトークン (入力レートへのフォールバック)",
|
||||
"customPricingNote": "特定のモデルのデフォルトの価格をオーバーライドできます。カスタム オーバーライドは、自動検出された価格設定よりも優先されます。",
|
||||
"editPricing": "価格の編集",
|
||||
"viewFullDetails": "詳細を表示"
|
||||
"viewFullDetails": "詳細を表示",
|
||||
"themeCoral": "コーラル"
|
||||
},
|
||||
"translator": {
|
||||
"title": "翻訳者",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "엔드포인트",
|
||||
"playground": "플레이그라운드"
|
||||
"playground": "플레이그라운드",
|
||||
"agents": "에이전트"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "캐시 항목을 생성하는 데 사용되는 토큰(입력 속도로 대체)",
|
||||
"customPricingNote": "특정 모델의 기본 가격을 재정의할 수 있습니다. 맞춤 재정의는 자동 감지된 가격보다 우선 적용됩니다.",
|
||||
"editPricing": "가격 편집",
|
||||
"viewFullDetails": "전체 세부정보 보기"
|
||||
"viewFullDetails": "전체 세부정보 보기",
|
||||
"themeCoral": "코랄"
|
||||
},
|
||||
"translator": {
|
||||
"title": "번역기",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Titik Akhir",
|
||||
"playground": "Playground"
|
||||
"playground": "Playground",
|
||||
"agents": "Ejen"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "Token yang digunakan untuk mencipta entri cache (sandar kepada kadar input)",
|
||||
"customPricingNote": "Anda boleh mengatasi harga lalai untuk model tertentu. Penggantian tersuai diutamakan berbanding harga yang dikesan secara automatik.",
|
||||
"editPricing": "Edit Harga",
|
||||
"viewFullDetails": "Lihat Butiran Penuh"
|
||||
"viewFullDetails": "Lihat Butiran Penuh",
|
||||
"themeCoral": "Koral"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Penterjemah",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Eindpunten",
|
||||
"playground": "Speeltuin"
|
||||
"playground": "Speeltuin",
|
||||
"agents": "Agenten"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "Tokens die worden gebruikt om cache-items te maken (terugval op invoersnelheid)",
|
||||
"customPricingNote": "U kunt de standaardprijzen voor specifieke modellen overschrijven. Aangepaste overschrijvingen hebben voorrang op automatisch gedetecteerde prijzen.",
|
||||
"editPricing": "Prijzen bewerken",
|
||||
"viewFullDetails": "Bekijk volledige details"
|
||||
"viewFullDetails": "Bekijk volledige details",
|
||||
"themeCoral": "Koraal"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Vertaler",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Endepunkter",
|
||||
"playground": "Lekeplass"
|
||||
"playground": "Lekeplass",
|
||||
"agents": "Agenter"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "Tokens som brukes til å lage cache-oppføringer (tilbake til inngangshastighet)",
|
||||
"customPricingNote": "Du kan overstyre standardpriser for spesifikke modeller. Egendefinerte overstyringer prioriteres fremfor automatisk oppdagede priser.",
|
||||
"editPricing": "Rediger priser",
|
||||
"viewFullDetails": "Se alle detaljer"
|
||||
"viewFullDetails": "Se alle detaljer",
|
||||
"themeCoral": "Korall"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Oversetter",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Mga Endpoint",
|
||||
"playground": "Playground"
|
||||
"playground": "Playground",
|
||||
"agents": "Mga Agent"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "Mga token na ginamit upang lumikha ng mga entry sa cache (fallback sa rate ng pag-input)",
|
||||
"customPricingNote": "Maaari mong i-override ang default na pagpepresyo para sa mga partikular na modelo. Mas inuuna ang mga custom na override kaysa sa awtomatikong natukoy na pagpepresyo.",
|
||||
"editPricing": "I-edit ang Pagpepresyo",
|
||||
"viewFullDetails": "Tingnan ang Buong Detalye"
|
||||
"viewFullDetails": "Tingnan ang Buong Detalye",
|
||||
"themeCoral": "Coral"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Tagasalin",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Punkty końcowe",
|
||||
"playground": "Plac zabaw"
|
||||
"playground": "Plac zabaw",
|
||||
"agents": "Agenci"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "Tokeny używane do tworzenia wpisów w pamięci podręcznej (powrót do szybkości wprowadzania)",
|
||||
"customPricingNote": "Możesz zastąpić domyślne ceny dla określonych modeli. Zastąpienia niestandardowe mają pierwszeństwo przed automatycznie wykrytymi cenami.",
|
||||
"editPricing": "Edytuj ceny",
|
||||
"viewFullDetails": "Zobacz pełne szczegóły"
|
||||
"viewFullDetails": "Zobacz pełne szczegóły",
|
||||
"themeCoral": "Koral"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Tłumacz",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Endpoints",
|
||||
"playground": "Playground"
|
||||
"playground": "Playground",
|
||||
"agents": "Agentes"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -1736,7 +1737,8 @@
|
||||
"cacheCreationTokenDesc": "Tokens usados para criar entradas de cache (fallback para taxa de input)",
|
||||
"customPricingNote": "Você pode sobrescrever preços padrão para modelos específicos. Sobrescritas personalizadas têm prioridade sobre preços detectados automaticamente.",
|
||||
"editPricing": "Editar Preços",
|
||||
"viewFullDetails": "Ver Detalhes Completos"
|
||||
"viewFullDetails": "Ver Detalhes Completos",
|
||||
"themeCoral": "Coral"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Tradutor",
|
||||
@@ -2421,5 +2423,22 @@
|
||||
"featureWebhooks": "Configuração de webhooks e assinaturas de eventos",
|
||||
"featureSwagger": "Geração automática de specs OpenAPI / Swagger",
|
||||
"featureAuth": "Gestão de chaves API e escopos OAuth por endpoint"
|
||||
},
|
||||
"agents": {
|
||||
"title": "Agentes CLI",
|
||||
"description": "Descubra agentes CLI instalados no seu sistema. Adicione agentes customizados para auto-detecção.",
|
||||
"refresh": "Atualizar",
|
||||
"installed": "Instalado",
|
||||
"notFound": "Não encontrado",
|
||||
"builtIn": "Nativo",
|
||||
"custom": "Customizado",
|
||||
"remove": "Remover",
|
||||
"addCustomAgent": "Adicionar Agente Customizado",
|
||||
"addCustomAgentDesc": "Registre qualquer ferramenta CLI para detecção. Ela será verificada automaticamente ao atualizar.",
|
||||
"agentName": "Nome do Agente",
|
||||
"binaryName": "Nome do Binário",
|
||||
"versionCommand": "Comando de Versão",
|
||||
"spawnArgs": "Argumentos",
|
||||
"addAgent": "Adicionar Agente"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeViolet": "Violeta",
|
||||
"themeOrange": "Laranja",
|
||||
"themeCyan": "Ciano",
|
||||
"playground": "Playground"
|
||||
"playground": "Playground",
|
||||
"agents": "Agentes"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -1743,7 +1744,8 @@
|
||||
"cacheCreationTokenDesc": "Tokens usados para criar entradas de cache (fallback para taxa de entrada)",
|
||||
"customPricingNote": "Você pode substituir o preço padrão de modelos específicos. As substituições personalizadas têm prioridade sobre os preços detectados automaticamente.",
|
||||
"editPricing": "Editar preços",
|
||||
"viewFullDetails": "Ver detalhes completos"
|
||||
"viewFullDetails": "Ver detalhes completos",
|
||||
"themeCoral": "Coral"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Tradutor",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Puncte finale",
|
||||
"playground": "Playground"
|
||||
"playground": "Playground",
|
||||
"agents": "Agenți"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "Jetoane utilizate pentru a crea intrări în cache (retur la rata de intrare)",
|
||||
"customPricingNote": "Puteți suprascrie prețurile implicite pentru anumite modele. Anulările personalizate au prioritate față de prețurile detectate automat.",
|
||||
"editPricing": "Editați prețul",
|
||||
"viewFullDetails": "Vezi detalii complete"
|
||||
"viewFullDetails": "Vezi detalii complete",
|
||||
"themeCoral": "Coral"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Traducător",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Оранжевый",
|
||||
"themeCyan": "Голубой",
|
||||
"endpoints": "Конечные точки",
|
||||
"playground": "Площадка"
|
||||
"playground": "Площадка",
|
||||
"agents": "Агенты"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Темы",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "Токены, используемые для создания записей в кэше (возврат к скорости ввода)",
|
||||
"customPricingNote": "Вы можете переопределить цены по умолчанию для определенных моделей. Пользовательские переопределения имеют приоритет над ценами, определяемыми автоматически.",
|
||||
"editPricing": "Изменить цену",
|
||||
"viewFullDetails": "Посмотреть полную информацию"
|
||||
"viewFullDetails": "Посмотреть полную информацию",
|
||||
"themeCoral": "Коралл"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Переводчик",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Koncové body",
|
||||
"playground": "Ihrisko"
|
||||
"playground": "Ihrisko",
|
||||
"agents": "Agenti"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "Tokeny používané na vytváranie záznamov vo vyrovnávacej pamäti (záložná rýchlosť vstupu)",
|
||||
"customPricingNote": "Predvolené ceny pre konkrétne modely môžete prepísať. Vlastné prepísania majú prednosť pred automaticky zistenými cenami.",
|
||||
"editPricing": "Upraviť ceny",
|
||||
"viewFullDetails": "Zobraziť úplné podrobnosti"
|
||||
"viewFullDetails": "Zobraziť úplné podrobnosti",
|
||||
"themeCoral": "Korál"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Prekladateľ",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Ändpunkter",
|
||||
"playground": "Lekplats"
|
||||
"playground": "Lekplats",
|
||||
"agents": "Agenter"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "Tokens som används för att skapa cacheposter (återgång till inmatningshastighet)",
|
||||
"customPricingNote": "Du kan åsidosätta standardpriser för specifika modeller. Anpassade åsidosättningar har prioritet framför automatiskt identifierade priser.",
|
||||
"editPricing": "Redigera prissättning",
|
||||
"viewFullDetails": "Visa fullständiga detaljer"
|
||||
"viewFullDetails": "Visa fullständiga detaljer",
|
||||
"themeCoral": "Korall"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Översättare",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "จุดปลายทาง",
|
||||
"playground": "Playground"
|
||||
"playground": "Playground",
|
||||
"agents": "เอเจนต์"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "ธีมส์",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "โทเค็นที่ใช้ในการสร้างรายการแคช (สำรองไปยังอัตราการป้อนข้อมูล)",
|
||||
"customPricingNote": "คุณสามารถแทนที่ราคาเริ่มต้นสำหรับรุ่นเฉพาะได้ การแทนที่แบบกำหนดเองจะมีลำดับความสำคัญมากกว่าการกำหนดราคาที่ตรวจพบอัตโนมัติ",
|
||||
"editPricing": "แก้ไขราคา",
|
||||
"viewFullDetails": "ดูรายละเอียดทั้งหมด"
|
||||
"viewFullDetails": "ดูรายละเอียดทั้งหมด",
|
||||
"themeCoral": "คอรัล"
|
||||
},
|
||||
"translator": {
|
||||
"title": "นักแปล",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Кінцеві точки",
|
||||
"playground": "Playground"
|
||||
"playground": "Playground",
|
||||
"agents": "Агенти"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "Токени, що використовуються для створення записів кешу (резервний вихід до швидкості введення)",
|
||||
"customPricingNote": "Ви можете змінити ціни за умовчанням для певних моделей. Спеціальні зміни мають пріоритет над автоматично визначеними цінами.",
|
||||
"editPricing": "Редагувати ціни",
|
||||
"viewFullDetails": "Переглянути повну інформацію"
|
||||
"viewFullDetails": "Переглянути повну інформацію",
|
||||
"themeCoral": "Корал"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Перекладач",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Điểm cuối",
|
||||
"playground": "Playground"
|
||||
"playground": "Playground",
|
||||
"agents": "Tác nhân"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "Mã thông báo được sử dụng để tạo mục nhập bộ đệm (dự phòng tốc độ đầu vào)",
|
||||
"customPricingNote": "Bạn có thể ghi đè giá mặc định cho các kiểu máy cụ thể. Ghi đè tùy chỉnh được ưu tiên hơn giá được tự động phát hiện.",
|
||||
"editPricing": "Chỉnh sửa giá",
|
||||
"viewFullDetails": "Xem chi tiết đầy đủ"
|
||||
"viewFullDetails": "Xem chi tiết đầy đủ",
|
||||
"themeCoral": "San hô"
|
||||
},
|
||||
"translator": {
|
||||
"title": "Người phiên dịch",
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "端点",
|
||||
"playground": "模型试验场"
|
||||
"playground": "模型试验场",
|
||||
"agents": "代理"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -1731,7 +1732,8 @@
|
||||
"cacheCreationTokenDesc": "用于创建缓存条目的令牌(回退到输入速率)",
|
||||
"customPricingNote": "您可以覆盖特定型号的默认定价。自定义覆盖优先于自动检测的定价。",
|
||||
"editPricing": "编辑定价",
|
||||
"viewFullDetails": "查看完整详情"
|
||||
"viewFullDetails": "查看完整详情",
|
||||
"themeCoral": "珊瑚色"
|
||||
},
|
||||
"translator": {
|
||||
"title": "翻译者",
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
* and running version commands. Used to offer ACP transport as an alternative
|
||||
* to the HTTP proxy method.
|
||||
*
|
||||
* Supports 14 built-in agents + user-defined custom agents from settings.
|
||||
*
|
||||
* Reference: https://github.com/iOfficeAI/AionUi (auto-detects CLI agents)
|
||||
*/
|
||||
|
||||
@@ -29,6 +31,19 @@ export interface CliAgentInfo {
|
||||
spawnArgs: string[];
|
||||
/** Protocol used for communication */
|
||||
protocol: "stdio" | "http";
|
||||
/** Whether this is a user-defined custom agent */
|
||||
isCustom?: boolean;
|
||||
}
|
||||
|
||||
/** Shape stored in settings DB for custom agents */
|
||||
export interface CustomAgentDef {
|
||||
id: string;
|
||||
name: string;
|
||||
binary: string;
|
||||
versionCommand: string;
|
||||
providerAlias: string;
|
||||
spawnArgs: string[];
|
||||
protocol: "stdio" | "http";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,34 +95,173 @@ const AGENT_DEFINITIONS: Omit<CliAgentInfo, "version" | "installed">[] = [
|
||||
spawnArgs: [],
|
||||
protocol: "stdio",
|
||||
},
|
||||
{
|
||||
id: "aider",
|
||||
name: "Aider",
|
||||
binary: "aider",
|
||||
versionCommand: "aider --version",
|
||||
providerAlias: "aider",
|
||||
spawnArgs: ["--no-auto-commits"],
|
||||
protocol: "stdio",
|
||||
},
|
||||
{
|
||||
id: "opencode",
|
||||
name: "OpenCode",
|
||||
binary: "opencode",
|
||||
versionCommand: "opencode --version",
|
||||
providerAlias: "opencode",
|
||||
spawnArgs: [],
|
||||
protocol: "stdio",
|
||||
},
|
||||
{
|
||||
id: "cline",
|
||||
name: "Cline",
|
||||
binary: "cline",
|
||||
versionCommand: "cline --version",
|
||||
providerAlias: "cline",
|
||||
spawnArgs: [],
|
||||
protocol: "stdio",
|
||||
},
|
||||
{
|
||||
id: "qwen-code",
|
||||
name: "Qwen Code",
|
||||
binary: "qwen",
|
||||
versionCommand: "qwen --version",
|
||||
providerAlias: "qwen",
|
||||
spawnArgs: [],
|
||||
protocol: "stdio",
|
||||
},
|
||||
{
|
||||
id: "forge",
|
||||
name: "ForgeCode",
|
||||
binary: "forge",
|
||||
versionCommand: "forge --version",
|
||||
providerAlias: "forge",
|
||||
spawnArgs: [],
|
||||
protocol: "stdio",
|
||||
},
|
||||
{
|
||||
id: "amazon-q",
|
||||
name: "Amazon Q Developer",
|
||||
binary: "q",
|
||||
versionCommand: "q --version",
|
||||
providerAlias: "amazon-q",
|
||||
spawnArgs: [],
|
||||
protocol: "stdio",
|
||||
},
|
||||
{
|
||||
id: "interpreter",
|
||||
name: "Open Interpreter",
|
||||
binary: "interpreter",
|
||||
versionCommand: "interpreter --version",
|
||||
providerAlias: "interpreter",
|
||||
spawnArgs: [],
|
||||
protocol: "stdio",
|
||||
},
|
||||
{
|
||||
id: "cursor-cli",
|
||||
name: "Cursor CLI",
|
||||
binary: "cursor",
|
||||
versionCommand: "cursor --version",
|
||||
providerAlias: "cursor",
|
||||
spawnArgs: [],
|
||||
protocol: "stdio",
|
||||
},
|
||||
{
|
||||
id: "warp",
|
||||
name: "Warp AI",
|
||||
binary: "warp",
|
||||
versionCommand: "warp --version",
|
||||
providerAlias: "warp",
|
||||
spawnArgs: [],
|
||||
protocol: "stdio",
|
||||
},
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Detection cache (60 seconds)
|
||||
// ---------------------------------------------------------------------------
|
||||
let _cachedAgents: CliAgentInfo[] | null = null;
|
||||
let _cacheTimestamp = 0;
|
||||
const CACHE_TTL_MS = 60_000;
|
||||
|
||||
/** Custom agents loaded from settings */
|
||||
let _customAgentDefs: CustomAgentDef[] = [];
|
||||
|
||||
/**
|
||||
* Set custom agent definitions from settings.
|
||||
*/
|
||||
export function setCustomAgents(agents: CustomAgentDef[]): void {
|
||||
_customAgentDefs = agents || [];
|
||||
_cachedAgents = null; // invalidate cache
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current custom agent definitions.
|
||||
*/
|
||||
export function getCustomAgentDefs(): CustomAgentDef[] {
|
||||
return _customAgentDefs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect a single agent by running its version command.
|
||||
*/
|
||||
function detectAgent(
|
||||
def: Omit<CliAgentInfo, "version" | "installed">,
|
||||
isCustom = false
|
||||
): CliAgentInfo {
|
||||
let version: string | null = null;
|
||||
let installed = false;
|
||||
|
||||
try {
|
||||
const output = execSync(def.versionCommand, {
|
||||
timeout: 5000,
|
||||
encoding: "utf-8",
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
}).trim();
|
||||
|
||||
// Extract version number from output
|
||||
const versionMatch = output.match(/(\d+\.\d+\.\d+(?:-\w+)?)/);
|
||||
version = versionMatch ? versionMatch[1] : output.split("\n")[0];
|
||||
installed = true;
|
||||
} catch {
|
||||
// Not installed or not runnable
|
||||
}
|
||||
|
||||
return { ...def, version, installed, isCustom };
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect installed CLI agents on the system.
|
||||
* Runs version commands to verify availability.
|
||||
* Results are cached for 60 seconds.
|
||||
*/
|
||||
export function detectInstalledAgents(): CliAgentInfo[] {
|
||||
return AGENT_DEFINITIONS.map((def) => {
|
||||
let version: string | null = null;
|
||||
let installed = false;
|
||||
const now = Date.now();
|
||||
if (_cachedAgents && now - _cacheTimestamp < CACHE_TTL_MS) {
|
||||
return _cachedAgents;
|
||||
}
|
||||
|
||||
try {
|
||||
const output = execSync(def.versionCommand, {
|
||||
timeout: 5000,
|
||||
encoding: "utf-8",
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
}).trim();
|
||||
// Merge built-in + custom definitions
|
||||
const allDefs = [
|
||||
...AGENT_DEFINITIONS.map((d) => ({ ...d, _custom: false })),
|
||||
..._customAgentDefs.map((d) => ({ ...d, _custom: true })),
|
||||
];
|
||||
|
||||
// Extract version number from output
|
||||
const versionMatch = output.match(/(\d+\.\d+\.\d+(?:-\w+)?)/);
|
||||
version = versionMatch ? versionMatch[1] : output.split("\n")[0];
|
||||
installed = true;
|
||||
} catch {
|
||||
// Not installed or not runnable
|
||||
}
|
||||
|
||||
return { ...def, version, installed };
|
||||
_cachedAgents = allDefs.map((def) => {
|
||||
const { _custom, ...rest } = def;
|
||||
return detectAgent(rest, _custom);
|
||||
});
|
||||
_cacheTimestamp = now;
|
||||
|
||||
return _cachedAgents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force refresh detection cache.
|
||||
*/
|
||||
export function refreshAgentCache(): CliAgentInfo[] {
|
||||
_cachedAgents = null;
|
||||
return detectInstalledAgents();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,7 @@ const navItemDefs = [
|
||||
const debugItemDefs = [
|
||||
{ href: "/dashboard/translator", i18nKey: "translator", icon: "translate" },
|
||||
{ href: "/dashboard/playground", i18nKey: "playground", icon: "science" },
|
||||
{ href: "/dashboard/agents", i18nKey: "agents", icon: "smart_toy" },
|
||||
];
|
||||
|
||||
const systemItemDefs = [{ href: "/dashboard/settings", i18nKey: "settings", icon: "settings" }];
|
||||
|
||||
@@ -36,4 +36,18 @@ export const updateSettingsSchema = z.object({
|
||||
a2aEnabled: z.boolean().optional(),
|
||||
// CLI Fingerprint compatibility (per-provider)
|
||||
cliCompatProviders: z.array(z.string().max(100)).optional(),
|
||||
// Custom CLI agent definitions for ACP
|
||||
customAgents: z
|
||||
.array(
|
||||
z.object({
|
||||
id: z.string().max(50),
|
||||
name: z.string().max(100),
|
||||
binary: z.string().max(200),
|
||||
versionCommand: z.string().max(300),
|
||||
providerAlias: z.string().max(50),
|
||||
spawnArgs: z.array(z.string().max(200)),
|
||||
protocol: z.enum(["stdio", "http"]),
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user