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 (