From 3980690e2ef8e90a074d0a4b3b799694642da0a9 Mon Sep 17 00:00:00 2001 From: Ronaldo Davi Date: Fri, 22 May 2026 14:46:33 -0300 Subject: [PATCH] feat(i18n): comprehensive pt-BR localization and UI refactoring --- src/app/(dashboard)/dashboard/a2a/page.tsx | 34 +- .../dashboard/agent-skills/page.tsx | 20 +- .../cache/components/CachePerformance.tsx | 10 +- .../cache/components/ReasoningCacheTab.tsx | 41 +- src/app/(dashboard)/dashboard/cache/page.tsx | 2 +- .../dashboard/cloud-agents/page.tsx | 961 ++- .../caveman/CavemanContextPageClient.tsx | 6 +- .../context/rtk/RtkContextPageClient.tsx | 5 +- src/app/(dashboard)/dashboard/mcp/page.tsx | 36 +- .../settings/components/AuthzSection.tsx | 471 ++ .../settings/components/PayloadRulesTab.tsx | 87 +- .../components/ProxyRegistryManager.tsx | 88 +- .../settings/components/RoutingTab.tsx | 290 +- .../settings/components/SecurityTab.tsx | 2 + .../components/VisionBridgeSettingsTab.tsx | 20 +- src/app/(dashboard)/dashboard/skills/page.tsx | 137 +- src/i18n/messages/en.json | 464 +- src/i18n/messages/pt-BR.json | 5865 +++++++++-------- src/shared/components/UsageAnalytics.tsx | 96 +- .../analytics/ApiKeyFilterDropdown.tsx | 18 +- .../analytics/CustomRangePicker.tsx | 26 +- src/shared/components/analytics/charts.tsx | 150 +- 22 files changed, 5287 insertions(+), 3542 deletions(-) create mode 100644 src/app/(dashboard)/dashboard/settings/components/AuthzSection.tsx diff --git a/src/app/(dashboard)/dashboard/a2a/page.tsx b/src/app/(dashboard)/dashboard/a2a/page.tsx index 3190bd99a9..05a835d640 100644 --- a/src/app/(dashboard)/dashboard/a2a/page.tsx +++ b/src/app/(dashboard)/dashboard/a2a/page.tsx @@ -20,6 +20,8 @@ function ServiceToggle({ onToggle: () => void; toggling: boolean; }) { + const t = useTranslations("a2aDashboard"); + const tCommon = useTranslations("common"); const online = enabled && status.online; const loading = enabled && status.loading; @@ -52,7 +54,7 @@ function ServiceToggle({ animation: online ? "pulse 2s infinite" : "none", }} /> - {loading ? "..." : online ? "Online" : "Offline"} + {loading ? "..." : online ? t("online") : t("offline")} )} @@ -127,15 +127,15 @@ export default function CachePerformance({
{hits}
-
Hits
+
{t("hits")}
{misses}
-
Misses
+
{t("misses")}
{totalRequests}
-
Total
+
{t("total")}
diff --git a/src/app/(dashboard)/dashboard/cache/components/ReasoningCacheTab.tsx b/src/app/(dashboard)/dashboard/cache/components/ReasoningCacheTab.tsx index 790269b753..d87b16768b 100644 --- a/src/app/(dashboard)/dashboard/cache/components/ReasoningCacheTab.tsx +++ b/src/app/(dashboard)/dashboard/cache/components/ReasoningCacheTab.tsx @@ -39,17 +39,6 @@ interface ReasoningCacheData { // ──────────────── Helpers ──────────────── -function timeAgo(dateStr: string): string { - const diff = Date.now() - new Date(dateStr).getTime(); - const minutes = Math.floor(diff / 60000); - if (minutes < 1) return "just now"; - if (minutes < 60) return `${minutes}m ago`; - const hours = Math.floor(minutes / 60); - if (hours < 24) return `${hours}h ago`; - const days = Math.floor(hours / 24); - return `${days}d ago`; -} - function formatChars(chars: number): string { if (chars >= 1_000_000) return `${(chars / 1_000_000).toFixed(1)}M`; if (chars >= 1_000) return `${(chars / 1_000).toFixed(1)}K`; @@ -142,6 +131,17 @@ export default function ReasoningCacheTab() { const [clearing, setClearing] = useState(false); const [expandedId, setExpandedId] = useState(null); + const timeAgo = (dateStr: string): string => { + const diff = Date.now() - new Date(dateStr).getTime(); + const minutes = Math.floor(diff / 60000); + if (minutes < 1) return t("justNow"); + if (minutes < 60) return t("minutesAgo", { minutes }); + const hours = Math.floor(minutes / 60); + if (hours < 24) return t("hoursAgo", { hours }); + const days = Math.floor(hours / 24); + return t("daysAgo", { days }); + }; + const fetchData = useCallback(async () => { try { const res = await fetch("/api/cache/reasoning"); @@ -281,10 +281,10 @@ export default function ReasoningCacheTab() { - + - + @@ -334,7 +334,7 @@ export default function ReasoningCacheTab() {
Provider{t("tableProvider")} {t("reasoningEntries")} {t("reasoningChars")}Share{t("tableShare")}
- + @@ -379,8 +379,8 @@ export default function ReasoningCacheTab() {
{t("reasoningToolCallId")} - Provider - Model + {t("tableProvider")} + {t("tableModel")} {t("reasoningChars")} {t("reasoningAge")} @@ -433,19 +433,20 @@ export default function ReasoningCacheTab() {
- Provider: {entry.provider} + {t("tableProvider")}:{" "} + {entry.provider} - Model: {entry.model} + {t("tableModel")}: {entry.model} - Created:{" "} + {t("created")}:{" "} {new Date(entry.createdAt).toLocaleString()} - Expires:{" "} + {t("expires")}:{" "} {new Date(entry.expiresAt).toLocaleString()} diff --git a/src/app/(dashboard)/dashboard/cache/page.tsx b/src/app/(dashboard)/dashboard/cache/page.tsx index 42080fb85d..964f56e2c9 100644 --- a/src/app/(dashboard)/dashboard/cache/page.tsx +++ b/src/app/(dashboard)/dashboard/cache/page.tsx @@ -481,7 +481,7 @@ export default function CachePage() {
{loading && ( -
+
)} diff --git a/src/app/(dashboard)/dashboard/cloud-agents/page.tsx b/src/app/(dashboard)/dashboard/cloud-agents/page.tsx index 1b7e4dc004..ee68c9d9a9 100644 --- a/src/app/(dashboard)/dashboard/cloud-agents/page.tsx +++ b/src/app/(dashboard)/dashboard/cloud-agents/page.tsx @@ -4,6 +4,8 @@ import { useState, useEffect, useCallback } from "react"; import { Card, Button, Input, Badge } from "@/shared/components"; import { useTranslations } from "next-intl"; +// ── Types ──────────────────────────────────────────────────────────────────── + interface CloudAgentTask { id: string; providerId: string; @@ -26,34 +28,75 @@ interface CloudAgentTask { }>; } +type TabId = "tasks" | "agents" | "settings"; +type TaskStatus = CloudAgentTask["status"]; + +// ── Constants ──────────────────────────────────────────────────────────────── + const CLOUD_AGENTS = [ { id: "jules", name: "Jules", provider: "Google", description: "Google's autonomous coding agent", - icon: "🟡", - color: "bg-yellow-500/10 text-yellow-600", + icon: "smart_toy", + iconBg: "bg-yellow-500/10", + iconColor: "text-yellow-600", }, { id: "devin", name: "Devin", provider: "Cognition", description: "Cognition's AI software engineer", - icon: "🔵", - color: "bg-blue-500/10 text-blue-600", + icon: "psychology", + iconBg: "bg-blue-500/10", + iconColor: "text-blue-600", }, { id: "codex-cloud", name: "Codex Cloud", provider: "OpenAI", description: "OpenAI's cloud-based coding agent", - icon: "⚡", - color: "bg-emerald-500/10 text-emerald-600", + icon: "cloud", + iconBg: "bg-emerald-500/10", + iconColor: "text-emerald-600", }, ]; +const STATUS_OPTIONS: { value: TaskStatus | "all"; labelKey: string }[] = [ + { value: "all", labelKey: "filterAll" }, + { value: "queued", labelKey: "statusPending" }, + { value: "running", labelKey: "statusRunning" }, + { value: "awaiting_approval", labelKey: "statusWaitingApproval" }, + { value: "completed", labelKey: "statusCompleted" }, + { value: "failed", labelKey: "statusFailed" }, + { value: "cancelled", labelKey: "statusCancelled" }, +]; + +// ── Helpers ────────────────────────────────────────────────────────────────── + +function getAgentInfo(providerId: string) { + return CLOUD_AGENTS.find((a) => a.id === providerId) || CLOUD_AGENTS[0]; +} + +function formatDuration(start: string, end: string) { + const ms = new Date(end).getTime() - new Date(start).getTime(); + if (ms < 1000) return `${ms}ms`; + const secs = Math.floor(ms / 1000); + if (secs < 60) return `${secs}s`; + const mins = Math.floor(secs / 60); + const remainSecs = secs % 60; + return `${mins}m ${remainSecs}s`; +} + +// ── Component ──────────────────────────────────────────────────────────────── + export default function CloudAgentsPage() { + const [activeTab, setActiveTab] = useState("tasks"); + const t = useTranslations("cloudAgents"); + + // ── Tasks state ────────────────────────────────────────────────────────── + const [tasks, setTasks] = useState([]); const [loading, setLoading] = useState(true); const [creating, setCreating] = useState(false); @@ -67,14 +110,48 @@ export default function CloudAgentsPage() { autoCreatePr: true, }); const [messageInput, setMessageInput] = useState(""); - const t = useTranslations("cloudAgents"); + const [statusFilter, setStatusFilter] = useState("all"); + const [providerFilter, setProviderFilter] = useState("all"); + + // ── Agents health state ────────────────────────────────────────────────── + + const [agentHealth, setAgentHealth] = useState>({}); + + // ── Settings state (localStorage) ──────────────────────────────────────── + + const [settings, setSettings] = useState({ + autoCreatePr: true, + requireApproval: false, + enabled: true, + }); + + // ── Load settings from localStorage ────────────────────────────────────── + + useEffect(() => { + try { + const stored = localStorage.getItem("omniroute-cloud-agents-settings"); + if (stored) setSettings(JSON.parse(stored)); + } catch { + // ignore + } + }, []); + + const updateSetting = (key: keyof typeof settings, value: boolean) => { + const next = { ...settings, [key]: value }; + setSettings(next); + try { + localStorage.setItem("omniroute-cloud-agents-settings", JSON.stringify(next)); + } catch { + // ignore + } + }; + + // ── Task helpers ───────────────────────────────────────────────────────── const upsertTask = useCallback((task: CloudAgentTask) => { setTasks((prev) => { - const exists = prev.some((current) => current.id === task.id); - return exists - ? prev.map((current) => (current.id === task.id ? task : current)) - : [task, ...prev]; + const exists = prev.some((c) => c.id === task.id); + return exists ? prev.map((c) => (c.id === task.id ? task : c)) : [task, ...prev]; }); setSelectedTask((current) => (current?.id === task.id ? task : current)); }, []); @@ -97,6 +174,48 @@ export default function CloudAgentsPage() { fetchTasks(); }, [fetchTasks]); + // ── Auto-poll when tasks are running/queued ────────────────────────────── + + const hasActiveTasks = tasks.some((t) => t.status === "running" || t.status === "queued"); + + useEffect(() => { + if (!hasActiveTasks) return; + const id = setInterval(() => { + fetchTasks(); + }, 5000); + return () => clearInterval(id); + }, [hasActiveTasks, fetchTasks]); + + // ── Fetch agent health ─────────────────────────────────────────────────── + + const fetchAgentHealth = useCallback(async () => { + try { + const res = await fetch("/api/v1/agents/health"); + if (res.ok) { + const data = await res.json(); + if (data.data) setAgentHealth(data.data); + } + } catch (err) { + console.error("Failed to fetch agent health:", err); + } + }, []); + + // ── Tab mount effects ──────────────────────────────────────────────────── + + useEffect(() => { + if (activeTab === "agents") fetchAgentHealth(); + }, [activeTab, fetchAgentHealth]); + + // ── Filtered tasks ─────────────────────────────────────────────────────── + + const filteredTasks = tasks.filter((task) => { + if (statusFilter !== "all" && task.status !== statusFilter) return false; + if (providerFilter !== "all" && task.providerId !== providerFilter) return false; + return true; + }); + + // ── Task actions (preserved from original) ─────────────────────────────── + const handleCreateTask = async (e: React.FormEvent) => { e.preventDefault(); setCreating(true); @@ -113,9 +232,7 @@ export default function CloudAgentsPage() { providerId: newTask.providerId, prompt: newTask.prompt, source, - options: { - autoCreatePr: newTask.autoCreatePr, - }, + options: { autoCreatePr: newTask.autoCreatePr }, }), }); if (res.ok) { @@ -143,10 +260,7 @@ export default function CloudAgentsPage() { const res = await fetch(`/api/v1/agents/tasks/${selectedTask.id}`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - action: "message", - message: messageInput, - }), + body: JSON.stringify({ action: "message", message: messageInput }), }); if (res.ok) { const data = await res.json(); @@ -198,44 +312,37 @@ export default function CloudAgentsPage() { }); if (res.ok) { setTasks((prev) => prev.filter((t) => t.id !== taskId)); - if (selectedTask?.id === taskId) { - setSelectedTask(null); - } + if (selectedTask?.id === taskId) setSelectedTask(null); } } catch (err) { console.error("Failed to delete task:", err); } }; + // ── Render helpers ─────────────────────────────────────────────────────── + const getStatusBadge = (status: string) => { - const statusMap: Record = { - queued: { color: "bg-zinc-500/10 text-zinc-500", label: t("statusPending") }, - running: { color: "bg-blue-500/10 text-blue-500", label: t("statusRunning") }, - awaiting_approval: { - color: "bg-amber-500/10 text-amber-600", - label: t("statusWaitingApproval"), - }, - completed: { color: "bg-emerald-500/10 text-emerald-600", label: t("statusCompleted") }, - failed: { color: "bg-red-500/10 text-red-500", label: t("statusFailed") }, - cancelled: { color: "bg-zinc-500/10 text-zinc-400", label: t("statusCancelled") }, + const statusMap: Record< + string, + { variant: "default" | "primary" | "success" | "warning" | "error" | "info"; label: string } + > = { + queued: { variant: "default", label: t("statusPending") }, + running: { variant: "info", label: t("statusRunning") }, + awaiting_approval: { variant: "warning", label: t("statusWaitingApproval") }, + completed: { variant: "success", label: t("statusCompleted") }, + failed: { variant: "error", label: t("statusFailed") }, + cancelled: { variant: "default", label: t("statusCancelled") }, }; const s = statusMap[status] || statusMap.queued; return ( - - {status === "running" && } + {s.label} - + ); }; - const getAgentInfo = (providerId: string) => { - return CLOUD_AGENTS.find((a) => a.id === providerId) || CLOUD_AGENTS[0]; - }; - const getPlanText = (task: CloudAgentTask) => { - return task.activities.find((activity) => activity.type === "plan")?.content || ""; + return task.activities.find((a) => a.type === "plan")?.content || ""; }; const formatResult = (result: CloudAgentTask["result"]) => { @@ -244,6 +351,16 @@ export default function CloudAgentsPage() { return JSON.stringify(result, null, 2); }; + // ── Tab definitions ────────────────────────────────────────────────────── + + const tabs: { id: TabId; label: string; icon: string }[] = [ + { id: "tasks", label: t("tasksTab") || "Tasks", icon: "task_alt" }, + { id: "agents", label: t("agentsTab") || "Agents", icon: "smart_toy" }, + { id: "settings", label: t("settingsTab") || "Settings", icon: "tune" }, + ]; + + // ── Loading state ──────────────────────────────────────────────────────── + if (loading) { return (
@@ -253,299 +370,543 @@ export default function CloudAgentsPage() { ); } + // ── Main render ────────────────────────────────────────────────────────── + return (
+ {/* Header */} -
-
-
-

{t("aboutTitle")}

-

{t("aboutDescription")}

-
+
+
+

{t("aboutTitle")}

+

{t("aboutDescription")}

-
- {CLOUD_AGENTS.map((agent) => ( -
-
- {agent.icon} -

{agent.name}

-
-

{agent.description}

-

{agent.provider}

+
+ + + {settings.enabled + ? t("agentsEnabled") || "Enabled" + : t("agentsDisabled") || "Disabled"} + +
+
+ + + {/* Tab bar */} +
+ {tabs.map((tab) => ( + + ))} +
+ + {/* ── Tasks Tab ──────────────────────────────────────────────────────── */} + {activeTab === "tasks" && ( +
+ {/* Create task form */} + +
+
+ add_task +
+
+

{t("newTaskTitle")}

+

{t("newTaskDescription")}

- ))} -
-
- {t("howItWorksTitle")} - {t("howItWorksDesc")} -
-
- - - -
-
- add_task -
-
-

{t("newTaskTitle")}

-

{t("newTaskDescription")}

-
-
-
-
-
- -
-
-
- -
Model{t("tableModel")} {t("reasoningEntries")} {t("reasoningAvgChars")} {t("reasoningChars")}