fix: 同步视频项目积分余额

This commit is contained in:
hh
2026-07-16 13:40:25 +08:00
parent 68ab717cb8
commit 95cd4abd3c
3 changed files with 36 additions and 8 deletions
+18 -4
View File
@@ -519,6 +519,7 @@ export function PipelinePage(props: {
exportResult: ExportPoll | null;
onRefreshExport: () => void;
onRefreshProject: () => Promise<void> | void;
onRefreshBilling: () => Promise<void> | void;
onUploadVideoSegment: (segmentId: string, file: File) => void;
onUploadBgm: (file: File, volume: number) => void;
onSaveTimeline: (payload: TimelineSavePayload) => void;
@@ -528,7 +529,7 @@ export function PipelinePage(props: {
project, loading, navigate, user, team, products, projects, assets, billing, notice, unreadCount, avatarChar, logout, onNotify,
textModels, onAdoptScript, onUpdateShot, onAddShot, onDeleteShot, onRerunShot, onSaveProjectMeta, onAdoptVideoVersion, onGenerateVoiceover,
onGenerateBaseAsset, onAdoptBaseAsset, onSetAdoptState, onDeleteBaseAsset, onAttachBaseAsset, onGenerateModel, onUploadModel, onGenerateTriview, onRenameModel, onGenerateStoryboard, onRerunStoryboardShot, onAdoptStoryboardShotVersion, onPollStoryboardQuiet, onSkipStoryboard,
onSubmitVideo, onSubmitAllVideos, onPollVideosQuiet, exportResult, onRefreshExport, onRefreshProject,
onSubmitVideo, onSubmitAllVideos, onPollVideosQuiet, exportResult, onRefreshExport, onRefreshProject, onRefreshBilling,
onUploadVideoSegment, onUploadBgm, onSaveTimeline, onSubmitExport
} = props;
@@ -775,6 +776,8 @@ export function PipelinePage(props: {
// (基础资产页「一直在轮询」的根因之一)。用 ref 稳住,effect 不再因它重跑。
const onRefreshProjectRef = useRef(onRefreshProject);
useEffect(() => { onRefreshProjectRef.current = onRefreshProject; });
const onRefreshBillingRef = useRef(onRefreshBilling);
useEffect(() => { onRefreshBillingRef.current = onRefreshBilling; });
// 审核轮询的「重启信号」:手动送审/一键送审后 +1,让已停的 poll-reviews 重新开跑去盯新提交的审核。
const [reviewWake, setReviewWake] = useState(0);
// 行38/流程步骤4 · 单卡生成 loading:按「busyKey」分别记录,各按钮互不影响(生成三视图不挡新增人物)
@@ -848,6 +851,7 @@ export function PipelinePage(props: {
finish();
if (s.status === "succeeded") {
await onRefreshProject(); // 拉回 metadata(cast/scenes/script_entities)+ 回填的每镜 refs
await onRefreshBilling(); // 实体提取成功已产生真实扣费,同步右上角现有余额
if ((mode === "gen" || mode === "full") && (s.entities?.length ?? 0) > 0) {
await runGenForEntities(s.entities, mode);
}
@@ -1508,6 +1512,7 @@ export function PipelinePage(props: {
setChatMsgs((list) => list.map((m) => (m.id === progressId ? { ...m, done: true, thinkingElapsedSeconds: m.thinkingElapsedSeconds ?? elapsedSinceStart(m.startedAt), thinkingDurationReady: m.thinkingDurationReady ?? false } : m)));
if (ok) {
await onRefreshProject();
await onRefreshBilling(); // SSE 的 saved 事件在后端完成脚本扣费后才发出
// 模型写了收尾就用它(真 agent 感);没写则兜底默认句
pushMsg("ai", summaryText || "镜头脚本已生成,左侧已刷新。可继续输入修改意见,或点「确认脚本」进入下一步。");
}
@@ -2505,7 +2510,7 @@ export function PipelinePage(props: {
// 流程步骤4 · 在基础资产趴轮询后端在途出图任务,重建「生成中」占位卡 loading(刷新后内存态丢失也能恢复);
// 在途数减少=有任务出图完成 → 刷新项目把占位卡换成真卡。离开该趴即停。
const prevPendingCountRef = useRef(0);
const prevPendingIdsRef = useRef<string[]>([]);
useEffect(() => {
if (activeDot !== 2 && viewStage !== 2) return;
let stopped = false;
@@ -2516,8 +2521,15 @@ export function PipelinePage(props: {
const res = await api.pendingAssets(project.id);
if (stopped) return;
const list = res.pending || [];
if (list.length < prevPendingCountRef.current) void onRefreshProjectRef.current();
prevPendingCountRef.current = list.length;
const completedIds = prevPendingIdsRef.current.filter((id) => !list.some((pending) => pending.id === id));
if (completedIds.length > 0) {
void onRefreshProjectRef.current();
// 刷新后认领的在途出图:先确认终态成功,失败/退款不刷新右上角余额。
void api.generateImageStatus(completedIds).then((result) => {
if (result.tasks.some((task) => task.status === "succeeded")) void onRefreshBillingRef.current();
}).catch(() => undefined);
}
prevPendingIdsRef.current = list.map((pending) => pending.id);
setPendingGen(list.map((p) => ({ kind: p.kind, label: p.label, is_triview: p.is_triview })));
// 没有在途出图、本地也没有生成在跑 → 没什么可等,停轮询;再点生成(genBusy 变)时本 effect 会重订阅恢复。
if (list.length === 0 && genBusy.size === 0) { stopped = true; return; }
@@ -2973,6 +2985,7 @@ export function PipelinePage(props: {
<div className="eg-title"> / </div>
<div className="mono eg-sub">// AI 认出角色 / 场景并出提示词,稍后你再逐个生成</div>
<button type="button" className="btn btn-primary btn-lg eg-extract-btn" onClick={() => void runExtract("only")}> / </button>
<div className="mono eg-cost-hint">// {pts(10)} 积分/次 · 失败不扣</div>
{extractErr && <div className="eg-err">{extractErr}</div>}
</>
)}
@@ -2998,6 +3011,7 @@ export function PipelinePage(props: {
<div className="info">
,
<br /><br />
<strong className="mono">// 提取角色 / 场景 +{pts(10)} 积分/次</strong>
<strong className="mono">// 人物 +{pts(20)} 积分/张</strong>
<strong className="mono">// 场景 +{pts(20)} 积分/张</strong>
<span style={{ color: "var(--black-alpha-48)" }}>()</span>