🐛 fix: fix new topic flick issue (#11473)

* fix agent tool display

* fix agent tool display

* don't fetch messages when topic is null

* fix welcome
This commit is contained in:
Arvin Xu
2026-01-14 00:15:11 +08:00
committed by GitHub
parent e6ff90b108
commit c53d372696
5 changed files with 104 additions and 23 deletions

View File

@@ -1,13 +1,16 @@
'use client';
import { AgentTool as SharedAgentTool } from '@/features/ProfileEditor';
import { useGroupProfileStore } from '@/store/groupProfile';
/**
* AgentTool for group profile editor
* - Uses default settings (no web browsing, no filterAvailableInWeb, uses metaList)
* - Passes agentId from group profile store to display the correct member's plugins
*/
const AgentTool = () => {
return <SharedAgentTool />;
const agentId = useGroupProfileStore((s) => s.activeTabId);
return <SharedAgentTool agentId={agentId} />;
};
export default AgentTool;

View File

@@ -63,7 +63,11 @@ const ChatList = memo<ChatListProps>(({ disableActionsBar, welcome, itemContent
);
const messagesInit = useConversationStore(dataSelectors.messagesInit);
if (!messagesInit) {
// When topicId is null (new conversation), show welcome directly without waiting for fetch
// because there's no server data to fetch - only local optimistic updates exist
const isNewConversation = !context.topicId;
if (!messagesInit && !isNewConversation) {
return <SkeletonList />;
}

View File

@@ -505,6 +505,48 @@ describe('DataSlice', () => {
);
});
it('should not fetch when topicId is null (new conversation state)', () => {
const store = createStore({
context: { agentId: 'test-session', topicId: null, threadId: null },
});
store.getState().useFetchMessages({
agentId: 'test-session',
topicId: null,
threadId: null,
});
// SWR should be called with null key when topicId is null
// This prevents fetching empty data that would overwrite local optimistic updates
expect(vi.mocked(useClientDataSWRWithSync)).toHaveBeenCalledWith(
null,
expect.any(Function),
expect.any(Object),
);
// messageService.getMessages should NOT be called
expect(messageService.getMessages).not.toHaveBeenCalled();
});
it('should not fetch when topicId is undefined (new conversation state)', () => {
const store = createStore({
context: { agentId: 'test-session', topicId: null, threadId: null },
});
store.getState().useFetchMessages({
agentId: 'test-session',
topicId: undefined as any,
threadId: null,
});
// SWR should be called with null key when topicId is undefined
expect(vi.mocked(useClientDataSWRWithSync)).toHaveBeenCalledWith(
null,
expect.any(Function),
expect.any(Object),
);
});
it('should use different SWR keys for different threadIds', () => {
const store1 = createStore({
context: { agentId: 'session-1', topicId: 'topic-1', threadId: 'thread-1' },

View File

@@ -103,8 +103,9 @@ export const dataSlice: StateCreator<
useFetchMessages: (context, skipFetch) => {
// When skipFetch is true, SWR key is null - no fetch occurs
// This is used when external messages are provided (e.g., creating new thread)
// Allow fetch if: has agentId (both agent topics and group topics have agentId)
const shouldFetch = !skipFetch && !!context.agentId;
// Also skip fetch when topicId is null (new conversation state) - there's no server data,
// only local optimistic updates. Fetching would return empty array and overwrite local data.
const shouldFetch = !skipFetch && !!context.agentId && !!context.topicId;
return useClientDataSWRWithSync<UIChatMessage[]>(
shouldFetch ? ['CONVERSATION_FETCH_MESSAGES', context] : null,

View File

@@ -16,7 +16,7 @@ import PluginStore from '@/features/PluginStore';
import { useCheckPluginsIsInstalled } from '@/hooks/useCheckPluginsIsInstalled';
import { useFetchInstalledPlugins } from '@/hooks/useFetchInstalledPlugins';
import { useAgentStore } from '@/store/agent';
import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/selectors';
import { agentSelectors, chatConfigByIdSelectors } from '@/store/agent/selectors';
import { serverConfigSelectors, useServerConfigStore } from '@/store/serverConfig';
import { useToolStore } from '@/store/tool';
import {
@@ -82,6 +82,11 @@ const KlavisIcon = memo<Pick<KlavisServerType, 'icon' | 'label'>>(({ icon, label
});
export interface AgentToolProps {
/**
* Optional agent ID to use instead of currentAgentConfig
* Used in group profile to specify which member's plugins to display
*/
agentId?: string;
/**
* Whether to filter tools by availableInWeb property
* @default false
@@ -100,15 +105,17 @@ export interface AgentToolProps {
}
const AgentTool = memo<AgentToolProps>(
({ showWebBrowsing = false, filterAvailableInWeb = false, useAllMetaList = false }) => {
({ agentId, showWebBrowsing = false, filterAvailableInWeb = false, useAllMetaList = false }) => {
const { t } = useTranslation('setting');
const config = useAgentStore(agentSelectors.currentAgentConfig, isEqual);
const activeAgentId = useAgentStore((s) => s.activeAgentId);
const effectiveAgentId = agentId || activeAgentId || '';
const config = useAgentStore(agentSelectors.getAgentConfigById(effectiveAgentId), isEqual);
// Plugin state management
const plugins = config?.plugins || [];
const toggleAgentPlugin = useAgentStore((s) => s.toggleAgentPlugin);
const updateAgentChatConfig = useAgentStore((s) => s.updateAgentChatConfig);
const updateAgentConfigById = useAgentStore((s) => s.updateAgentConfigById);
const updateAgentChatConfigById = useAgentStore((s) => s.updateAgentChatConfigById);
const installedPluginList = useToolStore(pluginSelectors.installedPluginMetaList, isEqual);
// Use appropriate builtin list based on prop
@@ -117,8 +124,8 @@ const AgentTool = memo<AgentToolProps>(
isEqual,
);
// Web browsing uses searchMode instead of plugins array
const isSearchEnabled = useAgentStore(agentChatConfigSelectors.isAgentEnableSearch);
// Web browsing uses searchMode instead of plugins array - use byId selector
const isSearchEnabled = useAgentStore(chatConfigByIdSelectors.isEnableSearchById(effectiveAgentId));
// Klavis 相关状态
const allKlavisServers = useToolStore(klavisStoreSelectors.getServers, isEqual);
@@ -144,11 +151,34 @@ const AgentTool = memo<AgentToolProps>(
// 使用 SWR 加载用户的 Klavis 集成(从数据库)
useFetchUserKlavisServers(isKlavisEnabledInEnv);
// Toggle web browsing via searchMode
// Toggle web browsing via searchMode - use byId action
const toggleWebBrowsing = useCallback(async () => {
if (!effectiveAgentId) return;
const nextMode = isSearchEnabled ? 'off' : 'auto';
await updateAgentChatConfig({ searchMode: nextMode });
}, [isSearchEnabled, updateAgentChatConfig]);
await updateAgentChatConfigById(effectiveAgentId, { searchMode: nextMode });
}, [isSearchEnabled, updateAgentChatConfigById, effectiveAgentId]);
// Toggle a plugin - use byId action
const togglePlugin = useCallback(
async (pluginId: string, state?: boolean) => {
if (!effectiveAgentId) return;
const currentPlugins = plugins;
const hasPlugin = currentPlugins.includes(pluginId);
const shouldEnable = state !== undefined ? state : !hasPlugin;
let newPlugins: string[];
if (shouldEnable && !hasPlugin) {
newPlugins = [...currentPlugins, pluginId];
} else if (!shouldEnable && hasPlugin) {
newPlugins = currentPlugins.filter((id) => id !== pluginId);
} else {
return;
}
await updateAgentConfigById(effectiveAgentId, { plugins: newPlugins });
},
[effectiveAgentId, plugins, updateAgentConfigById],
);
// Check if a tool is enabled (handles web browsing specially)
const isToolEnabled = useCallback(
@@ -167,10 +197,10 @@ const AgentTool = memo<AgentToolProps>(
if (showWebBrowsing && identifier === WEB_BROWSING_IDENTIFIER) {
await toggleWebBrowsing();
} else {
await toggleAgentPlugin(identifier);
await togglePlugin(identifier);
}
},
[toggleWebBrowsing, toggleAgentPlugin, showWebBrowsing],
[toggleWebBrowsing, togglePlugin, showWebBrowsing],
);
// Set default tab based on installed plugins (only on first load)
@@ -240,7 +270,7 @@ const AgentTool = memo<AgentToolProps>(
[isKlavisEnabledInEnv, allKlavisServers],
);
// Handle plugin remove via Tag close
// Handle plugin remove via Tag close - use byId actions
const handleRemovePlugin =
(
pluginId: string | { enabled: boolean; identifier: string; settings: Record<string, any> },
@@ -250,9 +280,10 @@ const AgentTool = memo<AgentToolProps>(
e.stopPropagation();
const identifier = typeof pluginId === 'string' ? pluginId : pluginId?.identifier;
if (showWebBrowsing && identifier === WEB_BROWSING_IDENTIFIER) {
await updateAgentChatConfig({ searchMode: 'off' });
if (!effectiveAgentId) return;
await updateAgentChatConfigById(effectiveAgentId, { searchMode: 'off' });
} else {
toggleAgentPlugin(identifier, false);
await togglePlugin(identifier, false);
}
};
@@ -304,13 +335,13 @@ const AgentTool = memo<AgentToolProps>(
label={item.title}
onUpdate={async () => {
setUpdating(true);
await toggleAgentPlugin(item.identifier);
await togglePlugin(item.identifier);
setUpdating(false);
}}
/>
),
})),
[installedPluginList, plugins, toggleAgentPlugin],
[installedPluginList, plugins, togglePlugin],
);
// All tab items (市场 tab)
@@ -411,7 +442,7 @@ const AgentTool = memo<AgentToolProps>(
label={item.title}
onUpdate={async () => {
setUpdating(true);
await toggleAgentPlugin(item.identifier);
await togglePlugin(item.identifier);
setUpdating(false);
}}
/>
@@ -435,7 +466,7 @@ const AgentTool = memo<AgentToolProps>(
plugins,
isToolEnabled,
handleToggleTool,
toggleAgentPlugin,
togglePlugin,
t,
]);