diff --git a/frontend/app/components/assistant-ui/thread.tsx b/frontend/app/components/assistant-ui/thread.tsx
index 10ca8a0..47511c2 100644
--- a/frontend/app/components/assistant-ui/thread.tsx
+++ b/frontend/app/components/assistant-ui/thread.tsx
@@ -298,12 +298,9 @@ async function cancelLatestRun(sessionId: string, runId?: string | null) {
const ChatTopActions: FC = () => {
const { openFiles } = useActivityPanel();
const [workspaceOpen, setWorkspaceOpen] = useState(false);
- const latestSessionId = useAuiState((s) =>
- findLatestMessageMetadataValue(s.thread.messages, "sessionId"),
- );
- const normalizedSessionId = normalizeSessionId(
- typeof latestSessionId === "string" ? latestSessionId : null,
- );
+ const currentRun = useReplayedRunState();
+ const normalizedSessionId = currentRun.sessionId;
+ const workspaceStatus = useJupyterWorkspaceStatus(normalizedSessionId);
return (
@@ -330,9 +327,7 @@ const ChatTopActions: FC = () => {
type="button"
className="flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-sm transition-colors hover:bg-muted"
onClick={() => {
- openFiles(
- typeof latestSessionId === "string" ? latestSessionId : null,
- );
+ openFiles(normalizedSessionId);
}}
>
@@ -344,7 +339,7 @@ const ChatTopActions: FC = () => {
onClick={() => setWorkspaceOpen(true)}
>
- 切换工作区
+ {workspaceStatus?.connected ? "Jupyter 工作区" : "切换工作区"}
@@ -381,6 +376,52 @@ const WORKSPACE_SWITCH_STEPS = [
"链接工作区",
];
+function useJupyterWorkspaceStatus(sessionId: string | null) {
+ const [status, setStatus] = useState
(null);
+
+ useEffect(() => {
+ if (!sessionId) {
+ setStatus(null);
+ return;
+ }
+ let cancelled = false;
+
+ async function refresh() {
+ try {
+ const response = await fetch(
+ `/api/claw/jupyter?session_id=${encodeURIComponent(sessionId)}`,
+ { cache: "no-store" },
+ );
+ const payload = (await response.json().catch(() => ({}))) as
+ | JupyterWorkspaceStatus
+ | Record;
+ if (cancelled) return;
+ if (!response.ok) {
+ setStatus(null);
+ return;
+ }
+ setStatus(payload as JupyterWorkspaceStatus);
+ } catch {
+ if (!cancelled) setStatus(null);
+ }
+ }
+
+ void refresh();
+ const onRefresh = () => void refresh();
+ window.addEventListener("focus", onRefresh);
+ window.addEventListener("claw-sessions-changed", onRefresh);
+ const interval = window.setInterval(refresh, 8000);
+ return () => {
+ cancelled = true;
+ window.removeEventListener("focus", onRefresh);
+ window.removeEventListener("claw-sessions-changed", onRefresh);
+ window.clearInterval(interval);
+ };
+ }, [sessionId]);
+
+ return status;
+}
+
function WorkspaceSwitchDialog({
open,
onOpenChange,
@@ -475,9 +516,13 @@ function WorkspaceSwitchDialog({