"use client"; import { AlertTriangle, History, Loader2, RefreshCw, RotateCcw, Sparkles } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; type Lang = "zh" | "en"; type ConfigIssue = { level: string; code: string; message: string; line?: number; }; type ConfigStat = { key: string; label: string; value: string; }; type ConfigAction = { id: string; label: string; description: string; }; type ConfigBackup = { id: string; source: string; reason: string; label: string; created_at: string; size: number; }; type ConfigInsights = { kind?: string; source?: string; summary?: string; issues?: ConfigIssue[]; stats?: ConfigStat[]; actions?: ConfigAction[]; backups?: ConfigBackup[]; }; type Props = { lang: Lang; rootId: string; filePath: string; fileContent: string; refreshKey: number; busy: string | null; fetchJson: (path: string, init?: RequestInit) => Promise; notify: (message: string) => void; onChangeContent: (content: string) => void; onRefreshListing?: () => Promise | void; }; function tr(lang: Lang, zh: string, en: string) { return lang === "zh" ? zh : en; } function formatBytes(size: number) { if (!size) return "0 B"; const units = ["B", "KB", "MB", "GB"]; let value = size; let unit = 0; while (value >= 1024 && unit < units.length - 1) { value /= 1024; unit += 1; } return `${value.toFixed(value >= 10 || unit === 0 ? 0 : 1)} ${units[unit]}`; } function issueTone(level: string) { if (level === "error") return "border-rose-500/30 bg-rose-500/10 text-rose-100"; if (level === "warning") return "border-amber-500/30 bg-amber-500/10 text-amber-100"; return "border-cyan-500/30 bg-cyan-500/10 text-cyan-100"; } export function ConfigAdvisorPanel({ lang, rootId, filePath, fileContent, refreshKey, busy, fetchJson, notify, onChangeContent, onRefreshListing, }: Props) { const [insights, setInsights] = useState(null); const [loading, setLoading] = useState(false); const [draftLoading, setDraftLoading] = useState(false); const [actionBusy, setActionBusy] = useState(null); const [restoreBusy, setRestoreBusy] = useState(null); const t = useCallback((zh: string, en: string) => tr(lang, zh, en), [lang]); const loadSavedInsights = useCallback(async () => { if (!rootId || !filePath) { setInsights(null); return; } setLoading(true); try { const params = new URLSearchParams({ root: rootId, path: filePath }); const data = await fetchJson(`/config_insights?${params.toString()}`); setInsights(data); } catch (reason) { setInsights(null); notify((reason as Error).message); } finally { setLoading(false); } }, [fetchJson, filePath, notify, rootId]); useEffect(() => { void loadSavedInsights(); }, [loadSavedInsights, refreshKey]); const analyzeDraft = async () => { if (!rootId || !filePath) return; setDraftLoading(true); try { const data = await fetchJson("/config_insights", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ root: rootId, path: filePath, content: fileContent }), }); setInsights(data); notify(t("已分析当前编辑内容", "Analyzed current editor draft")); } catch (reason) { notify((reason as Error).message); } finally { setDraftLoading(false); } }; const applyAction = async (actionId: string) => { if (!rootId || !filePath) return; setActionBusy(actionId); try { const data = await fetchJson<{ message?: string; content?: string; insights?: ConfigInsights }>( "/config_optimize", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ root: rootId, path: filePath, content: fileContent, action: actionId }), }, ); if (typeof data.content === "string") onChangeContent(data.content); if (data.insights) { setInsights((prev) => ({ ...prev, ...data.insights, backups: prev?.backups || [] })); } notify(data.message || t("已应用优化", "Optimization applied")); } catch (reason) { notify((reason as Error).message); } finally { setActionBusy(null); } }; const restoreFromBackup = async (backupId: string) => { if (!rootId || !filePath) return; setRestoreBusy(backupId); try { const data = await fetchJson<{ message?: string; content?: string }>("/config_restore", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ root: rootId, path: filePath, backup_id: backupId }), }); if (typeof data.content === "string") onChangeContent(data.content); await Promise.resolve(onRefreshListing?.()); await loadSavedInsights(); notify(data.message || t("已恢复历史版本", "Backup restored")); } catch (reason) { notify((reason as Error).message); } finally { setRestoreBusy(null); } }; if (!filePath) { return (
{t("选择文件后显示诊断、回滚和一键优化。", "Pick a file to see diagnostics, rollback, and one-click optimizations.")}
); } const issues = insights?.issues || []; const actions = insights?.actions || []; const backups = insights?.backups || []; const stats = insights?.stats || []; return (

{t("配置治理", "Config governance")}

{insights?.source ? ( {insights.source} ) : null}

{insights?.summary || t("校验配置风险、查看历史快照,并将常见优化应用到编辑器。", "Validate risks, inspect snapshots, and apply optimizations into the editor.")}

{stats.slice(0, 8).map((item) => (
{item.label}
{item.value}
))}

{t("风险与建议", "Risks & guidance")}

{issues.length ? (
{issues.map((item, index) => (
{item.message} {item.line ? ` (L${item.line})` : ""}
{item.code}
))}
) : (

{t("当前没有发现明显配置风险。", "No obvious config risks were detected.")}

)}
{actions.length ? ( actions.map((item) => ( )) ) : ( {t("当前文件没有可自动应用的预设。", "No auto-applicable presets for this file.")} )}

{t("历史快照", "Backup snapshots")}

{backups.length ? (
{backups.slice(0, 8).map((item) => (
{new Date(item.created_at).toLocaleString(lang === "zh" ? "zh-CN" : "en-US", { hour12: false })}
{item.reason} · {formatBytes(item.size)} {item.source === "legacy" ? ` · ${t("旧备份", "legacy")}` : ""}
))}
) : (

{t("当前文件还没有 Web 侧快照。保存、删除或恢复后会自动累积历史。", "No Web snapshots yet. Saving, deleting, or restoring will accumulate history automatically.")}

)}
); }