/** * 分享 Pack 对话框 * * 允许用户将当前配置导出并分享到 Pack 市场 */ import { useState, useEffect } from 'react' import { Package, Share2, Server, Layers, ListChecks, Tag, Loader2, Check, Info, } from 'lucide-react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' import { Checkbox } from '@/components/ui/checkbox' import { Badge } from '@/components/ui/badge' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from '@/components/ui/dialog' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' import { Separator } from '@/components/ui/separator' import { ScrollArea } from '@/components/ui/scroll-area' import { toast } from '@/hooks/use-toast' import { createPack, exportCurrentConfigAsPack, type PackProvider, type PackModel, type PackTaskConfigs, } from '@/lib/pack-api' // 任务类型名称映射 const TASK_TYPE_NAMES: Record = { utils: '通用工具', utils_small: '轻量工具', tool_use: '工具调用', replyer: '回复生成', planner: '规划推理', vlm: '视觉模型', voice: '语音处理', embedding: '向量嵌入', lpmm_entity_extract: '实体提取', lpmm_rdf_build: 'RDF构建', lpmm_qa: '问答模型', } // 预设标签 const PRESET_TAGS = [ '官方推荐', '性价比', '高性能', '免费模型', '国内可用', '海外模型', 'OpenAI', 'Claude', 'Gemini', '国产模型', '多模态', '轻量级', ] interface SharePackDialogProps { trigger?: React.ReactNode } export function SharePackDialog({ trigger }: SharePackDialogProps) { const [open, setOpen] = useState(false) const [step, setStep] = useState(1) const [loading, setLoading] = useState(false) const [submitting, setSubmitting] = useState(false) // 配置数据 const [providers, setProviders] = useState([]) const [models, setModels] = useState([]) const [taskConfig, setTaskConfig] = useState({}) // 选择状态 const [selectedProviders, setSelectedProviders] = useState>(new Set()) const [selectedModels, setSelectedModels] = useState>(new Set()) const [selectedTasks, setSelectedTasks] = useState>(new Set()) // Pack 信息 const [packName, setPackName] = useState('') const [packDescription, setPackDescription] = useState('') const [packAuthor, setPackAuthor] = useState('') const [packTags, setPackTags] = useState([]) // 加载当前配置 useEffect(() => { if (open && step === 1) { loadCurrentConfig() } }, [open, step]) const loadCurrentConfig = async () => { setLoading(true) try { const config = await exportCurrentConfigAsPack({ name: '', description: '', author: '', }) setProviders(config.providers) setModels(config.models) setTaskConfig(config.task_config) // 默认全选 setSelectedProviders(new Set(config.providers.map(p => p.name))) setSelectedModels(new Set(config.models.map(m => m.name))) setSelectedTasks(new Set(Object.keys(config.task_config))) } catch (error) { console.error('加载配置失败:', error) toast({ title: '加载当前配置失败', variant: 'destructive' }) } finally { setLoading(false) } } // 切换选择 const toggleProvider = (name: string) => { const newSet = new Set(selectedProviders) const newModels = new Set(selectedModels) const newTasks = new Set(selectedTasks) if (newSet.has(name)) { // 取消选择提供商 newSet.delete(name) // 取消选择该提供商下的所有模型 const providerModels = models.filter(m => m.api_provider === name) providerModels.forEach(m => newModels.delete(m.name)) // 检查任务配置,如果任务使用的所有模型都被取消选择了,也取消选择该任务 Object.entries(taskConfig).forEach(([key, config]) => { if (config.model_list) { const hasSelectedModel = config.model_list.some((modelName: string) => newModels.has(modelName)) if (!hasSelectedModel) { newTasks.delete(key) } } }) } else { // 选择提供商 newSet.add(name) // 自动选择该提供商下的所有模型 const providerModels = models.filter(m => m.api_provider === name) providerModels.forEach(m => newModels.add(m.name)) // 自动选择使用这些模型的任务 Object.entries(taskConfig).forEach(([key, config]) => { if (config.model_list) { const hasProviderModel = config.model_list.some((modelName: string) => { const model = models.find(m => m.name === modelName) return model && model.api_provider === name }) if (hasProviderModel) { newTasks.add(key) } } }) } setSelectedProviders(newSet) setSelectedModels(newModels) setSelectedTasks(newTasks) } const toggleModel = (name: string) => { const newModels = new Set(selectedModels) const newTasks = new Set(selectedTasks) if (newModels.has(name)) { // 取消选择模型 newModels.delete(name) // 检查任务配置,如果任务使用的所有模型都被取消选择了,也取消选择该任务 Object.entries(taskConfig).forEach(([key, config]) => { if (config.model_list) { const hasSelectedModel = config.model_list.some((modelName: string) => newModels.has(modelName)) if (!hasSelectedModel) { newTasks.delete(key) } } }) } else { // 选择模型 newModels.add(name) // 自动选择使用这个模型的任务 Object.entries(taskConfig).forEach(([key, config]) => { if (config.model_list && config.model_list.includes(name)) { newTasks.add(key) } }) } setSelectedModels(newModels) setSelectedTasks(newTasks) } const toggleTask = (key: string) => { const newSet = new Set(selectedTasks) if (newSet.has(key)) { newSet.delete(key) } else { newSet.add(key) } setSelectedTasks(newSet) } const toggleTag = (tag: string) => { if (packTags.includes(tag)) { setPackTags(packTags.filter(t => t !== tag)) } else if (packTags.length < 5) { setPackTags([...packTags, tag]) } else { toast({ title: '最多选择 5 个标签', variant: 'destructive' }) } } // 全选/取消全选 const selectAllProviders = () => { if (selectedProviders.size === providers.length) { setSelectedProviders(new Set()) } else { setSelectedProviders(new Set(providers.map(p => p.name))) } } const selectAllModels = () => { if (selectedModels.size === models.length) { setSelectedModels(new Set()) } else { setSelectedModels(new Set(models.map(m => m.name))) } } const selectAllTasks = () => { const taskKeys = Object.keys(taskConfig) if (selectedTasks.size === taskKeys.length) { setSelectedTasks(new Set()) } else { setSelectedTasks(new Set(taskKeys)) } } // 提交 const handleSubmit = async () => { // 验证 if (!packName.trim()) { toast({ title: '请输入模板名称', variant: 'destructive' }) return } if (!packDescription.trim()) { toast({ title: '请输入模板描述', variant: 'destructive' }) return } if (!packAuthor.trim()) { toast({ title: '请输入作者名称', variant: 'destructive' }) return } if (selectedProviders.size === 0 && selectedModels.size === 0 && selectedTasks.size === 0) { toast({ title: '请至少选择一项配置', variant: 'destructive' }) return } setSubmitting(true) try { // 过滤选中的配置 const selectedProviderConfigs = providers.filter(p => selectedProviders.has(p.name)) const selectedModelConfigs = models.filter(m => selectedModels.has(m.name)) const selectedTaskConfigs: PackTaskConfigs = {} for (const [key, config] of Object.entries(taskConfig)) { if (selectedTasks.has(key)) { selectedTaskConfigs[key as keyof PackTaskConfigs] = config } } await createPack({ name: packName.trim(), description: packDescription.trim(), author: packAuthor.trim(), tags: packTags, providers: selectedProviderConfigs, models: selectedModelConfigs, task_config: selectedTaskConfigs, }) toast({ title: '模板已提交审核,审核通过后将显示在市场中' }) setOpen(false) resetForm() } catch (error) { console.error('提交失败:', error) toast({ title: error instanceof Error ? error.message : '提交失败', variant: 'destructive' }) } finally { setSubmitting(false) } } // 重置表单 const resetForm = () => { setStep(1) setPackName('') setPackDescription('') setPackAuthor('') setPackTags([]) setSelectedProviders(new Set()) setSelectedModels(new Set()) setSelectedTasks(new Set()) } const totalSteps = 2 return ( {trigger || ( )} 分享配置模板 步骤 {step} / {totalSteps}: {step === 1 && '选择要分享的配置'} {step === 2 && '填写模板信息'} {loading ? (

正在加载当前配置...

) : ( <> {/* 步骤 1: 选择配置 */} {step === 1 && (
安全提示 分享的配置将不包含 API Key,其他用户需要自行配置。 API 提供商 {selectedProviders.size}/{providers.length} 模型配置 {selectedModels.size}/{models.length} 任务配置 {selectedTasks.size}/{Object.keys(taskConfig).length} {/* 提供商选择 */}
{providers.length === 0 ? (

暂无提供商配置

) : ( providers.map(provider => (
toggleProvider(provider.name)} /> {provider.client_type}
)) )}
{/* 模型选择 */}
{models.length === 0 ? (

暂无模型配置

) : ( models.map(model => (
toggleModel(model.name)} /> {model.api_provider}
)) )}
{/* 任务配置选择 */}
{Object.keys(taskConfig).length === 0 ? (

暂无任务配置

) : ( Object.entries(taskConfig).map(([key, config]) => (
toggleTask(key)} /> {config.model_list.length} 个模型
{config.model_list && config.model_list.length > 0 && (
{config.model_list.map((modelName: string) => { const model = models.find(m => m.name === modelName) const isSelected = selectedModels.has(modelName) return ( toggleModel(modelName)} > {modelName} {model && ( ({model.api_provider}) )} ) })}
)}
)) )}
)} {/* 步骤 2: 填写信息 */} {step === 2 && (
{/* 选择摘要 */}
{selectedProviders.size} 个提供商 {selectedModels.size} 个模型 {selectedTasks.size} 个任务
setPackName(e.target.value)} maxLength={50} />

{packName.length}/50