diff --git a/frontend-web/src/pages/ChatPage.tsx b/frontend-web/src/pages/ChatPage.tsx index 5df27895..2bec4781 100644 --- a/frontend-web/src/pages/ChatPage.tsx +++ b/frontend-web/src/pages/ChatPage.tsx @@ -101,6 +101,11 @@ export default function ChatPage() { const router = useRouter(); const searchParams = useSearchParams(); const pathname = usePathname(); + // `chatMode=full` is used by an embedded standard chat. It intentionally + // overrides the user's compact-layout preference for this route only, so + // the page keeps the full landing composition and its agent/history sidebar. + const fullChatMode = + searchParams.get("chatMode")?.trim().toLowerCase() === "full"; const { open: historySidebarOpen, toggleSidebar: toggleHistorySidebar } = useSidebar(); const { t } = useI18n(); @@ -452,7 +457,7 @@ export default function ChatPage() { // 主题底图(workspace-chat-bg)不生效。非紧凑(嵌入/standalone)保持原样。 const layoutMode = effectiveLayoutMode; const chromeByPreference = - layoutMode === "full" + fullChatMode || layoutMode === "full" ? true : layoutMode === "compact" ? false diff --git a/frontend-web/src/pages/LoginPage.tsx b/frontend-web/src/pages/LoginPage.tsx index fc7df978..a9bc8fb0 100644 --- a/frontend-web/src/pages/LoginPage.tsx +++ b/frontend-web/src/pages/LoginPage.tsx @@ -69,6 +69,7 @@ const DEFAULT_USERNAME = 'guest'; const IFRAME_LOGIN_SOURCE = 'iframe'; const DEFAULT_REDIRECT_PATH = '/page/workspace/chats/new'; const IFRAME_REDIRECT_PATH = '/page/workspace/chats_iframe/new'; +const FULL_CHAT_MODE = 'full'; type LoginStatus = 'loading' | 'success' | 'error'; @@ -89,13 +90,28 @@ export default function LoginPage() { () => readEmbedParam(searchParams.toString()) === 'on', [searchParams], ); - const redirectPath = useMemo( - () => - searchParams.get('from') === IFRAME_LOGIN_SOURCE - ? IFRAME_REDIRECT_PATH - : DEFAULT_REDIRECT_PATH, + // External consumers may still send `from=iframe` for the legacy protocol + // chat. `chatMode=full` is an explicit escape hatch: always use the regular + // full ChatPage instead, which is the route expected by ordinary iframe + // nesting and browser deep links. + const fullChatRequested = useMemo( + () => searchParams.get('chatMode')?.trim().toLowerCase() === FULL_CHAT_MODE, [searchParams], ); + const redirectPath = useMemo( + () => { + // Keep the mode on the destination URL so PageRoutes can retain the + // full chat workspace sidebar while hideChrome hides only the global + // application chrome. + if (fullChatRequested) { + return `${DEFAULT_REDIRECT_PATH}?chatMode=${FULL_CHAT_MODE}`; + } + return searchParams.get('from') === IFRAME_LOGIN_SOURCE + ? IFRAME_REDIRECT_PATH + : DEFAULT_REDIRECT_PATH; + }, + [fullChatRequested, searchParams], + ); const resolvedUsername = useMemo(() => { const raw = username?.trim(); return raw && raw.length > 0 ? raw : DEFAULT_USERNAME; diff --git a/frontend-web/src/pages/PageRoutes.tsx b/frontend-web/src/pages/PageRoutes.tsx index 9616ddb9..146e50af 100644 --- a/frontend-web/src/pages/PageRoutes.tsx +++ b/frontend-web/src/pages/PageRoutes.tsx @@ -69,6 +69,19 @@ function PageLayout() { () => isHideChromeActive() || readHideChromeParam(location.search) === "on", [location.pathname, location.search], ); + // `chatMode=full` is the embed-friendly standard chat view: hide the global + // Header/PageSidebar when `hideChrome=1`, but keep WorkspaceSidebar so users + // can still pick an agent and access their common agents/history. + const fullChatMode = useMemo(() => { + const mode = new URLSearchParams(location.search) + .get("chatMode") + ?.trim() + .toLowerCase(); + return ( + mode === "full" && + location.pathname.startsWith("/page/workspace/chats/") + ); + }, [location.pathname, location.search]); // 「大屏绘制」独立页(/page/strategy/dashboard-agent)本就是「带导航栏 + 侧边栏」的独立页, // 但它常被深链「上一步」从去 chrome 的工作区/新标签打开——那里 chromeByPreference 可能为 false // (界面模式非全量、或会话级 sidebar 未置位),导致整页没有顶栏/侧栏。故对该路由**强制全局 chrome** @@ -125,12 +138,17 @@ function PageLayout() { } /> diff --git a/frontend-web/src/pages/WorkspaceRoutes.tsx b/frontend-web/src/pages/WorkspaceRoutes.tsx index 778f02fe..6b44be99 100644 --- a/frontend-web/src/pages/WorkspaceRoutes.tsx +++ b/frontend-web/src/pages/WorkspaceRoutes.tsx @@ -72,14 +72,6 @@ function NewAgentPageWithKey() { return ; } -function isInIframe(): boolean { - try { - return window.self !== window.top; - } catch { - return true; - } -} - function shouldShowCompactTopActions(pathname: string): boolean { return ( pathname === '/page/workspace/agents' || @@ -246,10 +238,13 @@ function NewChatEntryRoute({ forceWorkspaceSidebarCollapsed: boolean; }; }) { - if (isInIframe()) { - return ; - } - + // Do not infer the chat experience from window.self !== window.top here. + // A generic `/chats/new` deep link must render the same standard chat both + // top-level and inside an iframe. The dedicated iframe protocol page remains + // available only through its explicit `/chats_iframe/...` route (LoginPage + // selects it for `from=iframe`). Otherwise a normal deep link was silently + // redirected to IframeChatPage, whose new-thread state intentionally has no + // manually selected agent and therefore stopped at "选择 Agent". return (