mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-05 08:10:39 +00:00
220 lines
5.8 KiB
TypeScript
220 lines
5.8 KiB
TypeScript
import useSWRMutation from "swr/mutation";
|
|
import { useSWRConfig, type ScopedMutator } from "swr";
|
|
import type {
|
|
PreviewUrlResponse,
|
|
Run,
|
|
SteerRunRequest,
|
|
SubmitAnswerRequest,
|
|
UpdateRunRequest,
|
|
} from "@qltysh/fabro-api-client";
|
|
|
|
import {
|
|
apiData,
|
|
authApi,
|
|
humanInTheLoopApi,
|
|
runsApi,
|
|
} from "./api-client";
|
|
import { mutateRunListCaches } from "./board-cache";
|
|
import { queryKeys } from "./query-keys";
|
|
import type { LifecycleAction, LifecycleActionError } from "./run-actions";
|
|
import {
|
|
approveRun,
|
|
archiveRun,
|
|
cancelRun,
|
|
denyRun,
|
|
isLifecycleActionError,
|
|
retryRun,
|
|
unarchiveRun,
|
|
} from "./run-actions";
|
|
|
|
export type PreviewRunArg = {
|
|
port: number;
|
|
expires_in_secs: number;
|
|
signed?: boolean;
|
|
};
|
|
|
|
export type PreviewMutationResult = {
|
|
intent: "preview";
|
|
url: string;
|
|
};
|
|
|
|
export type LifecycleMutationResult =
|
|
| {
|
|
intent: LifecycleAction;
|
|
ok: true;
|
|
run: Run;
|
|
}
|
|
| {
|
|
intent: LifecycleAction;
|
|
ok: false;
|
|
error: LifecycleActionError | null;
|
|
};
|
|
|
|
export function usePreviewRun(id: string | undefined) {
|
|
return useSWRMutation(
|
|
id ? queryKeys.runs.preview(id) : null,
|
|
async (_key, { arg }: { arg: PreviewRunArg }): Promise<PreviewMutationResult> => {
|
|
const result = await apiData<PreviewUrlResponse>(() =>
|
|
humanInTheLoopApi.generatePreviewUrl(id!, arg),
|
|
);
|
|
return { intent: "preview", url: result.url };
|
|
},
|
|
);
|
|
}
|
|
|
|
export function useCancelRun(id: string | undefined) {
|
|
return useLifecycleMutation(id, "cancel", cancelRun);
|
|
}
|
|
|
|
export function useApproveRun(id: string | undefined) {
|
|
return useLifecycleMutation(id, "approve", approveRun);
|
|
}
|
|
|
|
export function useDenyRun(id: string | undefined) {
|
|
return useLifecycleMutation(id, "deny", denyRun);
|
|
}
|
|
|
|
export function useArchiveRun(id: string | undefined) {
|
|
return useLifecycleMutation(id, "archive", archiveRun);
|
|
}
|
|
|
|
export function useUnarchiveRun(id: string | undefined) {
|
|
return useLifecycleMutation(id, "unarchive", unarchiveRun);
|
|
}
|
|
|
|
export function useRetryRun(id: string | undefined) {
|
|
return useLifecycleMutation(id, "retry", retryRun, (run, mutate) => {
|
|
void mutate(queryKeys.runs.detail(run.id), run, { revalidate: false });
|
|
if (run.parent_id) {
|
|
mutateRunListCaches(mutate);
|
|
}
|
|
});
|
|
}
|
|
|
|
function useLifecycleMutation(
|
|
id: string | undefined,
|
|
intent: LifecycleAction,
|
|
action: (id: string) => Promise<Run>,
|
|
onSuccessExtra?: (run: Run, mutate: ScopedMutator) => void,
|
|
) {
|
|
const { mutate } = useSWRConfig();
|
|
const key = id ? queryKeys.runs[intent](id) : null;
|
|
return useSWRMutation(
|
|
key,
|
|
async (): Promise<LifecycleMutationResult> => {
|
|
if (!id) {
|
|
return { intent, ok: false, error: null };
|
|
}
|
|
try {
|
|
return { intent, ok: true, run: await action(id) };
|
|
} catch (error) {
|
|
return {
|
|
intent,
|
|
ok: false,
|
|
error: isLifecycleActionError(error) ? error : null,
|
|
};
|
|
}
|
|
},
|
|
{
|
|
onSuccess: (result) => {
|
|
if (!id || !result.ok) return;
|
|
if (intent !== "retry") {
|
|
// Keep the returned lifecycle state visible while revalidation
|
|
// observes the durable follow-up event (notably a 202 cancel).
|
|
void mutate(queryKeys.runs.detail(id), result.run, { revalidate: true });
|
|
void mutate(queryKeys.runs.billing(id));
|
|
}
|
|
mutateRunListCaches(mutate);
|
|
onSuccessExtra?.(result.run, mutate);
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
export function useUpdateRunTitle(id: string | undefined) {
|
|
const { mutate } = useSWRConfig();
|
|
return useSWRMutation(
|
|
id ? queryKeys.runs.updateTitle(id) : null,
|
|
async (_key, { arg }: { arg: UpdateRunRequest }): Promise<Run> => {
|
|
if (!id) throw new Error("id is required");
|
|
return apiData(() => runsApi.updateRun(id, arg));
|
|
},
|
|
{
|
|
onSuccess: (run) => {
|
|
if (!id) return;
|
|
void mutate(queryKeys.runs.detail(id), run, { revalidate: false });
|
|
mutateRunListCaches(mutate);
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
export type SubmitInterviewAnswerArg = {
|
|
questionId: string;
|
|
answer: SubmitAnswerRequest;
|
|
};
|
|
|
|
export function useSubmitInterviewAnswer(runId: string | undefined) {
|
|
const { mutate } = useSWRConfig();
|
|
return useSWRMutation(
|
|
runId ? `interview-answer:${runId}` : null,
|
|
async (_key: string, { arg }: { arg: SubmitInterviewAnswerArg }) => {
|
|
if (!runId) throw new Error("runId is required");
|
|
await apiData(() =>
|
|
humanInTheLoopApi.submitRunAnswer(runId, arg.questionId, arg.answer),
|
|
);
|
|
},
|
|
{
|
|
onSuccess: () => {
|
|
if (!runId) return;
|
|
void mutate(queryKeys.runs.questions(runId, 25, 0));
|
|
void mutate(queryKeys.runs.detail(runId));
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
export function useInterruptRun(runId: string | undefined) {
|
|
const { mutate } = useSWRConfig();
|
|
return useSWRMutation(
|
|
runId ? `interrupt-run:${runId}` : null,
|
|
async (_key: string) => {
|
|
if (!runId) throw new Error("runId is required");
|
|
await apiData(() => humanInTheLoopApi.interruptRun(runId));
|
|
},
|
|
{
|
|
onSuccess: () => {
|
|
if (!runId) return;
|
|
void mutate(queryKeys.runs.detail(runId));
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
export function useSteerRun(runId: string | undefined) {
|
|
const { mutate } = useSWRConfig();
|
|
return useSWRMutation(
|
|
runId ? `steer-run:${runId}` : null,
|
|
async (_key: string, { arg }: { arg: SteerRunRequest }) => {
|
|
if (!runId) throw new Error("runId is required");
|
|
await apiData(() => humanInTheLoopApi.steerRun(runId, arg));
|
|
},
|
|
{
|
|
onSuccess: () => {
|
|
if (!runId) return;
|
|
void mutate(queryKeys.runs.detail(runId));
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
export function useLoginDevToken() {
|
|
return useSWRMutation(
|
|
queryKeys.auth.loginDevToken(),
|
|
async (_key, { arg }: { arg: { token: string } }) => {
|
|
return apiData(() => authApi.loginDevToken(arg), {
|
|
redirectOnUnauthorized: false,
|
|
});
|
|
},
|
|
);
|
|
}
|