File size: 4,893 Bytes
7b92798
 
6b21561
 
7b92798
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4a74c29
7b92798
 
 
 
 
 
4a74c29
7b92798
 
 
 
 
 
 
 
 
 
4a74c29
7b92798
 
 
 
 
 
 
 
4a74c29
7b92798
 
 
 
 
 
 
 
 
 
 
 
 
 
4a74c29
7b92798
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4a74c29
7b92798
 
 
 
 
 
 
 
 
 
82c10a3
 
 
 
 
 
 
 
 
 
 
291dc87
82c10a3
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import type { Decision, DemoRepo, IntentReceipt } from "./types";

/** Empty string = same origin (HF Space). Local dev: set NEXT_PUBLIC_API_URL in frontend/.env.local */
export const API_BASE = (process.env.NEXT_PUBLIC_API_URL ?? "").replace(/\/$/, "");

async function parseResponse<T>(r: Response): Promise<T> {
  const text = await r.text();
  if (!r.ok) {
    let detail = r.statusText || `HTTP ${r.status}`;
    if (text) {
      try {
        const err = JSON.parse(text) as { detail?: string | unknown };
        if (typeof err.detail === "string") detail = err.detail;
        else if (err.detail) detail = JSON.stringify(err.detail);
        else detail = text.slice(0, 200);
      } catch {
        detail = text.slice(0, 200);
      }
    }
    if (r.status === 0 || detail === "Failed to fetch") {
      throw new Error(
        `Cannot reach backend at ${API_BASE}. Start it with: cd backend && python -m uvicorn main:app --reload --port 8000`
      );
    }
    throw new Error(detail);
  }
  if (!text.trim()) {
    throw new Error(
      `Empty response from server. Check CORS (frontend port) and that the backend is running on ${API_BASE}.`
    );
  }
  try {
    return JSON.parse(text) as T;
  } catch {
    throw new Error(`Invalid JSON from server: ${text.slice(0, 120)}`);
  }
}

async function post<T>(path: string, body: unknown): Promise<T> {
  const r = await fetch(`${API_BASE}${path}`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  return parseResponse<T>(r);
}

async function get<T>(path: string): Promise<T> {
  const r = await fetch(`${API_BASE}${path}`);
  return parseResponse<T>(r);
}

export async function fetchDemoRepos(): Promise<{ repos: DemoRepo[] }> {
  return get("/api/demo-repos");
}

export async function fetchSampleAlert(): Promise<{ text: string }> {
  return get("/api/sample-alert");
}

export async function parseUpload(file: File) {
  const form = new FormData();
  form.append("file", file);
  const r = await fetch(`${API_BASE}/api/parse-upload`, { method: "POST", body: form });
  return parseResponse<{
    format: string;
    alert_text: string;
    title: string;
    finding_count: number;
    cve_ids?: string[];
  }>(r);
}

export function explainAlert(email: string, alert_text: string, session_id?: string) {
  return post<{
    analysis: Record<string, unknown>;
    explanation: string;
    source: string;
    session_id: string;
    cve_ids?: string[];
  }>("/api/explain", { email, alert_text, session_id });
}

export function auditCode(email: string, code: string, session_id?: string) {
  return post<{
    analysis: Record<string, unknown>;
    explanation: string;
    source: string;
    session_id: string;
  }>("/api/audit-code", { email, code, session_id });
}

export function scanGithub(
  email: string,
  repo_url: string,
  session_id?: string,
  deep_scan = true
) {
  return post<{
    scan: Record<string, unknown>;
    analysis: Record<string, unknown>;
    analysis_source: string;
    session_id: string;
  }>("/api/github/scan", { email, repo_url, session_id, deep_scan });
}

export function planFix(
  email: string,
  alert_text: string,
  opts?: {
    user_request?: string;
    session_id?: string;
    simulate_attack?: boolean;
    use_delegation_demo?: boolean;
  }
) {
  return post<{
    plan: { goal: string; steps: { action: string; description?: string }[] };
    decisions: Decision[];
    summary: string;
    armoriq_live: boolean;
    intent_receipt: IntentReceipt;
    plan_source: string;
    session_id: string;
    delegation_demo?: { scenario: string; message: string }[];
  }>("/api/plan-fix", {
    email,
    alert_text,
    user_request: opts?.user_request || "Create a safe remediation plan",
    session_id: opts?.session_id,
    simulate_attack: opts?.simulate_attack ?? false,
    use_delegation_demo: opts?.use_delegation_demo ?? false,
  });
}

export async function exportBrief(body: {
  email: string;
  session_id?: string;
  alert_text?: string;
  analysis?: Record<string, unknown> | null;
  plan?: { goal: string; steps: unknown[] } | null;
  decisions?: Decision[];
  receipt?: IntentReceipt | null;
}) {
  const r = await fetch(`${API_BASE}/api/export-brief`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  const text = await r.text();
  if (!r.ok) {
    throw new Error(text || "Export failed");
  }
  return text;
}

export function authSignup(email: string) {
  return post<{
    email: string;
    api_key: string;
    plan: string;
    install_snippet: string;
  }>("/api/auth/signup", { email });
}

export function authMe(apiKey: string) {
  return fetch(`${API_BASE}/api/auth/me`, {
    headers: { Authorization: `Bearer ${apiKey}` },
  }).then(parseResponse<{ email: string; plan: string; created_at: string }>);
}