Files
xiaoxia-saas/apps/web/src/pages/ai-avatar/components/PanelScript.tsx
T
saas-frontend f4aadc5f71
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 0s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m31s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m40s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m42s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m46s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m46s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 4m55s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 5m34s
AI Code Review / AI Code Review (pull_request) Successful in 6m36s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m38s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 13m7s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
fix(ux): #1894 文案/标题数据源全面切到 /api/v1/scripts
根因:
1. api/scripts 中 SCRIPTS_API_MOCK 被硬编码为 true,文案库/AI数字人/智能剪辑
   都拿 mock 数据,用户在文案库新建的文案、AI数字人手动输入的文案在其他
   页面选不到。
2. AI数字人页(PanelTitleConfig)、智能剪辑 step4(useStep4Title)仍在
   调已废弃的 @/api/titles,标题下拉为空。
3. AI数字人页自维护一份 getScripts 本地实现,返回类型/字段与共享
   @/api/scripts 不一致,且未共享 mock 开关。

修复:
- api/scripts/scripts.ts: SCRIPTS_API_MOCK 改为 env 开关(同 POINTS_API_MOCK
  模式,默认 false 走真实 API)。
- 删除废弃 api/titles/ 整个目录 + 相关单测(test/api/titles.test.ts、
  ai-avatar/components/TitleLibraryModal.tsx 孤儿组件)。
- ai-avatar/api/aiAvatar.ts: 文案库相关方法(getScripts/createScript/
  getScript/deleteScript)统一 re-export from @/api/scripts,不再
  本地重复实现。
- ai-avatar/types.ts: Script 类型改为 import("@/api/scripts").ScriptItem
  别名,保证字段对齐。
- ai-avatar/AiAvatarPage.tsx 内 ScriptSelectModalLazy: getScripts 适配
  { items, total } 分页结构,传 page_size:200 一次拿全。
- ai-avatar/components/PanelScript.tsx: 手动输入 tab 下增加「💾 保存到文案库」
  按钮,一键把当前口播文案以正文前20字为默认标题自动入库,解决主人反馈
  「历史文案在新文案库找不到」的问题;保存成功 toast 1s 自动关闭。
- ai-avatar/components/PanelTitleConfig.tsx: 标题 AutoComplete 数据源改为
  getScripts(),取 scripts[].title 去重作为候选,文案从「📚 标题库」
  改为「📚 文案库标题」。
- generate/hooks/useStep4Title/index.ts: userTitles 数据源改为 getScripts(),
  映射为 { content: s.title },复用下游消费逻辑(ut.content)。
- test/pages/GeneratePage.test.tsx: 把 @/api/titles mock 替换为
  @/api/scripts mock。

待跟进:
- P2 抖音错误提示友好化(等后端 #1963 合入后联调)。
2026-09-17 00:15:42 +08:00

107 lines
3.3 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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