Files
browser/platform_web/stores/operations.ts
T
2026-07-11 14:56:10 +08:00

94 lines
3.0 KiB
TypeScript

import { useCallback, useRef, useState } from "react";
import type { JobResponse } from "../api/types";
import type { OperationRecord, OperationStatus } from "../contracts/workspace";
export interface BeginOperationInput {
intent: string;
targetKind: OperationRecord["targetKind"];
targetId: string;
requester?: string;
}
export interface OperationUpdate {
status?: OperationStatus;
job?: JobResponse;
message?: string;
errorReason?: string;
diagnosticId?: string;
}
export interface OperationTracker {
operations: OperationRecord[];
begin: (input: BeginOperationInput) => string;
update: (operationId: string, update: OperationUpdate) => void;
succeed: (operationId: string, message?: string, job?: JobResponse) => void;
fail: (operationId: string, errorReason: string, diagnosticId?: string) => void;
isPending: (targetId: string, intent?: string) => boolean;
}
export function useOperationTracker(): OperationTracker {
const [operations, setOperations] = useState<OperationRecord[]>([]);
const sequence = useRef(0);
const begin = useCallback((input: BeginOperationInput): string => {
sequence.current += 1;
const now = new Date().toISOString();
const record: OperationRecord = {
id: `op-${Date.now()}-${sequence.current}`,
intent: input.intent,
targetKind: input.targetKind,
targetId: input.targetId,
requester: input.requester ?? "当前用户",
status: "pending",
createdAt: now,
updatedAt: now
};
setOperations((current) => [record, ...current]);
return record.id;
}, []);
const update = useCallback((operationId: string, patch: OperationUpdate) => {
setOperations((current) =>
current.map((operation) =>
operation.id === operationId
? {
...operation,
status: patch.status ?? operation.status,
jobId: patch.job?.id ?? operation.jobId,
jobState: patch.job?.state ?? operation.jobState,
message: patch.message ?? operation.message,
errorReason: patch.errorReason ?? operation.errorReason,
diagnosticId: patch.diagnosticId ?? operation.diagnosticId,
updatedAt: new Date().toISOString()
}
: operation
)
);
}, []);
const succeed = useCallback(
(operationId: string, message?: string, job?: JobResponse) => {
update(operationId, { status: "succeeded", message, job });
},
[update]
);
const fail = useCallback(
(operationId: string, errorReason: string, diagnosticId?: string) => {
update(operationId, { status: "failed", errorReason, diagnosticId: diagnosticId ?? operationId });
},
[update]
);
const isPending = useCallback(
(targetId: string, intent?: string) =>
operations.some(
(operation) => operation.status === "pending" && operation.targetId === targetId && (intent === undefined || operation.intent === intent)
),
[operations]
);
return { operations, begin, update, succeed, fail, isPending };
}