b68c29c69b
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 API Image (push) Failing after 1m5s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 1m50s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 2m33s
CI/CD Pipeline / Integration Tests (push) Successful in 3m53s
CI/CD Pipeline / Validate - Style (push) Successful in 3m55s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 5m46s
CI/CD Pipeline / Unit Tests (push) Successful in 9m34s
CI/CD Pipeline / Validate - Security (push) Successful in 9m40s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Failing after 17m11s
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 / Deploy Staging (Watchtower auto-deploy) (push) Failing after 5m47s
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
107 lines
3.3 KiB
TypeScript
Executable File
107 lines
3.3 KiB
TypeScript
Executable File
/**
|
||
* AI数字人 — 文案面板(步骤1用)
|
||
* #1894: 文案库选择走 @/api/scripts;手动输入支持一键「保存到文案库」
|
||
*/
|
||
import { useState } from "react"
|
||
import { message } from "antd"
|
||
import { createScript } from "../api/aiAvatar"
|
||
|
||
interface PanelScriptProps {
|
||
scriptText: string
|
||
onScriptTextChange: (text: string) => void
|
||
onOpenScriptModal: () => void
|
||
/** 手动保存到文案库后回调(把新脚本传入,父组件可更新 selectedScript) */
|
||
onScriptCreated?: (script: { id: string; title: string; content: string }) => void
|
||
}
|
||
|
||
type ScriptTab = "library" | "manual"
|
||
|
||
export function PanelScript({
|
||
scriptText,
|
||
onScriptTextChange,
|
||
onOpenScriptModal,
|
||
onScriptCreated,
|
||
}: PanelScriptProps) {
|
||
const [scriptTab, setScriptTab] = useState<ScriptTab>("library")
|
||
const [saving, setSaving] = useState(false)
|
||
|
||
const handleSaveToLibrary = async () => {
|
||
const text = scriptText.trim()
|
||
if (!text) {
|
||
message.warning("请先输入文案内容")
|
||
return
|
||
}
|
||
// 用正文前 20 字作为默认标题
|
||
const autoTitle = text.slice(0, 20).replace(/\n+/g, " ").trim() || "手动输入文案"
|
||
setSaving(true)
|
||
try {
|
||
const created = await createScript({ title: autoTitle, content: text, tags: [] })
|
||
message.success({ content: "已保存到文案库", duration: 1 })
|
||
onScriptCreated?.(created)
|
||
} catch {
|
||
message.error("保存到文案库失败,请稍后重试")
|
||
} finally {
|
||
setSaving(false)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="aa-script-lipsync">
|
||
{/* ── Tab 切换 ── */}
|
||
<div className="aa-script-tabs">
|
||
<button
|
||
type="button"
|
||
className={`aa-script-tab${scriptTab === "library" ? " active" : ""}`}
|
||
onClick={() => setScriptTab("library")}
|
||
>
|
||
从文案库选择
|
||
</button>
|
||
<button
|
||
type="button"
|
||
className={`aa-script-tab${scriptTab === "manual" ? " active" : ""}`}
|
||
onClick={() => setScriptTab("manual")}
|
||
>
|
||
手动输入
|
||
</button>
|
||
</div>
|
||
|
||
{scriptTab === "library" && (
|
||
<button
|
||
type="button"
|
||
className="aa-btn aa-btn--ghost aa-btn--full"
|
||
style={{ marginBottom: 8 }}
|
||
onClick={onOpenScriptModal}
|
||
>
|
||
📚 从文案库选择文案
|
||
</button>
|
||
)}
|
||
|
||
<textarea
|
||
className="aa-textarea"
|
||
value={scriptText}
|
||
readOnly={scriptTab === "library"}
|
||
placeholder={
|
||
scriptTab === "library" ? "点击上方按钮,从文案库选择文案…" : "请输入数字人口播文案…"
|
||
}
|
||
onChange={(e) => onScriptTextChange(e.target.value)}
|
||
/>
|
||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||
<div className="aa-char-count">{scriptText.length} 字</div>
|
||
{scriptTab === "manual" && scriptText.trim().length > 0 && (
|
||
<button
|
||
type="button"
|
||
className="aa-btn aa-btn--text"
|
||
disabled={saving}
|
||
onClick={handleSaveToLibrary}
|
||
style={{ fontSize: 12, padding: "2px 8px" }}
|
||
>
|
||
{saving ? "保存中..." : "💾 保存到文案库"}
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default PanelScript
|