import { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { api } from "@/lib/api"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Upload, Link, Trash2, Info, Download, CheckCircle2, AlertCircle, Loader2, ArrowLeft, Store, Search, Package } from "lucide-react"; import { Toast } from "@/components/ui/toast"; import { DynamicConfigForm } from "./preset/DynamicConfigForm"; // Schema 类型 interface InputOption { label: string; value: string | number | boolean; description?: string; disabled?: boolean; } interface DynamicOptions { type: 'static' | 'providers' | 'models' | 'custom'; options?: InputOption[]; providerField?: string; } interface Condition { field: string; operator?: 'eq' | 'ne' | 'in' | 'nin' | 'gt' | 'lt' | 'gte' | 'lte' | 'exists'; value?: any; } interface RequiredInput { id: string; type?: 'password' | 'input' | 'select' | 'multiselect' | 'confirm' | 'editor' | 'number'; label?: string; prompt?: string; placeholder?: string; options?: InputOption[] | DynamicOptions; when?: Condition | Condition[]; defaultValue?: any; required?: boolean; validator?: RegExp | string; min?: number; max?: number; rows?: number; dependsOn?: string[]; } interface PresetMetadata { id: string; name: string; version: string; description?: string; author?: string; homepage?: string; repository?: string; license?: string; keywords?: string[]; ccrVersion?: string; source?: string; sourceType?: 'local' | 'gist' | 'registry'; checksum?: string; installed: boolean; } interface PresetConfigSection { Providers?: Array<{ name: string; api_base_url?: string; models?: string[]; [key: string]: any; }>; [key: string]: any; } interface PresetDetail extends PresetMetadata { config?: PresetConfigSection; schema?: RequiredInput[]; template?: any; configMappings?: any[]; } interface MarketPreset { id: string; name: string; author?: string; description?: string; repo: string; } export function Presets() { const { t } = useTranslation(); const navigate = useNavigate(); const [presets, setPresets] = useState([]); const [loading, setLoading] = useState(true); const [installDialogOpen, setInstallDialogOpen] = useState(false); const [detailDialogOpen, setDetailDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [marketDialogOpen, setMarketDialogOpen] = useState(false); const [selectedPreset, setSelectedPreset] = useState(null); const [presetToDelete, setPresetToDelete] = useState(null); const [toast, setToast] = useState<{ message: string; type: 'success' | 'error' | 'warning' } | null>(null); const [installMethod, setInstallMethod] = useState<'file' | 'url'>('file'); const [installUrl, setInstallUrl] = useState(''); const [installFile, setInstallFile] = useState(null); const [installName, setInstallName] = useState(''); const [isInstalling, setIsInstalling] = useState(false); const [secrets, setSecrets] = useState>({}); const [isApplying, setIsApplying] = useState(false); const [marketSearch, setMarketSearch] = useState(''); const [marketPresets, setMarketPresets] = useState([]); const [marketLoading, setMarketLoading] = useState(false); const [installingFromMarket, setInstallingFromMarket] = useState(null); // 返回上一页 const handleGoBack = () => { navigate('/dashboard'); }; // 加载市场预设 const loadMarketPresets = async () => { setMarketLoading(true); try { const response = await api.getMarketPresets(); setMarketPresets(response.presets || []); } catch (error) { console.error('Failed to load market presets:', error); setToast({ message: t('presets.load_market_failed'), type: 'error' }); } finally { setMarketLoading(false); } }; // 从市场安装预设 const handleInstallFromMarket = async (preset: MarketPreset) => { try { setInstallingFromMarket(preset.id); await api.installPresetFromGitHub(preset.repo, preset.name); setToast({ message: t('presets.preset_installed'), type: 'success' }); setMarketDialogOpen(false); await loadPresets(); } catch (error: any) { console.error('Failed to install preset:', error); setToast({ message: t('presets.preset_install_failed', { error: error.message }), type: 'error' }); } finally { setInstallingFromMarket(null); } }; // 打开市场对话框时加载预设 useEffect(() => { if (marketDialogOpen && marketPresets.length === 0) { loadMarketPresets(); } }, [marketDialogOpen]); // 过滤市场预设 const filteredMarketPresets = marketPresets.filter(preset => preset.name.toLowerCase().includes(marketSearch.toLowerCase()) || preset.description?.toLowerCase().includes(marketSearch.toLowerCase()) || preset.author?.toLowerCase().includes(marketSearch.toLowerCase()) ); // 加载预设列表 const loadPresets = async () => { try { setLoading(true); const response = await api.getPresets(); setPresets(response.presets || []); } catch (error) { console.error('Failed to load presets:', error); setToast({ message: t('presets.load_presets_failed'), type: 'error' }); } finally { setLoading(false); } }; useEffect(() => { loadPresets(); }, []); // 查看预设详情 const handleViewDetail = async (preset: PresetMetadata) => { try { const detail = await api.getPreset(preset.id); setSelectedPreset({ ...preset, ...detail }); setDetailDialogOpen(true); // 初始化默认值 if (detail.schema && detail.schema.length > 0) { const initialValues: Record = {}; for (const input of detail.schema) { initialValues[input.id] = input.defaultValue ?? ''; } setSecrets(initialValues); } } catch (error) { console.error('Failed to load preset details:', error); setToast({ message: t('presets.load_preset_details_failed'), type: 'error' }); } }; // 安装预设 const handleInstall = async () => { try { setIsInstalling(true); if (installMethod === 'url' && installUrl) { await api.installPresetFromUrl(installUrl, installName || undefined); } else if (installMethod === 'file' && installFile) { await api.uploadPresetFile(installFile, installName || undefined); } else { setToast({ message: t('presets.please_provide_file_or_url'), type: 'warning' }); return; } setToast({ message: t('presets.preset_installed'), type: 'success' }); setInstallDialogOpen(false); setInstallUrl(''); setInstallFile(null); setInstallName(''); await loadPresets(); } catch (error: any) { console.error('Failed to install preset:', error); setToast({ message: t('presets.preset_install_failed', { error: error.message }), type: 'error' }); } finally { setIsInstalling(false); } }; // 应用预设(配置敏感信息) const handleApplyPreset = async (values?: Record) => { try { setIsApplying(true); // 使用传入的values或现有的secrets const inputValues = values || secrets; // 验证所有必填项都已填写 if (selectedPreset?.schema && selectedPreset.schema.length > 0) { // 验证在 DynamicConfigForm 中已完成 // 这里只做简单检查 for (const input of selectedPreset.schema) { if (input.required !== false && !inputValues[input.id]) { setToast({ message: t('presets.please_fill_field', { field: input.label || input.id }), type: 'warning' }); setIsApplying(false); return; } } } await api.applyPreset(selectedPreset!.name, inputValues); setToast({ message: t('presets.preset_applied'), type: 'success' }); setDetailDialogOpen(false); setSecrets({}); } catch (error: any) { console.error('Failed to apply preset:', error); setToast({ message: t('presets.preset_apply_failed', { error: error.message }), type: 'error' }); } finally { setIsApplying(false); } }; // 删除预设 const handleDelete = async () => { if (!presetToDelete) return; try { await api.deletePreset(presetToDelete); setToast({ message: t('presets.preset_deleted'), type: 'success' }); setDeleteDialogOpen(false); setPresetToDelete(null); await loadPresets(); } catch (error: any) { console.error('Failed to delete preset:', error); setToast({ message: t('presets.preset_delete_failed', { error: error.message }), type: 'error' }); } }; return ( {t('presets.title')} ({presets.length}) {loading ? (
) : presets.length === 0 ? (

{t('presets.no_presets')}

{t('presets.no_presets_hint')}

) : (
{presets.map((preset) => (

{preset.name}

v{preset.version}
{preset.description && (

{preset.description}

)} {preset.author && (

by {preset.author}

)}
))}
)}
{/* Install Dialog */} {t('presets.install_dialog_title')} {t('presets.install_dialog_description')}
{installMethod === 'file' ? (
setInstallFile(e.target.files?.[0] || null)} />
) : (
setInstallUrl(e.target.value)} />
)}
setInstallName(e.target.value)} />
{/* Detail Dialog */} {selectedPreset?.name} {selectedPreset?.version && ( v{selectedPreset.version} )}
{selectedPreset?.description && (

{selectedPreset.description}

)} {selectedPreset?.author && (

Author: {selectedPreset.author}

)} {selectedPreset?.homepage && (

Homepage: {selectedPreset.homepage}

)} {selectedPreset?.repository && (

Repository: {selectedPreset.repository}

)} {selectedPreset?.keywords && selectedPreset.keywords.length > 0 && (
Keywords:
{selectedPreset.keywords.map((keyword) => ( {keyword} ))}
)} {/* 配置表单 */} {selectedPreset?.schema && selectedPreset.schema.length > 0 && (

{t('presets.required_information')}

handleApplyPreset(values)} onCancel={() => setDetailDialogOpen(false)} isSubmitting={isApplying} initialValues={secrets} />
)}
{/* Market Presets Dialog */} {t('presets.market_title')} {t('presets.market_description')}
setMarketSearch(e.target.value)} className="pl-9" />
{marketLoading ? (
) : filteredMarketPresets.length === 0 ? (

{t('presets.no_presets_found')}

{t('presets.no_presets_found_hint')}

) : (
{filteredMarketPresets.map((preset) => (

{preset.name}

{preset.description && (

{preset.description}

)}
{preset.author && (
{t('presets.by', { author: preset.author })}
)}
))}
)}
{/* Delete Confirmation Dialog */} {t('presets.delete_dialog_title')} {t('presets.delete_dialog_description', { name: presetToDelete })} {toast && ( setToast(null)} /> )}
); }