fix: keep model activity alive and switch new chats
This commit is contained in:
parent
427a1f7ea8
commit
7b735e252f
@ -105,6 +105,16 @@ function debugLog(...args) {
|
|||||||
}
|
}
|
||||||
debugLog(...args);
|
debugLog(...args);
|
||||||
}
|
}
|
||||||
|
// 临时排查对话切换问题的调试输出
|
||||||
|
const TRACE_CONV = true;
|
||||||
|
const traceLog = (...args) => {
|
||||||
|
if (!TRACE_CONV) return;
|
||||||
|
try {
|
||||||
|
console.log('[conv-trace]', ...args);
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const appOptions = {
|
const appOptions = {
|
||||||
data() {
|
data() {
|
||||||
@ -136,6 +146,8 @@ const appOptions = {
|
|||||||
skipConversationHistoryReload: false,
|
skipConversationHistoryReload: false,
|
||||||
_scrollListenerReady: false,
|
_scrollListenerReady: false,
|
||||||
historyLoading: false,
|
historyLoading: false,
|
||||||
|
historyLoadingFor: null,
|
||||||
|
historyLoadSeq: 0,
|
||||||
mobileViewportQuery: null,
|
mobileViewportQuery: null,
|
||||||
modeMenuOpen: false,
|
modeMenuOpen: false,
|
||||||
conversationListRequestSeq: 0,
|
conversationListRequestSeq: 0,
|
||||||
@ -326,19 +338,27 @@ const appOptions = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
inputMessage() {
|
inputMessage() {
|
||||||
this.autoResizeInput();
|
this.autoResizeInput();
|
||||||
},
|
},
|
||||||
currentConversationId: {
|
currentConversationId: {
|
||||||
immediate: false,
|
immediate: false,
|
||||||
handler(newValue, oldValue) {
|
handler(newValue, oldValue) {
|
||||||
debugLog('currentConversationId 变化', { oldValue, newValue, skipConversationHistoryReload: this.skipConversationHistoryReload });
|
debugLog('currentConversationId 变化', { oldValue, newValue, skipConversationHistoryReload: this.skipConversationHistoryReload });
|
||||||
this.logMessageState('watch:currentConversationId', { oldValue, newValue, skipConversationHistoryReload: this.skipConversationHistoryReload });
|
traceLog('watch:currentConversationId', {
|
||||||
if (!newValue || typeof newValue !== 'string' || newValue.startsWith('temp_')) {
|
oldValue,
|
||||||
return;
|
newValue,
|
||||||
}
|
skipConversationHistoryReload: this.skipConversationHistoryReload,
|
||||||
if (this.skipConversationHistoryReload) {
|
historyLoading: this.historyLoading,
|
||||||
|
historyLoadingFor: this.historyLoadingFor,
|
||||||
|
historyLoadSeq: this.historyLoadSeq
|
||||||
|
});
|
||||||
|
this.logMessageState('watch:currentConversationId', { oldValue, newValue, skipConversationHistoryReload: this.skipConversationHistoryReload });
|
||||||
|
if (!newValue || typeof newValue !== 'string' || newValue.startsWith('temp_')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.skipConversationHistoryReload) {
|
||||||
this.skipConversationHistoryReload = false;
|
this.skipConversationHistoryReload = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1295,12 +1315,15 @@ const appOptions = {
|
|||||||
this.loadingMoreConversations = false;
|
this.loadingMoreConversations = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
async loadConversation(conversationId) {
|
async loadConversation(conversationId, options = {}) {
|
||||||
|
const force = Boolean(options.force);
|
||||||
debugLog('加载对话:', conversationId);
|
debugLog('加载对话:', conversationId);
|
||||||
this.logMessageState('loadConversation:start', { conversationId });
|
traceLog('loadConversation:start', { conversationId, currentConversationId: this.currentConversationId, force });
|
||||||
|
this.logMessageState('loadConversation:start', { conversationId, force });
|
||||||
|
|
||||||
if (conversationId === this.currentConversationId) {
|
if (!force && conversationId === this.currentConversationId) {
|
||||||
debugLog('已是当前对话,跳过加载');
|
debugLog('已是当前对话,跳过加载');
|
||||||
|
traceLog('loadConversation:skip-same', { conversationId });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1313,6 +1336,7 @@ const appOptions = {
|
|||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
debugLog('对话加载API成功:', result);
|
debugLog('对话加载API成功:', result);
|
||||||
|
traceLog('loadConversation:api-success', { conversationId, title: result.title });
|
||||||
|
|
||||||
// 2. 更新当前对话信息
|
// 2. 更新当前对话信息
|
||||||
this.skipConversationHistoryReload = true;
|
this.skipConversationHistoryReload = true;
|
||||||
@ -1331,6 +1355,10 @@ const appOptions = {
|
|||||||
await this.fetchAndDisplayHistory();
|
await this.fetchAndDisplayHistory();
|
||||||
this.fetchConversationTokenStatistics();
|
this.fetchConversationTokenStatistics();
|
||||||
this.updateCurrentContextTokens();
|
this.updateCurrentContextTokens();
|
||||||
|
traceLog('loadConversation:after-history', {
|
||||||
|
conversationId,
|
||||||
|
messagesLen: Array.isArray(this.messages) ? this.messages.length : 'n/a'
|
||||||
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.error('对话加载失败:', result.message);
|
console.error('对话加载失败:', result.message);
|
||||||
@ -1342,6 +1370,7 @@ const appOptions = {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载对话异常:', error);
|
console.error('加载对话异常:', error);
|
||||||
|
traceLog('loadConversation:error', { conversationId, error: error?.message || String(error) });
|
||||||
this.uiPushToast({
|
this.uiPushToast({
|
||||||
title: '加载对话异常',
|
title: '加载对话异常',
|
||||||
message: error.message || String(error),
|
message: error.message || String(error),
|
||||||
@ -1365,23 +1394,28 @@ const appOptions = {
|
|||||||
// 关键功能:获取并显示历史对话内容
|
// 关键功能:获取并显示历史对话内容
|
||||||
// ==========================================
|
// ==========================================
|
||||||
async fetchAndDisplayHistory() {
|
async fetchAndDisplayHistory() {
|
||||||
if (this.historyLoading) {
|
const targetConversationId = this.currentConversationId;
|
||||||
debugLog('历史消息正在加载,跳过重复请求');
|
if (!targetConversationId || targetConversationId.startsWith('temp_')) {
|
||||||
|
debugLog('没有当前对话ID,跳过历史加载');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 若同一对话正在加载,直接复用;若是切换对话则允许并发但后来的请求会赢
|
||||||
|
if (this.historyLoading && this.historyLoadingFor === targetConversationId) {
|
||||||
|
debugLog('同一对话历史正在加载,跳过重复请求');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadSeq = ++this.historyLoadSeq;
|
||||||
this.historyLoading = true;
|
this.historyLoading = true;
|
||||||
|
this.historyLoadingFor = targetConversationId;
|
||||||
try {
|
try {
|
||||||
debugLog('开始获取历史对话内容...');
|
debugLog('开始获取历史对话内容...');
|
||||||
this.logMessageState('fetchAndDisplayHistory:start', { conversationId: this.currentConversationId });
|
this.logMessageState('fetchAndDisplayHistory:start', { conversationId: this.currentConversationId });
|
||||||
|
|
||||||
if (!this.currentConversationId || this.currentConversationId.startsWith('temp_')) {
|
|
||||||
debugLog('没有当前对话ID,跳过历史加载');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 使用专门的API获取对话消息历史
|
// 使用专门的API获取对话消息历史
|
||||||
const messagesResponse = await fetch(`/api/conversations/${this.currentConversationId}/messages`);
|
const messagesResponse = await fetch(`/api/conversations/${targetConversationId}/messages`);
|
||||||
|
|
||||||
if (!messagesResponse.ok) {
|
if (!messagesResponse.ok) {
|
||||||
console.warn('无法获取消息历史,尝试备用方法');
|
console.warn('无法获取消息历史,尝试备用方法');
|
||||||
@ -1401,6 +1435,12 @@ const appOptions = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果在等待期间用户已切换到其他对话,则丢弃结果
|
||||||
|
if (loadSeq !== this.historyLoadSeq || this.currentConversationId !== targetConversationId) {
|
||||||
|
debugLog('检测到对话已切换,丢弃过期的历史加载结果');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const messagesData = await messagesResponse.json();
|
const messagesData = await messagesResponse.json();
|
||||||
debugLog('获取到消息数据:', messagesData);
|
debugLog('获取到消息数据:', messagesData);
|
||||||
|
|
||||||
@ -1445,7 +1485,11 @@ const appOptions = {
|
|||||||
this.logMessageState('fetchAndDisplayHistory:error-cleared');
|
this.logMessageState('fetchAndDisplayHistory:error-cleared');
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.historyLoading = false;
|
// 仅在本次加载仍是最新请求时清除 loading 状态
|
||||||
|
if (loadSeq === this.historyLoadSeq) {
|
||||||
|
this.historyLoading = false;
|
||||||
|
this.historyLoadingFor = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1692,6 +1736,10 @@ const appOptions = {
|
|||||||
|
|
||||||
async createNewConversation() {
|
async createNewConversation() {
|
||||||
debugLog('创建新对话...');
|
debugLog('创建新对话...');
|
||||||
|
traceLog('createNewConversation:start', {
|
||||||
|
currentConversationId: this.currentConversationId,
|
||||||
|
convCount: Array.isArray(this.conversations) ? this.conversations.length : 'n/a'
|
||||||
|
});
|
||||||
this.logMessageState('createNewConversation:start');
|
this.logMessageState('createNewConversation:start');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -1711,6 +1759,7 @@ const appOptions = {
|
|||||||
if (result.success) {
|
if (result.success) {
|
||||||
const newConversationId = result.conversation_id;
|
const newConversationId = result.conversation_id;
|
||||||
debugLog('新对话创建成功:', newConversationId);
|
debugLog('新对话创建成功:', newConversationId);
|
||||||
|
traceLog('createNewConversation:created', { newConversationId });
|
||||||
|
|
||||||
// 在本地列表插入占位,避免等待刷新
|
// 在本地列表插入占位,避免等待刷新
|
||||||
const placeholder = {
|
const placeholder = {
|
||||||
@ -1726,11 +1775,20 @@ const appOptions = {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// 直接加载新对话,确保状态一致
|
// 直接加载新对话,确保状态一致
|
||||||
await this.loadConversation(newConversationId);
|
// 如果 socket 事件已把 currentConversationId 设置为新ID,则强制加载一次以同步状态
|
||||||
|
await this.loadConversation(newConversationId, { force: true });
|
||||||
|
traceLog('createNewConversation:after-load', {
|
||||||
|
newConversationId,
|
||||||
|
currentConversationId: this.currentConversationId
|
||||||
|
});
|
||||||
|
|
||||||
// 刷新对话列表获取最新统计
|
// 刷新对话列表获取最新统计
|
||||||
this.conversationsOffset = 0;
|
this.conversationsOffset = 0;
|
||||||
await this.loadConversationsList();
|
await this.loadConversationsList();
|
||||||
|
traceLog('createNewConversation:after-refresh', {
|
||||||
|
newConversationId,
|
||||||
|
conversationsLen: Array.isArray(this.conversations) ? this.conversations.length : 'n/a'
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error('创建对话失败:', result.message);
|
console.error('创建对话失败:', result.message);
|
||||||
this.uiPushToast({
|
this.uiPushToast({
|
||||||
|
|||||||
@ -591,6 +591,7 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
// 监听对话变更事件
|
// 监听对话变更事件
|
||||||
ctx.socket.on('conversation_changed', (data) => {
|
ctx.socket.on('conversation_changed', (data) => {
|
||||||
socketLog('对话已切换:', data);
|
socketLog('对话已切换:', data);
|
||||||
|
console.log('[conv-trace] socket:conversation_changed', data);
|
||||||
ctx.currentConversationId = data.conversation_id;
|
ctx.currentConversationId = data.conversation_id;
|
||||||
ctx.currentConversationTitle = data.title || '';
|
ctx.currentConversationTitle = data.title || '';
|
||||||
ctx.promoteConversationToTop(data.conversation_id);
|
ctx.promoteConversationToTop(data.conversation_id);
|
||||||
@ -618,6 +619,7 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const convId = data.conversation_id;
|
const convId = data.conversation_id;
|
||||||
|
console.log('[conv-trace] socket:conversation_resolved', data);
|
||||||
ctx.currentConversationId = convId;
|
ctx.currentConversationId = convId;
|
||||||
if (data.title) {
|
if (data.title) {
|
||||||
ctx.currentConversationTitle = data.title;
|
ctx.currentConversationTitle = data.title;
|
||||||
|
|||||||
@ -2278,8 +2278,29 @@ def handle_message(data):
|
|||||||
"""发送消息到客户端"""
|
"""发送消息到客户端"""
|
||||||
socketio.emit(event_type, data, room=client_sid)
|
socketio.emit(event_type, data, room=client_sid)
|
||||||
|
|
||||||
|
# 模型活动事件:用于刷新“在线”心跳(回复/工具调用都算活动)
|
||||||
|
activity_events = {
|
||||||
|
'ai_message_start', 'thinking_start', 'thinking_chunk', 'thinking_end',
|
||||||
|
'text_start', 'text_chunk', 'text_end',
|
||||||
|
'tool_hint', 'tool_preparing', 'tool_start', 'update_action',
|
||||||
|
'append_payload', 'modify_payload', 'system_message',
|
||||||
|
'task_complete'
|
||||||
|
}
|
||||||
|
last_model_activity = 0.0
|
||||||
|
|
||||||
|
def send_with_activity(event_type, data):
|
||||||
|
"""模型产生输出或调用工具时刷新活跃时间,防止长回复被误判下线。"""
|
||||||
|
nonlocal last_model_activity
|
||||||
|
if event_type in activity_events:
|
||||||
|
now = time.time()
|
||||||
|
# 轻量节流:1 秒内多次事件只记一次
|
||||||
|
if now - last_model_activity >= 1.0:
|
||||||
|
record_user_activity(username)
|
||||||
|
last_model_activity = now
|
||||||
|
send_to_client(event_type, data)
|
||||||
|
|
||||||
# 传递客户端ID
|
# 传递客户端ID
|
||||||
socketio.start_background_task(process_message_task, terminal, message, send_to_client, client_sid)
|
socketio.start_background_task(process_message_task, terminal, message, send_with_activity, client_sid)
|
||||||
|
|
||||||
|
|
||||||
@socketio.on('client_chunk_log')
|
@socketio.on('client_chunk_log')
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user