mirror of
https://github.com/lobehub/lobehub
synced 2026-04-21 17:47:27 +00:00
🐛 fix: skip sendMessageInServer in Gateway mode + NavItem loading fix + i18n (#13681)
* 🐛 fix: reuse existing messages in execAgent when existingMessageIds provided When existingMessageIds contains [userMsgId, assistantMsgId], skip creating new messages and reuse the existing ones. This fixes duplicate messages in Gateway mode where sendMessageInServer already created the messages before execAgentTask is called. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * 🐛 fix: allow clicking NavItem while loading Loading state should only show a visual indicator, not block onClick. This fixes topic sidebar items being unclickable during agent execution. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Revert "🐛 fix: reuse existing messages in execAgent when existingMessageIds provided" This reverts commit 43b808024d5c4a0074b692a85083a72046ab47e0. * 🐛 fix: skip sendMessageInServer in Gateway mode to avoid duplicate messages Gateway mode now calls execAgentTask directly instead of going through sendMessageInServer first. The backend creates user + assistant messages and topic in one call. executeGatewayAgent handles topic switching internally after receiving the server response. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * 🌐 chore: add i18n for execServerAgentRuntime operation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * 🐛 fix: move temp message cleanup after executeGatewayAgent succeeds Keep temp messages visible during the gateway call so the UI isn't blank. On failure, mark the operation as failed instead of silently returning — temp messages remain so the user sees something went wrong. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ♻️ refactor: remove manual temp message cleanup in gateway mode switchTopic handles new topic navigation, and fetchAndReplaceMessages replaces the message list from DB — no need to manually delete temp messages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * 🐛 fix: clear _new key temp messages when gateway creates new topic Pass clearNewKey: true to switchTopic so temp messages from the optimistic create don't persist in the _new key after switching to the server-created topic. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ♻️ refactor: import ExecAgentResult from @lobechat/types Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e65e2c3628
commit
4d7cbfea8e
6 changed files with 66 additions and 44 deletions
|
|
@ -229,6 +229,7 @@
|
|||
"operation.contextCompression": "Context too long, compressing history...",
|
||||
"operation.execAgentRuntime": "Preparing response",
|
||||
"operation.execClientTask": "Executing task",
|
||||
"operation.execServerAgentRuntime": "Running… You can switch tasks or close the page—I'll keep going.",
|
||||
"operation.sendMessage": "Sending message",
|
||||
"owner": "Group owner",
|
||||
"pageCopilot.title": "Page Agent",
|
||||
|
|
|
|||
|
|
@ -229,6 +229,7 @@
|
|||
"operation.contextCompression": "上下文过长,正在压缩历史记录……",
|
||||
"operation.execAgentRuntime": "准备响应中",
|
||||
"operation.execClientTask": "执行任务中",
|
||||
"operation.execServerAgentRuntime": "运行中…你可以切换任务或关闭页面,我会继续执行。",
|
||||
"operation.sendMessage": "消息发送中",
|
||||
"owner": "群主",
|
||||
"pageCopilot.title": "文稿助理",
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ const NavItem = memo<NavItemProps>(
|
|||
if (href && !isModifierClick(e)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
if (disabled || loading) return;
|
||||
if (disabled) return;
|
||||
onClick?.(e);
|
||||
}}
|
||||
{...linkProps}
|
||||
|
|
|
|||
|
|
@ -248,6 +248,8 @@ export default {
|
|||
'operation.contextCompression': 'Context too long, compressing history...',
|
||||
'operation.execAgentRuntime': 'Preparing response',
|
||||
'operation.execClientTask': 'Executing task',
|
||||
'operation.execServerAgentRuntime':
|
||||
'Running… You can switch tasks or close the page — the task will keep going.',
|
||||
'operation.sendMessage': 'Sending message',
|
||||
'owner': 'Group owner',
|
||||
'pageCopilot.title': 'Page Agent',
|
||||
|
|
|
|||
|
|
@ -340,6 +340,31 @@ export class ConversationLifecycleActionImpl {
|
|||
inputSendErrorMsg: undefined,
|
||||
});
|
||||
|
||||
// ── Gateway mode: skip sendMessageInServer, let execAgentTask handle everything ──
|
||||
if (this.#get().isGatewayModeEnabled()) {
|
||||
this.#get().completeOperation(operationId);
|
||||
|
||||
try {
|
||||
const result = await this.#get().executeGatewayAgent({
|
||||
context: operationContext,
|
||||
message,
|
||||
});
|
||||
|
||||
return {
|
||||
assistantMessageId: result.assistantMessageId,
|
||||
userMessageId: result.userMessageId,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('[Gateway] Failed to start server-side agent:', e);
|
||||
this.#get().failOperation(operationId, {
|
||||
message: e instanceof Error ? e.message : 'Unknown error',
|
||||
type: 'GatewayError',
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Client mode: send via server API then run agent locally ──
|
||||
let data: SendMessageServerResponse | undefined;
|
||||
try {
|
||||
const { model, provider } = agentSelectors.getAgentConfigById(agentId)(getAgentStoreState());
|
||||
|
|
@ -582,32 +607,7 @@ export class ConversationLifecycleActionImpl {
|
|||
}
|
||||
}
|
||||
|
||||
// ── AI execution ──
|
||||
|
||||
// Gateway mode: server-side execution via WebSocket (opt-in via Labs toggle)
|
||||
if (this.#get().isGatewayModeEnabled()) {
|
||||
try {
|
||||
await this.#get().executeGatewayAgent({
|
||||
assistantMessageId: data.assistantMessageId,
|
||||
context: execContext,
|
||||
message,
|
||||
parentOperationId: operationId,
|
||||
topicId: data.topicId,
|
||||
userMessageId: data.userMessageId,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('[Gateway] Failed to start server-side agent:', e);
|
||||
if (data.topicId) this.#get().internal_updateTopicLoading(data.topicId, false);
|
||||
}
|
||||
|
||||
return {
|
||||
assistantMessageId: data.assistantMessageId,
|
||||
createdThreadId: data.createdThreadId,
|
||||
userMessageId: data.userMessageId,
|
||||
};
|
||||
}
|
||||
|
||||
// Client mode: run agent loop locally
|
||||
// ── AI execution (client mode) ──
|
||||
{
|
||||
const displayMessages = displayMessageSelectors.getDisplayMessagesByKey(
|
||||
messageMapKey(execContext),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { ConversationContext } from '@lobechat/types';
|
||||
import type { ConversationContext, ExecAgentResult } from '@lobechat/types';
|
||||
|
||||
import type {
|
||||
AgentStreamClientOptions,
|
||||
|
|
@ -169,20 +169,27 @@ export class GatewayActionImpl {
|
|||
* Execute agent task via Gateway WebSocket.
|
||||
* Call isGatewayModeEnabled() first to check availability.
|
||||
*/
|
||||
/**
|
||||
* Execute agent task via Gateway WebSocket.
|
||||
* The backend creates user + assistant messages and the topic (if needed).
|
||||
* Returns the result so the caller can handle topic switching.
|
||||
*/
|
||||
/**
|
||||
* Execute agent task via Gateway WebSocket.
|
||||
* The backend creates user + assistant messages and the topic (if needed),
|
||||
* then starts the agent. This method handles topic switching and WebSocket connection.
|
||||
*/
|
||||
executeGatewayAgent = async (params: {
|
||||
assistantMessageId: string;
|
||||
context: ConversationContext;
|
||||
message: string;
|
||||
parentOperationId: string;
|
||||
topicId?: string;
|
||||
userMessageId: string;
|
||||
}): Promise<void> => {
|
||||
const { assistantMessageId, context, message, parentOperationId, topicId, userMessageId } =
|
||||
params;
|
||||
}): Promise<ExecAgentResult> => {
|
||||
const { context, message } = params;
|
||||
|
||||
const agentGatewayUrl =
|
||||
window.global_serverConfigStore!.getState().serverConfig.agentGatewayUrl!;
|
||||
|
||||
const isCreateNewTopic = !context.topicId;
|
||||
|
||||
const result = await aiAgentService.execAgentTask({
|
||||
agentId: context.agentId,
|
||||
appContext: {
|
||||
|
|
@ -191,24 +198,33 @@ export class GatewayActionImpl {
|
|||
threadId: context.threadId,
|
||||
topicId: context.topicId,
|
||||
},
|
||||
existingMessageIds: [userMessageId, assistantMessageId],
|
||||
prompt: message,
|
||||
});
|
||||
|
||||
// If server created a new topic, switch to it and clean up the _new key temp messages
|
||||
if (isCreateNewTopic && result.topicId) {
|
||||
await this.#get().switchTopic(result.topicId, { clearNewKey: true });
|
||||
}
|
||||
|
||||
// Use the server-created topicId for the execution context
|
||||
const execContext = { ...context, topicId: result.topicId };
|
||||
|
||||
if (result.topicId) {
|
||||
this.#get().internal_updateTopicLoading(result.topicId, true);
|
||||
}
|
||||
|
||||
// Create a dedicated operation for gateway execution with correct context
|
||||
const { operationId: gatewayOpId } = this.#get().startOperation({
|
||||
context,
|
||||
parentOperationId,
|
||||
context: execContext,
|
||||
type: 'execServerAgentRuntime',
|
||||
});
|
||||
|
||||
// Associate the initial assistant message with the gateway operation
|
||||
// so the UI shows loading/generating state via the operation system
|
||||
this.#get().associateMessageWithOperation(assistantMessageId, gatewayOpId);
|
||||
// Associate the server-created assistant message with the gateway operation
|
||||
this.#get().associateMessageWithOperation(result.assistantMessageId, gatewayOpId);
|
||||
|
||||
const eventHandler = createGatewayEventHandler(this.#get, {
|
||||
assistantMessageId,
|
||||
context,
|
||||
assistantMessageId: result.assistantMessageId,
|
||||
context: execContext,
|
||||
operationId: gatewayOpId,
|
||||
});
|
||||
|
||||
|
|
@ -217,11 +233,13 @@ export class GatewayActionImpl {
|
|||
onEvent: eventHandler,
|
||||
onSessionComplete: () => {
|
||||
this.#get().completeOperation(gatewayOpId);
|
||||
if (topicId) this.#get().internal_updateTopicLoading(topicId, false);
|
||||
if (result.topicId) this.#get().internal_updateTopicLoading(result.topicId, false);
|
||||
},
|
||||
operationId: result.operationId,
|
||||
token: result.token || '',
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
private internal_cleanupGatewayConnection = (operationId: string): void => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue