Initial commit: Agentic OS troubleshooting platform
Self-hosted, Docker-based agentic troubleshooting platform: FastAPI backend + LangGraph agent, Next.js UI, tiered LLM routing (local Ollama -> Gemini -> DeepSeek -> OpenRouter), MCP server manager, encrypted device credentials, RBAC, audit log, project-memory + Obsidian integrations, and editable troubleshooting decision rules tuned for the GeneseasX vessel stack. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
80
frontend/lib/api.ts
Normal file
80
frontend/lib/api.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
export const API_BASE =
|
||||
process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000";
|
||||
|
||||
const TOKEN_KEY = "agentic_token";
|
||||
|
||||
export function getToken(): string | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
return window.localStorage.getItem(TOKEN_KEY);
|
||||
}
|
||||
|
||||
export function setToken(token: string | null) {
|
||||
if (typeof window === "undefined") return;
|
||||
if (token) window.localStorage.setItem(TOKEN_KEY, token);
|
||||
else window.localStorage.removeItem(TOKEN_KEY);
|
||||
}
|
||||
|
||||
export class ApiError extends Error {
|
||||
status: number;
|
||||
constructor(status: number, message: string) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
export async function api<T = any>(
|
||||
path: string,
|
||||
options: RequestInit = {}
|
||||
): Promise<T> {
|
||||
const headers = new Headers(options.headers);
|
||||
const token = getToken();
|
||||
if (token) headers.set("Authorization", `Bearer ${token}`);
|
||||
if (!headers.has("Content-Type") && options.body && !(options.body instanceof FormData)) {
|
||||
headers.set("Content-Type", "application/json");
|
||||
}
|
||||
const res = await fetch(`${API_BASE}${path}`, { ...options, headers });
|
||||
if (res.status === 401 && typeof window !== "undefined") {
|
||||
setToken(null);
|
||||
if (!window.location.pathname.startsWith("/login")) {
|
||||
window.location.href = "/login";
|
||||
}
|
||||
}
|
||||
if (!res.ok) {
|
||||
let detail = res.statusText;
|
||||
try {
|
||||
const body = await res.json();
|
||||
detail = body.detail || detail;
|
||||
} catch {}
|
||||
throw new ApiError(res.status, typeof detail === "string" ? detail : JSON.stringify(detail));
|
||||
}
|
||||
if (res.status === 204) return undefined as T;
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function login(email: string, password: string) {
|
||||
const form = new URLSearchParams();
|
||||
form.set("username", email);
|
||||
form.set("password", password);
|
||||
const res = await fetch(`${API_BASE}/api/auth/login`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: form.toString(),
|
||||
});
|
||||
if (!res.ok) {
|
||||
let detail = "Login failed";
|
||||
try {
|
||||
detail = (await res.json()).detail || detail;
|
||||
} catch {}
|
||||
throw new ApiError(res.status, detail);
|
||||
}
|
||||
return res.json() as Promise<{ access_token: string; role: string; email: string }>;
|
||||
}
|
||||
|
||||
export function wsUrl(path: string): string {
|
||||
const base = API_BASE.replace(/^http/, "ws");
|
||||
const token = getToken();
|
||||
const sep = path.includes("?") ? "&" : "?";
|
||||
return `${base}${path}${sep}token=${encodeURIComponent(token || "")}`;
|
||||
}
|
||||
|
||||
export const fetcher = (path: string) => api(path);
|
||||
Reference in New Issue
Block a user