Localize the remaining dashboard UI clusters and refresh the generated i18n resources. - Replace hardcoded labels across agent, plugin, mission, workflow node, research, document, activity, and miscellaneous dashboard components with app namespace translations. - Add and synchronize locale catalog entries for English, Spanish, French, Korean, Simplified Chinese, and Traditional Chinese. - Update i18n lint configuration and generated resource types, with a patch changeset for the published CLI package. Files changed: .changeset/fn-6769-i18n-cluster.md | 5 + i18next.config.ts | 47 +-- .../dashboard/app/components/ActiveAgentsPanel.tsx | 2 +- packages/dashboard/app/components/ActivityFeed.tsx | 12 +- .../dashboard/app/components/ActivityLogModal.tsx | 4 +- packages/dashboard/app/components/AddNodeModal.tsx | 4 +- .../dashboard/app/components/AgentDetailView.tsx | 6 +- .../app/components/AgentGenerationModal.tsx | 2 +- .../dashboard/app/components/AgentImportModal.tsx | 4 +- .../app/components/AgentOnboardingModal.tsx | 4 +- .../app/components/AgentPromptsManager.tsx | 2 +- .../dashboard/app/components/AgentRunHistory.tsx | 2 +- .../dashboard/app/components/AgentsOverviewBar.tsx | 2 +- .../dashboard/app/components/BranchGroupCard.tsx | 4 +- .../dashboard/app/components/CliBinaryPanel.tsx | 8 +- .../app/components/CursorCliProviderCard.tsx | 4 +- .../dashboard/app/components/DashboardLoader.tsx | 4 +- .../dashboard/app/components/DevServerView.tsx | 8 +- .../dashboard/app/components/DocumentsView.tsx | 6 +- .../dashboard/app/components/ErrorBoundary.tsx | 9 +- .../dashboard/app/components/ExecutorStatusBar.tsx | 5 +- .../dashboard/app/components/GitHubStarPrompt.tsx | 14 +- .../dashboard/app/components/GitManagerModal.tsx | 4 +- .../dashboard/app/components/GroupTaskModal.tsx | 2 +- packages/dashboard/app/components/Header.tsx | 4 +- .../dashboard/app/components/HermesRuntimeCard.tsx | 2 +- packages/dashboard/app/components/InsightsView.tsx | 2 +- .../dashboard/app/components/IssueMentionPopup.tsx | 5 +- packages/dashboard/app/components/MailboxView.tsx | 6 +- .../components/MilestoneSliceInterviewModal.tsx | 2 +- .../app/components/MissionInterviewModal.tsx | 2 +- .../dashboard/app/components/MissionManager.tsx | 8 +- .../app/components/ModelOnboardingModal.tsx | 2 +- .../dashboard/app/components/NodeDetailModal.tsx | 2 +- .../app/components/PiExtensionsManager.tsx | 10 +- .../dashboard/app/components/ProjectSelector.tsx | 6 +- packages/dashboard/app/components/QuickChatFAB.tsx | 44 +-- packages/dashboard/app/components/ResearchView.tsx | 2 +- packages/dashboard/app/components/RoutineCard.tsx | 6 +- .../dashboard/app/components/RoutineEditor.tsx | 2 +- packages/dashboard/app/components/RoutingTab.tsx | 2 +- packages/dashboard/app/components/ScheduleCard.tsx | 6 +- packages/dashboard/app/components/ScheduleForm.tsx | 2 +- packages/dashboard/app/components/ScriptsModal.tsx | 2 +- .../app/components/StashConflictModal.tsx | 4 +- .../dashboard/app/components/TerminalModal.tsx | 2 +- .../app/components/nodes/WorkflowNodeTypes.tsx | 47 +-- packages/i18n/locales/en/app.json | 274 +++++++-------- packages/i18n/locales/es/app.json | 144 ++++++-- packages/i18n/locales/fr/app.json | 144 ++++++-- packages/i18n/locales/ko/app.json | 144 ++++++-- packages/i18n/locales/zh-CN/app.json | 144 ++++++-- packages/i18n/locales/zh-TW/app.json | 144 ++++++-- packages/i18n/src/resources.d.ts | 375 +++++++++++++++++++-- 54 files changed, 1261 insertions(+), 442 deletions(-) Fusion-Task-Id: FN-6769 Fusion-Task-Lineage: a8733dc9-3b48-47e6-88c4-020f83ab41de
133 lines
4.7 KiB
TypeScript
133 lines
4.7 KiB
TypeScript
import { useState, useEffect, useCallback } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { CheckCircle, XCircle, Loader2, Square, Clock } from "lucide-react";
|
|
import type { AgentHeartbeatRun } from "../api";
|
|
import { fetchAgentRuns, stopAgentRun } from "../api";
|
|
import { useConfirm } from "../hooks/useConfirm";
|
|
|
|
interface AgentRunHistoryProps {
|
|
agentId: string;
|
|
projectId?: string;
|
|
/** Optional callback when a run row is clicked */
|
|
onRunClick?: (runId: string) => void;
|
|
}
|
|
|
|
const STATUS_ICONS: Record<string, { icon: typeof CheckCircle; color: string }> = {
|
|
completed: { icon: CheckCircle, color: "var(--color-success, #3fb950)" },
|
|
failed: { icon: XCircle, color: "var(--color-error, #f85149)" },
|
|
active: { icon: Loader2, color: "var(--in-progress, #bc8cff)" },
|
|
terminated: { icon: Square, color: "var(--text-muted, #8b949e)" },
|
|
};
|
|
|
|
export function AgentRunHistory({ agentId, projectId, onRunClick }: AgentRunHistoryProps) {
|
|
const { t } = useTranslation("app");
|
|
const [runs, setRuns] = useState<AgentHeartbeatRun[]>([]);
|
|
const { confirm } = useConfirm();
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
const loadRuns = useCallback(async () => {
|
|
setIsLoading(true);
|
|
try {
|
|
const data = await fetchAgentRuns(agentId, 50, projectId);
|
|
setRuns(data);
|
|
} catch {
|
|
setRuns([]);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [agentId, projectId]);
|
|
|
|
useEffect(() => {
|
|
void loadRuns();
|
|
}, [loadRuns]);
|
|
|
|
const handleStop = useCallback(async () => {
|
|
const shouldStop = await confirm({
|
|
title: t("agents.runs.stopTitle", "Stop Run"),
|
|
message: t("agents.runs.stopMessage", "Stop this run?"),
|
|
danger: true,
|
|
});
|
|
if (!shouldStop) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await stopAgentRun(agentId, projectId);
|
|
await loadRuns();
|
|
} catch {
|
|
// No-op: keep history view usable even if stop fails.
|
|
}
|
|
}, [agentId, projectId, loadRuns, confirm]);
|
|
|
|
if (isLoading) {
|
|
return <div className="agent-run-loading"><Loader2 className="animate-spin" size={20} /> {t("agents.runs.loading", "Loading runs...")}</div>;
|
|
}
|
|
|
|
if (runs.length === 0) {
|
|
return <div className="agent-run-empty">{t("agents.runs.empty", "No runs yet")}</div>;
|
|
}
|
|
|
|
return (
|
|
<div className="agent-run-history">
|
|
{runs.map(run => {
|
|
const statusInfo = STATUS_ICONS[run.status] ?? STATUS_ICONS.terminated;
|
|
const StatusIcon = statusInfo.icon;
|
|
const duration = run.endedAt
|
|
? Math.round((new Date(run.endedAt).getTime() - new Date(run.startedAt).getTime()) / 1000)
|
|
: null;
|
|
const usage = run.usageJson;
|
|
|
|
return (
|
|
<div
|
|
key={run.id}
|
|
className={onRunClick ? "agent-run-row agent-run-row--clickable" : "agent-run-row"}
|
|
onClick={onRunClick ? () => onRunClick(run.id) : undefined}
|
|
role={onRunClick ? "button" : undefined}
|
|
tabIndex={onRunClick ? 0 : undefined}
|
|
aria-label={onRunClick ? `Run ${run.id.slice(0, 8)}, ${run.status}` : undefined}
|
|
onKeyDown={onRunClick ? (e) => {
|
|
if (e.key === "Enter" || e.key === " ") {
|
|
e.preventDefault();
|
|
onRunClick(run.id);
|
|
}
|
|
} : undefined}
|
|
>
|
|
<StatusIcon size={16} style={{ color: statusInfo.color }} className={run.status === "active" ? "animate-spin" : ""} />
|
|
<div className="agent-run-info">
|
|
<span className="agent-run-id">{run.id}</span>
|
|
<span className="text-secondary">{new Date(run.startedAt).toLocaleString()}</span>
|
|
</div>
|
|
<div className="agent-run-meta">
|
|
{duration !== null && (
|
|
<span className="badge"><Clock size={12} /> {duration}s</span>
|
|
)}
|
|
{usage && (
|
|
<span className="badge text-secondary">
|
|
{t("agents.kTokens", "{{count}}k tokens", { count: Number(((usage.inputTokens + usage.outputTokens) / 1000).toFixed(1)) })}
|
|
</span>
|
|
)}
|
|
{run.triggerDetail && (
|
|
<span className="badge text-secondary">{run.triggerDetail}</span>
|
|
)}
|
|
</div>
|
|
{run.status === "active" && (
|
|
<button
|
|
className="btn btn--sm btn--danger"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
void handleStop();
|
|
}}
|
|
aria-label={t("agents.runs.stopAriaLabel", "Stop run")}
|
|
style={{ marginLeft: "8px" }}
|
|
>
|
|
<Square size={12} /> {t("agents.runs.stop", "Stop")}
|
|
</button>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|