diff --git a/CHANGELOG.md b/CHANGELOG.md index a8e0b3df..d58b1399 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### 修复 +- 组织页嵌入本机 openXYOS 不再因为边车「已连接」却空白:`CORS_ORIGIN` 即使漏了 `http://127.0.0.1:3780` / `localhost` / `[::1]`,服务端也会合并监听自源,模块脚本和登录不再 500。FreeOS 启动会先停掉 live 目录里旧的 Node,再用当前 `start-sidecar.ps1` 拉起;嵌套的 `openxyos\dist` 会在启动前提升到 live 根。若 livez 正常但自源请求失败,状态栏和预览区会明确提示,而不是一片白。 - Windows 安装成功后删除 `$INSTDIR\openxyos-runtime` 目录和 `openxyos-runtime.zip`(以及 `%LOCALAPPDATA%\FreeOS` 下同名暂存)。预配失败时保留这些文件以便排查。最终工作目录 `$INSTDIR\openxyos` 与 `%LOCALAPPDATA%\FreeOS\openxyos` 不受影响。 - ima 知识库挂接对齐官方 Agent Interface(https://ima.qq.com/agent-interface,落地页现发 ima-skill 1.1.10):用 Client ID / API Key 连接后,按 `search_knowledge_base` + `get_knowledge_base` 选知识库,按 `get_knowledge_list` / `search_knowledge` 自选文档;文件夹 ID 用接口返回的 `media_id`(不自造 `folder_` 前缀),选择会写入挂接。 - 模型页注册的本机 Ollama / GGUF 现在会出现在对话区模型选择器里,并走本机 `127.0.0.1:11434` 调用。注册会补齐 Ollama 供应商默认值、打开服务开关、热加载对话列表;Ollama 未启动时给出可操作错误,而不是静默失败。 diff --git a/dashboard/src/api/modules/orgModule.ts b/dashboard/src/api/modules/orgModule.ts index 795c8e85..f2c2f86d 100644 --- a/dashboard/src/api/modules/orgModule.ts +++ b/dashboard/src/api/modules/orgModule.ts @@ -52,6 +52,7 @@ export interface OrgControlPlane { export interface OrgOverview { enabled: boolean; sidecar_reachable: boolean; + sidecar_embed_ok?: boolean; sidecar_url: string; start_available: boolean; install_ready: boolean; diff --git a/dashboard/src/locales/en.json b/dashboard/src/locales/en.json index c2532a3f..dab0a7e8 100644 --- a/dashboard/src/locales/en.json +++ b/dashboard/src/locales/en.json @@ -20,6 +20,7 @@ "on": "Enabled", "off": "Disabled", "sidecarUp": "Local openXYOS connected", + "sidecarEmbedFailed": "Preview failed to load", "sidecarDown": "Opening local openXYOS", "sidecarOpening": "Opening local openXYOS", "lastSync": "Last sync", @@ -103,6 +104,7 @@ "disabled": "Organization module disabled", "previewTitle": "openXYOS preview", "previewOffline": "Opening local openXYOS (http://127.0.0.1:3780)…", + "previewBlank": "Local openXYOS is healthy, but the preview stayed blank: requests from its own origin (http://127.0.0.1:3780) were blocked by CORS, so scripts and styles never loaded. Fully quit and reopen FreeOS so the sidecar restarts with the current allow-list.", "previewLoading": "Opening local openXYOS…", "fullscreen": "Fullscreen", "exitFullscreen": "Exit fullscreen", diff --git a/dashboard/src/locales/zh.json b/dashboard/src/locales/zh.json index 6aec4802..71577adc 100644 --- a/dashboard/src/locales/zh.json +++ b/dashboard/src/locales/zh.json @@ -20,6 +20,7 @@ "on": "已启用", "off": "已关闭", "sidecarUp": "本机 openXYOS 已连接", + "sidecarEmbedFailed": "预览页未能加载", "sidecarDown": "正在打开本机 openXYOS", "sidecarOpening": "正在打开本机 openXYOS", "lastSync": "最近同步", @@ -103,6 +104,7 @@ "disabled": "组织模块已关闭", "previewTitle": "openXYOS 预览", "previewOffline": "正在打开本机 openXYOS(http://127.0.0.1:3780)…", + "previewBlank": "本机 openXYOS 健康检查已通过,但预览页是空白的:页面自己的地址(http://127.0.0.1:3780)被跨域拦截,脚本和样式没有加载。请完全退出并重新打开 FreeOS,用当前白名单重启本机服务。", "previewLoading": "正在打开本机 openXYOS…", "fullscreen": "全屏", "exitFullscreen": "退出全屏", diff --git a/dashboard/src/pages/Organization/Organization.module.less b/dashboard/src/pages/Organization/Organization.module.less index bae9a0b0..658dac7b 100644 --- a/dashboard/src/pages/Organization/Organization.module.less +++ b/dashboard/src/pages/Organization/Organization.module.less @@ -105,6 +105,21 @@ pointer-events: none; } +.previewBlank { + position: absolute; + inset: 0; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + padding: 32px 40px; + max-width: none; + text-align: center; + line-height: 1.6; + color: var(--fn-text-primary); + background: rgba(255, 255, 255, 0.94); +} + .drawerBody { display: flex; flex-direction: column; diff --git a/dashboard/src/pages/Organization/index.test.tsx b/dashboard/src/pages/Organization/index.test.tsx index 39b9eec2..5c0900b5 100644 --- a/dashboard/src/pages/Organization/index.test.tsx +++ b/dashboard/src/pages/Organization/index.test.tsx @@ -1,11 +1,12 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; import OrganizationPage from "./index"; -import type { OrgOverview } from "../../api/modules/orgModule"; +import { orgModuleApi, type OrgOverview } from "../../api/modules/orgModule"; const overview: OrgOverview = { enabled: true, sidecar_reachable: true, + sidecar_embed_ok: true, sidecar_url: "http://127.0.0.1:3780", start_available: false, install_ready: true, @@ -92,5 +93,20 @@ describe("OrganizationPage", () => { expect( screen.getAllByText("organization.downloadSource").length, ).toBeGreaterThan(0); + expect(screen.queryByTestId("org-preview-blank")).toBeNull(); + }); + + it("surfaces a blank-preview error when livez is up but embed origin fails", async () => { + vi.mocked(orgModuleApi.overview).mockResolvedValue({ + ...overview, + sidecar_embed_ok: false, + }); + render(); + expect(await screen.findByTestId("org-preview-blank")).toHaveTextContent( + "organization.previewBlank", + ); + expect( + screen.getByText("organization.sidecarEmbedFailed"), + ).toBeInTheDocument(); }); }); diff --git a/dashboard/src/pages/Organization/index.tsx b/dashboard/src/pages/Organization/index.tsx index c5145a9f..665429a6 100644 --- a/dashboard/src/pages/Organization/index.tsx +++ b/dashboard/src/pages/Organization/index.tsx @@ -29,6 +29,7 @@ import { } from "../../utils/desktopFolder"; import { message } from "../../utils/antdMessage"; import { resolveOpenxyosSourceDest } from "./pickSourceDest"; +import { shouldShowPreviewBlank } from "./sidecarRecover"; import styles from "./Organization.module.less"; type ActionKey = "assemble" | "pack" | "loop" | "sidecar" | "produce" | null; @@ -110,6 +111,11 @@ export default function OrganizationPage() { }, [applyOverview, overview?.sidecar_reachable]); const sidecarUp = Boolean(overview?.sidecar_reachable); + const embedOk = overview?.sidecar_embed_ok; + const previewBlank = shouldShowPreviewBlank({ + sidecarUp, + embedOk, + }); const firstRun = useMemo(() => { if (!overview) return false; return ( @@ -186,11 +192,19 @@ export default function OrganizationPage() { useEffect(() => { if (autoStartRef.current) return; - if (!overview || overview.sidecar_reachable) return; - if (!canSilentStart) return; + if (!overview) return; + const embedBroken = + overview.sidecar_reachable && overview.sidecar_embed_ok === false; + if (overview.sidecar_reachable && !embedBroken) return; + if (!embedBroken && !canSilentStart) return; autoStartRef.current = true; void startSidecar(); - }, [canSilentStart, overview, overview?.sidecar_reachable]); + }, [ + canSilentStart, + overview, + overview?.sidecar_reachable, + overview?.sidecar_embed_ok, + ]); const toggleModule = async (key: string, enabled: boolean) => { const next = { ...moduleToggles, [key]: enabled }; @@ -404,7 +418,9 @@ export default function OrganizationPage() { {overview?.enabled ? t("organization.on") : t("organization.off")} - {sidecarUp + {previewBlank + ? t("organization.sidecarEmbedFailed") + : sidecarUp ? t("organization.sidecarUp") : t("organization.sidecarOpening")} @@ -449,6 +465,14 @@ export default function OrganizationPage() { {t("organization.previewOffline")} )} + {previewBlank && ( +
+ {t("organization.previewBlank")} +
+ )}