commits
tags
import type { Schema } from "./schema";
export interface FlowInput {
name: string;
label?: string;
default?: string;
description?: string;
/** komma-separerad sträng i JSON:en; parsas till string[] i UI:t */
options?: string;
}
export interface Flow {
format?: string;
nodes: Step[];
edges: GraphEdge[];
start?: string | null;
settings: Record<string, unknown>;
layout?: Record<string, { x: number; y: number }>;
inputs?: FlowInput[];
input_sets?: Record<string, Record<string, string>>;
subflows?: Subflow[];
}
/** En lokal namngiven subgraf i samma flödesfil, anropbar med call_flow (target "#id"). */
export interface Subflow {
id: string;
name: string;
nodes: Step[];
edges: GraphEdge[];
start?: string | null;
inputs?: FlowInput[];
}
export interface CallArg {
name: string;
value: string;
}
export interface CallRet {
from: string;
to: string;
}
/** En nod i grafen: platt (type + fältvärden), plus canvas-position. `if`-noder
* bär sina villkor i `branches` (utan steg – grenarnas noder kopplas med kanter). */
export interface Step {
type: string;
id?: string;
label?: string;
enabled?: boolean;
on_error?: "stop" | "continue";
position?: { x: number; y: number };
branches?: Branch[];
fields?: RowField[];
/** call_flow */
target?: string;
in?: CallArg[];
out?: CallRet[];
[k: string]: unknown;
}
export interface GraphEdge {
id: string;
source: string;
target: string;
sourceHandle?: string;
targetHandle?: string;
}
export interface Branch {
condition: Condition;
}
export interface Condition {
kind: string;
[k: string]: unknown;
}
export interface RowField {
name: string;
by?: string;
value?: string;
role?: string;
}
export interface RunState {
status: "running" | "done" | "error";
logs: string[];
browser_open: boolean;
trace_ready: boolean;
vars: Record<string, unknown>;
}
export interface AssistResult {
nodes: Step[];
edges: GraphEdge[];
start?: string | null;
settings: Record<string, unknown>;
subflows?: Subflow[];
explanation?: string;
}
export interface InspectRow {
depth: number;
control_type: string;
name: string;
auto_id: string;
class_name: string;
}
export interface BlockMeta {
name: string;
description: string;
step_count: number;
builtin: boolean;
}
export interface Block {
name: string;
description: string;
nodes: Step[];
edges: GraphEdge[];
start?: string | null;
settings?: Record<string, unknown>;
}
async function j<T>(res: Response): Promise<T> {
if (!res.ok) {
let detail: unknown;
try {
detail = (await res.json()).detail;
} catch {
detail = res.statusText;
}
throw new ApiError(res.status, detail);
}
return res.json() as Promise<T>;
}
export class ApiError extends Error {
constructor(public status: number, public detail: unknown) {
super(typeof detail === "string" ? detail : JSON.stringify(detail));
}
}
export const api = {
schema: () => fetch("/api/step-schema").then((r) => j<Schema>(r)),
listFlows: () => fetch("/api/flows").then((r) => j<string[]>(r)),
getFlow: (name: string) => fetch(`/api/flows/${encodeURIComponent(name)}`).then((r) => j<Flow>(r)),
saveFlow: (name: string, flow: Flow) =>
fetch(`/api/flows/${encodeURIComponent(name)}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(flow),
}).then((r) => j<{ ok: boolean }>(r)),
deleteFlow: (name: string) =>
fetch(`/api/flows/${encodeURIComponent(name)}`, { method: "DELETE" }).then((r) => j<{ ok: boolean }>(r)),
runFlow: (
name: string,
closeMode: string,
initialVars: Record<string, string> = {},
subflow?: string,
) =>
fetch(`/api/flows/${encodeURIComponent(name)}/run`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ close_mode: closeMode, initial_vars: initialVars, subflow: subflow ?? null }),
}).then((r) => j<{ run_id: string }>(r)),
assist: (name: string, instruction: string, flow: Flow) =>
fetch(`/api/flows/${encodeURIComponent(name)}/assist`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
instruction,
nodes: flow.nodes,
edges: flow.edges,
start: flow.start ?? null,
settings: flow.settings,
subflows: flow.subflows ?? [],
}),
}).then((r) => j<AssistResult>(r)),
desktopWindows: () => fetch("/api/desktop/windows").then((r) => j<string[]>(r)),
inspectWindow: (title: string) =>
fetch("/api/desktop/inspect", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title }),
}).then((r) => j<InspectRow[]>(r)),
listBlocks: () => fetch("/api/blocks").then((r) => j<BlockMeta[]>(r)),
getBlock: (name: string) => fetch(`/api/blocks/${encodeURIComponent(name)}`).then((r) => j<Block>(r)),
saveBlock: (name: string, block: Block) =>
fetch(`/api/blocks/${encodeURIComponent(name)}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(block),
}).then((r) => j<{ ok: boolean }>(r)),
deleteBlock: (name: string) =>
fetch(`/api/blocks/${encodeURIComponent(name)}`, { method: "DELETE" }).then((r) => j<{ ok: boolean }>(r)),
listDesktopImages: () => fetch("/api/desktop-images").then((r) => j<string[]>(r)),
captureDesktopImage: (name: string) =>
fetch("/api/desktop-images/capture", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name }),
}).then((r) => j<{ ok: boolean; name: string; width: number; height: number }>(r)),
deleteDesktopImage: (name: string) =>
fetch(`/api/desktop-images/${encodeURIComponent(name)}`, { method: "DELETE" }).then((r) => j<{ ok: boolean }>(r)),
desktopImageUrl: (name: string) => `/api/desktop-images/${encodeURIComponent(name)}`,
run: (runId: string) => fetch(`/api/runs/${runId}`).then((r) => j<RunState>(r)),
closeBrowser: (runId: string) =>
fetch(`/api/runs/${runId}/close-browser`, { method: "POST" }).then((r) => j<{ ok: boolean }>(r)),
showTrace: (runId: string) =>
fetch(`/api/runs/${runId}/show-trace`, { method: "POST" }).then((r) => j<{ ok: boolean }>(r)),
exportUrl: (name: string) => `/api/flows/${encodeURIComponent(name)}/export`,
};