style: align agent preset dialog with dark workflow
This commit is contained in:
parent
89ac6d69db
commit
4f5d999ade
@ -23,7 +23,7 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { fetcher } from "@/lib/utils";
|
||||
import { cn, fetcher } from "@/lib/utils";
|
||||
import { imageGenerationService } from "@/services/canvas";
|
||||
import { agentService } from "@/services/agents";
|
||||
import type {
|
||||
@ -47,6 +47,8 @@ interface AgentPresetDialogProps {
|
||||
onOpenChange: (open: boolean) => void;
|
||||
initialPreset?: AgentPreset | null;
|
||||
onSaved?: (preset: AgentPreset) => void;
|
||||
entityLabel?: string;
|
||||
theme?: "light" | "dark";
|
||||
}
|
||||
|
||||
export function AgentPresetDialog({
|
||||
@ -54,6 +56,8 @@ export function AgentPresetDialog({
|
||||
onOpenChange,
|
||||
initialPreset,
|
||||
onSaved,
|
||||
entityLabel = "智能体",
|
||||
theme = "light",
|
||||
}: AgentPresetDialogProps) {
|
||||
const { data: modelsData } = useSWR<{
|
||||
models?: Array<{
|
||||
@ -89,6 +93,11 @@ export function AgentPresetDialog({
|
||||
]);
|
||||
const fileInputRefs = useRef<Array<HTMLInputElement | null>>([]);
|
||||
const avatarInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const isDark = theme === "dark";
|
||||
const entityNameLabel = `${entityLabel}名称`;
|
||||
const dialogTitle = initialPreset ? `编辑${entityLabel}` : `新建${entityLabel}`;
|
||||
const dialogDescription = `配置${entityLabel}名称、预览图、模型和槽位提示词。`;
|
||||
const submitLabel = initialPreset ? `保存${entityLabel}` : `创建${entityLabel}`;
|
||||
|
||||
const videoProviderTypes = useMemo(
|
||||
() => new Set(["volcano-video", "doubao-video", "qianwen-video", "fal-video", "ruxa-video"]),
|
||||
@ -235,13 +244,13 @@ export function AgentPresetDialog({
|
||||
try {
|
||||
const result = await imageGenerationService.uploadImage(file);
|
||||
if (!result.success || !result.data?.assetId || !result.data.url) {
|
||||
throw new Error(result.error || "头像上传失败");
|
||||
throw new Error(result.error || "预览图上传失败");
|
||||
}
|
||||
setAvatarAssetId(result.data.assetId);
|
||||
setAvatarUrl(result.data.url);
|
||||
toast.success("头像上传成功");
|
||||
toast.success("预览图上传成功");
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "头像上传失败");
|
||||
toast.error(error instanceof Error ? error.message : "预览图上传失败");
|
||||
} finally {
|
||||
setAvatarUploading(false);
|
||||
event.target.value = "";
|
||||
@ -298,7 +307,7 @@ export function AgentPresetDialog({
|
||||
|
||||
const handleCreate = async () => {
|
||||
if (!name.trim()) {
|
||||
toast.error("请输入智能体名称");
|
||||
toast.error(`请输入${entityNameLabel}`);
|
||||
return;
|
||||
}
|
||||
if (!defaultModelId) {
|
||||
@ -339,15 +348,15 @@ export function AgentPresetDialog({
|
||||
: await agentService.createPreset(basePayload);
|
||||
|
||||
if (!result.success || !result.data) {
|
||||
throw new Error(result.error || (initialPreset ? "更新智能体失败" : "创建智能体失败"));
|
||||
throw new Error(result.error || (initialPreset ? `更新${entityLabel}失败` : `创建${entityLabel}失败`));
|
||||
}
|
||||
|
||||
toast.success(initialPreset ? "智能体更新成功" : "智能体创建成功");
|
||||
toast.success(initialPreset ? `${entityLabel}更新成功` : `${entityLabel}创建成功`);
|
||||
onSaved?.(result.data);
|
||||
onOpenChange(false);
|
||||
resetState();
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "创建智能体失败");
|
||||
toast.error(error instanceof Error ? error.message : `创建${entityLabel}失败`);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
@ -361,26 +370,49 @@ export function AgentPresetDialog({
|
||||
if (!nextOpen) resetState();
|
||||
}}
|
||||
>
|
||||
<DialogContent className="max-w-3xl border-slate-200 bg-white opacity-100 shadow-2xl dark:border-slate-800 dark:bg-zinc-950">
|
||||
<DialogContent
|
||||
className={cn(
|
||||
"max-w-4xl opacity-100 shadow-2xl",
|
||||
isDark
|
||||
? [
|
||||
"border border-white/12 bg-[#0b0d0f] text-white shadow-[0_32px_80px_rgba(0,0,0,0.52)]",
|
||||
"[&>button]:rounded-full [&>button]:border [&>button]:border-white/10 [&>button]:bg-white/[0.03]",
|
||||
"[&>button]:text-white/58 [&>button]:opacity-100 [&>button:hover]:bg-white/[0.08] [&>button:hover]:text-white",
|
||||
]
|
||||
: "border-slate-200 bg-white dark:border-slate-800 dark:bg-zinc-950"
|
||||
)}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{initialPreset ? "编辑智能体" : "新建智能体"}</DialogTitle>
|
||||
<DialogDescription>配置模式名称、预览图、模型和槽位提示词。</DialogDescription>
|
||||
<DialogTitle className={cn(isDark ? "text-white" : "")}>{dialogTitle}</DialogTitle>
|
||||
<DialogDescription className={cn(isDark ? "text-white/48" : "")}>
|
||||
{dialogDescription}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<ScrollArea className="max-h-[70vh] pr-3">
|
||||
<ScrollArea className="max-h-[72vh] pr-3">
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium">预览图</div>
|
||||
<div className={cn("text-sm font-medium", isDark ? "text-white/88" : "")}>预览图</div>
|
||||
<div className="flex items-start gap-4">
|
||||
{avatarUrl ? (
|
||||
<img
|
||||
src={avatarUrl}
|
||||
alt="智能体预览图"
|
||||
className="h-24 w-24 rounded-2xl border object-cover shadow-sm"
|
||||
alt={`${entityLabel}预览图`}
|
||||
className={cn(
|
||||
"h-24 w-24 rounded-2xl border object-cover shadow-sm",
|
||||
isDark ? "border-white/10" : ""
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-24 w-24 items-center justify-center rounded-2xl border bg-muted text-sm text-muted-foreground shadow-sm">
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-24 w-24 items-center justify-center rounded-2xl border text-sm shadow-sm",
|
||||
isDark
|
||||
? "border-white/10 bg-black/30 text-white/35"
|
||||
: "bg-muted text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
未上传
|
||||
</div>
|
||||
)}
|
||||
@ -398,7 +430,12 @@ export function AgentPresetDialog({
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-11 px-5 text-base"
|
||||
className={cn(
|
||||
"h-11 px-5 text-base",
|
||||
isDark
|
||||
? "border-white/10 bg-white/[0.03] text-white hover:bg-white/[0.08] hover:text-white"
|
||||
: ""
|
||||
)}
|
||||
onClick={() => avatarInputRef.current?.click()}
|
||||
disabled={avatarUploading}
|
||||
>
|
||||
@ -413,29 +450,45 @@ export function AgentPresetDialog({
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium">智能体名称</div>
|
||||
<Input value={name} onChange={(event) => setName(event.target.value)} />
|
||||
<div className={cn("text-sm font-medium", isDark ? "text-white/88" : "")}>
|
||||
{entityNameLabel}
|
||||
</div>
|
||||
<Input
|
||||
value={name}
|
||||
onChange={(event) => setName(event.target.value)}
|
||||
className={cn(
|
||||
isDark
|
||||
? "border-white/10 bg-black/30 text-white placeholder:text-white/22 focus-visible:ring-emerald-400/35"
|
||||
: ""
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-3 md:grid-cols-3">
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium">可见性</div>
|
||||
<div className={cn("text-sm font-medium", isDark ? "text-white/88" : "")}>可见性</div>
|
||||
<Select
|
||||
value={visibility}
|
||||
onValueChange={(value: "public" | "private") => setVisibility(value)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger
|
||||
className={cn(
|
||||
isDark
|
||||
? "border-white/10 bg-black/30 text-white focus:ring-emerald-400/35"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="private">私有</SelectItem>
|
||||
<SelectItem value="public">公开</SelectItem>
|
||||
<SelectContent className={cn(isDark ? "border-white/10 bg-zinc-950 text-white" : "")}>
|
||||
<SelectItem value="private" className={cn(isDark ? "text-white focus:bg-white/10 focus:text-white" : "")}>私有</SelectItem>
|
||||
<SelectItem value="public" className={cn(isDark ? "text-white focus:bg-white/10 focus:text-white" : "")}>公开</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium">类型</div>
|
||||
<div className={cn("text-sm font-medium", isDark ? "text-white/88" : "")}>类型</div>
|
||||
<Select
|
||||
value={type}
|
||||
onValueChange={(value: AgentPresetType) => {
|
||||
@ -443,25 +496,41 @@ export function AgentPresetDialog({
|
||||
setDefaultModelId("");
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger
|
||||
className={cn(
|
||||
isDark
|
||||
? "border-white/10 bg-black/30 text-white focus:ring-emerald-400/35"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="image">生图</SelectItem>
|
||||
<SelectItem value="video">视频</SelectItem>
|
||||
<SelectItem value="text">生文</SelectItem>
|
||||
<SelectContent className={cn(isDark ? "border-white/10 bg-zinc-950 text-white" : "")}>
|
||||
<SelectItem value="image" className={cn(isDark ? "text-white focus:bg-white/10 focus:text-white" : "")}>生图</SelectItem>
|
||||
<SelectItem value="video" className={cn(isDark ? "text-white focus:bg-white/10 focus:text-white" : "")}>视频</SelectItem>
|
||||
<SelectItem value="text" className={cn(isDark ? "text-white focus:bg-white/10 focus:text-white" : "")}>生文</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium">对应模型</div>
|
||||
<div className={cn("text-sm font-medium", isDark ? "text-white/88" : "")}>对应模型</div>
|
||||
<Select value={defaultModelId} onValueChange={setDefaultModelId}>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger
|
||||
className={cn(
|
||||
isDark
|
||||
? "border-white/10 bg-black/30 text-white focus:ring-emerald-400/35"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
<SelectValue placeholder="选择模型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectContent className={cn(isDark ? "border-white/10 bg-zinc-950 text-white" : "")}>
|
||||
{filteredModels.map((model) => (
|
||||
<SelectItem key={model.id} value={model.id}>
|
||||
<SelectItem
|
||||
key={model.id}
|
||||
value={model.id}
|
||||
className={cn(isDark ? "text-white focus:bg-white/10 focus:text-white" : "")}
|
||||
>
|
||||
{model.displayName}
|
||||
</SelectItem>
|
||||
))}
|
||||
@ -471,23 +540,38 @@ export function AgentPresetDialog({
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium">描述</div>
|
||||
<div className={cn("text-sm font-medium", isDark ? "text-white/88" : "")}>描述</div>
|
||||
<Textarea
|
||||
value={description}
|
||||
onChange={(event) => setDescription(event.target.value)}
|
||||
rows={3}
|
||||
className={cn(
|
||||
isDark
|
||||
? "border-white/10 bg-black/30 text-white placeholder:text-white/22 focus-visible:ring-emerald-400/35"
|
||||
: ""
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-sm font-medium">槽位配置</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
<div className={cn("text-sm font-medium", isDark ? "text-white/88" : "")}>槽位配置</div>
|
||||
<div className={cn("text-xs", isDark ? "text-white/42" : "text-muted-foreground")}>
|
||||
当前共 {slots.length} 个槽位,使用时固定上传 3 张素材图:产品图1、产品图2、模特图
|
||||
</div>
|
||||
</div>
|
||||
<Button type="button" variant="outline" size="sm" onClick={handleAddSlot}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleAddSlot}
|
||||
className={cn(
|
||||
isDark
|
||||
? "border-white/10 bg-white/[0.03] text-white hover:bg-white/[0.08] hover:text-white"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
<Plus className="mr-1 h-4 w-4" />
|
||||
新增槽位
|
||||
</Button>
|
||||
@ -495,15 +579,24 @@ export function AgentPresetDialog({
|
||||
|
||||
<div className="space-y-3">
|
||||
{slots.map((slot, index) => (
|
||||
<div key={`slot-${index}`} className="rounded-2xl border p-3">
|
||||
<div
|
||||
key={`slot-${index}`}
|
||||
className={cn(
|
||||
"rounded-2xl border p-3",
|
||||
isDark ? "border-white/10 bg-black/20" : ""
|
||||
)}
|
||||
>
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<div className="font-medium">槽位 {index + 1}</div>
|
||||
<div className={cn("font-medium", isDark ? "text-white" : "")}>槽位 {index + 1}</div>
|
||||
{slots.length > 1 ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8"
|
||||
className={cn(
|
||||
"h-8 w-8",
|
||||
isDark ? "text-white/60 hover:bg-white/8 hover:text-white" : ""
|
||||
)}
|
||||
onClick={() => handleRemoveSlot(index)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
@ -513,33 +606,48 @@ export function AgentPresetDialog({
|
||||
|
||||
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium">槽位名称</div>
|
||||
<div className={cn("text-sm font-medium", isDark ? "text-white/88" : "")}>槽位名称</div>
|
||||
<Input
|
||||
value={slot.title || ""}
|
||||
onChange={(event) =>
|
||||
handleSlotChange(index, { title: event.target.value })
|
||||
}
|
||||
className={cn(
|
||||
isDark
|
||||
? "border-white/10 bg-black/30 text-white placeholder:text-white/22 focus-visible:ring-emerald-400/35"
|
||||
: ""
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium">上传提示语</div>
|
||||
<div className={cn("text-sm font-medium", isDark ? "text-white/88" : "")}>上传提示语</div>
|
||||
<Input
|
||||
value={slot.uploadLabel || ""}
|
||||
onChange={(event) =>
|
||||
handleSlotChange(index, { uploadLabel: event.target.value })
|
||||
}
|
||||
className={cn(
|
||||
isDark
|
||||
? "border-white/10 bg-black/30 text-white placeholder:text-white/22 focus-visible:ring-emerald-400/35"
|
||||
: ""
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 space-y-2">
|
||||
<div className="text-sm font-medium">提示词</div>
|
||||
<div className={cn("text-sm font-medium", isDark ? "text-white/88" : "")}>提示词</div>
|
||||
<Textarea
|
||||
rows={4}
|
||||
value={slot.promptTemplate}
|
||||
onChange={(event) =>
|
||||
handleSlotChange(index, { promptTemplate: event.target.value })
|
||||
}
|
||||
className={cn(
|
||||
isDark
|
||||
? "border-white/10 bg-black/30 text-white placeholder:text-white/22 focus-visible:ring-emerald-400/35"
|
||||
: ""
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -562,6 +670,11 @@ export function AgentPresetDialog({
|
||||
size="sm"
|
||||
onClick={() => fileInputRefs.current[index]?.click()}
|
||||
disabled={uploadingSlotIndex === index}
|
||||
className={cn(
|
||||
isDark
|
||||
? "border-white/10 bg-white/[0.03] text-white hover:bg-white/[0.08] hover:text-white"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
{uploadingSlotIndex === index ? (
|
||||
<Loader2 className="mr-1 h-4 w-4 animate-spin" />
|
||||
@ -577,13 +690,18 @@ export function AgentPresetDialog({
|
||||
<img
|
||||
src={asset.url}
|
||||
alt={asset.name || `参考图 ${assetIndex + 1}`}
|
||||
className="h-16 w-16 rounded-lg border object-cover"
|
||||
className={cn(
|
||||
"h-16 w-16 rounded-lg border object-cover",
|
||||
isDark ? "border-white/10" : ""
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-2 text-xs text-muted-foreground">未上传参考图</div>
|
||||
<div className={cn("mt-2 text-xs", isDark ? "text-white/40" : "text-muted-foreground")}>
|
||||
未上传参考图
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@ -594,16 +712,28 @@ export function AgentPresetDialog({
|
||||
</ScrollArea>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => onOpenChange(false)}
|
||||
className={cn(
|
||||
isDark
|
||||
? "border-white/10 bg-white/[0.03] text-white hover:bg-white/[0.08] hover:text-white"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleCreate}
|
||||
disabled={isSubmitting}
|
||||
className="bg-black text-white hover:bg-black/90 dark:bg-white dark:text-black dark:hover:bg-white/90"
|
||||
className={cn(
|
||||
isDark
|
||||
? "bg-gradient-to-r from-emerald-500 to-teal-500 text-white hover:from-emerald-400 hover:to-teal-400"
|
||||
: "bg-black text-white hover:bg-black/90 dark:bg-white dark:text-black dark:hover:bg-white/90"
|
||||
)}
|
||||
>
|
||||
{isSubmitting ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : null}
|
||||
{initialPreset ? "保存修改" : "创建智能体"}
|
||||
{submitLabel}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@ -250,6 +250,8 @@ export function AgentPresetPanelDarkV2({
|
||||
setShowCreateDialog(true);
|
||||
}}
|
||||
initialPreset={editingPreset}
|
||||
entityLabel={embeddedEntityLabel}
|
||||
theme="dark"
|
||||
onSaved={async (preset) => {
|
||||
await mutate();
|
||||
onSelectPreset(preset.id);
|
||||
@ -429,6 +431,8 @@ export function AgentPresetPanelDarkV2({
|
||||
setShowCreateDialog(true);
|
||||
}}
|
||||
initialPreset={editingPreset}
|
||||
entityLabel="智能体"
|
||||
theme="dark"
|
||||
onSaved={async (preset) => {
|
||||
await mutate();
|
||||
onSelectPreset(preset.id);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user