import { useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { AppMainCard } from "@/components/common/app-main-card" import { filterValuesBySchema } from "@/components/common/merge-plugin-schema" import { pluginStoreActions } from "@/store" import { ScrollArea } from "@/components/ui/scroll-area" import { Skeleton } from "@/components/ui/skeleton" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { validateRequiredFields } from "@/components/main/providers/utils" import type { AppConfig, ProviderPluginConfig } from "@/components/main/providers/types" import { PluginDetailConfigTabContent } from "./plugin-detail-config-tab-content" import { PluginDetailHeader } from "./plugin-detail-header" import { PluginDetailReadmeTabContent } from "./plugin-detail-readme-tab-content" import { PluginDetailTransformerTabContent } from "./plugin-detail-transformer-tab-content" import { pluginDetailTab, pluginReadmeStatus, type PluginDetailSelectedItem, type PluginDetailTab, type PluginInstalledItem, type PluginReadmeState, } from "./types" import { resolveConfigValues } from "./utils" import { usePluginConfigSection } from "./use-plugin-config-section" interface PluginDetailPanelProps { appConfig: AppConfig | null selectedItem: PluginDetailSelectedItem | null plugin: PluginInstalledItem | null activeTab: PluginDetailTab availableTabs: PluginDetailTab[] readmeState: PluginReadmeState | null isMutating: boolean onTabChange: (tab: PluginDetailTab) => void } function resolveFieldLabel(schema: ProviderPluginConfig[], fieldName: string) { const field = schema.find((f) => f.name === fieldName) return field?.alias || field?.name || fieldName } const EMPTY_SCHEMA: ProviderPluginConfig[] = [] const EMPTY_VALUES: Record = {} export function PluginDetailPanel({ appConfig, selectedItem, plugin, activeTab, availableTabs, readmeState, isMutating, onTabChange, }: PluginDetailPanelProps) { const { t } = useTranslation() const [isSaving, setIsSaving] = useState(false) const pluginFullName = plugin?.fullName ?? null const initialConfigSchema = plugin?.config.plugin.config ?? EMPTY_SCHEMA const initialTransformerSchema = plugin?.config.transformer.config ?? EMPTY_SCHEMA // Stable reference for stored values — the hook keys hydration on identity, // so we want the same `{}` reference render-to-render when nothing is set. const storedConfigValues = useMemo(() => { if (!plugin) return EMPTY_VALUES return resolveConfigValues(appConfig, plugin, pluginDetailTab.Config) as Record< string, unknown > }, [appConfig, plugin]) const storedTransformerValues = useMemo(() => { if (!plugin) return EMPTY_VALUES return resolveConfigValues(appConfig, plugin, pluginDetailTab.Transformer) as Record< string, unknown > }, [appConfig, plugin]) const configSection = usePluginConfigSection({ enabled: Boolean(plugin && activeTab === pluginDetailTab.Config), pluginFullName, target: { target: "plugin", pluginFullName: pluginFullName ?? "" }, initialSchema: initialConfigSchema, storedValues: storedConfigValues, }) const transformerSection = usePluginConfigSection({ enabled: Boolean(plugin && activeTab === pluginDetailTab.Transformer), pluginFullName, target: { target: "transformer", pluginFullName: pluginFullName ?? "" }, initialSchema: initialTransformerSchema, storedValues: storedTransformerValues, }) const handleTabChange = (value: string) => { const nextTab = value as PluginDetailTab if (!availableTabs.includes(nextTab)) return onTabChange(nextTab) } const handleSaveConfigTab = async () => { if (!plugin) return const missing = validateRequiredFields(configSection.schema, configSection.values) if (missing.length > 0) { const errors: Record = {} missing.forEach((name) => { errors[name] = t("FIELD_IS_REQUIRED", { field: resolveFieldLabel(configSection.schema, name), }) }) configSection.setFieldErrors(errors) return } configSection.setFieldErrors({}) setIsSaving(true) try { await pluginStoreActions.savePluginConfig( plugin.fullName, "config", filterValuesBySchema(configSection.schema, configSection.values) ) } finally { setIsSaving(false) } } const handleSaveTransformerTab = async () => { if (!plugin) return const missing = validateRequiredFields( transformerSection.schema, transformerSection.values ) if (missing.length > 0) { const errors: Record = {} missing.forEach((name) => { errors[name] = t("FIELD_IS_REQUIRED", { field: resolveFieldLabel(transformerSection.schema, name), }) }) transformerSection.setFieldErrors(errors) return } transformerSection.setFieldErrors({}) setIsSaving(true) try { await pluginStoreActions.savePluginConfig( plugin.fullName, "transformer", filterValuesBySchema(transformerSection.schema, transformerSection.values) ) } finally { setIsSaving(false) } } const tabLabelMap: Record = { [pluginDetailTab.Readme]: t("PLUGIN_DETAIL"), [pluginDetailTab.Config]: "Config", [pluginDetailTab.Transformer]: "Transformer", } if (!selectedItem) { if (readmeState?.status === pluginReadmeStatus.Loading) { return (
) } return (
{t("PLUGIN_EMPTY")}

{t("PLUGIN_EMPTY_DESCRIPTION")}

) } return (
{availableTabs.map((tab) => ( {tabLabelMap[tab]} ))}
) }