上传完整的WebUI前端仓库
This commit is contained in:
684
dashboard/src/components/share-pack-dialog.tsx
Normal file
684
dashboard/src/components/share-pack-dialog.tsx
Normal file
@@ -0,0 +1,684 @@
|
||||
/**
|
||||
* 分享 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<string, string> = {
|
||||
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<PackProvider[]>([])
|
||||
const [models, setModels] = useState<PackModel[]>([])
|
||||
const [taskConfig, setTaskConfig] = useState<PackTaskConfigs>({})
|
||||
|
||||
// 选择状态
|
||||
const [selectedProviders, setSelectedProviders] = useState<Set<string>>(new Set())
|
||||
const [selectedModels, setSelectedModels] = useState<Set<string>>(new Set())
|
||||
const [selectedTasks, setSelectedTasks] = useState<Set<string>>(new Set())
|
||||
|
||||
// Pack 信息
|
||||
const [packName, setPackName] = useState('')
|
||||
const [packDescription, setPackDescription] = useState('')
|
||||
const [packAuthor, setPackAuthor] = useState('')
|
||||
const [packTags, setPackTags] = useState<string[]>([])
|
||||
|
||||
// 加载当前配置
|
||||
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 (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{trigger || (
|
||||
<Button variant="outline">
|
||||
<Share2 className="w-4 h-4 mr-2" />
|
||||
分享配置
|
||||
</Button>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent className="max-w-2xl max-h-[85vh] flex flex-col">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Package className="w-5 h-5" />
|
||||
分享配置模板
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
步骤 {step} / {totalSteps}:
|
||||
{step === 1 && '选择要分享的配置'}
|
||||
{step === 2 && '填写模板信息'}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<ScrollArea className="h-[calc(85vh-220px)] pr-4">
|
||||
{loading ? (
|
||||
<div className="py-8 text-center">
|
||||
<Loader2 className="w-8 h-8 mx-auto animate-spin text-primary" />
|
||||
<p className="mt-4 text-muted-foreground">正在加载当前配置...</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* 步骤 1: 选择配置 */}
|
||||
{step === 1 && (
|
||||
<div className="space-y-4">
|
||||
<Alert>
|
||||
<Info className="h-4 w-4" />
|
||||
<AlertTitle>安全提示</AlertTitle>
|
||||
<AlertDescription>
|
||||
分享的配置将<strong>不包含</strong> API Key,其他用户需要自行配置。
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<Tabs defaultValue="providers" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-3">
|
||||
<TabsTrigger value="providers">
|
||||
<Server className="w-4 h-4 mr-2" />
|
||||
API 提供商
|
||||
<Badge variant="secondary" className="ml-2">
|
||||
{selectedProviders.size}/{providers.length}
|
||||
</Badge>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="models">
|
||||
<Layers className="w-4 h-4 mr-2" />
|
||||
模型配置
|
||||
<Badge variant="secondary" className="ml-2">
|
||||
{selectedModels.size}/{models.length}
|
||||
</Badge>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="tasks">
|
||||
<ListChecks className="w-4 h-4 mr-2" />
|
||||
任务配置
|
||||
<Badge variant="secondary" className="ml-2">
|
||||
{selectedTasks.size}/{Object.keys(taskConfig).length}
|
||||
</Badge>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* 提供商选择 */}
|
||||
<TabsContent value="providers" className="space-y-2 mt-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-end">
|
||||
<Button variant="ghost" size="sm" onClick={selectAllProviders}>
|
||||
{selectedProviders.size === providers.length ? '取消全选' : '全选'}
|
||||
</Button>
|
||||
</div>
|
||||
{providers.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground text-center py-2">
|
||||
暂无提供商配置
|
||||
</p>
|
||||
) : (
|
||||
providers.map(provider => (
|
||||
<div
|
||||
key={provider.name}
|
||||
className="flex items-center space-x-2 p-2 rounded hover:bg-muted"
|
||||
>
|
||||
<Checkbox
|
||||
id={`provider-${provider.name}`}
|
||||
checked={selectedProviders.has(provider.name)}
|
||||
onCheckedChange={() => toggleProvider(provider.name)}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`provider-${provider.name}`}
|
||||
className="flex-1 cursor-pointer"
|
||||
>
|
||||
<span className="font-medium">{provider.name}</span>
|
||||
<span className="text-xs text-muted-foreground ml-2">
|
||||
{provider.base_url}
|
||||
</span>
|
||||
</Label>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{provider.client_type}
|
||||
</Badge>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* 模型选择 */}
|
||||
<TabsContent value="models" className="space-y-2 mt-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-end">
|
||||
<Button variant="ghost" size="sm" onClick={selectAllModels}>
|
||||
{selectedModels.size === models.length ? '取消全选' : '全选'}
|
||||
</Button>
|
||||
</div>
|
||||
{models.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground text-center py-2">
|
||||
暂无模型配置
|
||||
</p>
|
||||
) : (
|
||||
models.map(model => (
|
||||
<div
|
||||
key={model.name}
|
||||
className="flex items-center space-x-2 p-2 rounded hover:bg-muted"
|
||||
>
|
||||
<Checkbox
|
||||
id={`model-${model.name}`}
|
||||
checked={selectedModels.has(model.name)}
|
||||
onCheckedChange={() => toggleModel(model.name)}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`model-${model.name}`}
|
||||
className="flex-1 cursor-pointer"
|
||||
>
|
||||
<span className="font-medium">{model.name}</span>
|
||||
<span className="text-xs text-muted-foreground ml-2">
|
||||
{model.model_identifier}
|
||||
</span>
|
||||
</Label>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{model.api_provider}
|
||||
</span>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* 任务配置选择 */}
|
||||
<TabsContent value="tasks" className="space-y-2 mt-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-end">
|
||||
<Button variant="ghost" size="sm" onClick={selectAllTasks}>
|
||||
{selectedTasks.size === Object.keys(taskConfig).length ? '取消全选' : '全选'}
|
||||
</Button>
|
||||
</div>
|
||||
{Object.keys(taskConfig).length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground text-center py-2">
|
||||
暂无任务配置
|
||||
</p>
|
||||
) : (
|
||||
Object.entries(taskConfig).map(([key, config]) => (
|
||||
<div
|
||||
key={key}
|
||||
className="space-y-2 p-2 rounded hover:bg-muted"
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={`task-${key}`}
|
||||
checked={selectedTasks.has(key)}
|
||||
onCheckedChange={() => toggleTask(key)}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`task-${key}`}
|
||||
className="flex-1 cursor-pointer"
|
||||
>
|
||||
<span className="font-medium">
|
||||
{TASK_TYPE_NAMES[key] || key}
|
||||
</span>
|
||||
</Label>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{config.model_list.length} 个模型
|
||||
</Badge>
|
||||
</div>
|
||||
{config.model_list && config.model_list.length > 0 && (
|
||||
<div className="ml-6 flex flex-wrap gap-1">
|
||||
{config.model_list.map((modelName: string) => {
|
||||
const model = models.find(m => m.name === modelName)
|
||||
const isSelected = selectedModels.has(modelName)
|
||||
return (
|
||||
<Badge
|
||||
key={modelName}
|
||||
variant={isSelected ? "default" : "outline"}
|
||||
className="text-xs cursor-pointer hover:opacity-80 transition-opacity"
|
||||
onClick={() => toggleModel(modelName)}
|
||||
>
|
||||
{modelName}
|
||||
{model && (
|
||||
<span className="ml-1 opacity-70">
|
||||
({model.api_provider})
|
||||
</span>
|
||||
)}
|
||||
</Badge>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 步骤 2: 填写信息 */}
|
||||
{step === 2 && (
|
||||
<div className="space-y-4">
|
||||
{/* 选择摘要 */}
|
||||
<div className="flex gap-4 text-sm p-3 bg-muted rounded-lg">
|
||||
<span className="flex items-center gap-1">
|
||||
<Server className="w-4 h-4" />
|
||||
{selectedProviders.size} 个提供商
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Layers className="w-4 h-4" />
|
||||
{selectedModels.size} 个模型
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<ListChecks className="w-4 h-4" />
|
||||
{selectedTasks.size} 个任务
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="pack-name">模板名称 *</Label>
|
||||
<Input
|
||||
id="pack-name"
|
||||
placeholder="例如:高性价比国产模型配置"
|
||||
value={packName}
|
||||
onChange={e => setPackName(e.target.value)}
|
||||
maxLength={50}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{packName.length}/50
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="pack-description">模板描述 *</Label>
|
||||
<Textarea
|
||||
id="pack-description"
|
||||
placeholder="详细描述这个配置模板的特点、适用场景等..."
|
||||
value={packDescription}
|
||||
onChange={e => setPackDescription(e.target.value)}
|
||||
rows={4}
|
||||
maxLength={500}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{packDescription.length}/500
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="pack-author">作者名称 *</Label>
|
||||
<Input
|
||||
id="pack-author"
|
||||
placeholder="你的昵称或 ID"
|
||||
value={packAuthor}
|
||||
onChange={e => setPackAuthor(e.target.value)}
|
||||
maxLength={30}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>标签(可选,最多 5 个)</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{PRESET_TAGS.map(tag => (
|
||||
<Badge
|
||||
key={tag}
|
||||
variant={packTags.includes(tag) ? 'default' : 'outline'}
|
||||
className="cursor-pointer transition-colors"
|
||||
onClick={() => toggleTag(tag)}
|
||||
>
|
||||
{packTags.includes(tag) && <Check className="w-3 h-3 mr-1" />}
|
||||
<Tag className="w-3 h-3 mr-1" />
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Alert>
|
||||
<Info className="h-4 w-4" />
|
||||
<AlertTitle>审核说明</AlertTitle>
|
||||
<AlertDescription>
|
||||
提交后需要经过审核才能在市场中展示。审核通常在 1-3 个工作日内完成。
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</ScrollArea>
|
||||
|
||||
<DialogFooter className="flex justify-between pt-4 border-t">
|
||||
<div>
|
||||
{step > 1 && (
|
||||
<Button variant="outline" onClick={() => setStep(step - 1)} disabled={submitting}>
|
||||
上一步
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setOpen(false)
|
||||
resetForm()
|
||||
}}
|
||||
disabled={submitting}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
{step < totalSteps ? (
|
||||
<Button
|
||||
onClick={() => setStep(step + 1)}
|
||||
disabled={
|
||||
loading ||
|
||||
(selectedProviders.size === 0 && selectedModels.size === 0 && selectedTasks.size === 0)
|
||||
}
|
||||
>
|
||||
下一步
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={handleSubmit} disabled={submitting}>
|
||||
{submitting && <Loader2 className="w-4 h-4 mr-2 animate-spin" />}
|
||||
提交审核
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user