+
+ {isDragging ?
释放文件以上传
: null}
)
) : (
-
+
+
+
+
+
{mode === "image" ? "拖拽或选择图片" : "拖拽或选择视频"}
+
{mode === "image" ? "支持 PNG / JPG / WebP" : "支持 MP4 / MOV / WebM"}
+
)}
diff --git a/src/features/script-tokens/ScriptTokensPage.tsx b/src/features/script-tokens/ScriptTokensPage.tsx
index 3c8a53e..4ecc163 100644
--- a/src/features/script-tokens/ScriptTokensPage.tsx
+++ b/src/features/script-tokens/ScriptTokensPage.tsx
@@ -1,6 +1,7 @@
import {
BarChartOutlined,
CheckCircleFilled,
+ CloseOutlined,
CopyOutlined,
DownloadOutlined,
FileTextOutlined,
@@ -8,7 +9,7 @@ import {
ThunderboltOutlined,
UploadOutlined,
} from "@ant-design/icons";
-import { useEffect, useRef, useState, type ChangeEvent, type KeyboardEvent } from "react";
+import { useEffect, useRef, useState, type ChangeEvent, type DragEvent, type KeyboardEvent } from "react";
import { evaluateScript } from "../../api/scriptEvalClient";
import { buildApiUrl, getStoredToken } from "../../api/serverConnection";
import { useSessionStore } from "../../stores";
@@ -302,6 +303,7 @@ function ScriptTokensPage() {
const [animatedScore, setAnimatedScore] = useState(0);
const [activeHistoryIndex, setActiveHistoryIndex] = useState
(0);
const [history, setHistory] = useState(loadHistory);
+ const [isDragging, setIsDragging] = useState(false);
const fileInputRef = useRef(null);
const scoreFrameRef = useRef(null);
@@ -324,9 +326,7 @@ function ScriptTokensPage() {
return () => { if (scoreFrameRef.current) cancelAnimationFrame(scoreFrameRef.current); };
}, [result]);
- const handleFileUpload = async (event: ChangeEvent) => {
- const file = event.target.files?.[0];
- if (!file) return;
+ const processUploadedFile = async (file: File) => {
const ext = getFileExtension(file.name);
const readable = isReadableTextFile(file, ext);
setUploadedFile({ name: file.name, size: file.size });
@@ -356,6 +356,12 @@ function ScriptTokensPage() {
} else {
setScript(`[已上传文件:${file.name}]\n\n暂不支持解析 ${ext ? ext.toUpperCase() : "未知"} 格式,请上传常见文本类文件。`);
}
+ };
+
+ const handleFileUpload = async (event: ChangeEvent) => {
+ const file = event.target.files?.[0];
+ if (!file) return;
+ await processUploadedFile(file);
event.target.value = "";
};
@@ -456,6 +462,30 @@ function ScriptTokensPage() {
fileInputRef.current?.click();
};
+ const handleDragOver = (event: DragEvent) => {
+ event.preventDefault();
+ event.stopPropagation();
+ if (event.dataTransfer.types.includes("Files")) {
+ setIsDragging(true);
+ }
+ };
+
+ const handleDragLeave = (event: DragEvent) => {
+ event.preventDefault();
+ event.stopPropagation();
+ if (event.currentTarget === event.target || !event.currentTarget.contains(event.relatedTarget as Node)) {
+ setIsDragging(false);
+ }
+ };
+
+ const handleDrop = (event: DragEvent) => {
+ event.preventDefault();
+ event.stopPropagation();
+ setIsDragging(false);
+ const file = event.dataTransfer.files[0];
+ if (file) processUploadedFile(file);
+ };
+
const grade = result ? getGrade(result.totalScore) : null;
const beatPct = result ? (result.totalScore >= 95 ? 97 : result.totalScore >= 88 ? 92 : result.totalScore >= 80 ? 85 : 72) : 0;
const compactTitle = uploadedFile?.name?.replace(/\.[^.]+$/, "") ?? "剧本评测";
@@ -472,14 +502,31 @@ function ScriptTokensPage() {
上传剧本
fileInputRef.current?.click()}
onKeyDown={uploadKeyDown}
+ onDragOver={handleDragOver}
+ onDragLeave={handleDragLeave}
+ onDrop={handleDrop}
>
+ {isDragging ? (
+
+
+ 释放文件以上传
+
+ ) : null}
{uploadedFile ? (
+
{uploadedFile.name}
diff --git a/src/features/subtitle-removal/SubtitleRemovalPage.tsx b/src/features/subtitle-removal/SubtitleRemovalPage.tsx
index cc11450..5f719fe 100644
--- a/src/features/subtitle-removal/SubtitleRemovalPage.tsx
+++ b/src/features/subtitle-removal/SubtitleRemovalPage.tsx
@@ -12,7 +12,7 @@ import {
SwapOutlined,
VideoCameraOutlined,
} from "@ant-design/icons";
-import { useCallback, useEffect, useRef, useState, type CSSProperties } from "react";
+import { useCallback, useEffect, useRef, useState, type CSSProperties, type DragEvent } from "react";
import { aiGenerationClient } from "../../api/aiGenerationClient";
import { waitForTask } from "../../api/taskSubscription";
import { saveToolTaskState, loadToolTaskState, clearToolTaskState } from "../workbench/toolKeepalive";
@@ -73,6 +73,7 @@ function SubtitleRemovalPage({
const [isProcessing, setIsProcessing] = useState(false);
const [isDownloading, setIsDownloading] = useState(false);
const [isSavingAsset, setIsSavingAsset] = useState(false);
+ const [isDragging, setIsDragging] = useState(false);
const activeTaskIdRef = useRef(activeTaskId);
activeTaskIdRef.current = activeTaskId;
const keepaliveRestoredRef = useRef(false);
@@ -125,10 +126,7 @@ function SubtitleRemovalPage({
event.currentTarget.value = "";
};
- const handleFileDrop = (event: React.DragEvent) => {
- event.preventDefault();
- const file = Array.from(event.dataTransfer.files).find((f) => f.type.startsWith("video/"));
- if (!file) return;
+ const processDroppedFile = (file: File) => {
if (sourcePreview.startsWith("blob:")) URL.revokeObjectURL(sourcePreview);
setSourceName(file.name);
setSourceFile(file);
@@ -140,6 +138,10 @@ function SubtitleRemovalPage({
setStatus(`已导入 ${file.name}`);
};
+ const handleDragOver = (e: DragEvent) => { e.preventDefault(); e.stopPropagation(); if (e.dataTransfer.types.includes("Files")) setIsDragging(true); };
+ const handleDragLeave = (e: DragEvent) => { e.preventDefault(); e.stopPropagation(); if (e.currentTarget === e.target || !e.currentTarget.contains(e.relatedTarget as Node)) setIsDragging(false); };
+ const handleFileDrop = (e: DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); const file = Array.from(e.dataTransfer.files).find((f) => f.type.startsWith("video/")); if (file) processDroppedFile(file); };
+
const handleImportUrl = () => {
const normalized = sourceUrl.trim();
if (!/^https?:\/\//i.test(normalized)) {
@@ -341,7 +343,13 @@ function SubtitleRemovalPage({
accept="video/mp4"
onChange={handleFileChange}
/>
- e.preventDefault()} onDrop={handleFileDrop}>
+
+ {isDragging ?
释放文件以上传
: null}
) : (
-
-
-
上传视频后在此预览
+
+
+
+
+
拖拽或选择视频
+
仅支持 MP4,最大 1GB,最高 1080P
)}
diff --git a/src/features/workbench/WorkbenchPage.tsx b/src/features/workbench/WorkbenchPage.tsx
index f40ff7e..013c61c 100644
--- a/src/features/workbench/WorkbenchPage.tsx
+++ b/src/features/workbench/WorkbenchPage.tsx
@@ -105,6 +105,9 @@ import {
type WorkbenchKeepaliveTask,
MODE_META,
MODE_OPTIONS,
+ CHAT_MODEL_OPTIONS,
+ THINKING_SPEED_OPTIONS,
+ THINKING_DEPTH_OPTIONS,
IMAGE_MODEL_OPTIONS,
VIDEO_MODEL_OPTIONS,
RATIO_OPTIONS,
@@ -337,9 +340,13 @@ function WorkbenchPage({
const [videoModel, setVideoModel] = useState(VIDEO_MODEL_OPTIONS[0].value);
const [videoFrameMode, setVideoFrameMode] = useState("omni");
const [videoRatio, setVideoRatio] = useState("16:9");
- const [videoDuration, setVideoDuration] = useState("4");
+ const [videoDuration, setVideoDuration] = useState("5");
const [videoQuality, setVideoQuality] = useState(() => getDefaultVideoQuality(VIDEO_MODEL_OPTIONS[0].value));
+ const [chatModel, setChatModel] = useState(CHAT_MODEL_OPTIONS[0].value);
+ const [thinkingSpeed, setThinkingSpeed] = useState(THINKING_SPEED_OPTIONS[0].value);
+ const [thinkingDepth, setThinkingDepth] = useState(THINKING_DEPTH_OPTIONS[0].value);
+
useEffect(() => {
let cancelled = false;
@@ -395,13 +402,13 @@ function WorkbenchPage({
const referenceCount = referenceItems.length;
const activeVideoModelValue = toHappyHorseDisplayModel(videoModel);
const activeModelValue =
- activeMode === "image" ? imageModel : activeMode === "video" ? activeVideoModelValue : CHAT_MODEL;
+ activeMode === "image" ? imageModel : activeMode === "video" ? activeVideoModelValue : chatModel;
const activeModel =
activeMode === "image"
? imageModelOptions.find((item) => item.value === imageModel)?.label || imageModel
: activeMode === "video"
? videoModelOptions.find((item) => item.value === activeVideoModelValue)?.label || activeVideoModelValue
- : "OmniChat";
+ : CHAT_MODEL_OPTIONS.find((item) => item.value === chatModel)?.label || chatModel;
const conversationRecords = useMemo
(
() =>
conversations.map((conversation) => ({
@@ -2720,6 +2727,46 @@ function WorkbenchPage({
ariaLabel="工作台模式"
direction={dropdownDirection}
/>
+ {activeMode === "chat" && (
+ <>
+ toggleToolbarMenu("chat-model")}
+ onClose={closeToolbarMenus}
+ onChange={setChatModel}
+ ariaLabel="对话模型"
+ direction={dropdownDirection}
+ />
+ toggleToolbarMenu("chat-speed")}
+ onClose={closeToolbarMenus}
+ onChange={setThinkingSpeed}
+ ariaLabel="思考速度"
+ direction={dropdownDirection}
+ />
+ toggleToolbarMenu("chat-depth")}
+ onClose={closeToolbarMenus}
+ onChange={setThinkingDepth}
+ ariaLabel="思考深度"
+ direction={dropdownDirection}
+ />
+ >
+ )}
{activeMode === "image" && (
<>
({ ...option }));
@@ -249,7 +270,6 @@ export const VIDEO_FRAME_OPTIONS: WorkbenchOption[] = [
];
export const VIDEO_DURATION_OPTIONS: WorkbenchOption[] = [
- { value: "4", label: "4s" },
{ value: "5", label: "5s" },
{ value: "6", label: "6s" },
{ value: "7", label: "7s" },
diff --git a/src/styles/pages/canvas.css b/src/styles/pages/canvas.css
index 41f3f09..e6c66cd 100644
--- a/src/styles/pages/canvas.css
+++ b/src/styles/pages/canvas.css
@@ -723,6 +723,43 @@
height: 1px;
}
+/* ── Canvas drag-and-drop visual feedback ─────────────────────────── */
+.studio-canvas.is-canvas-dragging::after {
+ content: "释放以上传图片";
+ position: absolute;
+ inset: 0;
+ z-index: 1000;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(33, 242, 154, 0.12);
+ border: 3px dashed #21f29a;
+ color: #111;
+ font-size: 20px;
+ font-weight: 700;
+ pointer-events: none;
+}
+
+.studio-canvas-text-composer.is-drag-over {
+ outline: 2px dashed #21f29a;
+ outline-offset: 2px;
+ background: rgba(33, 242, 154, 0.06);
+}
+
+.studio-canvas-text-composer.is-drag-over::after {
+ content: "释放图片以创建节点";
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 10;
+ color: #333;
+ font-size: 13px;
+ font-weight: 600;
+ pointer-events: none;
+}
+
/* Tool Modal Overlay */
.studio-canvas-tool-modal-overlay {
position: fixed;
diff --git a/src/styles/pages/dialog-generator.css b/src/styles/pages/dialog-generator.css
index bc479f3..0637705 100644
--- a/src/styles/pages/dialog-generator.css
+++ b/src/styles/pages/dialog-generator.css
@@ -553,6 +553,209 @@
display: inline-block;
}
+/* ── Generation controls ── */
+.dialog-generator-mode-switch {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 8px;
+}
+
+.dialog-generator-mode {
+ min-height: 42px;
+ border: 1px solid rgba(0, 255, 136, 0.16);
+ border-radius: 8px;
+ background: rgba(255, 255, 255, 0.04);
+ color: #9aa1b8;
+ cursor: pointer;
+ font-size: 14px;
+ font-weight: 800;
+ transition:
+ border-color 180ms ease,
+ background 180ms ease,
+ color 180ms ease,
+ transform 180ms ease;
+}
+
+.dialog-generator-mode:hover {
+ border-color: rgba(0, 255, 136, 0.32);
+ color: #dce3ed;
+ transform: translateY(-1px);
+}
+
+.dialog-generator-mode.is-active {
+ border-color: rgba(0, 255, 136, 0.42);
+ background: rgba(0, 255, 136, 0.08);
+ color: #00ff88;
+ box-shadow: 0 0 16px rgba(0, 255, 136, 0.08);
+}
+
+.dialog-generator-controls {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 8px;
+}
+
+.dialog-generator-pills {
+ position: relative;
+}
+
+.dialog-generator-pill {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ min-height: 38px;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ border-radius: 8px;
+ background: rgba(255, 255, 255, 0.04);
+ color: #dce3ed;
+ cursor: pointer;
+ padding: 0 12px;
+ font-size: 13px;
+ font-weight: 750;
+ transition:
+ border-color 180ms ease,
+ background 180ms ease,
+ color 180ms ease;
+}
+
+.dialog-generator-pill:hover {
+ border-color: rgba(0, 255, 136, 0.28);
+ color: #f6f8fb;
+}
+
+.dialog-generator-pill.is-open {
+ border-color: rgba(0, 255, 136, 0.38);
+ background: rgba(0, 255, 136, 0.08);
+ color: #00ff88;
+}
+
+.dialog-generator-pill .anticon {
+ font-size: 14px;
+}
+
+.dialog-generator-dropdown {
+ position: absolute;
+ z-index: 30;
+ top: calc(100% + 4px);
+ left: 0;
+ min-width: 148px;
+ border: 1px solid rgba(0, 255, 136, 0.18);
+ border-radius: 8px;
+ background: rgba(10, 16, 26, 0.96);
+ box-shadow:
+ 0 12px 36px rgba(0, 0, 0, 0.42),
+ 0 0 0 1px rgba(0, 255, 136, 0.06);
+ backdrop-filter: blur(18px);
+ padding: 4px;
+ overflow: hidden;
+}
+
+.dialog-generator-dropdown__item {
+ display: block;
+ width: 100%;
+ border: 0;
+ border-radius: 6px;
+ background: transparent;
+ color: #bcc4d6;
+ cursor: pointer;
+ padding: 9px 12px;
+ text-align: left;
+ font-size: 13px;
+ font-weight: 700;
+ transition:
+ background 120ms ease,
+ color 120ms ease;
+}
+
+.dialog-generator-dropdown__item:hover {
+ background: rgba(0, 255, 136, 0.08);
+ color: #e8eaef;
+}
+
+.dialog-generator-dropdown__item.is-active {
+ background: rgba(0, 255, 136, 0.12);
+ color: #00ff88;
+ font-weight: 850;
+}
+
+/* ── Video duration ── */
+.dialog-generator-duration {
+ display: grid;
+ gap: 8px;
+ width: 100%;
+}
+
+.dialog-generator-duration__label {
+ color: #9aa1b8;
+ font-size: 13px;
+ font-weight: 750;
+}
+
+.dialog-generator-duration__options {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+}
+
+.dialog-generator-duration__btn {
+ min-width: 42px;
+ min-height: 34px;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ border-radius: 6px;
+ background: rgba(255, 255, 255, 0.04);
+ color: #9aa1b8;
+ cursor: pointer;
+ padding: 0 8px;
+ font-size: 12px;
+ font-weight: 750;
+ transition:
+ border-color 180ms ease,
+ background 180ms ease,
+ color 180ms ease;
+}
+
+.dialog-generator-duration__btn:hover {
+ border-color: rgba(0, 255, 136, 0.28);
+ color: #dce3ed;
+}
+
+.dialog-generator-duration__btn.is-active {
+ border-color: rgba(0, 255, 136, 0.42);
+ background: rgba(0, 255, 136, 0.12);
+ color: #00ff88;
+}
+
+/* ── Generate button ── */
+.dialog-generator-run {
+ min-height: 48px;
+ border: 1px solid rgba(0, 255, 136, 0.28);
+ border-radius: 8px;
+ background: linear-gradient(135deg, rgba(0, 255, 136, 0.14) 0%, rgba(34, 240, 192, 0.08) 100%);
+ color: #00ff88;
+ cursor: pointer;
+ font-size: 16px;
+ font-weight: 900;
+ letter-spacing: 0.04em;
+ transition:
+ border-color 180ms ease,
+ background 180ms ease,
+ transform 180ms ease,
+ box-shadow 180ms ease;
+}
+
+.dialog-generator-run:hover:not(:disabled) {
+ border-color: rgba(0, 255, 136, 0.5);
+ background: linear-gradient(135deg, rgba(0, 255, 136, 0.2) 0%, rgba(34, 240, 192, 0.12) 100%);
+ transform: translateY(-1px);
+ box-shadow: 0 4px 24px rgba(0, 255, 136, 0.1);
+}
+
+.dialog-generator-run:disabled {
+ opacity: 0.52;
+ cursor: not-allowed;
+}
+
@media (max-width: 980px) {
.dialog-generator-shell {
grid-template-columns: 1fr;
diff --git a/src/styles/pages/ecommerce-video.css b/src/styles/pages/ecommerce-video.css
index 715671c..72fbf81 100644
--- a/src/styles/pages/ecommerce-video.css
+++ b/src/styles/pages/ecommerce-video.css
@@ -6,9 +6,13 @@
align-content: initial;
justify-items: initial;
gap: 0;
- overflow: hidden;
+ overflow: auto;
background: #0e1014;
- scrollbar-color: #353b45 #0e1014;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+}
+.product-clone-page[data-tool="clone"] .product-clone-preview--video::-webkit-scrollbar {
+ display: none;
}
.ecom-video-workspace {
@@ -20,6 +24,11 @@
overflow: hidden;
background: #0e1014;
color: #e5ebf4;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+}
+.ecom-video-workspace::-webkit-scrollbar {
+ display: none;
}
.ecom-video-flowbar {
@@ -112,6 +121,42 @@
gap: 8px;
}
+/* ── Flowbar zoom controls ─────────────────────────── */
+.ecom-video-flowbar__zoom {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+}
+.ecom-video-flowbar__zoom button {
+ width: 26px;
+ height: 26px;
+ border: 1px solid #2c3038;
+ border-radius: 6px;
+ background: #1a1d24;
+ color: #8890a0;
+ font-size: 14px;
+ font-weight: 700;
+ cursor: pointer;
+ display: grid;
+ place-items: center;
+ transition: border-color 150ms ease, color 150ms ease;
+}
+.ecom-video-flowbar__zoom button:hover:not(:disabled) {
+ border-color: #00ff88;
+ color: #00ff88;
+}
+.ecom-video-flowbar__zoom button:disabled {
+ opacity: 0.35;
+ cursor: not-allowed;
+}
+.ecom-video-flowbar__zoom span {
+ min-width: 38px;
+ text-align: center;
+ font-size: 11px;
+ font-weight: 600;
+ color: #6a7282;
+}
+
.ecom-video-flowbar__error {
max-width: min(260px, 28vw);
overflow: hidden;
@@ -181,8 +226,13 @@
background: #101318;
padding: 32px 40px;
display: flex;
- align-items: stretch;
+ align-items: flex-start;
justify-content: center;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+}
+.ecom-video-flow-canvas::-webkit-scrollbar {
+ display: none;
}
.ecom-video-flow-map {
@@ -418,22 +468,23 @@
width: 38px;
height: 38px;
place-items: center;
- border: 1px solid #3d3020;
- border-radius: 8px;
- background: #15181f;
- color: #ffe1ad;
+ border: 1px solid #00cc6a;
+ border-radius: 9px;
+ background: #00ff88;
+ color: #06130d;
font-size: 16px;
+ font-weight: 700;
cursor: pointer;
transition:
transform 150ms ease,
- border-color 150ms ease,
- background-color 150ms ease;
+ filter 150ms ease,
+ box-shadow 150ms ease;
}
.ecom-video-flow-dock button:hover {
- border-color: #4d3a1a;
- background: #241c12;
- transform: translateY(-1px);
+ filter: brightness(1.08);
+ box-shadow: 0 2px 12px rgba(0, 255, 136, 0.25);
+ transform: translateY(-2px);
}
.ecom-video-flow-notice {
diff --git a/src/styles/pages/ecommerce.css b/src/styles/pages/ecommerce.css
index 3835832..bfeee7d 100644
--- a/src/styles/pages/ecommerce.css
+++ b/src/styles/pages/ecommerce.css
@@ -990,8 +990,8 @@
overflow-x: hidden;
overflow-y: auto;
padding: 20px 18px;
- scrollbar-color: #3a3f49 #15171c;
scrollbar-width: thin;
+ scrollbar-color: #3a3f49 #15171c;
transition:
opacity 360ms ease,
transform var(--clone-settings-motion-duration) var(--clone-settings-motion-ease);
@@ -1541,12 +1541,11 @@
.product-clone-page[data-tool="clone"] .clone-ai-replicate-panel {
display: grid;
- flex: 0 0 272px;
- grid-template-rows: auto minmax(0, 1fr);
+ flex: 0 0 auto;
+ grid-template-rows: auto auto minmax(0, 1fr);
gap: 9px;
- height: 272px;
min-height: 0;
- overflow: hidden;
+ overflow: visible;
border: 1px solid #303540;
border-radius: 14px;
background: #1c1f26;
@@ -1608,7 +1607,7 @@
.product-clone-page[data-tool="clone"] .clone-ai-replicate-upload {
position: relative;
display: grid;
- min-height: 78px;
+ min-height: 96px;
overflow: visible;
place-items: center;
align-content: center;
@@ -1617,7 +1616,7 @@
border-radius: 12px;
background: #20242c;
color: #eef2f6;
- padding: 8px;
+ padding: 16px 12px;
cursor: pointer;
transition:
border-color 160ms ease,
@@ -1625,15 +1624,52 @@
transform 160ms ease;
}
+.product-clone-page[data-tool="clone"] .clone-ai-replicate-upload.has-files {
+ min-height: 120px;
+ place-items: center;
+ align-content: center;
+ gap: 8px;
+ padding: 10px;
+}
+
.product-clone-page[data-tool="clone"] .clone-ai-replicate-upload:hover {
border-color: #00ff88;
background: #202c28;
}
+.product-clone-page[data-tool="clone"] .clone-ai-replicate-upload.is-dragging {
+ border-color: #00ff88;
+ background: #1a2e24;
+ border-style: solid;
+}
+
.product-clone-page[data-tool="clone"] .clone-ai-replicate-upload:active {
transform: scale(0.98);
}
+.product-clone-page[data-tool="clone"] .clone-ai-replicate-upload-overlay {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ border-radius: 12px;
+ background: rgba(0, 255, 136, 0.08);
+ color: #00ff88;
+ pointer-events: none;
+}
+
+.product-clone-page[data-tool="clone"] .clone-ai-replicate-upload-overlay .anticon {
+ font-size: 28px;
+}
+
+.product-clone-page[data-tool="clone"] .clone-ai-replicate-upload-overlay span {
+ font-size: 14px;
+ font-weight: 800;
+}
+
.product-clone-page[data-tool="clone"] .clone-ai-replicate-upload > span {
display: inline-grid;
grid-template-columns: auto minmax(0, max-content);
@@ -1676,75 +1712,86 @@
font-weight: 800;
}
-.product-clone-page[data-tool="clone"] .clone-ai-replicate-preview {
- position: absolute;
- inset: 6px;
+/* ── Reference image file grid (inside upload button) ── */
+.product-clone-page[data-tool="clone"] .clone-ai-replicate-files {
display: grid;
- grid-auto-flow: column;
- grid-auto-columns: minmax(0, 56px);
- align-items: center;
- justify-content: center;
+ grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
gap: 6px;
- border-radius: 10px;
- background: #20242c;
- opacity: 0;
- pointer-events: none;
- transform: scale(0.98);
- transition:
- opacity 160ms ease,
- transform 160ms ease;
+ width: 100%;
+ overflow: visible;
}
-.product-clone-page[data-tool="clone"] .clone-ai-replicate-upload:hover .clone-ai-replicate-preview,
-.product-clone-page[data-tool="clone"] .clone-ai-replicate-upload:focus-visible .clone-ai-replicate-preview {
- opacity: 1;
- pointer-events: auto;
- transform: scale(1);
-}
-
-.product-clone-page[data-tool="clone"] .clone-ai-replicate-preview figure {
+.product-clone-page[data-tool="clone"] .clone-ai-replicate-file {
position: relative;
display: block;
- width: 56px;
- height: 52px;
+ aspect-ratio: 1;
min-width: 0;
overflow: visible;
margin: 0;
- border-radius: 8px;
+ border-radius: 6px;
}
-.product-clone-page[data-tool="clone"] .clone-ai-replicate-preview figure > img {
+.product-clone-page[data-tool="clone"] .clone-ai-replicate-file > img {
display: block;
width: 100%;
height: 100%;
min-width: 0;
- overflow: hidden;
border: 1px solid #3a4555;
- border-radius: 8px;
+ border-radius: 6px;
background: #111720;
object-fit: cover;
}
-.product-clone-page[data-tool="clone"] .clone-ai-replicate-preview figure:only-child {
- width: min(170px, 100%);
- height: 52px;
+.product-clone-page[data-tool="clone"] .clone-ai-replicate-file > img:hover {
+ border-color: #00ff88;
}
-.product-clone-page[data-tool="clone"] .clone-ai-replicate-preview figure:only-child > img {
- object-fit: contain;
-}
-
-.product-clone-page[data-tool="clone"] .clone-ai-replicate-preview b {
- display: grid;
- width: 42px;
- height: 42px;
- place-items: center;
- border: 1px solid #3a4555;
- border-radius: 999px;
- background: #151b24;
- color: #eef2f6;
+.product-clone-page[data-tool="clone"] .clone-ai-replicate-add-more {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 5px;
+ justify-self: center;
+ width: fit-content;
+ max-width: calc(100% - 8px);
+ height: 28px;
+ min-width: 0;
+ border-radius: 7px;
+ background: #2b3039;
+ color: #9aa4b4;
+ padding: 0 10px;
font-size: 12px;
- font-weight: 900;
+ font-weight: 750;
+ white-space: nowrap;
+}
+
+.product-clone-page[data-tool="clone"] .clone-ai-replicate-add-more .anticon {
+ font-size: 13px;
+ color: #5d84bd;
+}
+
+/* ── Portal-based zoom preview (avoids overflow clipping) ── */
+.clone-ai-zoom-portal {
+ position: fixed;
+ z-index: 9999;
+ width: min(280px, 52vw);
+ max-height: 340px;
+ border: 1px solid #3a4555;
+ border-radius: 14px;
+ background: #101115;
+ padding: 8px;
+ box-shadow: 0 22px 48px rgba(0, 0, 0, 0.5);
+ transform: translate(-50%, calc(-100% - 12px));
+ pointer-events: none;
+}
+
+.clone-ai-zoom-portal img {
+ display: block;
+ width: 100%;
+ height: auto;
+ max-height: 324px;
+ border-radius: 8px;
+ object-fit: contain;
}
.product-clone-page[data-tool="clone"] .clone-ai-replicate-link input {
@@ -2745,12 +2792,18 @@
position: relative;
display: grid;
min-height: 0;
- overflow: hidden;
- align-content: center;
+ overflow-y: auto;
+ overflow-x: hidden;
+ align-content: safe center;
justify-items: center;
gap: 22px;
background: #101115;
padding: 92px 46px 142px;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+}
+.product-clone-page[data-tool="clone"] .clone-ai-preview::-webkit-scrollbar {
+ display: none;
}
.product-clone-page[data-tool="clone"] .clone-ai-preview-header {
@@ -2781,6 +2834,50 @@
color: #00ff88;
}
+/* ── Preview zoom controls ─────────────────────────── */
+.product-clone-page[data-tool="clone"] .clone-ai-preview-zoom {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ margin-top: 8px;
+}
+.product-clone-page[data-tool="clone"] .clone-ai-preview-zoom button {
+ width: 28px;
+ height: 28px;
+ border: 1px solid #2c3038;
+ border-radius: 6px;
+ background: #1b1d23;
+ color: #a0a8b8;
+ font-size: 16px;
+ font-weight: 700;
+ cursor: pointer;
+ display: grid;
+ place-items: center;
+ transition: border-color 150ms ease, color 150ms ease;
+}
+.product-clone-page[data-tool="clone"] .clone-ai-preview-zoom button:hover:not(:disabled) {
+ border-color: #00ff88;
+ color: #00ff88;
+}
+.product-clone-page[data-tool="clone"] .clone-ai-preview-zoom button:disabled {
+ opacity: 0.35;
+ cursor: not-allowed;
+}
+.product-clone-page[data-tool="clone"] .clone-ai-preview-zoom span {
+ min-width: 42px;
+ text-align: center;
+ font-size: 12px;
+ font-weight: 600;
+ color: #758096;
+}
+.product-clone-page[data-tool="clone"] .clone-ai-preview-zoom-wrap {
+ flex-shrink: 0;
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ min-width: max-content;
+}
+
.product-clone-page[data-tool="clone"] .clone-ai-empty-state {
display: grid;
width: min(100%, 600px);
@@ -2888,7 +2985,8 @@
}
.product-clone-page[data-tool="clone"] .clone-ai-main-result {
- height: 440px;
+ width: 100%;
+ height: auto;
}
.product-clone-page[data-tool="clone"] .clone-ai-result-grid {
@@ -2898,12 +2996,12 @@
}
.product-clone-page[data-tool="clone"] .clone-ai-result-grid button {
- height: 210px;
+ width: 100%;
+ height: auto;
}
.product-clone-page[data-tool="clone"] .clone-ai-result-grid button:first-child {
grid-column: 1 / -1;
- height: 240px;
}
.product-clone-page[data-tool="clone"] .clone-ai-main-result span,
@@ -3510,8 +3608,8 @@
.product-set-thumb:focus-within .uploaded-image-zoom,
.product-clone-uploaded-thumb:hover .uploaded-image-zoom,
.product-clone-uploaded-thumb:focus-within .uploaded-image-zoom,
-.clone-ai-replicate-preview figure:hover .uploaded-image-zoom,
-.clone-ai-replicate-preview figure:focus-within .uploaded-image-zoom {
+.clone-ai-replicate-file:hover .uploaded-image-zoom,
+.clone-ai-replicate-file:focus-within .uploaded-image-zoom {
opacity: 1;
transform: translate(-50%, 0) scale(1);
visibility: visible;
@@ -9458,3 +9556,4 @@
min-height: calc(100% - 59px);
}
}
+
diff --git a/src/styles/pages/image-workbench.css b/src/styles/pages/image-workbench.css
index 20948ea..0b854ab 100644
--- a/src/styles/pages/image-workbench.css
+++ b/src/styles/pages/image-workbench.css
@@ -216,14 +216,14 @@
.image-workbench-layout {
display: grid;
- grid-template-columns: 280px 1fr 220px;
+ grid-template-columns: 280px 1fr;
flex: 1;
min-height: 0;
overflow: hidden;
}
.image-workbench-layout--inpaint {
- grid-template-columns: 260px 1fr 240px;
+ grid-template-columns: 260px 1fr;
}
.image-workbench-layout--camera {
@@ -278,6 +278,27 @@
position: relative;
}
+.image-workbench-upload-shell.is-dragging {
+ border-radius: var(--radius-sm);
+ outline: 2px dashed var(--accent);
+ outline-offset: -2px;
+}
+
+.image-workbench-upload-drop-overlay {
+ position: absolute;
+ inset: 0;
+ z-index: 10;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: var(--radius-sm);
+ background: rgba(var(--accent-rgb), 0.08);
+ color: var(--accent);
+ font-size: 15px;
+ font-weight: 800;
+ pointer-events: none;
+}
+
.image-workbench-upload {
display: flex;
flex-direction: column;
diff --git a/src/styles/pages/legacy-pages.css b/src/styles/pages/legacy-pages.css
index 3a7585d..07c635e 100644
--- a/src/styles/pages/legacy-pages.css
+++ b/src/styles/pages/legacy-pages.css
@@ -14618,6 +14618,55 @@
background: #ddf5e2;
}
+.agent-tool-pill.is-open {
+ background: #ddf5e2;
+ box-shadow: 1px 1px 0 #111;
+}
+
+.agent-tool-pills {
+ position: relative;
+}
+
+.agent-dropdown {
+ position: absolute;
+ top: calc(100% + 4px);
+ left: 0;
+ z-index: 120;
+ min-width: 160px;
+ background: #fff;
+ border: 3px solid #111;
+ box-shadow: 3px 3px 0 #111;
+ border-radius: 0;
+ overflow: hidden;
+}
+
+.agent-dropdown__item {
+ display: block;
+ width: 100%;
+ padding: 8px 14px;
+ border: none;
+ border-bottom: 1px solid #ddd;
+ background: #fff;
+ color: #111;
+ font-size: 13px;
+ text-align: left;
+ cursor: pointer;
+ transition: background 0.1s;
+}
+
+.agent-dropdown__item:last-child {
+ border-bottom: none;
+}
+
+.agent-dropdown__item:hover {
+ background: #ddf5e2;
+}
+
+.agent-dropdown__item.is-active {
+ background: #c8f0d6;
+ font-weight: 700;
+}
+
.agent-run-button {
display: flex;
align-items: center;
diff --git a/src/styles/pages/script-tokens-v5.css b/src/styles/pages/script-tokens-v5.css
index 4318d88..602325d 100644
--- a/src/styles/pages/script-tokens-v5.css
+++ b/src/styles/pages/script-tokens-v5.css
@@ -142,6 +142,7 @@
/* Upload zone */
.script-eval-v5-upload-zone {
+ position: relative;
border: 2px dashed var(--v5-border2);
border-radius: 12px;
padding: 22px 18px;
@@ -155,6 +156,37 @@
background: var(--v5-green-deep);
}
+.script-eval-v5-upload-zone.is-dragging {
+ border-color: var(--v5-green);
+ border-style: solid;
+ background: var(--v5-green-deep);
+}
+
+.script-eval-v5-upload-drop-overlay {
+ position: absolute;
+ inset: 0;
+ z-index: 10;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ border-radius: 12px;
+ background: rgba(0, 255, 136, 0.06);
+ color: var(--v5-green);
+ pointer-events: none;
+}
+
+.script-eval-v5-upload-drop-overlay .anticon {
+ font-size: 40px;
+ opacity: 0.8;
+}
+
+.script-eval-v5-upload-drop-overlay span {
+ font-size: 16px;
+ font-weight: 800;
+}
+
.script-eval-v5-upload-icon {
margin-bottom: 10px;
font-size: 38px;
@@ -195,10 +227,11 @@
}
.script-eval-v5-upload-done {
+ position: relative;
display: none;
align-items: center;
gap: 10px;
- padding: 12px 14px;
+ padding: 12px 28px 12px 14px;
border-radius: 8px;
background: var(--v5-green-deep);
border: 1px solid var(--v5-green-border);
@@ -208,6 +241,30 @@
display: flex;
}
+.script-eval-v5-upload-delete {
+ position: absolute;
+ top: 4px;
+ right: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 18px;
+ height: 18px;
+ border: 0;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.12);
+ color: var(--v5-text3);
+ cursor: pointer;
+ font-size: 10px;
+ line-height: 1;
+ transition: background 0.15s, color 0.15s;
+}
+
+.script-eval-v5-upload-delete:hover {
+ background: rgba(255, 77, 103, 0.5);
+ color: #fff;
+}
+
.script-eval-v5-upload-done .anticon {
font-size: 16px;
color: var(--v5-green);
@@ -218,7 +275,7 @@
font-size: 13px;
color: var(--v5-green);
font-weight: 600;
- flex: 1;
+ max-width: 16ch;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -2805,7 +2862,7 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
- max-width: 8em;
+ max-width: 16em;
}
.script-eval-v5-uf-size {
diff --git a/src/styles/pages/studio-layout.css b/src/styles/pages/studio-layout.css
index 0bfdbb2..8ca62a1 100644
--- a/src/styles/pages/studio-layout.css
+++ b/src/styles/pages/studio-layout.css
@@ -307,9 +307,10 @@
width: 56px;
height: 56px;
border-radius: var(--radius-sm);
- background: rgba(var(--accent-rgb), 0.13);
+ background: rgba(var(--accent-rgb), 0.22);
color: var(--accent);
font-size: 26px;
+ box-shadow: 0 0 20px rgba(var(--accent-rgb), 0.08);
}
.studio-canvas-ghost__title {
diff --git a/src/utils/enterpriseVideoPolicy.ts b/src/utils/enterpriseVideoPolicy.ts
index ee73655..2240ea2 100644
--- a/src/utils/enterpriseVideoPolicy.ts
+++ b/src/utils/enterpriseVideoPolicy.ts
@@ -8,27 +8,27 @@ export const ENTERPRISE_WANXIANG_I2V_MODEL = "wan2.7-i2v";
export const ENTERPRISE_VIDEO_MODEL_OPTIONS = [
{
value: HAPPY_HORSE_UI_MODEL,
- label: "HappyHorse 1.0 · 0.72 积分/秒起",
+ label: "HappyHorse 1.0",
description: "自动匹配文生视频、首帧图生视频或参考图生视频",
},
{
value: VIDU_UI_MODEL,
- label: "Vidu Q3 Turbo · 0.40 积分/秒起",
+ label: "Vidu Q3 Turbo",
description: "自动匹配文生视频或图生视频,支持16秒",
},
{
value: PIXVERSE_UI_MODEL,
- label: "PixVerse V6 · 0.40 积分/秒起",
+ label: "PixVerse V6",
description: "自动匹配文生视频或图生视频,擅长动作特效",
},
{
value: ENTERPRISE_WANXIANG_I2V_MODEL,
- label: "万相 图生视频 · 0.60 积分/秒起",
+ label: "万相 图生视频",
description: "图生视频模型,支持首帧图驱动",
},
{
value: ENTERPRISE_KLING_MODEL,
- label: "Kling V3 Omni · 0.60 积分/秒起",
+ label: "Kling V3 Omni",
description: "支持文生视频、图生视频及多模态参考生成",
},
] as const;