import { cookies } from "next/headers"; export const SESSION_COOKIE_NAME = "popiart_session"; type ApiEnvelope = { ok: boolean; data?: T; error?: { code?: string; message?: string; details?: unknown; }; }; export type PopiartUser = { id: string; email: string; name: string; scopes?: string[]; }; export type LoginResponse = { key: string; token: string; user: PopiartUser; }; export type AuthSessionView = { user: PopiartUser; session_key?: string; upstream_key_masked?: string; gateway_user_id?: number; gateway_access_token_masked?: string; gateway_bound: boolean; }; export type Skill = { id: string; name: string; description: string; tags: string[]; version: string; model_type: string; route_key?: string; estimated_duration_s: number; }; export type SkillListResponse = { items: Skill[]; total: number; limit: number; offset: number; }; export type BudgetSummary = { period: { start: string; end: string; }; used: { tokens: number; cost_usd: number; }; limit: { monthly_tokens: number; monthly_cost_usd: number; }; remaining: { tokens: number; cost_usd: number; }; }; export type BudgetUsageRow = { dimension: string; tokens_used: number; cost_usd: number; job_count: number; }; export type BudgetUsage = { rows: BudgetUsageRow[]; total: { tokens_used: number; cost_usd: number; job_count: number; }; }; export type Project = { id: string; name: string; }; export type ProjectListResponse = { items: Project[]; total: number; limit: number; offset: number; }; export type BillingPlan = { id: string; product_type: string; badge: string; name: string; price: string; cadence: string; summary: string; features: string[]; cta: string; highlight?: boolean; }; export type BillingCatalog = { plans: BillingPlan[]; capabilities: { subscription_status: boolean; credit_balance: boolean; invoices: boolean; checkout: boolean; }; source: string; note?: string; }; export type BillingSubscription = { billing_preference: string; subscriptions: Array<{ subscription?: { id?: number; status?: string; amount_total?: number; amount_used?: number; start_time?: number; end_time?: number; next_reset_time?: number; member_level?: string; }; }>; all_subscriptions: Array>; subscription_points: Record< string, { available_points?: number; total_points?: number; used_points?: number; } >; }; export type BillingCredits = { wallets: Array<{ id: number; source_type: string; points: number; points_total: number; expire_at: number; status: string; priority: number; origin_currency: string; origin_amount: number; }>; balance: number; }; export type BillingSubscriptionPlan = { plan: { id: number; title: string; subtitle: string; description: string; price_amount: number; currency: string; price_amount_cny?: number; price_amount_usd?: number; points_amount: number; duration_unit: string; duration_value: number; recommended?: boolean; member_level?: string; total_amount?: number; quota_reset_period?: string; }; }; export type BillingSubscriptionPlans = { items: BillingSubscriptionPlan[]; }; export type BillingPointsPackage = { id: number; name: string; currency: string; price_amount: number; points_amount: number; bonus_points: number; enabled: boolean; }; export type BillingPointsPackages = { items: BillingPointsPackage[]; }; export type BillingCheckoutResult = { message?: string; trade_no?: string; url?: string; code_url?: string; data?: Record; }; export type BillingOrderPage = { page: number; page_size: number; total: number; items: Array>; }; export type BillingInvoices = { subscription_orders: BillingOrderPage; point_orders: BillingOrderPage; }; export type GatewayBindResponse = { gateway_user_id: number; gateway_username?: string; gateway_display_name?: string; gateway_email?: string; gateway_bound: boolean; }; export class PopiartApiError extends Error { status: number; code?: string; details?: unknown; constructor(status: number, message: string, code?: string, details?: unknown) { super(message); this.name = "PopiartApiError"; this.status = status; this.code = code; this.details = details; } } function stripTrailingSlash(value: string) { return value.replace(/\/+$/, ""); } export function getPopiartEndpoint() { const configured = process.env.POPIART_ENDPOINT ?? process.env.POPIART_SERVER_URL ?? "http://127.0.0.1:8080/v1"; return stripTrailingSlash(configured); } export function buildPopiartUrl(pathname: string) { const path = pathname.startsWith("/") ? pathname : `/${pathname}`; return `${getPopiartEndpoint()}${path}`; } async function readEnvelope(response: Response) { try { return (await response.json()) as ApiEnvelope; } catch { return null; } } export async function popiartFetchEnvelope(pathname: string, init: RequestInit = {}) { const response = await fetch(buildPopiartUrl(pathname), { ...init, cache: "no-store", }); const payload = await readEnvelope(response); return { response, payload }; } export async function popiartRequest( pathname: string, options: { method?: string; body?: BodyInit | object; headers?: HeadersInit; token?: string; } = {}, ) { const headers = new Headers(options.headers); let body: BodyInit | undefined; if (options.token) { headers.set("Authorization", `Bearer ${options.token}`); } if (options.body instanceof FormData || typeof options.body === "string") { body = options.body; } else if (options.body !== undefined) { headers.set("Content-Type", "application/json"); body = JSON.stringify(options.body); } const { response, payload } = await popiartFetchEnvelope(pathname, { method: options.method ?? (body ? "POST" : "GET"), headers, body, }); if (!response.ok || !payload?.ok) { throw new PopiartApiError( response.status, payload?.error?.message ?? `Request failed with status ${response.status}`, payload?.error?.code, payload?.error?.details, ); } return payload.data as T; } export async function getSessionToken() { const cookieStore = await cookies(); return cookieStore.get(SESSION_COOKIE_NAME)?.value ?? null; } export async function getViewerSession() { const token = await getSessionToken(); if (!token) { return null; } try { return await popiartRequest("/auth/me", { token }); } catch (error) { if (error instanceof PopiartApiError && error.status === 401) { return null; } throw error; } } export async function getSkillsCatalog(search?: string) { const token = await getSessionToken(); if (!token) { return null; } const query = new URLSearchParams(); query.set("limit", "100"); if (search) { query.set("search", search); } return popiartRequest(`/skills?${query.toString()}`, { token }); } export async function getBudgetSummary() { const token = await getSessionToken(); if (!token) { return null; } return popiartRequest("/budget", { token }); } export async function getBudgetUsage() { const token = await getSessionToken(); if (!token) { return null; } return popiartRequest("/budget/usage", { token }); } export async function getProjects() { const token = await getSessionToken(); if (!token) { return null; } return popiartRequest("/projects", { token }); } export async function getBillingCatalog() { return popiartRequest("/billing/catalog"); } export async function getBillingSubscription() { const token = await getSessionToken(); if (!token) { return null; } return popiartRequest("/billing/subscription", { token }); } export async function getBillingCredits() { const token = await getSessionToken(); if (!token) { return null; } return popiartRequest("/billing/credits", { token }); } export async function getBillingSubscriptionPlans() { const token = await getSessionToken(); if (!token) { return null; } return popiartRequest("/billing/subscription/plans", { token }); } export async function getBillingPointsPackages() { const token = await getSessionToken(); if (!token) { return null; } return popiartRequest("/billing/points/packages", { token }); } export async function getBillingInvoices(options?: { keyword?: string; page?: number; pageSize?: number; }) { const token = await getSessionToken(); if (!token) { return null; } const query = new URLSearchParams(); if (options?.keyword) { query.set("keyword", options.keyword); } if (options?.page && options.page > 0) { query.set("p", String(options.page)); } if (options?.pageSize && options.pageSize > 0) { query.set("page_size", String(options.pageSize)); } const suffix = query.toString(); return popiartRequest(`/billing/invoices${suffix ? `?${suffix}` : ""}`, { token }); }