foxygit / RPA-Remisser Log in
commits tags

/webui/frontend/src/lib/api.ts · 6.3 KB

raw
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`,
};