ai-chatbot/components/canvas/nodes/image-node/image-node-dialogs.tsx

232 lines
7.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { Box, Globe2, Loader2, RefreshCw, Zap } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
interface ThreeDModelOption {
id: string;
displayName: string;
description?: string | null;
}
type ThreeDGenerationDialogMode = "model" | "world" | null;
interface PromptRegenerateDialogProps {
isProcessing: boolean;
nodeId: string;
onConfirm: () => void;
onOpenChange: (open: boolean) => void;
onPromptChange: (value: string) => void;
open: boolean;
prompt: string;
}
interface ThreeDGenerationDialogProps {
costLabel?: string;
costShortLabel?: string | null;
isProcessing: boolean;
mode: ThreeDGenerationDialogMode;
onConfirm: () => void;
onModeChange: (mode: ThreeDGenerationDialogMode) => void;
onTripoModelChange: (value: string) => void;
onWorldModelChange: (value: string) => void;
selectedTripoModelEntry?: ThreeDModelOption | null;
selectedTripoModelVersion: string;
selectedWorldModelEntry?: ThreeDModelOption | null;
selectedWorldModelVersion: string;
tripoModelOptions: ThreeDModelOption[];
worldModelOptions: ThreeDModelOption[];
}
export function PromptRegenerateDialog({
isProcessing,
nodeId,
onConfirm,
onOpenChange,
onPromptChange,
open,
prompt,
}: PromptRegenerateDialogProps) {
return (
<Dialog onOpenChange={onOpenChange} open={open}>
<DialogContent
className="border-slate-200 bg-white opacity-100 shadow-2xl backdrop-blur-none sm:max-w-[560px] dark:border-slate-700 dark:bg-slate-950"
overlayClassName="bg-black"
>
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<RefreshCw className="h-5 w-5" />
提示词重绘
</DialogTitle>
</DialogHeader>
<div className="space-y-3 py-2">
<p className="text-muted-foreground text-sm">
在保留当前图片主体的基础上,用新提示词生成一张新图。原图不会被覆盖。
</p>
<div className="space-y-2">
<Label htmlFor={`prompt-regenerate-${nodeId}`}>新提示词</Label>
<Textarea
className="resize-y border-slate-300 bg-white opacity-100 dark:border-slate-700 dark:bg-slate-950"
id={`prompt-regenerate-${nodeId}`}
onChange={(event) => onPromptChange(event.target.value)}
placeholder="例如:保持主体不变,把背景改成黄昏海边,氛围光影更柔和,细节更丰富"
rows={6}
value={prompt}
/>
</div>
</div>
<DialogFooter>
<Button onClick={() => onOpenChange(false)} variant="outline">
取消
</Button>
<Button disabled={isProcessing || !prompt.trim()} onClick={onConfirm}>
{isProcessing ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
) : (
<RefreshCw className="mr-2 h-4 w-4" />
)}
开始重绘
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
export function ThreeDGenerationDialog({
costLabel,
costShortLabel,
isProcessing,
mode,
onConfirm,
onModeChange,
onTripoModelChange,
onWorldModelChange,
selectedTripoModelEntry,
selectedTripoModelVersion,
selectedWorldModelEntry,
selectedWorldModelVersion,
tripoModelOptions,
worldModelOptions,
}: ThreeDGenerationDialogProps) {
const isWorldMode = mode === "world";
const selectedEntry = isWorldMode
? selectedWorldModelEntry
: selectedTripoModelEntry;
const options = isWorldMode ? worldModelOptions : tripoModelOptions;
return (
<Dialog
onOpenChange={(open) => {
if (!open) {
onModeChange(null);
}
}}
open={mode !== null}
>
<DialogContent
className="border-slate-200 bg-white opacity-100 shadow-2xl backdrop-blur-none sm:max-w-[560px] dark:border-slate-700 dark:bg-slate-950"
overlayClassName="bg-black/35 backdrop-blur-[1px]"
>
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
{isWorldMode ? (
<Globe2 className="h-5 w-5" />
) : (
<Box className="h-5 w-5" />
)}
{isWorldMode ? "选择 3D 世界模型" : "选择 3D 模型"}
</DialogTitle>
</DialogHeader>
<div className="space-y-4 py-2">
<p className="text-muted-foreground text-sm">
{isWorldMode
? "先选择用于图生世界的生成质量,再开始创建 3D 世界任务。"
: "先选择用于图生 3D 的模型版本,再开始创建 3D 模型任务。"}
</p>
<div className="space-y-2">
<Label>{isWorldMode ? "世界模型" : "模型版本"}</Label>
<Select
onValueChange={(value) => {
if (isWorldMode) {
onWorldModelChange(value);
return;
}
onTripoModelChange(value);
}}
value={
isWorldMode ? selectedWorldModelVersion : selectedTripoModelVersion
}
>
<SelectTrigger className="border-slate-300 bg-white dark:border-slate-700 dark:bg-slate-950">
<SelectValue placeholder="选择模型" />
</SelectTrigger>
<SelectContent className="border-slate-200 bg-white dark:border-slate-700 dark:bg-slate-950">
{options.map((option) => (
<SelectItem key={option.id} value={option.id}>
{option.displayName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="rounded-xl border border-slate-200 bg-slate-50 p-3 text-sm dark:border-slate-800 dark:bg-slate-900/80">
<div className="font-medium text-slate-900 dark:text-slate-100">
{selectedEntry?.displayName || "未选择模型"}
</div>
<div className="mt-1 text-slate-600 text-xs dark:text-slate-400">
{selectedEntry?.description || "当前模型暂无说明。"}
</div>
{costLabel ? (
<div className="mt-3 inline-flex items-center gap-1.5 rounded-md bg-slate-900 px-2.5 py-1 text-[12px] font-medium text-white shadow-sm dark:bg-white dark:text-slate-950">
<Zap className="h-3.5 w-3.5 fill-current" />
{costLabel}
</div>
) : null}
</div>
</div>
<DialogFooter>
<Button onClick={() => onModeChange(null)} variant="outline">
取消
</Button>
<Button
disabled={isProcessing || !selectedEntry}
onClick={() => void onConfirm()}
>
{isProcessing ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
) : isWorldMode ? (
<Globe2 className="mr-2 h-4 w-4" />
) : (
<Box className="mr-2 h-4 w-4" />
)}
<span>开始生成</span>
{costShortLabel ? (
<span className="ml-2 inline-flex items-center gap-1 rounded-md bg-black/18 px-1.5 py-0.5 text-[12px] font-semibold text-current dark:bg-white/18">
<Zap className="h-3 w-3 fill-current" />
{costShortLabel}
</span>
) : null}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}