Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

# ─── API endpoint ────────────────────────────────────────────────────────
# REQUIRED. Your deployed Django backend.
# VITE_API_URL=https://agentic-enterprise.onrender.com/api
# VITE_API_URL=https://aos-api.viewdns.net/api

# ─── Google Sign-In ──────────────────────────────────────────────────────
# Get from https://console.cloud.google.com/apis/credentials
Expand Down
1,563 changes: 1,563 additions & 0 deletions frontend/pnpm-lock.yaml

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion frontend/src/api/agents.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { api } from './client'

function qs(params = {}) {
const filtered = Object.entries(params).filter(([, value]) => value !== undefined && value !== null && value !== '')
const query = new URLSearchParams(filtered).toString()
return query ? `?${query}` : ''
}

export const agents = {
list: () => api.get('/registry/agents/'),
create: (data) => api.post('/registry/agents/', data),
get: (id) => api.get(`/registry/agents/${id}/`),
blueprints: () => api.get('/registry/blueprints/'),
pendingActions: () => api.get('/intelligence/pending-actions/').then(d => Array.isArray(d) ? { results: d, count: d.length } : d),
pendingActions: (params = {}) => api.get(`/intelligence/pending-actions/${qs(params)}`).then(d => Array.isArray(d) ? { results: d, count: d.length } : d),
approve: (id, data) => api.post(`/intelligence/pending-actions/${id}/approve/`, data || { decision: 'APPROVED' }),
reject: (id, data) => api.post(`/intelligence/pending-actions/${id}/approve/`, data || { decision: 'DENIED' }),
escalations: () => api.get('/intelligence/escalations/'),
Expand Down
82 changes: 45 additions & 37 deletions frontend/src/api/client.js
Original file line number Diff line number Diff line change
@@ -1,72 +1,80 @@
const DEFAULT_API_URL = 'http://127.0.0.1:8000/api'
const BASE = (import.meta.env.VITE_API_URL || DEFAULT_API_URL).replace(/\/+$/, '')
const DEFAULT_API_URL = "https://aos-api.viewdns.net/api";
const BASE = (import.meta.env.VITE_API_URL || DEFAULT_API_URL).replace(
/\/+$/,
"",
);

function getAccess() {
return localStorage.getItem('aos_access')
return localStorage.getItem("aos_access");
}
function getRefresh() {
return localStorage.getItem('aos_refresh')
return localStorage.getItem("aos_refresh");
}
function setTokens({ access, refresh }) {
localStorage.setItem('aos_access', access)
if (refresh) localStorage.setItem('aos_refresh', refresh)
localStorage.setItem("aos_access", access);
if (refresh) localStorage.setItem("aos_refresh", refresh);
}
function clearTokens() {
localStorage.removeItem('aos_access')
localStorage.removeItem('aos_refresh')
localStorage.removeItem("aos_access");
localStorage.removeItem("aos_refresh");
}

async function refreshAccess() {
const refresh = getRefresh()
if (!refresh) throw new Error('No refresh token')
const refresh = getRefresh();
if (!refresh) throw new Error("No refresh token");
const res = await fetch(`${BASE}/token/refresh/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refresh }),
})
});
if (!res.ok) {
clearTokens()
throw new Error('Session expired')
clearTokens();
throw new Error("Session expired");
}
const data = await res.json()
setTokens(data)
return data.access
const data = await res.json();
setTokens(data);
return data.access;
}

async function request(path, options = {}) {
const headers = { 'Content-Type': 'application/json', ...options.headers }
const access = getAccess()
if (access) headers['Authorization'] = `Bearer ${access}`
const headers = { "Content-Type": "application/json", ...options.headers };
const access = getAccess();
if (access) headers["Authorization"] = `Bearer ${access}`;

let res = await fetch(`${BASE}${path}`, { ...options, headers })
let res = await fetch(`${BASE}${path}`, { ...options, headers });

if (res.status === 401 && getRefresh()) {
try {
const newAccess = await refreshAccess()
headers['Authorization'] = `Bearer ${newAccess}`
res = await fetch(`${BASE}${path}`, { ...options, headers })
const newAccess = await refreshAccess();
headers["Authorization"] = `Bearer ${newAccess}`;
res = await fetch(`${BASE}${path}`, { ...options, headers });
} catch {
clearTokens()
window.location.href = '/login'
throw new Error('Session expired')
clearTokens();
window.location.href = "/login";
throw new Error("Session expired");
}
}

if (!res.ok) {
const err = await res.json().catch(() => ({}))
throw Object.assign(new Error(err.detail || 'Request failed'), { status: res.status, data: err })
const err = await res.json().catch(() => ({}));
throw Object.assign(new Error(err.detail || "Request failed"), {
status: res.status,
data: err,
});
}

if (res.status === 204) return null
return res.json()
if (res.status === 204) return null;
return res.json();
}

export const api = {
get: (path, opts) => request(path, { method: 'GET', ...opts }),
post: (path, body, opts) => request(path, { method: 'POST', body: JSON.stringify(body), ...opts }),
patch: (path, body, opts) => request(path, { method: 'PATCH', body: JSON.stringify(body), ...opts }),
delete: (path, opts) => request(path, { method: 'DELETE', ...opts }),
get: (path, opts) => request(path, { method: "GET", ...opts }),
post: (path, body, opts) =>
request(path, { method: "POST", body: JSON.stringify(body), ...opts }),
patch: (path, body, opts) =>
request(path, { method: "PATCH", body: JSON.stringify(body), ...opts }),
delete: (path, opts) => request(path, { method: "DELETE", ...opts }),
setTokens,
clearTokens,
getAccess,
}
};
19 changes: 13 additions & 6 deletions frontend/src/api/observe.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import { api } from './client'

const BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000/api'
const BASE = (import.meta.env.VITE_API_URL || 'https://aos-api.viewdns.net/api').replace(/\/+$/, '')

function qs(params = {}) {
const filtered = Object.entries(params).filter(([, value]) => value !== undefined && value !== null && value !== '')
const query = new URLSearchParams(filtered).toString()
return query ? `?${query}` : ''
}

export const observe = {
tasks: (params = {}) => {
const qs = new URLSearchParams(params).toString()
return api.get(`/intelligence/tasks/${qs ? '?' + qs : ''}`)
return api.get(`/intelligence/tasks/${qs(params)}`)
.then(d => Array.isArray(d) ? { results: d, count: d.length } : d)
},
conversations: (params = {}) => {
const qs = new URLSearchParams(params).toString()
return api.get(`/intelligence/conversations/${qs ? '?' + qs : ''}`)
return api.get(`/intelligence/conversations/${qs(params)}`)
.then(d => Array.isArray(d) ? { results: d, count: d.length } : d)
},
executionReplay: (executionId) =>
api.get(`/swarm/executions/${executionId}/replay/`),

recentRuns: (type = 'template') => api.get(`/swarm/executions/?type=${type}`),
recentRuns: (params = 'template') => {
const queryParams = typeof params === 'string' ? { type: params } : params
return api.get(`/swarm/executions/${qs(queryParams)}`)
},
failureAnalytics: () => api.get('/intelligence/analytics/failures/'),
retryAnalytics: () => api.get('/intelligence/analytics/retries/'),
workflowGraph: () => api.get('/intelligence/analytics/workflow-graph/'),
Expand Down
21 changes: 13 additions & 8 deletions frontend/src/api/ops.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,43 @@
import { api } from './client'

function qs(params = {}) {
const filtered = Object.entries(params).filter(([, value]) => value !== undefined && value !== null && value !== '')
const query = new URLSearchParams(filtered).toString()
return query ? `?${query}` : ''
}

export const ops = {
overview: (params = {}) => {
const qs = new URLSearchParams(Object.entries(params).filter(([, value]) => value !== undefined && value !== null && value !== '')).toString()
return api.get(`/ops/overview/${qs ? '?' + qs : ''}`)
return api.get(`/ops/overview/${qs(params)}`)
},
connectors: () => api.get('/ops/connectors/'),
accounts: {
list: () => api.get('/ops/accounts/'),
list: (params = {}) => api.get(`/ops/accounts/${qs(params)}`),
create: (data) => api.post('/ops/accounts/', data),
},
leads: {
list: () => api.get('/ops/leads/'),
list: (params = {}) => api.get(`/ops/leads/${qs(params)}`),
create: (data) => api.post('/ops/leads/', data),
convert: (id, data) => api.post(`/ops/leads/${id}/convert/`, data),
sync: (id, data) => api.post(`/ops/leads/${id}/sync/`, data),
},
opportunities: {
list: () => api.get('/ops/opportunities/'),
list: (params = {}) => api.get(`/ops/opportunities/${qs(params)}`),
create: (data) => api.post('/ops/opportunities/', data),
sync: (id, data) => api.post(`/ops/opportunities/${id}/sync/`, data),
},
tickets: {
list: () => api.get('/ops/tickets/'),
list: (params = {}) => api.get(`/ops/tickets/${qs(params)}`),
create: (data) => api.post('/ops/tickets/', data),
resolve: (id, data) => api.post(`/ops/tickets/${id}/resolve/`, data),
sync: (id, data) => api.post(`/ops/tickets/${id}/sync/`, data),
},
touchpoints: {
list: () => api.get('/ops/touchpoints/'),
list: (params = {}) => api.get(`/ops/touchpoints/${qs(params)}`),
create: (data) => api.post('/ops/touchpoints/', data),
},
queue: {
list: () => api.get('/ops/queue/'),
list: (params = {}) => api.get(`/ops/queue/${qs(params)}`),
process: (limit = 25, params = {}) => api.post('/ops/queue/process/', { limit, ...params }),
retry: (id) => api.post(`/ops/queue/${id}/retry/`),
},
Expand Down
Loading
Loading