feat: 多页面拖拽上传、滚动条精简、UI优化
- 剧本评测/分辨率提升/数字人/角色迁移/图片工作台/去水印/电商:新增外部拖拽文件上传 - 电商:爆款图复刻上传框支持拖拽+大滚动条,短视频/模特图/详情图滚动条精简回退 - 图片工作台:右侧输出面板移至左侧提示词上方,删除局部重绘遮罩/结果框 - 数字人:生成按钮改为「开始生成」 - 局部重绘:编辑遮罩→编辑页面 - 对话框生成器:新增对话/视频模式、模型/速度/深度选择按钮 - 视频时长默认改为5秒 - 工具箱页面空状态logo统一绿底亮色图标 - 多处CSS滚动条和布局优化
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { useCallback, useRef, useState, type CSSProperties, type MouseEvent as ReactMouseEvent, type TouchEvent as ReactTouchEvent } from "react";
|
||||
import { useCallback, useEffect, useRef, useState, type CSSProperties, type MouseEvent as ReactMouseEvent, type TouchEvent as ReactTouchEvent } from "react";
|
||||
import { ApartmentOutlined, DownOutlined, RobotOutlined, ThunderboltOutlined } from "@ant-design/icons";
|
||||
|
||||
type DialogStyle = "style1" | "style2" | "style3" | "style4";
|
||||
type GenerationMode = "dialog" | "video";
|
||||
|
||||
interface DialogItem {
|
||||
id: number;
|
||||
@@ -39,16 +41,68 @@ const textColorOptions = [
|
||||
{ value: "#00ff88", label: "绿色" },
|
||||
];
|
||||
|
||||
const dialogModelOptions = [
|
||||
{ id: "gemini", label: "Gemini" },
|
||||
{ id: "wanxian", label: "万相" },
|
||||
{ id: "deepseek", label: "DeepSeek" },
|
||||
];
|
||||
|
||||
const thinkingSpeedOptions = [
|
||||
{ id: "default", label: "默认" },
|
||||
{ id: "high", label: "高" },
|
||||
{ id: "ultra", label: "急速" },
|
||||
];
|
||||
|
||||
const thinkingDepthOptions = [
|
||||
{ id: "default", label: "默认" },
|
||||
{ id: "strong", label: "强" },
|
||||
{ id: "extreme", label: "极限" },
|
||||
];
|
||||
|
||||
const videoDurationOptions = [
|
||||
{ value: "5", label: "5s" },
|
||||
{ value: "6", label: "6s" },
|
||||
{ value: "7", label: "7s" },
|
||||
{ value: "8", label: "8s" },
|
||||
{ value: "9", label: "9s" },
|
||||
{ value: "10", label: "10s" },
|
||||
{ value: "11", label: "11s" },
|
||||
{ value: "12", label: "12s" },
|
||||
{ value: "13", label: "13s" },
|
||||
{ value: "14", label: "14s" },
|
||||
{ value: "15", label: "15s" },
|
||||
];
|
||||
|
||||
function DialogGeneratorPage() {
|
||||
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const previewRef = useRef<HTMLDivElement | null>(null);
|
||||
const dragRef = useRef<DragState | null>(null);
|
||||
const nextIdRef = useRef(0);
|
||||
const controlsRef = useRef<HTMLDivElement>(null);
|
||||
const [backgroundUrl, setBackgroundUrl] = useState("");
|
||||
const [dialogs, setDialogs] = useState<DialogItem[]>([]);
|
||||
const [selectedTextColor, setSelectedTextColor] = useState(textColorOptions[0].value);
|
||||
const [activeDragId, setActiveDragId] = useState<number | null>(null);
|
||||
|
||||
// ── Generation state ──
|
||||
const [generationMode, setGenerationMode] = useState<GenerationMode>("dialog");
|
||||
const [dialogModel, setDialogModel] = useState(dialogModelOptions[0].id);
|
||||
const [thinkingSpeed, setThinkingSpeed] = useState(thinkingSpeedOptions[0].id);
|
||||
const [thinkingDepth, setThinkingDepth] = useState(thinkingDepthOptions[0].id);
|
||||
const [videoDuration, setVideoDuration] = useState(videoDurationOptions[0].value);
|
||||
const [activeDropdown, setActiveDropdown] = useState<string | null>(null);
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (controlsRef.current && !controlsRef.current.contains(event.target as Node)) {
|
||||
setActiveDropdown(null);
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, []);
|
||||
|
||||
const handleFile = useCallback((file?: File | null) => {
|
||||
if (!file || !file.type.startsWith("image/")) return;
|
||||
const reader = new FileReader();
|
||||
@@ -194,6 +248,141 @@ function DialogGeneratorPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="dialog-generator-section">
|
||||
<h2>生成设置</h2>
|
||||
<div className="dialog-generator-mode-switch" role="radiogroup" aria-label="生成模式">
|
||||
<button
|
||||
type="button"
|
||||
className={`dialog-generator-mode${generationMode === "dialog" ? " is-active" : ""}`}
|
||||
role="radio"
|
||||
aria-checked={generationMode === "dialog"}
|
||||
onClick={() => setGenerationMode("dialog")}
|
||||
>
|
||||
对话模式
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`dialog-generator-mode${generationMode === "video" ? " is-active" : ""}`}
|
||||
role="radio"
|
||||
aria-checked={generationMode === "video"}
|
||||
onClick={() => setGenerationMode("video")}
|
||||
>
|
||||
视频模式
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="dialog-generator-controls" ref={controlsRef}>
|
||||
{generationMode === "dialog" ? (
|
||||
<>
|
||||
<div className="dialog-generator-pills">
|
||||
<button
|
||||
type="button"
|
||||
className={`dialog-generator-pill${activeDropdown === "model" ? " is-open" : ""}`}
|
||||
onClick={() => setActiveDropdown(activeDropdown === "model" ? null : "model")}
|
||||
>
|
||||
<RobotOutlined />
|
||||
{dialogModelOptions.find((m) => m.id === dialogModel)?.label ?? "模型选择"}
|
||||
<DownOutlined />
|
||||
</button>
|
||||
{activeDropdown === "model" && (
|
||||
<div className="dialog-generator-dropdown">
|
||||
{dialogModelOptions.map((m) => (
|
||||
<button
|
||||
key={m.id}
|
||||
type="button"
|
||||
className={`dialog-generator-dropdown__item${dialogModel === m.id ? " is-active" : ""}`}
|
||||
onClick={() => { setDialogModel(m.id); setActiveDropdown(null); }}
|
||||
>
|
||||
{m.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="dialog-generator-pills">
|
||||
<button
|
||||
type="button"
|
||||
className={`dialog-generator-pill${activeDropdown === "speed" ? " is-open" : ""}`}
|
||||
onClick={() => setActiveDropdown(activeDropdown === "speed" ? null : "speed")}
|
||||
>
|
||||
<ThunderboltOutlined />
|
||||
{thinkingSpeedOptions.find((s) => s.id === thinkingSpeed)?.label ?? "思考速度"}
|
||||
<DownOutlined />
|
||||
</button>
|
||||
{activeDropdown === "speed" && (
|
||||
<div className="dialog-generator-dropdown">
|
||||
{thinkingSpeedOptions.map((s) => (
|
||||
<button
|
||||
key={s.id}
|
||||
type="button"
|
||||
className={`dialog-generator-dropdown__item${thinkingSpeed === s.id ? " is-active" : ""}`}
|
||||
onClick={() => { setThinkingSpeed(s.id); setActiveDropdown(null); }}
|
||||
>
|
||||
{s.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="dialog-generator-pills">
|
||||
<button
|
||||
type="button"
|
||||
className={`dialog-generator-pill${activeDropdown === "depth" ? " is-open" : ""}`}
|
||||
onClick={() => setActiveDropdown(activeDropdown === "depth" ? null : "depth")}
|
||||
>
|
||||
<ApartmentOutlined />
|
||||
{thinkingDepthOptions.find((d) => d.id === thinkingDepth)?.label ?? "思考深度"}
|
||||
<DownOutlined />
|
||||
</button>
|
||||
{activeDropdown === "depth" && (
|
||||
<div className="dialog-generator-dropdown">
|
||||
{thinkingDepthOptions.map((d) => (
|
||||
<button
|
||||
key={d.id}
|
||||
type="button"
|
||||
className={`dialog-generator-dropdown__item${thinkingDepth === d.id ? " is-active" : ""}`}
|
||||
onClick={() => { setThinkingDepth(d.id); setActiveDropdown(null); }}
|
||||
>
|
||||
{d.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="dialog-generator-duration">
|
||||
<span className="dialog-generator-duration__label">视频时长</span>
|
||||
<div className="dialog-generator-duration__options">
|
||||
{videoDurationOptions.map((opt) => (
|
||||
<button
|
||||
key={opt.value}
|
||||
type="button"
|
||||
className={`dialog-generator-duration__btn${videoDuration === opt.value ? " is-active" : ""}`}
|
||||
onClick={() => setVideoDuration(opt.value)}
|
||||
>
|
||||
{opt.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="dialog-generator-run"
|
||||
disabled={isGenerating}
|
||||
onClick={() => {
|
||||
setIsGenerating(true);
|
||||
// TODO: wire to actual generation API
|
||||
setTimeout(() => setIsGenerating(false), 2000);
|
||||
}}
|
||||
>
|
||||
{isGenerating ? "生成中..." : "生成"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button type="button" className="dialog-generator-clear" onClick={() => setDialogs([])}>
|
||||
清空全部文字
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user