fix: preserve full chat embed mode navigation

This commit is contained in:
董宏凯 committed 2026-07-29 22:25:42 +08:00
1 parent 4450f342d8
commit e52261fc3d
3 files changed
+71 -9

No files matched your search

@@ -83,6 +83,7 @@ import {
sortThreadsPinnedFirst,
titleOfThread,
} from "@/core/threads/utils";
import { buildFullChatNavigationPath } from "@/core/page-layout/chats-iframe-route";
import { env } from "@/env";
import { desensitize } from "@/lib/desensitize";
import { isIMEComposing } from "@/lib/ime";
@@ -185,15 +186,24 @@ export function GeneralQaSidebar() {
// 当前嵌入的智能体(URL 上的 ?agent=):用于高亮对应卡片。
const searchParams = useSearchParams();
const activeAgentId = searchParams.get("agent");
const currentSearch = searchParams.toString();
const newChatHref = buildFullChatNavigationPath(NEW_CHAT_PATH, currentSearch);
const moreAgentsHref = buildFullChatNavigationPath(MORE_AGENTS_HREF, currentSearch);
const moreChatsHref = buildFullChatNavigationPath(MORE_CHATS_HREF, currentSearch);
// 点击常用智能体 = 在通用问答右侧**原地嵌入**该智能体的对话页:停留在 /chats/new,
// 仅给 URL 加 ?agent=<id>,由 NewChatContent 把右侧问答区换成 AgentChatPage。
// 不跳转独立页面、左侧栏不变。每次点击都重开一个干净会话(NewChatContent 的 key)。
const handleSelectAgent = useCallback(
(agentId: string) => {
navigate(`/page/workspace/chats/new?agent=${encodeURIComponent(agentId)}`);
navigate(
buildFullChatNavigationPath(
`${NEW_CHAT_PATH}?agent=${encodeURIComponent(agentId)}`,
currentSearch,
),
);
},
[navigate],
[currentSearch, navigate],
);
// ── 最近的对话取数 + 搜索 ──
@@ -241,7 +251,7 @@ export function GeneralQaSidebar() {
const handleNewChatClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
if (event.metaKey || event.ctrlKey || event.shiftKey || event.button === 1) return;
event.preventDefault();
navigate(NEW_CHAT_PATH, { replace: true, state: { resetAt: Date.now() } });
navigate(newChatHref, { replace: true, state: { resetAt: Date.now() } });
};
const handleDelete = useCallback(
@@ -257,10 +267,17 @@ export function GeneralQaSidebar() {
nextThreadPath = listPathOfThread(threads[threadIndex - 1]!);
}
}
void router.push(nextThreadPath);
void router.push(buildFullChatNavigationPath(nextThreadPath, currentSearch));
}
},
[agentIdFromPath, deleteThread, router, threadIdFromPath, threads],
[
agentIdFromPath,
currentSearch,
deleteThread,
router,
threadIdFromPath,
threads,
],
);
const handleRenameClick = useCallback((threadId: string, currentTitle: string) => {
@@ -314,7 +331,7 @@ export function GeneralQaSidebar() {
底色用「主色混入透明」=主色低透明度叠在当前侧栏底色上,浅/红/蓝/深蓝主题都自适应
(不要混 white,否则深蓝主题下会变成浅色块)。 */}
<Link
href={NEW_CHAT_PATH}
href={newChatHref}
onClick={handleNewChatClick}
style={{ borderColor: "var(--primary)", color: "var(--primary)" }}
className={cn(
@@ -415,7 +432,7 @@ export function GeneralQaSidebar() {
);
})}
<Link
href={MORE_AGENTS_HREF}
href={moreAgentsHref}
className="flex items-center justify-between rounded-lg px-3 py-1.5 text-[12px] text-muted-foreground/70 transition-colors hover:bg-accent/50 hover:text-foreground"
>
<span>{t.common.more}</span>
@@ -454,7 +471,10 @@ export function GeneralQaSidebar() {
</div>
) : (
filteredThreads.map((thread) => {
const href = listPathOfThread(thread);
const href = buildFullChatNavigationPath(
listPathOfThread(thread),
currentSearch,
);
const isActive = href === pathname;
const isBoot = isBootstrapThread(thread);
const pinned = isThreadPinned(thread);
@@ -576,7 +596,7 @@ export function GeneralQaSidebar() {
)}
{!query && (
<Link
href={MORE_CHATS_HREF}
href={moreChatsHref}
className="mt-0.5 flex items-center justify-between rounded-lg px-3 py-1.5 text-[12px] text-muted-foreground/70 transition-colors hover:bg-accent/50 hover:text-foreground"
>
<span>更多最近对话</span>
@@ -3,6 +3,7 @@ import test from "node:test";
import {
buildStandardChatRedirectPath,
buildFullChatNavigationPath,
buildEmbeddedIframeRedirectPath,
buildChatsIframeRedirectPath,
buildChatsIframeThreadPath,
@@ -280,6 +281,27 @@ test("builds a standard full-chat URL without leaking the legacy marker", () =>
);
});
test("keeps full-chat presentation flags when the sidebar opens an agent or thread", () => {
assert.equal(
buildFullChatNavigationPath(
"/page/workspace/chats/new?agent=agent-1",
"?hideChrome=1&chatMode=full",
),
"/page/workspace/chats/new?agent=agent-1&hideChrome=1&chatMode=full",
);
assert.equal(
buildFullChatNavigationPath(
"/page/workspace/chats/thread-123",
"?embed=1&theme=dark-blue&chatMode=full",
),
"/page/workspace/chats/thread-123?embed=1&theme=dark-blue&chatMode=full",
);
assert.equal(
buildFullChatNavigationPath("/page/workspace/chats/new", ""),
"/page/workspace/chats/new",
);
});
test("preserves explicit Brainy iframe mode and theme across redirects", () => {
assert.equal(
buildChatsIframeRedirectPath("?chatMode=iframe&theme=dark-blue&ticket=secret"),
@@ -107,6 +107,26 @@ export function buildStandardChatRedirectPath(
return query ? `${pathname}?${query}` : pathname;
}
/**
* Keep an explicitly selected standard-chat presentation while navigating
* between `/chats/new` and a concrete chat thread. Without this, a click in
* the full-chat sidebar drops `hideChrome=1&chatMode=full`; an iframe host
* then looks like the legacy bare integration and is incorrectly redirected
* to the private AgentSelector workspace.
*
* This deliberately whitelists presentation flags through
* `buildStandardChatRedirectPath` instead of copying arbitrary login/query
* values into user-facing chat URLs.
*/
export function buildFullChatNavigationPath(
destinationPath: string,
search: string,
): string {
return hasIframePresentationOverride(search) || isExplicitFullChatMode(search)
? buildStandardChatRedirectPath(search, destinationPath)
: destinationPath;
}
/**
* Preserve only the presentation parameters consumed by the iframe workspace.
* Login credentials must never be copied into the post-login address bar.