style: align agent preset dialog with dark workflow
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-03-18 15:33:57 +08:00
parent 89ac6d69db
commit 4f5d999ade
2 changed files with 183 additions and 49 deletions

View File

@ -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>

View File

@ -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);