Fix: restore video generation UI features
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m1s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m1s
- Remove 图片生成 option — only 视频生成 available - Remove Seedance 2.0 Fast — fixed to Seedance 2.0 - Implement @ mention system with contentEditable editor: - Click @ or type @ to show reference popup - Select media to insert dimmed mention tag - Hover over mention shows media preview tooltip - Videos auto-play on hover
This commit is contained in:
parent
8ef3d17553
commit
7b804df30f
@ -4,11 +4,10 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.textarea {
|
.editor {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
resize: none;
|
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
@ -17,8 +16,136 @@
|
|||||||
max-height: 144px;
|
max-height: 144px;
|
||||||
font-family: 'Noto Sans SC', system-ui, sans-serif;
|
font-family: 'Noto Sans SC', system-ui, sans-serif;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.textarea::placeholder {
|
.placeholder {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
left: 0;
|
||||||
color: #5a5a6a;
|
color: #5a5a6a;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
pointer-events: none;
|
||||||
|
font-family: 'Noto Sans SC', system-ui, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @ mention tag (inserted as contentEditable=false span) */
|
||||||
|
.mention {
|
||||||
|
display: inline;
|
||||||
|
padding: 1px 6px;
|
||||||
|
margin: 0 2px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(0, 184, 230, 0.12);
|
||||||
|
color: rgba(0, 184, 230, 0.7);
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: default;
|
||||||
|
user-select: none;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mention:hover {
|
||||||
|
background: rgba(0, 184, 230, 0.22);
|
||||||
|
color: rgba(0, 184, 230, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mention popup */
|
||||||
|
.mentionPopup {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 100;
|
||||||
|
background: #1e1e2e;
|
||||||
|
border: 1px solid #2a2a3a;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 6px;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 280px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
||||||
|
animation: fadeIn 0.12s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(-4px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.mentionHeader {
|
||||||
|
padding: 4px 8px 6px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #5a5a6a;
|
||||||
|
border-bottom: 1px solid #2a2a3a;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mentionItem {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.1s;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mentionItem:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mentionThumb {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: #2a2a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbMedia {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mentionLabel {
|
||||||
|
flex: 1;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mentionType {
|
||||||
|
color: #5a5a6a;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover preview tooltip */
|
||||||
|
.previewTooltip {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 200;
|
||||||
|
transform: translate(-50%, -100%);
|
||||||
|
background: #1e1e2e;
|
||||||
|
border: 1px solid #2a2a3a;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 6px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
|
||||||
|
pointer-events: none;
|
||||||
|
animation: fadeIn 0.1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.previewMedia {
|
||||||
|
display: block;
|
||||||
|
width: 160px;
|
||||||
|
height: 100px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.previewLabel {
|
||||||
|
text-align: center;
|
||||||
|
color: #8a8a9a;
|
||||||
|
font-size: 11px;
|
||||||
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,74 +1,280 @@
|
|||||||
import { useRef, useEffect, useCallback } from 'react';
|
import { useRef, useEffect, useCallback, useState } from 'react';
|
||||||
import { useInputBarStore } from '../store/inputBar';
|
import { useInputBarStore } from '../store/inputBar';
|
||||||
|
import type { UploadedFile } from '../types';
|
||||||
import styles from './PromptInput.module.css';
|
import styles from './PromptInput.module.css';
|
||||||
|
|
||||||
const placeholders: Record<string, string> = {
|
const placeholders: Record<string, string> = {
|
||||||
universal: '上传1-5张参考图或视频,输入文字,自由组合图、文、音、视频多元素,定义精彩互动。',
|
universal: '上传参考图或视频,输入文字,通过 @ 引用素材',
|
||||||
keyframe: '输入描述,定义首帧到尾帧的运动过程',
|
keyframe: '输入描述,定义首帧到尾帧的运动过程',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function PromptInput() {
|
export function PromptInput() {
|
||||||
const prompt = useInputBarStore((s) => s.prompt);
|
const prompt = useInputBarStore((s) => s.prompt);
|
||||||
const setPrompt = useInputBarStore((s) => s.setPrompt);
|
const setPrompt = useInputBarStore((s) => s.setPrompt);
|
||||||
|
const editorHtml = useInputBarStore((s) => s.editorHtml);
|
||||||
|
const setEditorHtml = useInputBarStore((s) => s.setEditorHtml);
|
||||||
const mode = useInputBarStore((s) => s.mode);
|
const mode = useInputBarStore((s) => s.mode);
|
||||||
|
const references = useInputBarStore((s) => s.references);
|
||||||
const insertAtTrigger = useInputBarStore((s) => s.insertAtTrigger);
|
const insertAtTrigger = useInputBarStore((s) => s.insertAtTrigger);
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
||||||
|
const editorRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [showMentionPopup, setShowMentionPopup] = useState(false);
|
||||||
|
const [mentionPos, setMentionPos] = useState({ top: 0, left: 0 });
|
||||||
|
const [hoverRef, setHoverRef] = useState<UploadedFile | null>(null);
|
||||||
|
const [hoverPos, setHoverPos] = useState({ top: 0, left: 0 });
|
||||||
|
|
||||||
// Auto-focus
|
// Auto-focus
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
textareaRef.current?.focus();
|
editorRef.current?.focus();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Auto-resize textarea
|
// Sync editor when editorHtml resets (e.g. after submit)
|
||||||
const autoResize = useCallback(() => {
|
|
||||||
const el = textareaRef.current;
|
|
||||||
if (!el) return;
|
|
||||||
el.style.height = 'auto';
|
|
||||||
el.style.height = Math.min(el.scrollHeight, 144) + 'px';
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Sync textarea value when prompt changes externally (submit clear, reEdit restore)
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const el = textareaRef.current;
|
const el = editorRef.current;
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
if (el.value !== prompt) {
|
if (editorHtml === '' && el.innerHTML !== '') {
|
||||||
el.value = prompt;
|
el.innerHTML = '';
|
||||||
autoResize();
|
|
||||||
}
|
}
|
||||||
}, [prompt, autoResize]);
|
}, [editorHtml]);
|
||||||
|
|
||||||
// Handle @ button from toolbar
|
// Handle @ button from toolbar
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (insertAtTrigger === 0) return;
|
if (insertAtTrigger === 0) return;
|
||||||
const el = textareaRef.current;
|
if (references.length === 0) return;
|
||||||
if (!el) return;
|
openMentionPopup();
|
||||||
const start = el.selectionStart;
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
const end = el.selectionEnd;
|
}, [insertAtTrigger]);
|
||||||
const text = el.value;
|
|
||||||
const newValue = text.substring(0, start) + '@' + text.substring(end);
|
|
||||||
setPrompt(newValue);
|
|
||||||
// Restore cursor position after the inserted @
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
el.selectionStart = el.selectionEnd = start + 1;
|
|
||||||
el.focus();
|
|
||||||
});
|
|
||||||
}, [insertAtTrigger, setPrompt]);
|
|
||||||
|
|
||||||
const handleChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
const openMentionPopup = useCallback(() => {
|
||||||
setPrompt(e.target.value);
|
const el = editorRef.current;
|
||||||
autoResize();
|
if (!el) return;
|
||||||
}, [setPrompt, autoResize]);
|
el.focus();
|
||||||
|
|
||||||
|
const sel = window.getSelection();
|
||||||
|
if (!sel || sel.rangeCount === 0) return;
|
||||||
|
|
||||||
|
const range = sel.getRangeAt(0);
|
||||||
|
const rect = range.getBoundingClientRect();
|
||||||
|
const editorRect = el.getBoundingClientRect();
|
||||||
|
|
||||||
|
setMentionPos({
|
||||||
|
top: rect.bottom - editorRect.top + 4,
|
||||||
|
left: Math.max(0, rect.left - editorRect.left),
|
||||||
|
});
|
||||||
|
setShowMentionPopup(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const extractText = useCallback(() => {
|
||||||
|
const el = editorRef.current;
|
||||||
|
if (!el) return;
|
||||||
|
setPrompt(el.textContent || '');
|
||||||
|
setEditorHtml(el.innerHTML);
|
||||||
|
}, [setPrompt, setEditorHtml]);
|
||||||
|
|
||||||
|
const handleInput = useCallback(() => {
|
||||||
|
extractText();
|
||||||
|
|
||||||
|
// Detect if user just typed @
|
||||||
|
const sel = window.getSelection();
|
||||||
|
if (!sel || sel.rangeCount === 0) return;
|
||||||
|
const range = sel.getRangeAt(0);
|
||||||
|
const node = range.startContainer;
|
||||||
|
if (node.nodeType !== Node.TEXT_NODE) return;
|
||||||
|
|
||||||
|
const text = node.textContent || '';
|
||||||
|
const offset = range.startOffset;
|
||||||
|
if (offset > 0 && text[offset - 1] === '@' && references.length > 0) {
|
||||||
|
// Remove the typed @
|
||||||
|
const before = text.substring(0, offset - 1);
|
||||||
|
const after = text.substring(offset);
|
||||||
|
node.textContent = before + after;
|
||||||
|
|
||||||
|
// Restore cursor
|
||||||
|
const newRange = document.createRange();
|
||||||
|
newRange.setStart(node, before.length);
|
||||||
|
newRange.collapse(true);
|
||||||
|
sel.removeAllRanges();
|
||||||
|
sel.addRange(newRange);
|
||||||
|
|
||||||
|
openMentionPopup();
|
||||||
|
}
|
||||||
|
}, [extractText, references.length, openMentionPopup]);
|
||||||
|
|
||||||
|
const insertMention = useCallback((ref: UploadedFile) => {
|
||||||
|
setShowMentionPopup(false);
|
||||||
|
const el = editorRef.current;
|
||||||
|
if (!el) return;
|
||||||
|
|
||||||
|
el.focus();
|
||||||
|
const sel = window.getSelection();
|
||||||
|
if (!sel || sel.rangeCount === 0) return;
|
||||||
|
|
||||||
|
const range = sel.getRangeAt(0);
|
||||||
|
range.deleteContents();
|
||||||
|
|
||||||
|
// Create mention span
|
||||||
|
const mention = document.createElement('span');
|
||||||
|
mention.className = styles.mention;
|
||||||
|
mention.contentEditable = 'false';
|
||||||
|
mention.dataset.refId = ref.id;
|
||||||
|
mention.dataset.refType = ref.type;
|
||||||
|
mention.textContent = `@${ref.label}`;
|
||||||
|
|
||||||
|
// Insert mention + trailing space
|
||||||
|
range.insertNode(mention);
|
||||||
|
|
||||||
|
const space = document.createTextNode('\u00A0');
|
||||||
|
mention.after(space);
|
||||||
|
|
||||||
|
// Move cursor after space
|
||||||
|
const newRange = document.createRange();
|
||||||
|
newRange.setStartAfter(space);
|
||||||
|
newRange.collapse(true);
|
||||||
|
sel.removeAllRanges();
|
||||||
|
sel.addRange(newRange);
|
||||||
|
|
||||||
|
extractText();
|
||||||
|
}, [extractText]);
|
||||||
|
|
||||||
|
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
||||||
|
if (showMentionPopup && e.key === 'Escape') {
|
||||||
|
e.preventDefault();
|
||||||
|
setShowMentionPopup(false);
|
||||||
|
}
|
||||||
|
}, [showMentionPopup]);
|
||||||
|
|
||||||
|
const handlePaste = useCallback((e: React.ClipboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const text = e.clipboardData.getData('text/plain');
|
||||||
|
document.execCommand('insertText', false, text);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Mention hover — delegated event
|
||||||
|
const handleMouseOver = useCallback((e: React.MouseEvent) => {
|
||||||
|
const target = (e.target as HTMLElement).closest('[data-ref-id]') as HTMLElement | null;
|
||||||
|
if (!target) return;
|
||||||
|
const refId = target.dataset.refId;
|
||||||
|
const ref = references.find((r) => r.id === refId);
|
||||||
|
if (!ref) return;
|
||||||
|
|
||||||
|
const rect = target.getBoundingClientRect();
|
||||||
|
const wrapperRect = editorRef.current!.parentElement!.getBoundingClientRect();
|
||||||
|
setHoverRef(ref);
|
||||||
|
setHoverPos({
|
||||||
|
top: rect.top - wrapperRect.top - 8,
|
||||||
|
left: rect.left - wrapperRect.left + rect.width / 2,
|
||||||
|
});
|
||||||
|
}, [references]);
|
||||||
|
|
||||||
|
const handleMouseOut = useCallback((e: React.MouseEvent) => {
|
||||||
|
const target = (e.target as HTMLElement).closest('[data-ref-id]');
|
||||||
|
if (target) setHoverRef(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Close mention popup on click outside
|
||||||
|
useEffect(() => {
|
||||||
|
if (!showMentionPopup) return;
|
||||||
|
const handler = (e: MouseEvent) => {
|
||||||
|
const popup = document.querySelector(`.${styles.mentionPopup}`);
|
||||||
|
if (popup && !popup.contains(e.target as Node)) {
|
||||||
|
setShowMentionPopup(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('mousedown', handler);
|
||||||
|
return () => document.removeEventListener('mousedown', handler);
|
||||||
|
}, [showMentionPopup]);
|
||||||
|
|
||||||
|
// Compute available (un-mentioned) references
|
||||||
|
const getMentionedIds = () => {
|
||||||
|
const ids = new Set<string>();
|
||||||
|
const el = editorRef.current;
|
||||||
|
if (el) {
|
||||||
|
el.querySelectorAll('[data-ref-id]').forEach((span) => {
|
||||||
|
const id = (span as HTMLElement).dataset.refId;
|
||||||
|
if (id) ids.add(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
};
|
||||||
|
const mentionedIds = getMentionedIds();
|
||||||
|
const availableRefs = references.filter((r) => !mentionedIds.has(r.id));
|
||||||
|
|
||||||
|
const isEmpty = !prompt.trim() && !editorHtml;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
<textarea
|
{isEmpty && (
|
||||||
ref={textareaRef}
|
<div className={styles.placeholder}>{placeholders[mode]}</div>
|
||||||
className={styles.textarea}
|
)}
|
||||||
rows={1}
|
<div
|
||||||
placeholder={placeholders[mode]}
|
ref={editorRef}
|
||||||
value={prompt}
|
className={styles.editor}
|
||||||
onChange={handleChange}
|
contentEditable
|
||||||
|
suppressContentEditableWarning
|
||||||
|
onInput={handleInput}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onPaste={handlePaste}
|
||||||
|
onMouseOver={handleMouseOver}
|
||||||
|
onMouseOut={handleMouseOut}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Mention popup */}
|
||||||
|
{showMentionPopup && availableRefs.length > 0 && (
|
||||||
|
<div
|
||||||
|
className={styles.mentionPopup}
|
||||||
|
style={{ top: mentionPos.top, left: mentionPos.left }}
|
||||||
|
>
|
||||||
|
<div className={styles.mentionHeader}>选择引用素材</div>
|
||||||
|
{availableRefs.map((ref) => (
|
||||||
|
<button
|
||||||
|
key={ref.id}
|
||||||
|
className={styles.mentionItem}
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
insertMention(ref);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={styles.mentionThumb}>
|
||||||
|
{ref.type === 'video' ? (
|
||||||
|
<video src={ref.previewUrl} muted className={styles.thumbMedia} />
|
||||||
|
) : (
|
||||||
|
<img src={ref.previewUrl} alt="" className={styles.thumbMedia} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span className={styles.mentionLabel}>{ref.label}</span>
|
||||||
|
<span className={styles.mentionType}>
|
||||||
|
{ref.type === 'video' ? '视频' : '图片'}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Hover preview tooltip */}
|
||||||
|
{hoverRef && (
|
||||||
|
<div
|
||||||
|
className={styles.previewTooltip}
|
||||||
|
style={{ top: hoverPos.top, left: hoverPos.left }}
|
||||||
|
>
|
||||||
|
{hoverRef.type === 'video' ? (
|
||||||
|
<video
|
||||||
|
src={hoverRef.previewUrl}
|
||||||
|
autoPlay
|
||||||
|
loop
|
||||||
|
muted
|
||||||
|
playsInline
|
||||||
|
className={styles.previewMedia}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<img
|
||||||
|
src={hoverRef.previewUrl}
|
||||||
|
alt={hoverRef.label}
|
||||||
|
className={styles.previewMedia}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className={styles.previewLabel}>{hoverRef.label}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,12 +67,10 @@ const ChevronDown = () => (
|
|||||||
|
|
||||||
const generationTypeItems = [
|
const generationTypeItems = [
|
||||||
{ label: '视频生成', value: 'video' as GenerationType, icon: <VideoIcon /> },
|
{ label: '视频生成', value: 'video' as GenerationType, icon: <VideoIcon /> },
|
||||||
{ label: '图片生成', value: 'image' as GenerationType, icon: <ImageIcon /> },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const modelItems = [
|
const modelItems = [
|
||||||
{ label: 'Seedance 2.0', value: 'seedance_2.0' as ModelOption, icon: <DiamondIcon /> },
|
{ label: 'Seedance 2.0', value: 'seedance_2.0' as ModelOption, icon: <DiamondIcon /> },
|
||||||
{ label: 'Seedance 2.0 Fast', value: 'seedance_2.0_fast' as ModelOption, icon: <LightningIcon /> },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const modeItems = [
|
const modeItems = [
|
||||||
@ -140,38 +138,17 @@ export function Toolbar() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.toolbar}>
|
<div className={styles.toolbar}>
|
||||||
{/* Generation type dropdown */}
|
{/* Generation type — fixed to video */}
|
||||||
<Dropdown
|
<button className={`${styles.btn} ${styles.primary}`}>
|
||||||
items={generationTypeItems}
|
<VideoIcon />
|
||||||
value={generationType}
|
<span className={styles.label}>视频生成</span>
|
||||||
onSelect={(v) => setGenerationType(v as GenerationType)}
|
</button>
|
||||||
minWidth={150}
|
|
||||||
trigger={
|
|
||||||
<button className={`${styles.btn} ${styles.primary}`}>
|
|
||||||
<VideoIcon />
|
|
||||||
<span className={styles.label}>
|
|
||||||
{generationType === 'video' ? '视频生成' : '图片生成'}
|
|
||||||
</span>
|
|
||||||
<ChevronDown />
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Model selector dropdown */}
|
{/* Model — fixed to Seedance 2.0 */}
|
||||||
<Dropdown
|
<button className={styles.btn}>
|
||||||
items={modelItems}
|
<DiamondIcon />
|
||||||
value={model}
|
<span className={styles.label}>Seedance 2.0</span>
|
||||||
onSelect={(v) => setModel(v as ModelOption)}
|
</button>
|
||||||
minWidth={190}
|
|
||||||
trigger={
|
|
||||||
<button className={styles.btn}>
|
|
||||||
<DiamondIcon />
|
|
||||||
<span className={styles.label}>
|
|
||||||
{model === 'seedance_2.0' ? 'Seedance 2.0' : 'Seedance 2.0 Fast'}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Mode selector */}
|
{/* Mode selector */}
|
||||||
<Dropdown
|
<Dropdown
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user