优化复刻视频脚本和下拉框样式
This commit is contained in:
@@ -9,6 +9,7 @@ import { isPublicGenerationError, presentGenerationError } from "../generation-e
|
||||
import type { Notice, Page } from "./route-config";
|
||||
import { stageOrder, statusPill } from "./stage-config";
|
||||
import { ConfirmModal, MediaLightbox, TeamModal, useBodyScrollLock } from "../components/overlays";
|
||||
import { CustomSelect } from "../components/custom-select";
|
||||
import { DEFAULT_BILLING_RATES, estimateCost, FC_MODELS, modelLabel } from "../components/free-create/constants";
|
||||
import { ModelLibrary } from "../components/model-library";
|
||||
import { ReviewBadge, type ReviewStatus } from "../components/review-badge";
|
||||
@@ -1198,25 +1199,33 @@ export function PipelinePage(props: {
|
||||
return (
|
||||
<div className="as-spec-fields" aria-label="成片规格">
|
||||
<label className="as-spec-field">
|
||||
<select className="select" value={outputAspect} onChange={(event) => changeOutputSpec({ aspect_ratio: event.target.value })}>
|
||||
{OUTPUT_RATIOS.map((option) => <option key={option.value} value={option.value}>{option.label}</option>)}
|
||||
</select>
|
||||
<CustomSelect fill size="sm" value={outputAspect} onChange={(next) => changeOutputSpec({ aspect_ratio: next })} options={OUTPUT_RATIOS} />
|
||||
</label>
|
||||
<label className="as-spec-field">
|
||||
<select className="select" value={outputResolution} onChange={(event) => changeOutputSpec({ resolution: event.target.value })}>
|
||||
{OUTPUT_RESOLUTIONS.map((option) => (
|
||||
<option key={option.value} value={option.value} disabled={Boolean(supportedResolutions.length && !supportedResolutions.includes(option.value))}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<CustomSelect
|
||||
fill
|
||||
size="sm"
|
||||
value={outputResolution}
|
||||
onChange={(next) => changeOutputSpec({ resolution: next })}
|
||||
options={OUTPUT_RESOLUTIONS.map((option) => ({
|
||||
...option,
|
||||
disabled: Boolean(supportedResolutions.length && !supportedResolutions.includes(option.value)),
|
||||
}))}
|
||||
/>
|
||||
</label>
|
||||
<label className="as-spec-field">
|
||||
<select className="select" value={outputModelId} onChange={(event) => changeOutputSpec({ video_model_config_id: event.target.value })} disabled={!videoConfigs.length}>
|
||||
{videoConfigs.length ? videoConfigs.map((config) => (
|
||||
<option key={config.id} value={config.id}>{FC_MODELS.find((item) => item.name === config.name)?.label || config.display_name || modelLabel(config.name)}</option>
|
||||
)) : <option value="">暂无模型</option>}
|
||||
</select>
|
||||
<CustomSelect
|
||||
fill
|
||||
size="sm"
|
||||
value={outputModelId}
|
||||
disabled={!videoConfigs.length}
|
||||
placeholder="暂无模型"
|
||||
onChange={(next) => changeOutputSpec({ video_model_config_id: next })}
|
||||
options={videoConfigs.map((config) => ({
|
||||
value: config.id,
|
||||
label: FC_MODELS.find((item) => item.name === config.name)?.label || config.display_name || modelLabel(config.name),
|
||||
}))}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
@@ -3222,27 +3231,42 @@ export function PipelinePage(props: {
|
||||
沿用模板的镜数、每镜作用和节奏,文案、画面、模特与故事板都会按{setupProduct?.title || "当前商品"}重新生成 —— 不是把上一条片子换个商品名。
|
||||
</div>
|
||||
)}
|
||||
<label className="setup-field">
|
||||
<div className="setup-field">
|
||||
<span className="sf-k">视频结构</span>
|
||||
<select className="setup-select" value={setupStructure} onChange={(e) => { setupTouched.current = true; setSetupStructure(e.target.value as VideoStructure); }}>
|
||||
{structureOptions.map((k) => <option key={k} value={k}>{VIDEO_STRUCTURES[k]}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<CustomSelect
|
||||
fill
|
||||
size="sm"
|
||||
className="setup-select"
|
||||
value={setupStructure}
|
||||
onChange={(next) => { setupTouched.current = true; setSetupStructure(next as VideoStructure); }}
|
||||
options={structureOptions.map((k) => ({ value: k, label: VIDEO_STRUCTURES[k] }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="setup-rec">{STRUCTURE_HINT[setupStructure]}</div>
|
||||
<label className="setup-field">
|
||||
<div className="setup-field">
|
||||
<span className="sf-k">人物</span>
|
||||
<select className="setup-select" value={setupPersona} onChange={(e) => { setupTouched.current = true; setSetupPersona(e.target.value); }}>
|
||||
{SETUP_PERSONA_KEYS.map((k) => <option key={k} value={k}>{WIZ_PERSONA_LABEL[k]}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<CustomSelect
|
||||
fill
|
||||
size="sm"
|
||||
className="setup-select"
|
||||
value={setupPersona}
|
||||
onChange={(next) => { setupTouched.current = true; setSetupPersona(next); }}
|
||||
options={SETUP_PERSONA_KEYS.map((k) => ({ value: k, label: WIZ_PERSONA_LABEL[k] }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="setup-rec">{recommended.reason} · {WIZ_PERSONA_LABEL[recommended.persona] || recommended.persona}</div>
|
||||
{/* 时长:15/30/45/60;每镜固定 15 秒 */}
|
||||
<label className="setup-field">
|
||||
<div className="setup-field">
|
||||
<span className="sf-k">时长</span>
|
||||
<select className="setup-select" value={setupDuration} onChange={(e) => { setupTouched.current = true; setSetupDuration(clampDuration(Number(e.target.value))); }}>
|
||||
{DURATION_OPTIONS.map((s) => <option key={s} value={s}>{s} 秒</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<CustomSelect
|
||||
fill
|
||||
size="sm"
|
||||
className="setup-select"
|
||||
value={String(setupDuration)}
|
||||
onChange={(next) => { setupTouched.current = true; setSetupDuration(clampDuration(Number(next))); }}
|
||||
options={DURATION_OPTIONS.map((s) => ({ value: String(s), label: `${s} 秒` }))}
|
||||
/>
|
||||
</div>
|
||||
<div className={`setup-rec${durationHint ? " warn" : ""}`}>{durationHint || `口播 × ${VIDEO_STRUCTURES[setupStructure]}建议 ${durationSuggest} 秒左右`}</div>
|
||||
<div className="setup-foot">
|
||||
{/* ← 返回:收起设定卡,回到「脚本辅助生成 / 上传脚本」入口页 */}
|
||||
@@ -4101,9 +4125,13 @@ export function PipelinePage(props: {
|
||||
{voStale && <div style={{ fontSize: "12px", color: "#B45309", marginBottom: 6 }}>字幕文本已修改,与现有配音不一致,建议重新生成</div>}
|
||||
<div className="props-row" style={{ marginBottom: 6 }}>
|
||||
<span className="k">音色</span>
|
||||
<select value={voVoicePick || voInfo?.voice_type || VO_VOICES[0].key} onChange={(e) => setVoVoicePick(e.target.value)} style={{ flex: 1, fontSize: "12px", padding: "4px 6px", border: "1px solid var(--border-faint)", borderRadius: "6px", background: "var(--surface)", color: "var(--accent-black)" }}>
|
||||
{VO_VOICES.map((v) => <option key={v.key} value={v.key}>{v.label}</option>)}
|
||||
</select>
|
||||
<CustomSelect
|
||||
fill
|
||||
size="sm"
|
||||
value={voVoicePick || voInfo?.voice_type || VO_VOICES[0].key}
|
||||
onChange={setVoVoicePick}
|
||||
options={VO_VOICES.map((v) => ({ value: v.key, label: v.label }))}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: 6 }}>
|
||||
<button className="btn btn-sm" type="button" disabled={loading || !edState.clips.some((c) => c.subtitle.trim())} onClick={() => onGenerateVoiceover({ items: edState.clips.map((c, idx) => ({ index: idx, text: c.subtitle.trim() })).filter((item) => item.text), voice_type: voVoicePick || voInfo?.voice_type || VO_VOICES[0].key })}>{voInfo ? "重新生成配音" : `生成配音 · ${pts(10)} 积分/500字`}</button>
|
||||
|
||||
Reference in New Issue
Block a user