import type { AddressInput, AlertRecord, ApiResponse, CreateQuoteResult, CreateHostCustomerResponse, HostCustomerRecord, LoginResponse, MarkupConfig, MetricsDashboard, MothershipAddressCandidate, MothershipCandidatesResult, PaginatedResult, QueueStats, QuoteQueryLogRecord, QuoteDetail, QuoteRequestBody, Priority1QuoteRequestBody, FlockQuoteRequestBody, RpaStatus, } from "@/lib/frontend/types"; import { CANDIDATES_FETCH_TIMEOUT_MS } from "@/lib/frontend/constants"; async function parseJson(response: Response): Promise> { const contentType = response.headers.get("content-type") ?? ""; if (!contentType.includes("application/json")) { const hint = response.status === 404 ? "询价结果接口不可用(GET /api/quotes/{id}),请重新部署并确认已包含动态路由" : response.ok ? "响应解析失败" : `服务异常 (HTTP ${response.status})`; return { code: "INTERNAL_ERROR", message: hint, data: null, }; } try { return (await response.json()) as ApiResponse; } catch { return { code: "INTERNAL_ERROR", message: "响应解析失败", data: null, }; } } function hostHeaders( serviceToken: string | undefined, customerId: string, ): Record { const headers: Record = { "X-Customer-Id": customerId, "Content-Type": "application/json", "Cache-Control": "no-store", }; if (serviceToken?.trim()) { headers.Authorization = `Bearer ${serviceToken}`; } return headers; } function hostFetchInit( serviceToken: string | undefined, customerId: string, init: RequestInit, ): RequestInit { return { ...init, headers: hostHeaders(serviceToken, customerId), credentials: "include", cache: "no-store", }; } function adminHeaders(token: string): Record { return { Authorization: `Bearer ${token}`, "Content-Type": "application/json", "Cache-Control": "no-store", }; } export async function hostCreateQuote( apiBaseUrl: string, serviceToken: string | undefined, body: QuoteRequestBody, ): Promise> { const response = await fetch( `${apiBaseUrl}/api/quotes`, hostFetchInit(serviceToken, body.customer_id, { method: "POST", body: JSON.stringify(body), }), ); return parseJson(response); } export async function hostCreatePriority1Quote( apiBaseUrl: string, serviceToken: string | undefined, body: Priority1QuoteRequestBody, ): Promise> { const response = await fetch( `${apiBaseUrl}/api/priority1/quotes`, hostFetchInit(serviceToken, body.customer_id, { method: "POST", body: JSON.stringify(body), }), ); return parseJson(response); } export async function hostCreateFlockQuote( apiBaseUrl: string, serviceToken: string | undefined, body: FlockQuoteRequestBody, ): Promise> { const response = await fetch( `${apiBaseUrl}/api/flock/quotes`, hostFetchInit(serviceToken, body.customer_id, { method: "POST", body: JSON.stringify(body), }), ); return parseJson(response); } export async function hostFetchMothershipCandidates( apiBaseUrl: string, serviceToken: string | undefined, customerId: string, pickup: Pick, delivery: Pick, ): Promise> { const controller = new AbortController(); const timeout = setTimeout( () => controller.abort(), CANDIDATES_FETCH_TIMEOUT_MS, ); try { const response = await fetch( `${apiBaseUrl}/api/addresses/mothership-candidates`, hostFetchInit(serviceToken, customerId, { method: "POST", body: JSON.stringify({ customer_id: customerId, pickup_address: pickup, delivery_address: delivery, }), signal: controller.signal, }), ); return parseJson(response); } catch (error) { if (error instanceof Error && error.name === "AbortError") { return { code: "QUOTE_TIMEOUT", message: "地址联想超时,MotherShip 响应较慢,请稍后重试", data: null, }; } throw error; } finally { clearTimeout(timeout); } } export type MothershipSuggestResult = { candidates: MothershipAddressCandidate[]; }; /** 登录后一级:单边键入 MotherShip 地址联想 */ export async function hostSuggestMothershipAddress( apiBaseUrl: string, serviceToken: string | undefined, customerId: string, query: string, ): Promise> { const controller = new AbortController(); const timeout = setTimeout( () => controller.abort(), CANDIDATES_FETCH_TIMEOUT_MS, ); try { const response = await fetch( `${apiBaseUrl}/api/addresses/mothership-suggest`, hostFetchInit(serviceToken, customerId, { method: "POST", body: JSON.stringify({ customer_id: customerId, query, }), signal: controller.signal, }), ); return parseJson(response); } catch (error) { if (error instanceof Error && error.name === "AbortError") { return { code: "QUOTE_TIMEOUT", message: "地址联想超时,MotherShip 响应较慢,请稍后重试", data: null, }; } throw error; } finally { clearTimeout(timeout); } } export async function hostPreheatQuoteSession( apiBaseUrl: string, serviceToken: string | undefined, customerId: string, quoteSessionId: string, ): Promise { try { await fetch( `${apiBaseUrl}/api/addresses/mothership-candidates/preheat`, hostFetchInit(serviceToken, customerId, { method: "POST", body: JSON.stringify({ customer_id: customerId, quote_session_id: quoteSessionId, }), }), ); } catch { /* 预热失败不阻断主流程 */ } } export async function hostConfirmMothershipAddresses( apiBaseUrl: string, serviceToken: string | undefined, customerId: string, quoteSessionId: string, pickup: MothershipCandidatesResult["pickup_candidates"][number], delivery: MothershipCandidatesResult["delivery_candidates"][number], ): Promise> { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 300_000); try { const response = await fetch( `${apiBaseUrl}/api/addresses/mothership-candidates/confirm`, hostFetchInit(serviceToken, customerId, { method: "POST", body: JSON.stringify({ customer_id: customerId, quote_session_id: quoteSessionId, pickup, delivery, }), signal: controller.signal, }), ); return parseJson(response); } catch (error) { if (error instanceof Error && error.name === "AbortError") { return { code: "QUOTE_TIMEOUT", message: "地址确认超时,请重试", data: null, }; } throw error; } finally { clearTimeout(timeout); } } export async function hostReleaseQuoteSession( apiBaseUrl: string, serviceToken: string | undefined, customerId: string, quoteSessionId: string, ): Promise { try { await fetch( `${apiBaseUrl}/api/addresses/mothership-candidates/release`, hostFetchInit(serviceToken, customerId, { method: "POST", body: JSON.stringify({ customer_id: customerId, quote_session_id: quoteSessionId, }), }), ); } catch { /* 释放失败不阻断 UI */ } } export async function hostGetQuote( apiBaseUrl: string, serviceToken: string | undefined, customerId: string, quoteId: string, ): Promise> { const response = await fetch( `${apiBaseUrl}/api/quotes/${quoteId}`, hostFetchInit(serviceToken, customerId, { method: "GET", }), ); return parseJson(response); } export type EmbedDemoLoginType = "password" | "api_key"; export type EmbedDemoSession = { customer_id: string; /** 登录类型:账号密码 / API Key(旧会话缺省为 password) */ login_type?: EmbedDemoLoginType; markup: MarkupConfig; /** 客户是否已绑定承运商官网账密(驱动登录后查价 UI) */ providers?: { mothership: { has_password: boolean }; flock: { has_password: boolean }; }; }; export async function embedDemoLogin( apiBaseUrl: string, customerId: string, password: string, ): Promise> { const response = await fetch(`${apiBaseUrl}/api/embed-demo/login`, { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ login_type: "password", customer_id: customerId, password, }), cache: "no-store", }); return parseJson(response); } /** API Key 登录:只填 Key,服务端反查租户 */ export async function embedDemoLoginWithKey( apiBaseUrl: string, apiKey: string, ): Promise> { const response = await fetch(`${apiBaseUrl}/api/embed-demo/login`, { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ login_type: "api_key", api_key: apiKey }), cache: "no-store", }); return parseJson(response); } export type EmbedProviderCredential = { provider: "mothership" | "flock"; email: string | null; /** 明文密码(embed 自助核对用;无配置时为 null) */ password: string | null; has_password: boolean; updated_at: string | null; }; /** 读取当前演示客户各承运商账密状态(邮箱 + 是否已配置) */ export async function embedGetProviderCredentials( apiBaseUrl: string, ): Promise> { const response = await fetch( `${apiBaseUrl}/api/embed-demo/provider-credentials`, { credentials: "include", cache: "no-store" }, ); return parseJson(response); } /** 保存/修改当前演示客户承运商账密 */ export async function embedSaveProviderCredential( apiBaseUrl: string, provider: "mothership" | "flock", email: string, password: string, ): Promise> { const response = await fetch( `${apiBaseUrl}/api/embed-demo/provider-credentials`, { method: "PUT", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ provider, email, password }), cache: "no-store", }, ); return parseJson(response); } export async function embedDemoMe( apiBaseUrl: string, ): Promise> { const response = await fetch(`${apiBaseUrl}/api/embed-demo/me`, { credentials: "include", cache: "no-store", }); return parseJson(response); } export async function embedDemoLogout(apiBaseUrl: string): Promise { await fetch(`${apiBaseUrl}/api/embed-demo/logout`, { method: "POST", credentials: "include", cache: "no-store", }); } export async function adminLogin( apiBaseUrl: string, username: string, password: string, ): Promise> { const response = await fetch(`${apiBaseUrl}/api/auth/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password }), cache: "no-store", }); return parseJson(response); } export async function adminGetAlerts( apiBaseUrl: string, token: string, page: number, size: number, type?: string, status?: string, ): Promise>> { const params = new URLSearchParams({ page: String(page), size: String(size), }); if (type) params.set("type", type); if (status) params.set("status", status); const response = await fetch(`${apiBaseUrl}/api/alerts?${params}`, { headers: adminHeaders(token), cache: "no-store", }); return parseJson(response); } export async function adminResolveAlert( apiBaseUrl: string, token: string, alertId: string, resolverId: string, ): Promise> { const response = await fetch(`${apiBaseUrl}/api/alerts/${alertId}/resolve`, { method: "POST", headers: adminHeaders(token), body: JSON.stringify({ resolver_id: resolverId }), cache: "no-store", }); return parseJson(response); } export async function adminGetCustomers( apiBaseUrl: string, token: string, page: number, size: number, keyword?: string, ): Promise>> { const params = new URLSearchParams({ page: String(page), size: String(size), }); if (keyword) params.set("keyword", keyword); const response = await fetch( `${apiBaseUrl}/api/admin/customers?${params}`, { headers: adminHeaders(token), cache: "no-store", }, ); return parseJson(response); } export async function adminCreateCustomer( apiBaseUrl: string, token: string, body: { name: string; remark?: string; embed_password?: string }, ): Promise> { const response = await fetch(`${apiBaseUrl}/api/admin/customers`, { method: "POST", headers: adminHeaders(token), body: JSON.stringify(body), cache: "no-store", }); return parseJson(response); } export async function adminUpdateCustomer( apiBaseUrl: string, token: string, customerId: string, body: { name?: string; status?: "active" | "disabled"; remark?: string | null; embed_password?: string; }, ): Promise> { const response = await fetch( `${apiBaseUrl}/api/admin/customers/${customerId}`, { method: "PATCH", headers: adminHeaders(token), body: JSON.stringify(body), cache: "no-store", }, ); return parseJson(response); } export async function adminRotateCustomerKey( apiBaseUrl: string, token: string, customerId: string, ): Promise> { const response = await fetch( `${apiBaseUrl}/api/admin/customers/${customerId}/rotate-key`, { method: "POST", headers: adminHeaders(token), cache: "no-store", }, ); return parseJson(response); } export type ProviderCredentialProvider = "mothership" | "flock"; export type ProviderCredentialRecord = { provider: ProviderCredentialProvider; email: string | null; /** 管理端查看接口回传明文密码 */ password?: string | null; has_password: boolean; updated_at: string | null; }; export async function adminGetProviderCredentials( apiBaseUrl: string, token: string, customerId: string, ): Promise< ApiResponse<{ customer_id: string; list: ProviderCredentialRecord[] }> > { const response = await fetch( `${apiBaseUrl}/api/admin/customers/${customerId}/provider-credentials`, { headers: adminHeaders(token), cache: "no-store", }, ); return parseJson(response); } export async function adminUpsertProviderCredential( apiBaseUrl: string, token: string, customerId: string, body: { provider: ProviderCredentialProvider; email?: string; password?: string; clear?: boolean; }, ): Promise> { const response = await fetch( `${apiBaseUrl}/api/admin/customers/${customerId}/provider-credentials`, { method: "PUT", headers: adminHeaders(token), body: JSON.stringify(body), cache: "no-store", }, ); return parseJson(response); } export async function adminGetMarkupConfigs( apiBaseUrl: string, token: string, page: number, size: number, keyword?: string, ): Promise>> { const params = new URLSearchParams({ page: String(page), size: String(size), }); if (keyword) params.set("keyword", keyword); const response = await fetch( `${apiBaseUrl}/api/admin/markup-configs?${params}`, { headers: adminHeaders(token), cache: "no-store", }, ); return parseJson(response); } export async function adminUpdateMarkupConfig( apiBaseUrl: string, token: string, customerId: string, body: { markup_type: "percent" | "fixed"; markup_percent?: number; markup_fixed_amount?: number; remark?: string; }, ): Promise> { const response = await fetch( `${apiBaseUrl}/api/admin/markup-configs/${customerId}`, { method: "PUT", headers: adminHeaders(token), body: JSON.stringify(body), cache: "no-store", }, ); return parseJson(response); } export async function adminGetMetrics( apiBaseUrl: string, token: string, ): Promise> { const response = await fetch(`${apiBaseUrl}/api/metrics/dashboard`, { headers: adminHeaders(token), cache: "no-store", }); return parseJson(response); } export async function adminGetRpaStatus( apiBaseUrl: string, token: string, ): Promise> { const response = await fetch(`${apiBaseUrl}/api/rpa/status`, { headers: adminHeaders(token), cache: "no-store", }); return parseJson(response); } export async function adminGetQueryLogs( apiBaseUrl: string, token: string, page: number, size: number, customerId?: string, outcome?: string, ): Promise>> { const params = new URLSearchParams({ page: String(page), size: String(size), }); if (customerId) params.set("customer_id", customerId); if (outcome) params.set("outcome", outcome); const response = await fetch(`${apiBaseUrl}/api/admin/query-logs?${params}`, { headers: adminHeaders(token), cache: "no-store", }); return parseJson(response); } export async function adminGetQueueStats( apiBaseUrl: string, token: string, ): Promise> { const response = await fetch(`${apiBaseUrl}/api/admin/queues/stats`, { headers: adminHeaders(token), cache: "no-store", }); return parseJson(response); } export async function adminPauseWorker( apiBaseUrl: string, token: string, workerId: string, durationMinutes: number, ): Promise> { const response = await fetch(`${apiBaseUrl}/api/rpa/worker/pause`, { method: "POST", headers: adminHeaders(token), body: JSON.stringify({ worker_id: workerId, duration_minutes: durationMinutes, }), cache: "no-store", }); return parseJson(response); }