lobehub/src/features/Electron/titlebar/RecentlyViewed/plugins/agentTopicPlugin.ts
Innei 730169e6b6
feat(electron): add + button to TabBar for new topic in active context (#13972)
*  feat(electron): add + button to TabBar to open new topic in active context

Introduce a pluggable `createNewTabAction` extension on RecentlyViewed
plugins so each page type can decide whether (and how) to spawn a new
tab from the active tab. Implemented for agent / agent-topic /
group / group-topic — clicking `+` creates a fresh topic under the
current agent/group and opens it as a new tab; other page types hide
the button by default.

*  feat(electron): support new tab from page context

Page plugin now implements `createNewTabAction`, creating a fresh
untitled document via `usePageStore().createPage` and opening it as
a new `page` tab.

* 🐛 fix(electron): refresh page list after creating a new page via TabBar +

`createPage` only hits the service; without refreshing the documents
list, the sidebar / PageExplorer wouldn't show the freshly-created
page until the next full reload.

* 🐛 fix(electron): highlight new page in sidebar when opened via TabBar +

Switch to `createNewPage`, which runs the full optimistic flow —
dispatches the new document into the sidebar list and sets
`selectedPageId` — so the nav item active state stays in sync with
the freshly-opened page tab.

* 🐛 fix(electron): dispatch real page doc into sidebar list for TabBar +

The earlier `createNewPage` approach relied on an optimistic temp
document that SWR revalidation can clobber before the real doc
replaces it, leaving the new page absent from the sidebar. Create
the page via `createPage` first, then synthesize a `LobeDocument`
from the server response and dispatch it into the list alongside
setting `selectedPageId` — the nav item now appears and highlights
in sync with the new tab.
2026-04-20 01:04:51 +08:00

99 lines
3.3 KiB
TypeScript

import { MessageSquare } from 'lucide-react';
import { useChatStore } from '@/store/chat';
import { type AgentTopicParams, type PageReference, type ResolvedPageData } from '../types';
import { buildAgentNewTopicAction } from './newTabHelpers';
import { type NewTabAction, type PluginContext, type RecentlyViewedPlugin } from './types';
import { createPageReference } from './types';
const AGENT_PATH_REGEX = /^\/agent\/([^/?]+)$/;
export const agentTopicPlugin: RecentlyViewedPlugin<'agent-topic'> = {
checkExists(reference: PageReference<'agent-topic'>, ctx: PluginContext): boolean {
const { agentId, topicId } = reference.params;
const agentMeta = ctx.getAgentMeta(agentId);
const topic = ctx.getTopic(topicId);
// Both agent and topic must exist
return agentMeta !== undefined && Object.keys(agentMeta).length > 0 && topic !== undefined;
},
createNewTabAction(
reference: PageReference<'agent-topic'>,
ctx: PluginContext,
): NewTabAction | null {
return buildAgentNewTopicAction(reference.params.agentId, ctx);
},
generateId(reference: PageReference<'agent-topic'>): string {
const { agentId, topicId } = reference.params;
return `agent-topic:${agentId}:${topicId}`;
},
generateUrl(reference: PageReference<'agent-topic'>): string {
const { agentId, topicId } = reference.params;
return `/agent/${agentId}?topic=${topicId}`;
},
getDefaultIcon() {
return MessageSquare;
},
// Higher priority than agent plugin to match topic URLs first
matchUrl(pathname: string, searchParams: URLSearchParams): boolean {
// Match /agent/:id with topic param
return AGENT_PATH_REGEX.test(pathname) && searchParams.has('topic');
},
onActivate(reference: PageReference<'agent-topic'>) {
useChatStore.getState().switchTopic(reference.params.topicId);
},
parseUrl(pathname: string, searchParams: URLSearchParams): PageReference<'agent-topic'> | null {
const match = pathname.match(AGENT_PATH_REGEX);
if (!match) return null;
const topicId = searchParams.get('topic');
if (!topicId) return null;
const agentId = match[1];
const params: AgentTopicParams = { agentId, topicId };
const id = this.generateId({ params } as PageReference<'agent-topic'>);
return createPageReference('agent-topic', params, id);
},
priority: 20,
resolve(reference: PageReference<'agent-topic'>, ctx: PluginContext): ResolvedPageData {
const { agentId, topicId } = reference.params;
const agentMeta = ctx.getAgentMeta(agentId);
const topic = ctx.getTopic(topicId);
const cached = reference.cached;
const agentExists = agentMeta !== undefined && Object.keys(agentMeta).length > 0;
const topicExists = topic !== undefined;
const hasStoreData = agentExists && topicExists;
// Use topic title if available, otherwise fall back to agent title, then cached
const title =
topic?.title ||
agentMeta?.title ||
cached?.title ||
ctx.t('navigation.chat', { ns: 'electron' });
return {
avatar: agentMeta?.avatar ?? cached?.avatar,
backgroundColor: agentMeta?.backgroundColor ?? cached?.backgroundColor,
exists: hasStoreData || cached !== undefined,
icon: this.getDefaultIcon!(),
reference,
title,
url: this.generateUrl(reference),
};
},
type: 'agent-topic',
};