mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-26 13:19:34 +07:00
chore: remove typing interval
This commit is contained in:
@@ -26,7 +26,6 @@ import {
|
||||
renderStopped,
|
||||
splitMessage,
|
||||
} from './replyTemplate';
|
||||
import { startTypingKeepAlive, stopTypingKeepAlive } from './typingKeepAlive';
|
||||
|
||||
const log = debug('lobe-server:bot:agent-bridge');
|
||||
|
||||
@@ -391,9 +390,7 @@ export class AgentBridgeService {
|
||||
const queueMode = isQueueAgentRuntimeEnabled();
|
||||
let queueHandoffSucceeded = false;
|
||||
|
||||
// Keep typing indicator alive (e.g. Telegram expires after ~5s, Discord after ~10s)
|
||||
const platformThreadId = botContext?.platformThreadId ?? thread.id;
|
||||
startTypingKeepAlive(platformThreadId, () => thread.startTyping());
|
||||
|
||||
try {
|
||||
// executeWithCallback handles progress message (post + edit at each step)
|
||||
@@ -421,9 +418,8 @@ export class AgentBridgeService {
|
||||
} finally {
|
||||
AgentBridgeService.activeThreads.delete(thread.id);
|
||||
// In queue mode, the callback owns cleanup only after webhook handoff succeeds.
|
||||
// If setup fails before that point, clean up locally to avoid leaked keepalive/reactions.
|
||||
// If setup fails before that point, clean up locally to avoid leaked reactions.
|
||||
if (!queueMode || !queueHandoffSucceeded) {
|
||||
stopTypingKeepAlive(platformThreadId);
|
||||
await this.removeReceivedReaction(thread, message, client);
|
||||
}
|
||||
}
|
||||
@@ -492,9 +488,7 @@ export class AgentBridgeService {
|
||||
);
|
||||
await thread.startTyping();
|
||||
|
||||
// Keep typing indicator alive
|
||||
const platformThreadId = botContext?.platformThreadId ?? thread.id;
|
||||
startTypingKeepAlive(platformThreadId, () => thread.startTyping());
|
||||
|
||||
try {
|
||||
// executeWithCallback handles progress message (post + edit at each step)
|
||||
@@ -527,7 +521,6 @@ export class AgentBridgeService {
|
||||
AgentBridgeService.activeThreads.delete(thread.id);
|
||||
// In queue mode, the callback owns cleanup only after webhook handoff succeeds.
|
||||
if (!queueMode || !queueHandoffSucceeded) {
|
||||
stopTypingKeepAlive(platformThreadId);
|
||||
await this.removeReceivedReaction(thread, message, opts.client);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
renderStopped,
|
||||
splitMessage,
|
||||
} from './replyTemplate';
|
||||
import { stopTypingKeepAlive } from './typingKeepAlive';
|
||||
|
||||
const log = debug('lobe-server:bot:callback');
|
||||
|
||||
@@ -85,9 +84,6 @@ export class BotCallbackService {
|
||||
await this.handleStep(body, messenger, progressMessageId, client);
|
||||
}
|
||||
} else if (type === 'completion') {
|
||||
// Stop typing keepalive before sending the final message
|
||||
stopTypingKeepAlive(platformThreadId);
|
||||
|
||||
await this.handleCompletion(
|
||||
body,
|
||||
messenger,
|
||||
|
||||
@@ -5,8 +5,6 @@ const mockExecAgent = vi.hoisted(() => vi.fn());
|
||||
const mockFormatPrompt = vi.hoisted(() => vi.fn());
|
||||
const mockGetPlatform = vi.hoisted(() => vi.fn());
|
||||
const mockIsQueueAgentRuntimeEnabled = vi.hoisted(() => vi.fn());
|
||||
const mockStartTypingKeepAlive = vi.hoisted(() => vi.fn());
|
||||
const mockStopTypingKeepAlive = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock('@/database/models/topic', () => ({
|
||||
TopicModel: vi.fn(),
|
||||
@@ -49,11 +47,6 @@ vi.mock('@/server/services/bot/platforms', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@/server/services/bot/typingKeepAlive', () => ({
|
||||
startTypingKeepAlive: mockStartTypingKeepAlive,
|
||||
stopTypingKeepAlive: mockStopTypingKeepAlive,
|
||||
}));
|
||||
|
||||
const { AgentBridgeService } = await import('../AgentBridgeService');
|
||||
|
||||
const FAKE_DB = {} as any;
|
||||
@@ -119,7 +112,7 @@ describe('AgentBridgeService', () => {
|
||||
mockIsQueueAgentRuntimeEnabled.mockReturnValue(true);
|
||||
});
|
||||
|
||||
it('cleans up keepalive and received reaction when queue-mode mention setup fails before callback handoff', async () => {
|
||||
it('cleans up received reaction when queue-mode mention setup fails before callback handoff', async () => {
|
||||
const service = new AgentBridgeService(FAKE_DB, USER_ID);
|
||||
const thread = createThread();
|
||||
const message = createMessage();
|
||||
@@ -132,8 +125,6 @@ describe('AgentBridgeService', () => {
|
||||
debounceMs: 0,
|
||||
});
|
||||
|
||||
expect(mockStartTypingKeepAlive).toHaveBeenCalledWith(THREAD_ID, expect.any(Function));
|
||||
expect(mockStopTypingKeepAlive).toHaveBeenCalledWith(THREAD_ID);
|
||||
const [mentionReactionThreadId, mentionReactionMessageId, mentionReactionEmoji] =
|
||||
thread.adapter.removeReaction.mock.calls[0];
|
||||
expect(mentionReactionThreadId).toBe(THREAD_ID);
|
||||
@@ -142,7 +133,7 @@ describe('AgentBridgeService', () => {
|
||||
expect(mockExecAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('cleans up keepalive and received reaction when queue-mode subscribed-message setup fails before callback handoff', async () => {
|
||||
it('cleans up received reaction when queue-mode subscribed-message setup fails before callback handoff', async () => {
|
||||
const service = new AgentBridgeService(FAKE_DB, USER_ID);
|
||||
const thread = createThread({ topicId: 'topic-1' });
|
||||
const message = createMessage();
|
||||
@@ -155,8 +146,6 @@ describe('AgentBridgeService', () => {
|
||||
debounceMs: 0,
|
||||
});
|
||||
|
||||
expect(mockStartTypingKeepAlive).toHaveBeenCalledWith(THREAD_ID, expect.any(Function));
|
||||
expect(mockStopTypingKeepAlive).toHaveBeenCalledWith(THREAD_ID);
|
||||
const [replyReactionThreadId, replyReactionMessageId, replyReactionEmoji] =
|
||||
thread.adapter.removeReaction.mock.calls[0];
|
||||
expect(replyReactionThreadId).toBe(THREAD_ID);
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import debug from 'debug';
|
||||
|
||||
const log = debug('lobe-server:bot:typing-keepalive');
|
||||
|
||||
const TYPING_INTERVAL_MS = 4000;
|
||||
|
||||
/**
|
||||
* In-memory registry of active typing intervals.
|
||||
* Keyed by platformThreadId so both AgentBridgeService (start)
|
||||
* and BotCallbackService (stop) can reference the same entry.
|
||||
*/
|
||||
const activeIntervals = new Map<string, NodeJS.Timeout>();
|
||||
|
||||
/**
|
||||
* Start a repeating typing indicator for a thread.
|
||||
* Calls `typingFn` immediately, then every TYPING_INTERVAL_MS.
|
||||
* Returns a cleanup function (also accessible via `stopTypingKeepAlive`).
|
||||
*/
|
||||
export function startTypingKeepAlive(threadId: string, typingFn: () => Promise<void>): () => void {
|
||||
// Clear any existing interval for this thread (safety)
|
||||
stopTypingKeepAlive(threadId);
|
||||
|
||||
log('start: threadId=%s, interval=%dms', threadId, TYPING_INTERVAL_MS);
|
||||
|
||||
const interval = setInterval(() => {
|
||||
typingFn().catch(() => {
|
||||
// Typing failures are non-critical — ignore silently
|
||||
});
|
||||
}, TYPING_INTERVAL_MS);
|
||||
|
||||
activeIntervals.set(threadId, interval);
|
||||
|
||||
return () => stopTypingKeepAlive(threadId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the typing keepalive for a thread.
|
||||
* Safe to call even if no interval is active.
|
||||
*/
|
||||
export function stopTypingKeepAlive(threadId: string): void {
|
||||
const interval = activeIntervals.get(threadId);
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
activeIntervals.delete(threadId);
|
||||
log('stop: threadId=%s', threadId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user