feat: 多页面拖拽上传、滚动条精简、UI优化

- 剧本评测/分辨率提升/数字人/角色迁移/图片工作台/去水印/电商:新增外部拖拽文件上传
- 电商:爆款图复刻上传框支持拖拽+大滚动条,短视频/模特图/详情图滚动条精简回退
- 图片工作台:右侧输出面板移至左侧提示词上方,删除局部重绘遮罩/结果框
- 数字人:生成按钮改为「开始生成」
- 局部重绘:编辑遮罩→编辑页面
- 对话框生成器:新增对话/视频模式、模型/速度/深度选择按钮
- 视频时长默认改为5秒
- 工具箱页面空状态logo统一绿底亮色图标
- 多处CSS滚动条和布局优化
This commit is contained in:
OmniAI Developer
2026-06-05 18:01:55 +08:00
parent 8fbb2ec95e
commit 5b87594e36
22 changed files with 1796 additions and 195 deletions
@@ -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>