From ad00ea1c5f42f42d68619a67197ede0aac14c55b Mon Sep 17 00:00:00 2001 From: lyon Date: Tue, 16 Jun 2026 06:17:54 +0800 Subject: [PATCH] fix(web): add workbench session deep links --- internal/dev-entrypoint/cloud-web-runtime.mjs | 8 +++- web/hwlab-cloud-web/index.html | 3 +- web/hwlab-cloud-web/src/api/workbench.ts | 1 + .../src/components/layout/AppShell.vue | 8 +++- .../src/components/workbench/SessionRail.vue | 10 +++- web/hwlab-cloud-web/src/router/guards.ts | 2 +- web/hwlab-cloud-web/src/router/index.ts | 5 +- web/hwlab-cloud-web/src/stores/workbench.ts | 29 +++++++++++- web/hwlab-cloud-web/src/utils/index.ts | 10 ++++ .../src/views/workbench/CodeWorkbenchView.vue | 47 ++++++++++++++++++- 10 files changed, 111 insertions(+), 12 deletions(-) diff --git a/internal/dev-entrypoint/cloud-web-runtime.mjs b/internal/dev-entrypoint/cloud-web-runtime.mjs index 5d0c3b4a..adebaff2 100644 --- a/internal/dev-entrypoint/cloud-web-runtime.mjs +++ b/internal/dev-entrypoint/cloud-web-runtime.mjs @@ -22,7 +22,7 @@ const CLIENT_DISCONNECT_ERROR_CODES = new Set([ ]); const CLIENT_DISCONNECT_ERROR_PATTERN = /\b(?:socket hang up|client disconnected|premature close)\b/iu; const CLOUD_WEB_ERROR_HANDLER_INSTALLED = Symbol.for("hwlab.cloud-web.error-handler-installed"); -const CLIENT_ROUTE_FALLBACK_PATHS = new Set(["/gate", "/diagnostics/gate", "/help", "/skills", "/access"]); +const CLIENT_ROUTE_FALLBACK_PATHS = new Set(["/workbench", "/workspace", "/gate", "/diagnostics/gate", "/help", "/skills", "/access"]); const STATIC_ASSET_EXTENSION_PATTERN = /\.[A-Za-z0-9][A-Za-z0-9_-]*$/u; export async function serveCloudWeb(options) { @@ -68,7 +68,7 @@ export function createCloudWebServer({ } const routePath = normalizeCloudWebRoutePath(url.pathname); - const relativePath = routePath === "/" || CLIENT_ROUTE_FALLBACK_PATHS.has(routePath) || isCloudWebClientRouteRequest(request, routePath) + const relativePath = routePath === "/" || CLIENT_ROUTE_FALLBACK_PATHS.has(routePath) || isCloudWebWorkbenchSessionRoute(routePath) || isCloudWebClientRouteRequest(request, routePath) ? "index.html" : url.pathname.slice(1); for (const root of roots) { @@ -115,6 +115,10 @@ export function createCloudWebServer({ }); } +function isCloudWebWorkbenchSessionRoute(routePath) { + return /^\/(?:workbench|workspace)\/sessions\/cnv_[A-Za-z0-9_.:-]+$/u.test(routePath); +} + function normalizeCloudWebRoutePath(pathname) { return String(pathname || "/").replace(/\/+$/u, "") || "/"; } diff --git a/web/hwlab-cloud-web/index.html b/web/hwlab-cloud-web/index.html index 3e022d9d..0c58db7e 100644 --- a/web/hwlab-cloud-web/index.html +++ b/web/hwlab-cloud-web/index.html @@ -9,7 +9,8 @@ (function () { var hash = window.location.hash; var path = window.location.pathname.replace(/\/+$/g, "") || "/"; - if (path !== "/" && path !== "/workspace") return; + var isWorkbenchPath = path === "/" || path === "/workbench" || path === "/workspace" || /^\/(?:workbench|workspace)\/sessions\/cnv_[A-Za-z0-9_.:-]+$/.test(path); + if (!isWorkbenchPath) return; if (hash && hash !== "#/workspace" && hash !== "#/") return; var projectId = new URLSearchParams(window.location.search).get("projectId"); if (!projectId) { diff --git a/web/hwlab-cloud-web/src/api/workbench.ts b/web/hwlab-cloud-web/src/api/workbench.ts index 97a29b45..adb980b2 100644 --- a/web/hwlab-cloud-web/src/api/workbench.ts +++ b/web/hwlab-cloud-web/src/api/workbench.ts @@ -6,6 +6,7 @@ export const workbenchAPI = { updateWorkspace: (workspaceId: string, payload: Record): Promise> => fetchJson(`/v1/workbench/workspace/${encodeURIComponent(workspaceId)}`, { method: "PATCH", body: JSON.stringify(payload), timeoutMs: 8000, timeoutName: "workspace update" }), selectConversation: (workspaceId: string, payload: Record): Promise> => fetchJson(`/v1/workbench/workspace/${encodeURIComponent(workspaceId)}/select-conversation`, { method: "POST", body: JSON.stringify(payload), timeoutMs: 8000, timeoutName: "select conversation" }), conversations: (projectId: string): Promise> => fetchJson(`/v1/agent/conversations?projectId=${encodeURIComponent(projectId)}`, { timeoutMs: 8000, timeoutName: "conversations" }), + conversation: (conversationId: string): Promise> => fetchJson(`/v1/agent/conversations/${encodeURIComponent(conversationId)}`, { timeoutMs: 8000, timeoutName: "conversation" }), deleteConversation: (conversationId: string, payload: Record): Promise> => fetchJson(`/v1/agent/conversations/${encodeURIComponent(conversationId)}`, { method: "DELETE", body: JSON.stringify(payload), timeoutMs: 8000, timeoutName: "delete conversation" }), saveConversation: (conversationId: string, payload: Record): Promise> => fetchJson(`/v1/agent/conversations/${encodeURIComponent(conversationId)}`, { method: "PUT", body: JSON.stringify(payload), timeoutMs: 8000, timeoutName: "save conversation" }) }; diff --git a/web/hwlab-cloud-web/src/components/layout/AppShell.vue b/web/hwlab-cloud-web/src/components/layout/AppShell.vue index d5e794e5..5f12561c 100644 --- a/web/hwlab-cloud-web/src/components/layout/AppShell.vue +++ b/web/hwlab-cloud-web/src/components/layout/AppShell.vue @@ -23,7 +23,7 @@ const navSections = [ ]; const shellless = computed(() => route.name === "Login" || route.name === "Register" || route.name === "NotFound"); -const showWorkbenchDiagnostics = computed(() => route.name === "CodeWorkbench"); +const showWorkbenchDiagnostics = computed(() => route.meta.section === "workbench"); watch(showWorkbenchDiagnostics, (visible) => { if (!visible) diagnosticsOpen.value = false; @@ -38,6 +38,10 @@ async function go(path: string): Promise { window.setTimeout(() => app.setLoading(false), 120); } } + +function isNavItemActive(item: { name: string }): boolean { + return route.name === item.name || (item.name === "CodeWorkbench" && route.meta.section === "workbench"); +}