fix(frontend): sync generated conversation title
This commit is contained in:
parent
16473c824d
commit
eaf270766f
@ -1675,7 +1675,8 @@ def get_current_conversation(terminal: WebTerminal, workspace: UserWorkspace, us
|
|||||||
"id": current_id,
|
"id": current_id,
|
||||||
"title": "新对话",
|
"title": "新对话",
|
||||||
"messages_count": 0,
|
"messages_count": 0,
|
||||||
"is_temporary": True
|
"is_temporary": True,
|
||||||
|
"title_locked": False
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1683,13 +1684,15 @@ def get_current_conversation(terminal: WebTerminal, workspace: UserWorkspace, us
|
|||||||
try:
|
try:
|
||||||
conversation_data = terminal.context_manager.conversation_manager.load_conversation(current_id)
|
conversation_data = terminal.context_manager.conversation_manager.load_conversation(current_id)
|
||||||
if conversation_data:
|
if conversation_data:
|
||||||
|
metadata = conversation_data.get("metadata", {}) or {}
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"success": True,
|
"success": True,
|
||||||
"data": {
|
"data": {
|
||||||
"id": current_id,
|
"id": current_id,
|
||||||
"title": conversation_data.get("title", "未知对话"),
|
"title": conversation_data.get("title", "未知对话"),
|
||||||
"messages_count": len(conversation_data.get("messages", [])),
|
"messages_count": len(conversation_data.get("messages", [])),
|
||||||
"is_temporary": False
|
"is_temporary": False,
|
||||||
|
"title_locked": bool(metadata.get("title_locked", False))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -1322,6 +1322,141 @@ export const taskPollingMethods = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isPlaceholderConversationTitle(title: any) {
|
||||||
|
const normalized = String(title || '').trim();
|
||||||
|
return !normalized || normalized === '新对话';
|
||||||
|
},
|
||||||
|
|
||||||
|
applyConversationTitleUpdate(conversationId: string, title: string, source = 'unknown') {
|
||||||
|
const normalizedTitle = String(title || '').trim();
|
||||||
|
const normalizedConversationId = String(conversationId || '').trim();
|
||||||
|
if (!normalizedConversationId || this.isPlaceholderConversationTitle(normalizedTitle)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
if (normalizedConversationId === this.currentConversationId) {
|
||||||
|
if (this.currentConversationTitle !== normalizedTitle) {
|
||||||
|
// 确保 watcher 能从“新对话”切到真实标题并播放现有标题打字动画。
|
||||||
|
this.suppressTitleTyping = false;
|
||||||
|
this.titleReady = true;
|
||||||
|
this.currentConversationTitle = normalizedTitle;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(this.conversations)) {
|
||||||
|
let listChanged = false;
|
||||||
|
const nextConversations = this.conversations.map((conv: any) => {
|
||||||
|
if (!conv || conv.id !== normalizedConversationId || conv.title === normalizedTitle) {
|
||||||
|
return conv;
|
||||||
|
}
|
||||||
|
listChanged = true;
|
||||||
|
return {
|
||||||
|
...conv,
|
||||||
|
title: normalizedTitle
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (listChanged) {
|
||||||
|
this.conversations = nextConversations;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
debugLog('[TaskPolling] 已同步生成后的对话标题:', {
|
||||||
|
conversationId: normalizedConversationId,
|
||||||
|
title: normalizedTitle,
|
||||||
|
source
|
||||||
|
});
|
||||||
|
this.$forceUpdate();
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchGeneratedConversationTitle(conversationId: string, source = 'unknown') {
|
||||||
|
const normalizedConversationId = String(conversationId || '').trim();
|
||||||
|
if (!normalizedConversationId || normalizedConversationId !== this.currentConversationId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/conversations/current', { cache: 'no-store' });
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}`);
|
||||||
|
}
|
||||||
|
const result = await response.json();
|
||||||
|
const data = result?.data || {};
|
||||||
|
if (!result?.success || data.id !== normalizedConversationId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const normalizedTitle = String(data.title || '').trim();
|
||||||
|
const titleLocked = Boolean(data.title_locked || data.metadata?.title_locked);
|
||||||
|
if (this.isPlaceholderConversationTitle(normalizedTitle) || !titleLocked) {
|
||||||
|
// Unlocked titles are usually first-message fallback titles; keep waiting for the final generated title.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.applyConversationTitleUpdate(normalizedConversationId, normalizedTitle, source);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[TaskPolling] 同步生成后的对话标题失败:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
scheduleGeneratedTitleRefresh(reason = 'unknown', options: any = {}) {
|
||||||
|
const conversationId = String(options.conversationId || this.currentConversationId || '').trim();
|
||||||
|
if (!conversationId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标题模型是后台独立请求,慢时可能在主任务完成后几十秒才落盘。
|
||||||
|
// 这里用“总等待窗口”而不是少量固定次数,避免刚好错过最终 title_locked=true 的标题。
|
||||||
|
const deadlineMs = Math.max(15000, Number(options.deadlineMs || 90000));
|
||||||
|
const maxAttempts = Math.max(1, Number(options.maxAttempts || 60));
|
||||||
|
const delays = Array.isArray(options.delays)
|
||||||
|
? options.delays
|
||||||
|
: [0, 800, 1200, 1600, 2200, 3000, 4000, 5000, 6000];
|
||||||
|
const startedAt = Date.now();
|
||||||
|
const seq = Number(this.generatedTitleRefreshSeq || 0) + 1;
|
||||||
|
this.generatedTitleRefreshSeq = seq;
|
||||||
|
if (this.generatedTitleRefreshTimer) {
|
||||||
|
clearTimeout(this.generatedTitleRefreshTimer);
|
||||||
|
this.generatedTitleRefreshTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const run = async (attempt: number) => {
|
||||||
|
if (this.generatedTitleRefreshSeq !== seq || this.currentConversationId !== conversationId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.generatedTitleRefreshTimer = null;
|
||||||
|
const updated = await this.fetchGeneratedConversationTitle(
|
||||||
|
conversationId,
|
||||||
|
`${reason}:attempt-${attempt}`
|
||||||
|
);
|
||||||
|
if (updated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const elapsedMs = Date.now() - startedAt;
|
||||||
|
if (attempt >= maxAttempts || elapsedMs >= deadlineMs) {
|
||||||
|
debugLog('[TaskPolling] 停止等待生成标题', {
|
||||||
|
reason,
|
||||||
|
conversationId,
|
||||||
|
attempt,
|
||||||
|
elapsedMs,
|
||||||
|
deadlineMs
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const delay = Number(delays[Math.min(attempt, delays.length - 1)] || 1000);
|
||||||
|
const nextDelay = Math.min(delay, Math.max(0, deadlineMs - elapsedMs));
|
||||||
|
this.generatedTitleRefreshTimer = window.setTimeout(() => {
|
||||||
|
void run(attempt + 1);
|
||||||
|
}, nextDelay);
|
||||||
|
};
|
||||||
|
|
||||||
|
void run(1);
|
||||||
|
},
|
||||||
|
|
||||||
handleTaskComplete(data: any) {
|
handleTaskComplete(data: any) {
|
||||||
const pendingToolsBefore =
|
const pendingToolsBefore =
|
||||||
typeof this.hasPendingToolActions === 'function' ? this.hasPendingToolActions() : null;
|
typeof this.hasPendingToolActions === 'function' ? this.hasPendingToolActions() : null;
|
||||||
@ -1410,6 +1545,12 @@ export const taskPollingMethods = {
|
|||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
this.scheduleTodoListRefresh(100);
|
this.scheduleTodoListRefresh(100);
|
||||||
|
// 标题生成可能晚于主任务完成;主轮询停止后主动短轮询当前会话标题,避免必须刷新页面。
|
||||||
|
this.scheduleGeneratedTitleRefresh('task_complete', {
|
||||||
|
conversationId: data?.conversation_id || this.currentConversationId,
|
||||||
|
deadlineMs: 90000,
|
||||||
|
maxAttempts: 60
|
||||||
|
});
|
||||||
const pendingToolsAfter =
|
const pendingToolsAfter =
|
||||||
typeof this.hasPendingToolActions === 'function' ? this.hasPendingToolActions() : null;
|
typeof this.hasPendingToolActions === 'function' ? this.hasPendingToolActions() : null;
|
||||||
jsonDebug('handleTaskComplete:after', {
|
jsonDebug('handleTaskComplete:after', {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user