|
|
@@ -16,6 +16,25 @@ const stripScopeBlocks = (content: string) => {
|
|
|
.replace(/<scope\b[^>]*>[\s\S]*/gi, "");
|
|
|
};
|
|
|
|
|
|
+/** 剥掉 `<silence>` 与 `<scope>` 块后剩下的内容 */
|
|
|
+const stripNonVisibleBlocks = (content: unknown) =>
|
|
|
+ stripScopeBlocks(stripSilenceTags(String(content || "")));
|
|
|
+
|
|
|
+/**
|
|
|
+ * 内容在"可见"意义下是否为空。
|
|
|
+ *
|
|
|
+ * ⚠️ 这是**唯一**的判空口径。`normalizeSessionHistory`(shared.ts)与本文件的
|
|
|
+ * `isInterruptedEmptyScopeMessage` 都必须用它。
|
|
|
+ *
|
|
|
+ * 曾经两处口径不一致——一处用 `content.trim()`(`<scope>` 进度块算作有内容),
|
|
|
+ * 一处用 `stripScopeBlocks()`(进度块不算内容)。而新协议的正文要等到 `done`
|
|
|
+ * 才写入,处理期间内容**只有进度块**,于是切会话时:
|
|
|
+ * 归一化认为"有内容"→ 不替换文案、把状态翻成 Finish;
|
|
|
+ * 渲染层认为"没内容"→ 显示「请求已取消」。请求其实还在跑,属于误报。
|
|
|
+ */
|
|
|
+export const hasVisibleMessageContent = (content: unknown): boolean =>
|
|
|
+ stripNonVisibleBlocks(content).trim().length > 0;
|
|
|
+
|
|
|
export function isInterruptedEmptyScopeMessage(message: any): boolean {
|
|
|
if (!message) {
|
|
|
return false;
|
|
|
@@ -35,12 +54,13 @@ export function isInterruptedEmptyScopeMessage(message: any): boolean {
|
|
|
const content = String(message.content || "");
|
|
|
const visibleContent = stripEmptyScopeBlocks(stripSilenceTags(content)).trim();
|
|
|
|
|
|
+ // 统一口径:见 hasVisibleMessageContent 的说明
|
|
|
if (message.history === true) {
|
|
|
- return stripScopeBlocks(stripSilenceTags(content)).trim().length === 0;
|
|
|
+ return !hasVisibleMessageContent(content);
|
|
|
}
|
|
|
|
|
|
if (STOPPED_STATES.has(state)) {
|
|
|
- return stripScopeBlocks(stripSilenceTags(content)).trim().length === 0;
|
|
|
+ return !hasVisibleMessageContent(content);
|
|
|
}
|
|
|
|
|
|
if (!/<scope\b/i.test(content)) {
|