Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Y
yaoai-video
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
姚珂
yaoai-video
Commits
27725d8e
Commit
27725d8e
authored
Apr 26, 2026
by
yaoke.yk
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
分镜页面整体布局不变,细节优化
parent
b7185a90
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
107 additions
and
100 deletions
+107
-100
StoryboardWorkspace.tsx
doc/html/src/app/pages/StoryboardWorkspace.tsx
+107
-100
No files found.
doc/html/src/app/pages/StoryboardWorkspace.tsx
View file @
27725d8e
...
...
@@ -39,6 +39,9 @@ const DEFAULT_VIDEO_MODEL = VIDEO_MODEL_OPTIONS[0].value;
const
MAX_CHARACTERS
=
5
;
const
MAX_PROPS
=
5
;
const
PROMPT_PREVIEW_LIMIT
=
80
;
const
FOCUS_RING
=
"focus:outline-none focus:ring-2 focus:ring-[#1c64ff]/20 focus:border-[#1c64ff]/45"
;
const
CONTROL_SURFACE
=
"border border-slate-200/80 bg-white shadow-[0_1px_2px_rgba(15,23,42,0.04)]"
;
const
SOFT_PANEL
=
"border border-slate-200/80 bg-white/95 shadow-[0_8px_24px_rgba(15,23,42,0.05)]"
;
const
FREEFORM_PLACEHOLDER
=
`【镜头1】 时长2s | 蒙太奇快切 | 固定机位快速切出
画面定格在角色刚转头的瞬间,背景虚化为暖色光斑
...
...
@@ -281,11 +284,11 @@ export function StoryboardWorkspace() {
const
promptPreviewText
=
freeformPrompt
.
trim
();
return
(
<
div
className=
"h-full flex overflow-hidden bg-
back
ground"
>
<
div
className=
"h-full flex overflow-hidden bg-
[#f4f7fb] text-fore
ground"
>
{
/* ─── Far Left: Episode list strip ─── */
}
<
div
className=
"flex-shrink-0 border-r border-
border bg-background flex flex-col items-center overflow-hidden py-5
"
style=
{
{
width
:
80
}
}
>
<
span
className=
"font-semibold mb-5"
style=
{
{
color
:
"#
9ba0b1
"
,
fontSize
:
16
,
letterSpacing
:
1
}
}
>
<
div
className=
"flex-shrink-0 border-r border-
slate-200/80 bg-white/90 flex flex-col items-center overflow-hidden py-5 shadow-[1px_0_0_rgba(15,23,42,0.02)]
"
style=
{
{
width
:
80
}
}
>
<
span
className=
"font-semibold mb-5"
style=
{
{
color
:
"#
7c8598
"
,
fontSize
:
16
,
letterSpacing
:
1
}
}
>
集数
</
span
>
<
div
...
...
@@ -308,18 +311,18 @@ export function StoryboardWorkspace() {
key=
{
ep
.
id
}
onClick=
{
()
=>
handleEpisodeSelect
(
ep
)
}
title=
{
ep
.
title
?
`第${ep.episodeNumber}集 · ${ep.title}`
:
`第${ep.episodeNumber}集`
}
className=
{
`flex items-center justify-center font-bold tabular-nums transition-all flex-shrink-0 ${
active ? "" : "hover:bg-[#
eceef4]
"
className=
{
`flex items-center justify-center font-bold tabular-nums transition-all
duration-200
flex-shrink-0 ${
active ? "" : "hover:bg-[#
f0f4fb] hover:text-[#475569] hover:shadow-sm
"
}`
}
style=
{
{
width
:
50
,
height
:
56
,
borderRadius
:
16
,
fontSize
:
22
,
backgroundColor
:
active
?
"#1c64ff"
:
"#f
6f7fb
"
,
color
:
active
?
"#ffffff"
:
"#
a0a5b5
"
,
boxShadow
:
active
?
"0
6px 14px rgba(28, 100, 255, 0.35)"
:
"none
"
,
border
:
"none
"
,
backgroundColor
:
active
?
"#1c64ff"
:
"#f
fffff
"
,
color
:
active
?
"#ffffff"
:
"#
8b95a7
"
,
boxShadow
:
active
?
"0
12px 24px rgba(28, 100, 255, 0.28)"
:
"0 1px 2px rgba(15, 23, 42, 0.05)
"
,
border
:
active
?
"1px solid rgba(28, 100, 255, 0.35)"
:
"1px solid rgba(148, 163, 184, 0.22)
"
,
}
}
>
{
ep
.
episodeNumber
}
...
...
@@ -331,16 +334,16 @@ export function StoryboardWorkspace() {
</
div
>
{
/* ─── Column 2: Storyboard accordion list (top: prompt summary, then cards) ─── */
}
<
div
className=
"flex flex-col flex-shrink-0 border-r border-
border overflow-hidden bg-background
"
style=
{
{
width
:
380
}
}
>
<
div
className=
"flex flex-col flex-shrink-0 border-r border-
slate-200/80 overflow-hidden bg-white/95
"
style=
{
{
width
:
380
}
}
>
{
/* Top: 分镜表 / 备注描述 */
}
<
div
className=
"px-4 py-3 border-b border-
border bg-[#fafbfd] flex-shrink-0 relative
"
>
<
span
className=
"absolute left-0 top-3 bottom-3 w-[
2px] bg-[#1c64ff]/40
rounded-r-full"
/>
<
div
className=
"px-4 py-3 border-b border-
slate-200/80 bg-white/95 flex-shrink-0 relative shadow-[0_1px_0_rgba(15,23,42,0.03)]
"
>
<
span
className=
"absolute left-0 top-3 bottom-3 w-[
3px] bg-[#1c64ff]/55
rounded-r-full"
/>
<
div
className=
"flex items-center justify-between mb-1.5"
>
<
span
className=
"text-[11px] font-semibold tracking-[0.12em] uppercase text-muted-foreground"
>
分镜备注
</
span
>
{
promptPreviewText
&&
(
<
button
onClick=
{
()
=>
setShowPromptDetails
(
true
)
}
className=
"text-[10px] text-[#1c64ff] hover:
underline flex items-center gap-0.5 font-medium
"
className=
"text-[10px] text-[#1c64ff] hover:
text-[#164fd0] flex items-center gap-0.5 font-semibold transition-colors
"
title=
"查看完整内容"
>
<
Info
className=
"w-3 h-3"
/>
...
...
@@ -358,11 +361,11 @@ export function StoryboardWorkspace() {
</
div
>
{
/* Action row: new + AI generate */
}
<
div
className=
"h-12 px-3 border-b border-
border flex items-center gap-2 flex-shrink-0 bg-background
"
>
<
div
className=
"h-12 px-3 border-b border-
slate-200/80 flex items-center gap-2 flex-shrink-0 bg-white/95
"
>
<
button
onClick=
{
handleCreate
}
disabled=
{
createSb
.
isPending
||
!
eid
}
className=
"flex-1 flex items-center justify-center gap-1.5 py-1.5 rounded-lg bg-[#1c64ff] text-white text-xs font-semibold tracking-wide
transition-all duration-150 hover:shadow-[0_4px_12px_rgba(28,100,255,0.3
)] hover:-translate-y-px disabled:opacity-50 disabled:hover:translate-y-0 disabled:hover:shadow-none"
className=
"flex-1 flex items-center justify-center gap-1.5 py-1.5 rounded-lg bg-[#1c64ff] text-white text-xs font-semibold tracking-wide
shadow-[0_6px_14px_rgba(28,100,255,0.22)] transition-all duration-150 hover:bg-[#1857e8] hover:shadow-[0_8px_18px_rgba(28,100,255,0.28
)] hover:-translate-y-px disabled:opacity-50 disabled:hover:translate-y-0 disabled:hover:shadow-none"
>
{
createSb
.
isPending
?
<
Loader2
className=
"w-3 h-3 animate-spin"
/>
:
<
Plus
className=
"w-3 h-3"
/>
}
新建分镜
...
...
@@ -371,14 +374,14 @@ export function StoryboardWorkspace() {
onClick=
{
handleGenerateAll
}
disabled=
{
generateSbs
.
isPending
||
!
eid
}
title=
"AI批量生成分镜"
className=
"p-2 rounded-lg border border-border bg-background text-muted-foreground hover:bg-[#f6f7fb] hover:border-[#1c64ff]/30 hover:text-[#1c64ff] transition-colors disabled:opacity-50"
className=
{
`p-2 rounded-lg text-muted-foreground hover:bg-[#f6f9ff] hover:border-[#1c64ff]/30 hover:text-[#1c64ff] transition-colors disabled:opacity-50 ${CONTROL_SURFACE}`
}
>
{
generateSbs
.
isPending
?
<
Loader2
className=
"w-4 h-4 animate-spin"
/>
:
<
Wand2
className=
"w-4 h-4"
/>
}
</
button
>
</
div
>
{
/* Cards (accordion) */
}
<
div
className=
"flex-1 overflow-y-auto p
y-1
"
>
<
div
className=
"flex-1 overflow-y-auto p
x-2 py-2 bg-[#f7f9fd]
"
>
{
!
eid
?
(
<
div
className=
"px-3 py-8 text-center"
>
<
p
className=
"text-xs text-muted-foreground"
>
请从左侧选择集数
</
p
>
...
...
@@ -425,8 +428,8 @@ export function StoryboardWorkspace() {
</
div
>
{
/* ─── Column 3: Video generation panel (5-section prompt + duration + button) ─── */
}
<
div
className=
"flex flex-col flex-shrink-0 border-r border-
border overflow-hidden bg-[#fafb
fd]"
style=
{
{
width
:
340
}
}
>
<
div
className=
"h-14 px-4 border-b border-
border flex items-center flex-shrink-0 bg-background
"
>
<
div
className=
"flex flex-col flex-shrink-0 border-r border-
slate-200/80 overflow-hidden bg-[#f7f9
fd]"
style=
{
{
width
:
340
}
}
>
<
div
className=
"h-14 px-4 border-b border-
slate-200/80 flex items-center flex-shrink-0 bg-white/95 shadow-[0_1px_0_rgba(15,23,42,0.03)]
"
>
<
span
className=
"text-[13px] font-semibold text-foreground tracking-tight"
>
分镜视频生成
</
span
>
{
expanded
&&
(
<
span
className=
"ml-2 text-[11px] text-muted-foreground tabular-nums"
>
...
...
@@ -440,7 +443,7 @@ export function StoryboardWorkspace() {
<
p
className=
"text-xs text-muted-foreground italic mt-4"
>
展开左侧分镜后填写提示词
</
p
>
)
:
(
<>
<
div
className=
"flex items-start gap-1.5 px-
2.5 py-2 rounded-lg bg-[#1c64ff]/[0.06] border border-[#1c64ff]/15
"
>
<
div
className=
"flex items-start gap-1.5 px-
3 py-2.5 rounded-xl bg-white border border-[#1c64ff]/15 shadow-[0_4px_14px_rgba(28,100,255,0.06)]
"
>
<
Sparkles
className=
"w-3.5 h-3.5 text-[#1c64ff] mt-0.5 flex-shrink-0"
/>
<
p
className=
"text-[11px] text-[#1c64ff] leading-relaxed"
>
角色、场景、道具预设已默认参考,无需再次添加引用
...
...
@@ -450,13 +453,13 @@ export function StoryboardWorkspace() {
value=
{
freeformPrompt
}
onChange=
{
(
e
)
=>
setFreeformPrompt
(
e
.
target
.
value
)
}
placeholder=
{
FREEFORM_PLACEHOLDER
}
className=
"flex-1 min-h-[280px] px-3 py-2.5 rounded-lg border border-border bg-background text-sm text-foreground leading-relaxed resize-none focus:outline-none focus:ring-2 focus:ring-[#1c64ff]/20 focus:border-[#1c64ff]/40 font-mono"
className=
{
`flex-1 min-h-[280px] px-3 py-2.5 rounded-xl border border-slate-200/80 bg-white text-sm text-foreground leading-relaxed resize-none font-mono shadow-inner shadow-slate-100/80 ${FOCUS_RING}`
}
/>
</>
)
}
</
div
>
<
div
className=
"px-3 py-3 border-t border-
border flex-shrink-0 bg-background space-y-2
"
>
<
div
className=
"px-3 py-3 border-t border-
slate-200/80 flex-shrink-0 bg-white/95 space-y-2 shadow-[0_-1px_0_rgba(15,23,42,0.03)]
"
>
<
div
className=
"flex items-center gap-2"
>
<
div
className=
"relative flex-1"
>
<
select
...
...
@@ -464,7 +467,7 @@ export function StoryboardWorkspace() {
onChange=
{
(
e
)
=>
setSelectedModel
(
e
.
target
.
value
)
}
disabled=
{
!
expanded
}
title=
"视频生成模型"
className=
"w-full appearance-none pl-3 pr-7 py-2 rounded-lg border border-border bg-[#fafbfd] text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-[#1c64ff]/20 focus:border-[#1c64ff]/40 cursor-pointer disabled:opacity-50 transition-colors"
className=
{
`w-full appearance-none pl-3 pr-7 py-2 rounded-lg bg-[#f8fafc] text-xs text-foreground cursor-pointer disabled:opacity-50 transition-colors ${CONTROL_SURFACE} ${FOCUS_RING}`
}
>
{
VIDEO_MODEL_OPTIONS
.
map
((
m
)
=>
(
<
option
key=
{
m
.
value
}
value=
{
m
.
value
}
>
{
m
.
label
}
</
option
>
...
...
@@ -477,7 +480,7 @@ export function StoryboardWorkspace() {
value=
{
selectedRatio
}
onChange=
{
(
e
)
=>
setSelectedRatio
(
e
.
target
.
value
)
}
disabled=
{
!
expanded
}
className=
"w-full appearance-none pl-3 pr-7 py-2 rounded-lg border border-border bg-[#fafbfd] text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-[#1c64ff]/20 focus:border-[#1c64ff]/40 cursor-pointer disabled:opacity-50 transition-colors"
className=
{
`w-full appearance-none pl-3 pr-7 py-2 rounded-lg bg-[#f8fafc] text-xs text-foreground cursor-pointer disabled:opacity-50 transition-colors ${CONTROL_SURFACE} ${FOCUS_RING}`
}
>
{
RATIO_OPTIONS
.
map
((
r
)
=>
(
<
option
key=
{
r
.
value
}
value=
{
r
.
value
}
>
{
r
.
label
}
</
option
>
...
...
@@ -490,7 +493,7 @@ export function StoryboardWorkspace() {
value=
{
selectedDuration
}
onChange=
{
(
e
)
=>
setSelectedDuration
(
Number
(
e
.
target
.
value
))
}
disabled=
{
!
expanded
}
className=
"appearance-none pl-2 pr-6 py-2 rounded-lg border border-border bg-[#fafbfd] text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-[#1c64ff]/20 focus:border-[#1c64ff]/40 cursor-pointer w-16 disabled:opacity-50 transition-colors tabular-nums"
className=
{
`appearance-none pl-2 pr-6 py-2 rounded-lg bg-[#f8fafc] text-xs text-foreground cursor-pointer w-16 disabled:opacity-50 transition-colors tabular-nums ${CONTROL_SURFACE} ${FOCUS_RING}`
}
>
{
DURATION_OPTIONS
.
map
((
d
)
=>
(
<
option
key=
{
d
}
value=
{
d
}
>
{
d
}
</
option
>
...
...
@@ -504,8 +507,8 @@ export function StoryboardWorkspace() {
title=
{
audioOn
?
"已开启配音"
:
"已关闭配音"
}
className=
{
`p-2 rounded-lg border text-xs transition-colors disabled:opacity-50 ${
audioOn
? "border-[#1c64ff] bg-[#1c64ff]/10 text-[#1c64ff]"
: "border-
border bg-[#fafbfd] text-muted-foreground hover:bg-[#eceef4
]"
? "border-[#1c64ff] bg-[#1c64ff]/10 text-[#1c64ff]
shadow-[0_4px_12px_rgba(28,100,255,0.12)]
"
: "border-
slate-200/80 bg-[#f8fafc] text-muted-foreground hover:bg-[#eef4ff] hover:text-[#1c64ff
]"
}`
}
>
{
audioOn
?
<
Volume2
className=
"w-3.5 h-3.5"
/>
:
<
VolumeX
className=
"w-3.5 h-3.5"
/>
}
...
...
@@ -516,7 +519,7 @@ export function StoryboardWorkspace() {
onClick=
{
handleGenerate
}
disabled=
{
!
canGenerate
||
generatingVideoId
===
expanded
?.
id
}
title=
{
!
canGenerate
?
"需要:至少 1 个出镜角色 + 场景图 + 提示词"
:
""
}
className=
"w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-
lg bg-[#1c64ff] text-white text-xs font-semibold tracking-wide transition-all duration-150 hover:shadow-[0_6px_14px_rgba(28,100,255,0.35
)] hover:-translate-y-px disabled:opacity-50 disabled:hover:translate-y-0 disabled:hover:shadow-none whitespace-nowrap"
className=
"w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-
xl bg-[#1c64ff] text-white text-xs font-semibold tracking-wide shadow-[0_9px_20px_rgba(28,100,255,0.26)] transition-all duration-150 hover:bg-[#1857e8] hover:shadow-[0_12px_24px_rgba(28,100,255,0.32
)] hover:-translate-y-px disabled:opacity-50 disabled:hover:translate-y-0 disabled:hover:shadow-none whitespace-nowrap"
>
{
generatingVideoId
===
expanded
?.
id
?
(
<
Loader2
className=
"w-3.5 h-3.5 animate-spin"
/>
...
...
@@ -529,8 +532,8 @@ export function StoryboardWorkspace() {
</
div
>
{
/* ─── Column 4: Big video preview + thumbnail timeline ─── */
}
<
div
className=
"flex-1 flex flex-col overflow-hidden min-w-0 bg-
background
"
>
<
div
className=
"h-14 px-5 border-b border-
border flex items-center justify-between flex-shrink-0 bg-background
"
>
<
div
className=
"flex-1 flex flex-col overflow-hidden min-w-0 bg-
white/90
"
>
<
div
className=
"h-14 px-5 border-b border-
slate-200/80 flex items-center justify-between flex-shrink-0 bg-white/95 shadow-[0_1px_0_rgba(15,23,42,0.03)]
"
>
<
span
className=
"text-[13px] font-semibold text-foreground tracking-tight"
>
视频预览
{
previewSb
&&
(
...
...
@@ -545,7 +548,7 @@ export function StoryboardWorkspace() {
href=
{
assemblyTask
.
downloadUrl
}
target=
"_blank"
rel=
"noreferrer"
className=
"flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-emerald-500 text-white text-xs font-medium
hover:bg-emerald-600 hover:shadow-[0_4px_12px_rgba(16,185,129,0.3
)] transition-all duration-150"
className=
"flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-emerald-500 text-white text-xs font-medium
shadow-[0_6px_14px_rgba(16,185,129,0.22)] hover:bg-emerald-600 hover:shadow-[0_8px_18px_rgba(16,185,129,0.28
)] transition-all duration-150"
>
<
Download
className=
"w-3.5 h-3.5"
/>
下载合集
...
...
@@ -559,7 +562,7 @@ export function StoryboardWorkspace() {
assemblyTask
?.
status
===
"pending"
||
storyboards
.
length
===
0
}
className=
"flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-border bg-background text-xs text-foreground hover:bg-[#f6f7fb] hover:border-[#1c64ff]/30 hover:text-[#1c64ff] transition-colors disabled:opacity-50 disabled:hover:bg-background disabled:hover:border-border disabled:hover:text-foreground"
className=
{
`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs text-foreground hover:bg-[#f6f9ff] hover:border-[#1c64ff]/30 hover:text-[#1c64ff] transition-colors disabled:opacity-50 disabled:hover:bg-white disabled:hover:border-slate-200 disabled:hover:text-foreground ${CONTROL_SURFACE}`
}
>
{
startAssembly
.
isPending
||
assemblyTask
?.
status
===
"running"
?
(
<
Loader2
className=
"w-3.5 h-3.5 animate-spin"
/>
...
...
@@ -570,7 +573,7 @@ export function StoryboardWorkspace() {
</
button
>
<
button
onClick=
{
()
=>
navigate
(
`/project/${projectId}/video`
)
}
className=
"flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-border bg-background text-xs text-foreground hover:bg-[#f6f7fb] hover:border-[#1c64ff]/30 hover:text-[#1c64ff] transition-colors"
className=
{
`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs text-foreground hover:bg-[#f6f9ff] hover:border-[#1c64ff]/30 hover:text-[#1c64ff] transition-colors ${CONTROL_SURFACE}`
}
>
视频管理
<
ArrowRight
className=
"w-3.5 h-3.5"
/>
...
...
@@ -578,14 +581,14 @@ export function StoryboardWorkspace() {
</
div
>
</
div
>
<
div
className=
"flex-1 overflow-y-auto p-5 min-h-0 bg-[#f
afbfd
]"
>
<
div
className=
"flex-1 overflow-y-auto p-5 min-h-0 bg-[#f
4f7fb
]"
>
{
!
previewSb
?
(
<
div
className=
"h-full flex items-center justify-center"
>
<
p
className=
"text-sm text-muted-foreground"
>
展开任意分镜后查看视频
</
p
>
</
div
>
)
:
previewTask
?.
status
===
"succeeded"
&&
previewTask
.
resultVideoUrl
?
(
<
div
className=
"space-y-3"
>
<
div
className=
"rounded-
xl overflow-hidden bg-black aspect-video shadow-[0_8px_24px_rgba(15,23,42,0.12)]
"
>
<
div
className=
"rounded-
2xl overflow-hidden bg-black aspect-video shadow-[0_18px_45px_rgba(15,23,42,0.18)] ring-1 ring-black/10
"
>
<
video
key=
{
previewTask
.
resultVideoUrl
}
src=
{
previewTask
.
resultVideoUrl
}
...
...
@@ -605,16 +608,16 @@ export function StoryboardWorkspace() {
</
a
>
</
div
>
)
:
previewTask
?.
status
===
"running"
||
previewTask
?.
status
===
"submitted"
||
previewTask
?.
status
===
"pending"
?
(
<
div
className=
"h-full flex flex-col items-center justify-center gap-4"
>
<
div
className=
{
`h-full flex flex-col items-center justify-center gap-4 rounded-2xl ${SOFT_PANEL}`
}
>
<
div
className=
"relative w-16 h-16 flex items-center justify-center"
>
<
span
className=
"absolute inset-0 rounded-full bg-[#1c64ff]/10 animate-pulse"
/>
<
span
className=
"absolute inset-0 rounded-full bg-[#1c64ff]/10 animate-pulse
ring-8 ring-[#1c64ff]/5
"
/>
<
Loader2
className=
"w-8 h-8 text-[#1c64ff] animate-spin relative"
/>
</
div
>
<
p
className=
"text-sm font-semibold text-foreground tracking-tight"
>
视频生成中…
</
p
>
<
p
className=
"text-xs text-muted-foreground"
>
通常需要 30-60 秒
</
p
>
</
div
>
)
:
previewTask
?.
status
===
"failed"
?
(
<
div
className=
"rounded-
xl border border-red-200 bg-red-50 p-6
"
>
<
div
className=
"rounded-
2xl border border-red-200 bg-red-50 p-6 shadow-[0_8px_24px_rgba(185,28,28,0.08)]
"
>
<
div
className=
"flex items-center gap-2 mb-2"
>
<
X
className=
"w-4 h-4 text-red-500"
/>
<
span
className=
"text-sm font-medium text-red-700"
>
生成失败
</
span
>
...
...
@@ -623,7 +626,7 @@ export function StoryboardWorkspace() {
</
div
>
)
:
(
<
div
className=
"h-full flex flex-col items-center justify-center gap-4"
>
<
div
className=
"w-full max-w-sm aspect-video rounded-
xl border-[1.5px] border-dashed border-border bg-background
flex flex-col items-center justify-center gap-3"
>
<
div
className=
"w-full max-w-sm aspect-video rounded-
2xl border-[1.5px] border-dashed border-slate-300/80 bg-white/90 shadow-[0_10px_30px_rgba(15,23,42,0.05)]
flex flex-col items-center justify-center gap-3"
>
<
Play
className=
"w-12 h-12 text-muted-foreground/30"
/>
<
p
className=
"text-sm text-muted-foreground"
>
尚未生成视频
</
p
>
<
p
className=
"text-xs text-muted-foreground/70"
>
在左侧填好素材+提示词后生成
</
p
>
...
...
@@ -634,8 +637,8 @@ export function StoryboardWorkspace() {
{
/* Thumbnail timeline */
}
{
storyboards
.
length
>
0
&&
(
<
div
className=
"border-t border-
border bg-background flex-shrink-0
"
>
<
div
className=
"px-3 py-
2.5
flex items-center gap-2 overflow-x-auto"
>
<
div
className=
"border-t border-
slate-200/80 bg-white/95 flex-shrink-0 shadow-[0_-1px_0_rgba(15,23,42,0.03)]
"
>
<
div
className=
"px-3 py-
3
flex items-center gap-2 overflow-x-auto"
>
{
storyboards
.
map
((
sb
)
=>
{
const
t
=
getTaskForSb
(
sb
);
const
isActive
=
activeVideoSbId
===
sb
.
id
;
...
...
@@ -650,8 +653,8 @@ export function StoryboardWorkspace() {
title=
{
`分镜 ${String(sb.sequenceNum).padStart(3, "0")}`
}
className=
{
`relative flex-shrink-0 w-[88px] h-[52px] rounded-lg overflow-hidden border transition-all duration-150 ${
isActive
? "border-[#1c64ff] ring-2 ring-[#1c64ff]/25 shadow-[0_
4px_12px_rgba(28,100,255,0.
2)]"
: "border-
border hover:border-[#1c64ff]/40 hover:-translate-y-px
"
? "border-[#1c64ff] ring-2 ring-[#1c64ff]/25 shadow-[0_
8px_18px_rgba(28,100,255,0.2
2)]"
: "border-
slate-200 hover:border-[#1c64ff]/40 hover:-translate-y-px hover:shadow-sm
"
}`
}
>
{
url
?
(
...
...
@@ -662,7 +665,7 @@ export function StoryboardWorkspace() {
className=
"w-full h-full object-cover pointer-events-none"
/>
)
:
(
<
div
className=
"w-full h-full bg-[#f
6f7fb
] flex items-center justify-center"
>
<
div
className=
"w-full h-full bg-[#f
8fafc
] flex items-center justify-center"
>
<
ImageIcon
className=
"w-4 h-4 text-muted-foreground/40"
/>
</
div
>
)
}
...
...
@@ -732,24 +735,28 @@ function StoryboardCard(props: StoryboardCardProps) {
const
[
descCollapsed
,
setDescCollapsed
]
=
useState
(
false
);
return
(
<
div
className=
{
`mx-2 my-1 rounded-xl border ${isExpanded ? "border-primary/40 bg-card shadow-sm" : "border-border hover:bg-muted/50"} transition-colors`
}
>
<
div
className=
{
`my-2 rounded-2xl border transition-all duration-200 ${
isExpanded
? "border-[#1c64ff]/35 bg-white shadow-[0_12px_30px_rgba(15,23,42,0.08)]"
: "border-slate-200/80 bg-white/80 hover:bg-white hover:border-[#1c64ff]/25 hover:shadow-[0_8px_18px_rgba(15,23,42,0.05)]"
}`
}
>
<
button
onClick=
{
onToggle
}
className=
"w-full px-3.5 pt-3 pb-2
flex items-center gap-2 text-left
"
className=
"w-full px-3.5 pt-3 pb-2
.5 flex items-center gap-2 text-left rounded-t-2xl
"
>
<
span
className=
"text-sm font-semibold text-foreground flex-1 truncate"
>
分镜信息
{
String
(
sb
.
sequenceNum
).
padStart
(
3
,
"0"
)
}
</
span
>
<
span
className=
{
`w-2
h-2 rounded-full flex-shrink-0
${dotClass}`
}
/>
<
span
className=
{
`w-2
.5 h-2.5 rounded-full flex-shrink-0 ring-4 ring-white
${dotClass}`
}
/>
</
button
>
{
isExpanded
&&
(
<
div
className=
"px-3.5 pb-3.5 space-y-3.5"
>
<
div
className=
"px-3.5 pb-3.5 space-y-3.5
border-t border-slate-100
"
>
{
/* 分镜描述 */
}
<
div
>
<
button
onClick=
{
()
=>
setDescCollapsed
((
v
)
=>
!
v
)
}
className=
"w-full flex items-center justify-between mb-1.5"
className=
"w-full flex items-center justify-between mb-1.5
rounded-md text-left
"
>
<
span
className=
"text-xs font-medium text-foreground"
>
分镜描述
</
span
>
{
descCollapsed
...
...
@@ -762,7 +769,7 @@ function StoryboardCard(props: StoryboardCardProps) {
value=
{
props
.
shortDescription
}
onChange=
{
(
e
)
=>
props
.
onShortDescriptionChange
(
e
.
target
.
value
)
}
placeholder=
"简要描述此分镜的剧情或画面"
className=
"w-full px-3 py-2 rounded-lg border border-border bg-background text-xs text-foreground leading-relaxed resize-none focus:outline-none focus:ring-1 focus:ring-primary/30"
className=
{
`w-full px-3 py-2 rounded-xl border border-slate-200/80 bg-[#f8fafc] text-xs text-foreground leading-relaxed resize-none shadow-inner shadow-slate-100/80 ${FOCUS_RING}`
}
/>
)
}
</
div
>
...
...
@@ -796,7 +803,7 @@ function StoryboardCard(props: StoryboardCardProps) {
<
div
className=
"pt-1 flex justify-end"
>
<
button
onClick=
{
onDelete
}
className=
"text-[11px] text-destructive hover:
underline flex items-center gap-1
"
className=
"text-[11px] text-destructive hover:
text-red-700 flex items-center gap-1 rounded-md px-2 py-1 hover:bg-red-50 transition-colors
"
>
<
Trash2
className=
"w-3 h-3"
/>
删除该分镜
...
...
@@ -825,13 +832,13 @@ function CharacterRowList({
characterKeys
,
keyToCharacter
,
characters
,
onRemove
,
onSwap
,
onAdd
,
}:
CharacterRowListProps
)
{
return
(
<
div
>
<
div
className=
"rounded-xl bg-[#f8fafc] p-3 ring-1 ring-slate-200/70"
>
<
div
className=
"flex items-center justify-between mb-2"
>
<
span
className=
"text-xs font-medium text-foreground"
>
出镜角色
</
span
>
<
button
onClick=
{
onAdd
}
disabled=
{
characterKeys
.
length
>=
MAX_CHARACTERS
}
className=
"flex items-center gap-0.5 text-[11px] text-
primary hover:underline disabled:opacity-40 disabled:no-underline
"
className=
"flex items-center gap-0.5 text-[11px] text-
[#1c64ff] hover:text-[#164fd0] font-medium disabled:opacity-40
"
>
<
Plus
className=
"w-3 h-3"
/>
添加
...
...
@@ -889,8 +896,8 @@ function CharacterRow({
});
return
(
<
div
className=
"flex items-center gap-1.5"
>
<
div
className=
"w-8 h-8 rounded-
md overflow-hidden border border-border bg-muted
flex-shrink-0"
>
<
div
className=
"flex items-center gap-1.5
rounded-xl bg-white p-1.5 ring-1 ring-slate-200/70
"
>
<
div
className=
"w-8 h-8 rounded-
lg overflow-hidden border border-slate-200 bg-slate-100
flex-shrink-0"
>
{
url
?
(
<
img
src=
{
url
}
alt=
{
character
?.
name
??
""
}
className=
"w-full h-full object-cover"
/>
)
:
(
...
...
@@ -901,7 +908,7 @@ function CharacterRow({
<
select
value=
{
tosKey
}
onChange=
{
(
e
)
=>
onSwap
(
e
.
target
.
value
)
}
className=
"w-full appearance-none pl-2 pr-6 py-1.5 rounded border border-border bg-background text-xs text-foreground focus:outline-none focus:ring-1 focus:ring-primary/30 cursor-pointer truncate"
className=
{
`w-full appearance-none pl-2 pr-6 py-1.5 rounded-lg border border-slate-200/80 bg-white text-xs text-foreground cursor-pointer truncate ${FOCUS_RING}`
}
>
{
!
character
&&
<
option
value=
{
tosKey
}
>
(已失效)
</
option
>
}
{
options
.
map
((
c
)
=>
{
...
...
@@ -914,7 +921,7 @@ function CharacterRow({
<
div
className=
"relative flex-1 min-w-0"
>
<
select
disabled
className=
"w-full appearance-none pl-2 pr-6 py-1.5 rounded
border border-border bg-muted/4
0 text-xs text-muted-foreground cursor-not-allowed truncate"
className=
"w-full appearance-none pl-2 pr-6 py-1.5 rounded
-lg border border-slate-200/80 bg-slate-100/8
0 text-xs text-muted-foreground cursor-not-allowed truncate"
>
<
option
>
未配置音色
</
option
>
</
select
>
...
...
@@ -922,14 +929,14 @@ function CharacterRow({
</
div
>
<
button
onClick=
{
onRemove
}
className=
"px-2 py-1.5 rounded
border border-border text-[11px] text-muted-foreground hover:bg-muted hover:text-foreground
"
className=
"px-2 py-1.5 rounded
-lg border border-slate-200/80 text-[11px] text-muted-foreground hover:bg-red-50 hover:text-red-600 transition-colors
"
>
删除
</
button
>
<
button
onClick=
{
handleRegen
}
disabled=
{
!
character
||
isRegenerating
}
className=
"px-2 py-1.5 rounded
border border-border text-[11px] text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-40 flex items-center gap-1
"
className=
"px-2 py-1.5 rounded
-lg border border-slate-200/80 text-[11px] text-muted-foreground hover:bg-[#f6f9ff] hover:text-[#1c64ff] disabled:opacity-40 flex items-center gap-1 transition-colors
"
>
{
isRegenerating
&&
<
Loader2
className=
"w-3 h-3 animate-spin"
/>
}
重绘
...
...
@@ -953,13 +960,13 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) {
const
selected
=
usable
.
find
((
s
)
=>
s
.
imageTosKey
===
selectedKey
)
??
null
;
return
(
<
div
>
<
div
className=
"rounded-xl bg-[#f8fafc] p-3 ring-1 ring-slate-200/70"
>
<
div
className=
"flex items-center justify-between mb-2"
>
<
span
className=
"text-xs font-medium text-foreground"
>
分镜场景
</
span
>
{
usable
.
length
>
0
&&
(
<
button
onClick=
{
()
=>
setShowPicker
((
v
)
=>
!
v
)
}
className=
"flex items-center gap-0.5 text-[11px] text-
primary hover:underline
"
className=
"flex items-center gap-0.5 text-[11px] text-
[#1c64ff] hover:text-[#164fd0] font-medium
"
>
<
Plus
className=
"w-3 h-3"
/>
{
selected
?
"更换"
:
"选择"
}
...
...
@@ -972,7 +979,7 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) {
type=
"button"
onClick=
{
()
=>
usable
.
length
>
0
&&
setShowPicker
((
v
)
=>
!
v
)
}
disabled=
{
usable
.
length
===
0
}
className=
"w-full aspect-video rounded-
lg overflow-hidden border border-border bg-muted mb-2 block hover:border-primary/40
transition disabled:cursor-not-allowed"
className=
"w-full aspect-video rounded-
xl overflow-hidden border border-slate-200/80 bg-slate-100 mb-2 block hover:border-[#1c64ff]/40 hover:shadow-sm
transition disabled:cursor-not-allowed"
>
{
selected
?.
imageUrl
?
(
<
img
src=
{
selected
.
imageUrl
}
alt=
{
selected
.
name
}
className=
"w-full h-full object-cover"
/>
...
...
@@ -988,17 +995,17 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) {
{
/* Bottom row: scene name + role label + actions menu */
}
<
div
className=
"flex items-center gap-1.5"
>
<
div
className=
"flex-1 min-w-0 px-2.5 py-1.5 rounded-
md border border-border bg-background
text-xs text-foreground truncate"
>
<
div
className=
"flex-1 min-w-0 px-2.5 py-1.5 rounded-
lg border border-slate-200/80 bg-white
text-xs text-foreground truncate"
>
{
selected
?.
name
||
<
span
className=
"text-muted-foreground"
>
请选择场景
</
span
>
}
</
div
>
<
div
className=
"px-2.5 py-1.5 rounded-
md border border-border bg-muted/3
0 text-xs text-muted-foreground flex-shrink-0"
>
<
div
className=
"px-2.5 py-1.5 rounded-
lg border border-slate-200/80 bg-white/8
0 text-xs text-muted-foreground flex-shrink-0"
>
主场景图
</
div
>
<
div
className=
"relative flex-shrink-0"
>
<
button
onClick=
{
()
=>
setMenuOpen
((
v
)
=>
!
v
)
}
disabled=
{
!
selected
}
className=
"p-1.5 rounded-
md border border-border text-muted-foreground hover:bg-muted disabled:opacity-40
"
className=
"p-1.5 rounded-
lg border border-slate-200/80 bg-white text-muted-foreground hover:bg-[#f6f9ff] hover:text-[#1c64ff] disabled:opacity-40 transition-colors
"
>
<
MoreHorizontal
className=
"w-3.5 h-3.5"
/>
</
button
>
...
...
@@ -1008,16 +1015,16 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) {
className=
"fixed inset-0 z-10 cursor-default"
onClick=
{
()
=>
setMenuOpen
(
false
)
}
/>
<
div
className=
"absolute right-0 top-full mt-1 z-20 bg-
popover border border-border rounded-md
shadow-lg overflow-hidden min-w-[100px]"
>
<
div
className=
"absolute right-0 top-full mt-1 z-20 bg-
white border border-slate-200/80 rounded-xl
shadow-lg overflow-hidden min-w-[100px]"
>
<
button
onClick=
{
()
=>
{
setShowPicker
(
true
);
setMenuOpen
(
false
);
}
}
className=
"w-full px-3 py-1.5 text-left text-xs text-foreground hover:bg-
muted
"
className=
"w-full px-3 py-1.5 text-left text-xs text-foreground hover:bg-
[#f6f9ff]
"
>
更换场景
</
button
>
<
button
onClick=
{
()
=>
{
onSelect
(
""
);
setMenuOpen
(
false
);
}
}
className=
"w-full px-3 py-1.5 text-left text-xs text-destructive hover:bg-
muted
"
className=
"w-full px-3 py-1.5 text-left text-xs text-destructive hover:bg-
red-50
"
>
清除选择
</
button
>
...
...
@@ -1028,7 +1035,7 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) {
</
div
>
{
showPicker
&&
(
<
div
className=
"mt-2 rounded-
lg border border-border bg-muted/3
0 p-2"
>
<
div
className=
"mt-2 rounded-
xl border border-slate-200/80 bg-white/8
0 p-2"
>
{
usable
.
length
===
0
?
(
<
p
className=
"text-[11px] text-muted-foreground italic text-center py-2"
>
先到「场景」页创建场景
</
p
>
)
:
(
...
...
@@ -1044,7 +1051,7 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) {
}
}
title=
{
s
.
name
}
className=
{
`relative aspect-square rounded-md overflow-hidden border-2 ${
active ? "border-
primary ring-2 ring-primary/30" : "border-border hover:border-primary
/40"
active ? "border-
[#1c64ff] ring-2 ring-[#1c64ff]/25" : "border-slate-200 hover:border-[#1c64ff]
/40"
}`
}
>
{
s
.
imageUrl
?
(
...
...
@@ -1056,7 +1063,7 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) {
<
span
className=
"text-[10px] text-white truncate block"
>
{
s
.
name
}
</
span
>
</
div
>
{
active
&&
(
<
div
className=
"absolute top-1 right-1 w-4 h-4 rounded-full bg-
primary flex items-center justify-center
"
>
<
div
className=
"absolute top-1 right-1 w-4 h-4 rounded-full bg-
[#1c64ff] flex items-center justify-center shadow-sm
"
>
<
Check
className=
"w-2.5 h-2.5 text-white"
/>
</
div
>
)
}
...
...
@@ -1090,13 +1097,13 @@ function PropGrid({ scenes, sceneKey, selectedKeys, onToggle }: PropGridProps) {
const
canAddMore
=
selectedKeys
.
length
<
MAX_PROPS
;
return
(
<
div
>
<
div
className=
"rounded-xl bg-[#f8fafc] p-3 ring-1 ring-slate-200/70"
>
<
div
className=
"flex items-center justify-between mb-2"
>
<
span
className=
"text-xs font-medium text-foreground"
>
场景道具
</
span
>
<
button
onClick=
{
()
=>
setShowPicker
((
v
)
=>
!
v
)
}
disabled=
{
!
canAddMore
&&
!
showPicker
}
className=
"flex items-center gap-0.5 text-[11px] text-
primary hover:underline disabled:opacity-40 disabled:no-underline
"
className=
"flex items-center gap-0.5 text-[11px] text-
[#1c64ff] hover:text-[#164fd0] font-medium disabled:opacity-40
"
>
<
Plus
className=
"w-3 h-3"
/>
添加
...
...
@@ -1112,7 +1119,7 @@ function PropGrid({ scenes, sceneKey, selectedKeys, onToggle }: PropGridProps) {
key=
{
s
.
id
}
onClick=
{
()
=>
onToggle
(
s
.
imageTosKey
!
)
}
title=
{
`移除 ${s.name}`
}
className=
"relative aspect-square rounded-
lg overflow-hidden border border-border bg-muted group
"
className=
"relative aspect-square rounded-
xl overflow-hidden border border-slate-200/80 bg-slate-100 group hover:shadow-sm transition-shadow
"
>
{
s
.
imageUrl
?
(
<
img
src=
{
s
.
imageUrl
}
alt=
{
s
.
name
}
className=
"w-full h-full object-cover"
/>
...
...
@@ -1131,7 +1138,7 @@ function PropGrid({ scenes, sceneKey, selectedKeys, onToggle }: PropGridProps) {
)
}
{
showPicker
&&
(
<
div
className=
"rounded-
lg border border-border bg-muted/3
0 p-2"
>
<
div
className=
"rounded-
xl border border-slate-200/80 bg-white/8
0 p-2"
>
{
available
.
length
===
0
?
(
<
p
className=
"text-[11px] text-muted-foreground italic text-center py-2"
>
{
allCandidates
.
length
===
0
?
"先到「场景」页创建场景"
:
"已添加全部可选道具"
}
...
...
@@ -1147,7 +1154,7 @@ function PropGrid({ scenes, sceneKey, selectedKeys, onToggle }: PropGridProps) {
}
}
disabled=
{
!
canAddMore
}
title=
{
s
.
name
}
className=
"relative aspect-square rounded-
md overflow-hidden border border-border bg-background hover:border-primary/40 disabled:opacity-40
"
className=
"relative aspect-square rounded-
lg overflow-hidden border border-slate-200 bg-white hover:border-[#1c64ff]/40 hover:shadow-sm disabled:opacity-40 transition
"
>
{
s
.
imageUrl
?
(
<
img
src=
{
s
.
imageUrl
}
alt=
{
s
.
name
}
className=
"w-full h-full object-cover"
/>
...
...
@@ -1200,14 +1207,14 @@ function CharacterPickerModal({
const
usable
=
characters
.
filter
((
c
)
=>
!!
characterFrontKey
(
c
));
return
(
<
div
className=
"fixed inset-0 bg-
black/50
z-50 flex items-center justify-center p-4"
onClick=
{
onClose
}
>
<
div
className=
"fixed inset-0 bg-
slate-950/55 backdrop-blur-sm
z-50 flex items-center justify-center p-4"
onClick=
{
onClose
}
>
<
div
className=
"bg-
background rounded-xl shadow-xl w-full max-w-2xl max-h-[80vh] flex flex-col
"
className=
"bg-
white rounded-2xl shadow-[0_24px_70px_rgba(15,23,42,0.28)] ring-1 ring-white/60 w-full max-w-2xl max-h-[80vh] flex flex-col overflow-hidden
"
onClick=
{
(
e
)
=>
e
.
stopPropagation
()
}
>
<
div
className=
"flex items-center justify-between px-5 py-3 border-b border-
border
"
>
<
div
className=
"flex items-center justify-between px-5 py-3 border-b border-
slate-200/80 bg-[#f8fafc]
"
>
<
h3
className=
"text-sm font-semibold text-foreground"
>
添加出镜角色
</
h3
>
<
button
onClick=
{
onClose
}
className=
"p-1 hover:bg-
muted rounded
"
>
<
button
onClick=
{
onClose
}
className=
"p-1 hover:bg-
white rounded-lg transition-colors
"
>
<
X
className=
"w-4 h-4 text-muted-foreground"
/>
</
button
>
</
div
>
...
...
@@ -1231,8 +1238,8 @@ function CharacterPickerModal({
onClick=
{
()
=>
{
onPick
(
k
);
onClose
();
}
}
className=
{
`relative rounded-lg overflow-hidden border-2 transition ${
isAdded
? "border-
primary
opacity-50 cursor-not-allowed"
: "border-
border hover:border-primary/60
"
? "border-
[#1c64ff]
opacity-50 cursor-not-allowed"
: "border-
slate-200 hover:border-[#1c64ff]/60 hover:shadow-sm
"
}`
}
>
<
div
className=
"aspect-square bg-muted"
>
...
...
@@ -1246,7 +1253,7 @@ function CharacterPickerModal({
<
p
className=
"text-xs font-medium text-foreground truncate"
>
{
c
.
name
}
</
p
>
</
div
>
{
isAdded
&&
(
<
div
className=
"absolute top-1 right-1 w-5 h-5 rounded-full bg-
primary flex items-center justify-center
"
>
<
div
className=
"absolute top-1 right-1 w-5 h-5 rounded-full bg-
[#1c64ff] flex items-center justify-center shadow-sm
"
>
<
Check
className=
"w-3 h-3 text-white"
/>
</
div
>
)
}
...
...
@@ -1257,17 +1264,17 @@ function CharacterPickerModal({
)
}
</
div
>
<
div
className=
"px-5 py-3 border-t border-
border
flex justify-between items-center gap-2"
>
<
div
className=
"px-5 py-3 border-t border-
slate-200/80 bg-[#f8fafc]
flex justify-between items-center gap-2"
>
<
button
onClick=
{
()
=>
setCreateMode
(
true
)
}
className=
"px-3 py-1.5 rounded-lg border border-
primary/40 text-primary text-xs hover:bg-primary/10 flex items-center gap-1.5
"
className=
"px-3 py-1.5 rounded-lg border border-
[#1c64ff]/35 text-[#1c64ff] text-xs hover:bg-[#1c64ff]/10 flex items-center gap-1.5 transition-colors
"
>
<
Plus
className=
"w-3 h-3"
/>
创建新角色变体
</
button
>
<
button
onClick=
{
onClose
}
className=
"px-4 py-1.5 rounded-lg border border-
border text-xs text-foreground hover:bg-muted
"
className=
"px-4 py-1.5 rounded-lg border border-
slate-200/80 bg-white text-xs text-foreground hover:bg-[#f6f9ff] transition-colors
"
>
完成
</
button
>
...
...
@@ -1322,11 +1329,11 @@ function AddCharacterModal({ projectId, onClose, onAdded }: AddCharacterModalPro
};
return
(
<
div
className=
"fixed inset-0 bg-
black/50
z-50 flex items-center justify-center p-4"
>
<
div
className=
"bg-
background rounded-xl shadow-xl w-full max-w-md max-h-[90vh] flex flex-col
"
>
<
div
className=
"flex items-center justify-between px-5 py-3 border-b border-
border
"
>
<
div
className=
"fixed inset-0 bg-
slate-950/55 backdrop-blur-sm
z-50 flex items-center justify-center p-4"
>
<
div
className=
"bg-
white rounded-2xl shadow-[0_24px_70px_rgba(15,23,42,0.28)] ring-1 ring-white/60 w-full max-w-md max-h-[90vh] flex flex-col overflow-hidden
"
>
<
div
className=
"flex items-center justify-between px-5 py-3 border-b border-
slate-200/80 bg-[#f8fafc]
"
>
<
h3
className=
"text-sm font-semibold text-foreground"
>
新增出镜角色变体
</
h3
>
<
button
onClick=
{
onClose
}
className=
"p-1 hover:bg-
muted rounded
"
>
<
button
onClick=
{
onClose
}
className=
"p-1 hover:bg-
white rounded-lg transition-colors
"
>
<
X
className=
"w-4 h-4 text-muted-foreground"
/>
</
button
>
</
div
>
...
...
@@ -1342,7 +1349,7 @@ function AddCharacterModal({ projectId, onClose, onAdded }: AddCharacterModalPro
value=
{
name
}
onChange=
{
(
e
)
=>
setName
(
e
.
target
.
value
)
}
placeholder=
"例如:陆长生 - 白衣"
className=
"w-full px-3 py-2 rounded-lg border border-border bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary/20"
className=
{
`w-full px-3 py-2 rounded-xl border border-slate-200/80 bg-white text-sm ${FOCUS_RING}`
}
/>
</
div
>
<
div
>
...
...
@@ -1352,24 +1359,24 @@ function AddCharacterModal({ projectId, onClose, onAdded }: AddCharacterModalPro
value=
{
imagePrompt
}
onChange=
{
(
e
)
=>
setImagePrompt
(
e
.
target
.
value
)
}
placeholder=
"例如:男性,二十岁出头,身穿白色长袍,气质温雅,黑色长发束起"
className=
"w-full px-3 py-2 rounded-lg border border-border bg-background text-sm leading-relaxed resize-none focus:outline-none focus:ring-2 focus:ring-primary/20"
className=
{
`w-full px-3 py-2 rounded-xl border border-slate-200/80 bg-white text-sm leading-relaxed resize-none ${FOCUS_RING}`
}
/>
</
div
>
{
error
&&
<
p
className=
"text-xs text-red-500"
>
{
error
}
</
p
>
}
</
div
>
<
div
className=
"px-5 py-3 border-t border-
border
flex justify-end gap-2"
>
<
div
className=
"px-5 py-3 border-t border-
slate-200/80 bg-[#f8fafc]
flex justify-end gap-2"
>
<
button
onClick=
{
onClose
}
disabled=
{
busy
}
className=
"px-4 py-1.5 rounded-lg border border-
border text-xs text-foreground hover:bg-muted disabled:opacity-50
"
className=
"px-4 py-1.5 rounded-lg border border-
slate-200/80 bg-white text-xs text-foreground hover:bg-[#f6f9ff] disabled:opacity-50 transition-colors
"
>
取消
</
button
>
<
button
onClick=
{
handleSubmit
}
disabled=
{
busy
}
className=
"px-4 py-1.5 rounded-lg bg-
gradient-to-r from-[#5b5ff9] to-[#8b5ff9] text-white text-xs hover:shadow-md disabled:opacity-50 flex items-center gap-1.5
"
className=
"px-4 py-1.5 rounded-lg bg-
[#1c64ff] text-white text-xs shadow-[0_6px_14px_rgba(28,100,255,0.22)] hover:bg-[#1857e8] hover:shadow-[0_8px_18px_rgba(28,100,255,0.28)] disabled:opacity-50 flex items-center gap-1.5 transition-all
"
>
{
busy
&&
<
Loader2
className=
"w-3 h-3 animate-spin"
/>
}
创建并生成
...
...
@@ -1390,20 +1397,20 @@ interface PromptDetailsModalProps {
function
PromptDetailsModal
({
text
,
onClose
}:
PromptDetailsModalProps
)
{
const
trimmed
=
text
.
trim
();
return
(
<
div
className=
"fixed inset-0 bg-
black/50
z-50 flex items-center justify-center p-4"
onClick=
{
onClose
}
>
<
div
className=
"fixed inset-0 bg-
slate-950/55 backdrop-blur-sm
z-50 flex items-center justify-center p-4"
onClick=
{
onClose
}
>
<
div
className=
"bg-
background rounded-xl shadow-xl w-full max-w-lg max-h-[80vh] flex flex-col
"
className=
"bg-
white rounded-2xl shadow-[0_24px_70px_rgba(15,23,42,0.28)] ring-1 ring-white/60 w-full max-w-lg max-h-[80vh] flex flex-col overflow-hidden
"
onClick=
{
(
e
)
=>
e
.
stopPropagation
()
}
>
<
div
className=
"flex items-center justify-between px-5 py-3 border-b border-
border
"
>
<
div
className=
"flex items-center justify-between px-5 py-3 border-b border-
slate-200/80 bg-[#f8fafc]
"
>
<
h3
className=
"text-sm font-semibold text-foreground"
>
分镜提示词全文
</
h3
>
<
button
onClick=
{
onClose
}
className=
"p-1 hover:bg-
muted rounded
"
>
<
button
onClick=
{
onClose
}
className=
"p-1 hover:bg-
white rounded-lg transition-colors
"
>
<
X
className=
"w-4 h-4 text-muted-foreground"
/>
</
button
>
</
div
>
<
div
className=
"flex-1 overflow-y-auto p-5"
>
{
trimmed
?
(
<
pre
className=
"text-sm text-foreground leading-relaxed whitespace-pre-wrap font-mono"
>
<
pre
className=
"text-sm text-foreground leading-relaxed whitespace-pre-wrap font-mono
rounded-xl border border-slate-200/80 bg-[#f8fafc] p-4
"
>
{
trimmed
}
</
pre
>
)
:
(
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment