Merge public-gh/master into paperclip-company-import-export

This commit is contained in:
Dotta
2026-03-16 17:02:39 -05:00
125 changed files with 38085 additions and 683 deletions

View File

@@ -11,11 +11,19 @@ export const assetsApi = {
const safeFile = new File([buffer], file.name, { type: file.type });
const form = new FormData();
form.append("file", safeFile);
if (namespace && namespace.trim().length > 0) {
form.append("namespace", namespace.trim());
}
form.append("file", safeFile);
return api.postForm<AssetImage>(`/companies/${companyId}/assets/images`, form);
},
};
uploadCompanyLogo: async (companyId: string, file: File) => {
const buffer = await file.arrayBuffer();
const safeFile = new File([buffer], file.name, { type: file.type });
const form = new FormData();
form.append("file", safeFile);
return api.postForm<AssetImage>(`/companies/${companyId}/logo`, form);
},
};

20
ui/src/api/budgets.ts Normal file
View File

@@ -0,0 +1,20 @@
import type {
BudgetIncident,
BudgetIncidentResolutionInput,
BudgetOverview,
BudgetPolicySummary,
BudgetPolicyUpsertInput,
} from "@paperclipai/shared";
import { api } from "./client";
export const budgetsApi = {
overview: (companyId: string) =>
api.get<BudgetOverview>(`/companies/${companyId}/budgets/overview`),
upsertPolicy: (companyId: string, data: BudgetPolicyUpsertInput) =>
api.post<BudgetPolicySummary>(`/companies/${companyId}/budgets/policies`, data),
resolveIncident: (companyId: string, incidentId: string, data: BudgetIncidentResolutionInput) =>
api.post<BudgetIncident>(
`/companies/${companyId}/budget-incidents/${encodeURIComponent(incidentId)}/resolve`,
data,
),
};

View File

@@ -14,14 +14,18 @@ export const companiesApi = {
list: () => api.get<Company[]>("/companies"),
get: (companyId: string) => api.get<Company>(`/companies/${companyId}`),
stats: () => api.get<CompanyStats>("/companies/stats"),
create: (data: { name: string; description?: string | null; budgetMonthlyCents?: number }) =>
create: (data: {
name: string;
description?: string | null;
budgetMonthlyCents?: number;
}) =>
api.post<Company>("/companies", data),
update: (
companyId: string,
data: Partial<
Pick<
Company,
"name" | "description" | "status" | "budgetMonthlyCents" | "requireBoardApprovalForNewAgents" | "brandColor"
"name" | "description" | "status" | "budgetMonthlyCents" | "requireBoardApprovalForNewAgents" | "brandColor" | "logoAssetId"
>
>,
) => api.patch<Company>(`/companies/${companyId}`, data),

View File

@@ -1,14 +1,19 @@
import type { CostSummary, CostByAgent } from "@paperclipai/shared";
import type {
CostSummary,
CostByAgent,
CostByProviderModel,
CostByBiller,
CostByAgentModel,
CostByProject,
CostWindowSpendRow,
FinanceSummary,
FinanceByBiller,
FinanceByKind,
FinanceEvent,
ProviderQuotaResult,
} from "@paperclipai/shared";
import { api } from "./client";
export interface CostByProject {
projectId: string | null;
projectName: string | null;
costCents: number;
inputTokens: number;
outputTokens: number;
}
function dateParams(from?: string, to?: string): string {
const params = new URLSearchParams();
if (from) params.set("from", from);
@@ -22,6 +27,33 @@ export const costsApi = {
api.get<CostSummary>(`/companies/${companyId}/costs/summary${dateParams(from, to)}`),
byAgent: (companyId: string, from?: string, to?: string) =>
api.get<CostByAgent[]>(`/companies/${companyId}/costs/by-agent${dateParams(from, to)}`),
byAgentModel: (companyId: string, from?: string, to?: string) =>
api.get<CostByAgentModel[]>(`/companies/${companyId}/costs/by-agent-model${dateParams(from, to)}`),
byProject: (companyId: string, from?: string, to?: string) =>
api.get<CostByProject[]>(`/companies/${companyId}/costs/by-project${dateParams(from, to)}`),
byProvider: (companyId: string, from?: string, to?: string) =>
api.get<CostByProviderModel[]>(`/companies/${companyId}/costs/by-provider${dateParams(from, to)}`),
byBiller: (companyId: string, from?: string, to?: string) =>
api.get<CostByBiller[]>(`/companies/${companyId}/costs/by-biller${dateParams(from, to)}`),
financeSummary: (companyId: string, from?: string, to?: string) =>
api.get<FinanceSummary>(`/companies/${companyId}/costs/finance-summary${dateParams(from, to)}`),
financeByBiller: (companyId: string, from?: string, to?: string) =>
api.get<FinanceByBiller[]>(`/companies/${companyId}/costs/finance-by-biller${dateParams(from, to)}`),
financeByKind: (companyId: string, from?: string, to?: string) =>
api.get<FinanceByKind[]>(`/companies/${companyId}/costs/finance-by-kind${dateParams(from, to)}`),
financeEvents: (companyId: string, from?: string, to?: string, limit: number = 100) =>
api.get<FinanceEvent[]>(`/companies/${companyId}/costs/finance-events${dateParamsWithLimit(from, to, limit)}`),
windowSpend: (companyId: string) =>
api.get<CostWindowSpendRow[]>(`/companies/${companyId}/costs/window-spend`),
quotaWindows: (companyId: string) =>
api.get<ProviderQuotaResult[]>(`/companies/${companyId}/costs/quota-windows`),
};
function dateParamsWithLimit(from?: string, to?: string, limit?: number): string {
const params = new URLSearchParams();
if (from) params.set("from", from);
if (to) params.set("to", to);
if (limit) params.set("limit", String(limit));
const qs = params.toString();
return qs ? `?${qs}` : "";
}