// ---- previewSlice ---- // // 拥有 previewKey / previewCode / previewCodeSize / previewLoading / previewError。 // Action: loadPreview (用 AbortController 取消旧请求 + request-id 序列号忽略过期响应)。 import type { StateCreator } from "zustand"; import { bumpPreviewRequest, getPreviewController, getPreviewRequest, setPreviewController, } from "./helpers"; import type { PreviewSliceActions, PreviewSliceState, } from "./types"; import type { ScriptWorkspaceStore } from "./useScriptWorkspaceStore"; export const createPreviewSlice: StateCreator< ScriptWorkspaceStore, [], [], PreviewSliceState & PreviewSliceActions > = (set) => { const initial: PreviewSliceState = { previewKey: null, previewCode: null, previewCodeSize: null, previewLoading: false, previewError: null, }; return { ...initial, loadPreview: async (workspaceId, filePath) => { const existing = getPreviewController(); if (existing) { existing.abort(); } const requestId = bumpPreviewRequest(); const previewKey = `${workspaceId}::${filePath}`; set({ previewKey, previewLoading: true, previewError: null }); const controller = new AbortController(); setPreviewController(controller); try { const url = `/jupyter/${workspaceId}/api/contents/${filePath}` + `?type=file&content=1&hash=1&format=text`; const response = await fetch(url, { signal: controller.signal, credentials: "include", cache: "no-store", }); if (!response.ok) { throw new Error(`Failed to load file: ${response.status}`); } const data = await response.json(); if (getPreviewRequest() !== requestId) return; set({ previewKey, previewCode: data.content ?? "", previewCodeSize: data.size ?? 0, previewLoading: false, previewError: null, }); } catch (error) { if (getPreviewRequest() !== requestId) return; if (error instanceof Error && error.name === "AbortError") return; set({ previewKey, previewCode: null, previewCodeSize: null, previewLoading: false, previewError: error instanceof Error ? error.message : "加载预览失败", }); } finally { if (getPreviewRequest() === requestId) { setPreviewController(null); } } }, }; };