| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100 |
- "use client";
- import { useCallback, useEffect, useRef, useState } from "react";
- import { errorMessage, getExecutionView, listScriptBuilds } from "@/lib/api";
- import { isTerminalStatus, type BuildSummary, type ExecutionViewV8 } from "@/lib/types";
- const RUN_QUERY_KEY = "run";
- const LAST_RUN_STORAGE_KEY = "script-build:last-run";
- export function initialBuildId(builds: BuildSummary[], search: string, storedId?: string | null) {
- const queryId = new URLSearchParams(search).get(RUN_QUERY_KEY)?.trim();
- if (queryId) return queryId;
- const remembered = storedId?.trim();
- if (remembered && builds.some((build) => build.id === remembered)) return remembered;
- return builds[0]?.id || "";
- }
- export function useExecutionView() {
- const [builds, setBuilds] = useState<BuildSummary[]>([]);
- const [selectedId, setSelectedId] = useState("");
- const [view, setView] = useState<ExecutionViewV8 | null>(null);
- const [loading, setLoading] = useState(true);
- const [refreshing, setRefreshing] = useState(false);
- const [error, setError] = useState<string | null>(null);
- const generation = useRef(0);
- const loadView = useCallback(async (id: string, signal?: AbortSignal, quiet = false) => {
- const requestId = ++generation.current;
- quiet ? setRefreshing(true) : setLoading(true);
- try {
- if (!id) throw new Error("请输入真实 Run ID");
- const data = await getExecutionView(id, signal);
- if (requestId !== generation.current) return;
- setView(data);
- setError(null);
- const loadedId = String(data.header.id);
- setSelectedId(loadedId);
- rememberBuildId(loadedId);
- } catch (requestError) {
- if (signal?.aborted || requestId !== generation.current) return;
- if (quiet && view) {
- setError(`刷新失败,保留 ${view.capturedAt} 的快照:${errorMessage(requestError)}`);
- } else {
- setView(null);
- setError(`无法读取构建 #${id}:${errorMessage(requestError)}`);
- }
- } finally {
- if (requestId === generation.current) { setLoading(false); setRefreshing(false); }
- }
- }, [view]);
- useEffect(() => {
- const controller = new AbortController();
- void listScriptBuilds(controller.signal)
- .then((items) => {
- setBuilds(items);
- const preferred = initialBuildId(items, window.location.search, readRememberedBuildId());
- setSelectedId(preferred);
- if (!preferred) throw new Error("当前没有可读取的真实构建记录");
- return loadView(preferred, controller.signal);
- })
- .catch((listError) => { if (!controller.signal.aborted) { setView(null); setLoading(false); setError(`无法读取真实构建列表:${errorMessage(listError)}`); } });
- return () => controller.abort();
- }, []);
- useEffect(() => {
- if (!view || isTerminalStatus(view.header.status)) return;
- const timer = window.setTimeout(() => void loadView(String(view.header.id), undefined, true), 3000);
- return () => window.clearTimeout(timer);
- }, [view?.capturedAt, view?.header.id, view?.header.status, loadView]);
- const selectBuild = useCallback((id: string) => {
- const normalized = id.trim();
- if (!normalized) return;
- setSelectedId(normalized); setView(null); setError(null);
- void loadView(normalized);
- }, [loadView]);
- return { builds, selectedId, view, loading, refreshing, error, selectBuild };
- }
- function readRememberedBuildId() {
- try {
- return window.localStorage.getItem(LAST_RUN_STORAGE_KEY);
- } catch {
- return null;
- }
- }
- function rememberBuildId(id: string) {
- try {
- window.localStorage.setItem(LAST_RUN_STORAGE_KEY, id);
- } catch {
- // URL persistence still works when storage is unavailable.
- }
- const url = new URL(window.location.href);
- if (url.searchParams.get(RUN_QUERY_KEY) === id) return;
- url.searchParams.set(RUN_QUERY_KEY, id);
- window.history.replaceState(window.history.state, "", url);
- }
|