f9daa08b2e
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check push changed paths (push) Successful in 2s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 31s
CI/CD Pipeline / Build Staging API Image (push) Successful in 40s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m46s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 2m29s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m1s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 36s
CI/CD Pipeline / Integration Tests (push) Successful in 4m53s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m36s
CI/CD Pipeline / Validate - Style (push) Failing after 5m37s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 6m10s
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
166 lines
5.6 KiB
TypeScript
166 lines
5.6 KiB
TypeScript
/**
|
||
* Step 1 素材选择组件(#1899 原 Step2 前移,#1913 传递 selectedTemplate)
|
||
* 新增片段数量输入,系统按片段数智能匹配素材
|
||
*/
|
||
import React from "react"
|
||
import type { EditPlanClip } from "@/api/template-editor"
|
||
import { useStep2Materials } from "../hooks/useStep2Materials"
|
||
import MaterialModeTabs from "./material/MaterialModeTabs"
|
||
import ManualMaterialList from "./material/ManualMaterialList"
|
||
import SmartMatchInput from "./material/SmartMatchInput"
|
||
import SmartMatchResults from "./material/SmartMatchResults"
|
||
|
||
interface Step2MaterialSelectProps {
|
||
/** 剪辑模式:random 随机混剪 / narrative 叙事剪辑(#1970) */
|
||
editMode?: "random" | "narrative"
|
||
materialMode: "manual" | "auto"
|
||
onMaterialModeChange: (mode: "manual" | "auto") => void
|
||
selectedMaterials: string[]
|
||
onSelectedMaterialsChange: (ids: string[]) => void
|
||
smartSelectedIds: string[]
|
||
onSmartSelectedIdsChange: (ids: string[]) => void
|
||
/** 片段数量(#1899) */
|
||
clipCount: number
|
||
onClipCountChange: (n: number) => void
|
||
/** 当前选中的模板/草稿 ID;空字符串时由后端自动兜底(#1913) */
|
||
selectedTemplate?: string
|
||
/** 服务端 clips 创建成功后的回调 */
|
||
onServerClipsChange?: (clips: EditPlanClip[]) => void
|
||
}
|
||
|
||
const Step2MaterialSelect: React.FC<Step2MaterialSelectProps> = (props) => {
|
||
const m = useStep2Materials({
|
||
materialMode: props.materialMode,
|
||
onMaterialModeChange: props.onMaterialModeChange,
|
||
selectedMaterials: props.selectedMaterials,
|
||
onSelectedMaterialsChange: props.onSelectedMaterialsChange,
|
||
smartSelectedIds: props.smartSelectedIds,
|
||
onSmartSelectedIdsChange: props.onSmartSelectedIdsChange,
|
||
clipCount: props.clipCount,
|
||
selectedTemplate: props.selectedTemplate,
|
||
onServerClipsChange: props.onServerClipsChange,
|
||
})
|
||
|
||
return (
|
||
<div className="xx-form-section">
|
||
<h3>📦 选择素材</h3>
|
||
|
||
{/* 叙事剪辑:AI 智能匹配提示卡(#1970) */}
|
||
{props.editMode === "narrative" && (
|
||
<div
|
||
style={{
|
||
marginTop: 12,
|
||
padding: "12px 16px",
|
||
background: "linear-gradient(135deg,#ede9fe,#f5f3ff)",
|
||
border: "1px solid #c4b5fd",
|
||
borderRadius: 8,
|
||
fontSize: 13,
|
||
color: "#5b21b6",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 8,
|
||
}}
|
||
>
|
||
<span style={{ fontSize: 18 }}>🤖</span>
|
||
<span>AI智能匹配:系统将根据您的文案内容,从素材库自动匹配合适的视频片段</span>
|
||
</div>
|
||
)}
|
||
|
||
{/* 片段数量(#1899) */}
|
||
<div className="xx-form-field" style={{ marginTop: 12 }}>
|
||
<label>片段数量</label>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
|
||
<input
|
||
type="number"
|
||
min={1}
|
||
max={10}
|
||
step={1}
|
||
value={props.clipCount}
|
||
onChange={(e) => {
|
||
const v = parseInt(e.target.value, 10)
|
||
if (!isNaN(v)) props.onClipCountChange(Math.max(1, Math.min(10, v)))
|
||
}}
|
||
style={{
|
||
width: 80,
|
||
padding: "8px 12px",
|
||
border: "1px solid var(--line, #e5e7eb)",
|
||
borderRadius: 8,
|
||
background: "var(--bg-card, #fff)",
|
||
color: "var(--text-primary, #111)",
|
||
fontSize: 14,
|
||
}}
|
||
/>
|
||
<span style={{ fontSize: 12, color: "var(--text-secondary, #6b7280)" }}>
|
||
系统将按片段数量智能匹配素材(1-10,默认3)
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<MaterialModeTabs mode={m.materialMode} onModeChange={m.onMaterialModeChange} />
|
||
|
||
<div className="xx-form-field" style={{ marginTop: 12 }}>
|
||
<label>选择视频库</label>
|
||
{m.libraries.length === 0 && !m.materialsLoading ? (
|
||
<div className="xx-empty-state">
|
||
<p>暂无视频素材库</p>
|
||
<p style={{ fontSize: 13, color: "var(--text-tertiary)" }}>
|
||
请先在「素材库」中创建视频素材库并上传视频
|
||
</p>
|
||
</div>
|
||
) : (
|
||
<select
|
||
value={m.selectedLibraryId}
|
||
onChange={(e) => m.setSelectedLibraryId(e.target.value)}
|
||
>
|
||
{m.libraries.map((lib) => (
|
||
<option key={lib.id} value={lib.id}>
|
||
{lib.name}
|
||
</option>
|
||
))}
|
||
</select>
|
||
)}
|
||
</div>
|
||
|
||
{m.materialMode === "manual" && (
|
||
<>
|
||
<div
|
||
style={{
|
||
marginTop: 14,
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 10,
|
||
}}
|
||
>
|
||
<span className="xx-pill xx-pill-ok">已选 {m.selectedMaterials.length} 个素材</span>
|
||
</div>
|
||
|
||
<ManualMaterialList
|
||
materials={m.selectableMaterials}
|
||
materialsLoading={m.materialsLoading}
|
||
allExhausted={m.allMaterialsExhausted}
|
||
selectedMaterials={m.selectedMaterials}
|
||
onToggle={m.handleToggleMaterial}
|
||
/>
|
||
</>
|
||
)}
|
||
|
||
{m.materialMode === "auto" && (
|
||
<div className="xx-smart-match-section">
|
||
<SmartMatchInput
|
||
matching={m.smartMatching}
|
||
onMatch={m.handleSmartMatch}
|
||
hasMatched={m.hasMatched}
|
||
onRefresh={m.handleRefreshMatch}
|
||
materialsCount={m.selectableMaterials.items.length}
|
||
loading={m.materialsLoading}
|
||
/>
|
||
|
||
<SmartMatchResults matching={m.smartMatching} hasMatched={m.hasMatched} />
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default Step2MaterialSelect
|