fix(create): 创作页 - 上一条还在生成时无法提交下一条 (#754)

提交闸门覆盖了整个生成过程:`submit()` 会一直等到任务出结果才在 finally 里放闸。
于是上一条视频还在跑时点发送会被 `tryAcquire()` 静默拒绝——没有提示、没有反应,
用户只看到按钮在转,以为坏了;重试走的是同一条 submit 路径,一并被挡住。

闸门本来只需要防住「同一次创建任务被重复触发」(连点、回车连击),不该跨越整个
生成过程:

- 闸门改为令牌制:`tryAcquire()` 返回令牌,`release(token)` 只对持有者生效,
  避免并发提交互相解锁;
- 任务一旦建成(bindTask)立刻放闸,与已有的 `busy` 释放时机对齐,因此上一条在
  生成时也能继续提交下一条;
- 被拒绝时给出提示,而不是静默丢弃(这条以前完全没反馈);
- 同步更新闸门单测,补上持有者令牌的并发用例。

验证:`tsc --noEmit`、ESLint、`bun run build` 通过;`bun test` 2204 通过,
唯一失败项 agent-api-reliability 在纯净上游基线上同样失败,与本改动无关。
This commit is contained in:
xupengchong-del
2026-10-13 12:13:27 +08:00
committed by GitHub
parent 237b73a1a3
commit ede6cc1e70
4 changed files with 60 additions and 13 deletions

View File

@@ -1,14 +1,23 @@
/**
* 创作页的提交闸门:只防止「同一次创建任务」被重复触发(快速连点、回车连击)。
*
* 它必须只覆盖提交阶段,不能覆盖整个生成过程——否则上一条还在生成时,
* 后面的提交会被静默丢弃。释放用令牌确认持有者,避免并发提交互相解锁。
*/
export function createCreationSubmitGate() {
let locked = false;
let owner: symbol | null = null;
return {
tryAcquire() {
if (locked) return false;
locked = true;
return true;
/** 取到提交权时返回令牌;已被占用返回 null。 */
tryAcquire(): symbol | null {
if (owner) return null;
const token = Symbol("creation-submit");
owner = token;
return token;
},
release() {
locked = false;
/** 只有仍持有该令牌的调用方才能解锁。 */
release(token: symbol) {
if (owner === token) owner = null;
},
};
}

View File

@@ -588,11 +588,14 @@ export default function CreatePage() {
const releaseRetryLock = () => {
if (retryLockKey) retryPreparingRef.current.delete(retryLockKey);
};
if (!submitGateRef.current.tryAcquire()) {
const submitToken = submitGateRef.current.tryAcquire();
if (!submitToken) {
// 以前这里是静默返回:上一条还在生成时点发送毫无反应,只能靠猜。
releaseRetryLock();
toast.warning("上一条还在提交中,稍等片刻再发送");
return;
}
const releaseSubmitGate = () => submitGateRef.current.release();
const releaseSubmitGate = () => submitGateRef.current.release(submitToken);
const text = prompt.trim();
if (!text || busy || !activeConversation) {
releaseRetryLock();
@@ -680,6 +683,9 @@ export default function CreatePage() {
if (abortRef.current === controller) {
abortRef.current = null;
setBusy(false);
// 任务已经建出来了:提交阶段到此结束。立刻放闸,这样上一条还在生成时
// 也能继续提交下一条,不必等它跑完。
releaseSubmitGate();
}
};
updateActive((conversation) => ({