import axios from "axios"; const api = axios.create({ baseURL: "/v1/admin" }); export type KgProjectContext = { tenantId: string; projectId: string; graphName: string; }; export const PROJECT_CONTEXT_KEY = "znkg.currentProjectContext"; export const DEFAULT_PROJECT_CONTEXT: KgProjectContext = { tenantId: "CityGraph-new2", projectId: "CityGraph-new2", graphName: "guiyang_new2", }; export const EMPTY_PROJECT_CONTEXT: KgProjectContext = { tenantId: "", projectId: "", graphName: "", }; const PROJECT_DISPLAY_NAMES: Record = { "CityGraph-new2": "城市图谱", guiyang_new2: "贵阳城市图谱", city_graph: "城市图谱", travel_agency: "旅行社", travel_graph: "旅行社线路制定", travel_fixed_route_item: "旅行社固定线路资源图谱", travel_agency_2_0_test: "旅行社2.0测试", baixinghui_travel_agency: "百姓惠旅行社", }; const GRAPH_DISPLAY_NAMES: Record = { guiyang_new2: "贵阳城市图谱", travel_agency: "旅行社资料库", travel_graph: "旅行社线路制定", travel_fixed_route_item: "旅行社固定线路资源图谱", travel_agency_2_0_test: "旅行社2.0测试图谱", baixinghui_travel_agency: "百姓惠旅行社图谱", }; const STATUS_DISPLAY_NAMES: Record = { active: "启用中", published: "已发布", draft: "草稿", archived: "已归档", }; export function displayProjectName(value?: string | null): string { const key = String(value || "").trim(); return PROJECT_DISPLAY_NAMES[key] || key || "-"; } export function displayGraphName(value?: string | null): string { const key = String(value || "").trim(); return GRAPH_DISPLAY_NAMES[key] || PROJECT_DISPLAY_NAMES[key] || key || "-"; } export function displayTenantName(value?: string | null): string { return displayProjectName(value); } export function displayStatusName(value?: string | null): string { const key = String(value || "").trim(); return STATUS_DISPLAY_NAMES[key] || key || "-"; } export function getProjectContext(): KgProjectContext { try { const stored = localStorage.getItem(PROJECT_CONTEXT_KEY); if (stored === null) return DEFAULT_PROJECT_CONTEXT; const parsed = JSON.parse(stored); if (parsed?.projectId === "") { return EMPTY_PROJECT_CONTEXT; } return { tenantId: parsed.tenantId || DEFAULT_PROJECT_CONTEXT.tenantId, projectId: parsed.projectId || DEFAULT_PROJECT_CONTEXT.projectId, graphName: parsed.graphName || DEFAULT_PROJECT_CONTEXT.graphName, }; } catch { return DEFAULT_PROJECT_CONTEXT; } } export function setProjectContext(context: KgProjectContext) { localStorage.setItem(PROJECT_CONTEXT_KEY, JSON.stringify(context)); window.dispatchEvent(new CustomEvent("znkg-project-context", { detail: context })); } export function clearProjectContext() { setProjectContext(EMPTY_PROJECT_CONTEXT); } api.interceptors.request.use((config) => { const token = localStorage.getItem("token"); if (token) config.headers.Authorization = `Bearer ${token}`; const context = getProjectContext(); if (context.tenantId) config.headers["X-KG-Tenant-Id"] = context.tenantId; else delete config.headers["X-KG-Tenant-Id"]; if (context.projectId) config.headers["X-KG-Project-Id"] = context.projectId; else delete config.headers["X-KG-Project-Id"]; if (context.graphName) config.headers["X-KG-Graph-Name"] = context.graphName; else delete config.headers["X-KG-Graph-Name"]; return config; }); api.interceptors.response.use( (r) => r, (err) => { if (err.response?.status === 401) { localStorage.removeItem("token"); window.location.href = "/admin/login"; } return Promise.reject(err); } ); export default api; // ── Auth ── export const login = (username: string, password: string) => api.post("/auth/login", { username, password }); export const whoami = () => api.get("/auth/me"); // ── Projects / Graph releases ── export type ProjectSummary = { project_id: string; tenant_id?: string; display_name?: string; name?: string; description?: string | null; status?: string; metadata_jsonb?: { spatial_map?: Partial; [key: string]: unknown; }; created_at?: string; updated_at?: string; }; export type SpatialMapScope = "libo" | "guizhou"; export type SpatialMapRegionLevel = "province" | "city" | "district"; export type SpatialMapConfig = { enabled: boolean; scope: SpatialMapScope; region_name: string; region_adcode: string; region_level: SpatialMapRegionLevel; }; export type GraphReleaseSummary = { graph_release_id: string | number; project_id: string; tenant_id?: string; graph_name: string; alias?: string; status?: string; schema_id?: string | number | null; created_at?: string; updated_at?: string; }; export type GraphProjectProvisionRequest = { project_id: string; display_name: string; tenant_id?: string; graph_name?: string; spatial_map?: SpatialMapConfig; schema: Record; graph_data: { nodes: Array>; relations: Array>; }; }; export type GraphProjectProvisionResponse = { project?: ProjectSummary; graph_release?: GraphReleaseSummary; schema?: Record; graph?: { graph_name: string; nodes: number; relations: number }; counts?: Record; }; export type ProjectDeletionGraphImpact = { graph_name: string; exists_in_falkordb: boolean | null; shared: boolean; other_project_ids: string[]; will_delete: boolean; }; export type ProjectDeletionImpact = { project_id: string; display_name: string; counts: Record; graphs: ProjectDeletionGraphImpact[]; protected_resources: string[]; falkordb_error?: string; }; export type GraphProjectDeleteResponse = { status: "deleted" | "deleted_with_cleanup_pending"; project_id: string; deleted_counts: Record; deleted_graphs: string[]; already_absent_graphs: string[]; shared_graphs_preserved: string[]; cleanup_pending: Array<{ graph_name: string; error: string; task_id?: string | number; }>; protected_resources: string[]; }; export const listProjects = () => api.get("/projects"); export const createProject = (data: unknown) => api.post("/projects", data); export const listGraphReleases = (projectId: string) => api.get(`/projects/${encodeURIComponent(projectId)}/graph-releases`); export const createGraphRelease = (projectId: string, data: unknown) => api.post(`/projects/${encodeURIComponent(projectId)}/graph-releases`, data); export const provisionGraphProject = (data: GraphProjectProvisionRequest) => api.post("/projects/provision", data); export const updateGraphProject = (projectId: string, data: { display_name: string }) => api.patch(`/projects/${encodeURIComponent(projectId)}`, data); export const getGraphProjectDeletionImpact = (projectId: string) => api.get( `/projects/${encodeURIComponent(projectId)}/deletion-impact`, ); export const deleteGraphProject = (projectId: string, confirmProjectId: string) => api.delete(`/projects/${encodeURIComponent(projectId)}`, { params: { confirm_project_id: confirmProjectId }, }); // ── Project relational data platform ── export const listProjectDatabases = () => api.get("/data-platform/databases"); export const createRelationalDatabase = (data: unknown) => api.post("/data-platform/databases", data); export const updateRelationalDatabase = (projectId: string, data: unknown) => api.patch(`/data-platform/databases/${encodeURIComponent(projectId)}`, data); export const deleteRelationalDatabase = (projectId: string, data: unknown) => api.delete(`/data-platform/databases/${encodeURIComponent(projectId)}`, { data }); export const listProjectDataTables = (projectId: string) => api.get(`/data-platform/databases/${encodeURIComponent(projectId)}/tables`); export const executeProjectDatabaseSql = (projectId: string, sql: string) => api.post( `/data-platform/databases/${encodeURIComponent(projectId)}/console/execute`, { sql }, ); export const getProjectDataTableInspection = (projectId: string, tableCode: string) => api.get( `/data-platform/databases/${encodeURIComponent(projectId)}/tables/${encodeURIComponent(tableCode)}/inspection`, ); export const createProjectDataTableColumn = ( projectId: string, tableCode: string, data: unknown, ) => api.post( `/data-platform/databases/${encodeURIComponent(projectId)}/tables/${encodeURIComponent(tableCode)}/columns`, data, ); export const updateProjectDataTableColumn = ( projectId: string, tableCode: string, columnCode: string, data: unknown, ) => api.patch( `/data-platform/databases/${encodeURIComponent(projectId)}/tables/${encodeURIComponent(tableCode)}/columns/${encodeURIComponent(columnCode)}`, data, ); export const deleteProjectDataTableColumn = ( projectId: string, tableCode: string, columnCode: string, data: unknown, ) => api.delete( `/data-platform/databases/${encodeURIComponent(projectId)}/tables/${encodeURIComponent(tableCode)}/columns/${encodeURIComponent(columnCode)}`, { data }, ); export const createProjectDataTable = (projectId: string, data: unknown) => api.post(`/data-platform/databases/${encodeURIComponent(projectId)}/tables`, data); export const createProjectDataTableFromSql = (projectId: string, data: unknown) => api.post( `/data-platform/databases/${encodeURIComponent(projectId)}/tables/from-sql`, data, ); export const updateProjectDataTable = ( projectId: string, tableCode: string, data: unknown, ) => api.patch( `/data-platform/databases/${encodeURIComponent(projectId)}/tables/${encodeURIComponent(tableCode)}`, data, ); export const deleteProjectDataTable = ( projectId: string, tableCode: string, data: unknown, ) => api.delete( `/data-platform/databases/${encodeURIComponent(projectId)}/tables/${encodeURIComponent(tableCode)}`, { data }, ); export const listProjectTableRecords = ( projectId: string, tableCode: string, params?: Record, ) => api.get( `/data-platform/databases/${encodeURIComponent(projectId)}/tables/${encodeURIComponent(tableCode)}/records`, { params }, ); export const createProjectTableRecord = ( projectId: string, tableCode: string, data: unknown, ) => api.post( `/data-platform/databases/${encodeURIComponent(projectId)}/tables/${encodeURIComponent(tableCode)}/records`, data, ); export const updateProjectTableRecord = ( projectId: string, tableCode: string, recordId: string, data: unknown, ) => api.patch( `/data-platform/databases/${encodeURIComponent(projectId)}/tables/${encodeURIComponent(tableCode)}/records/${encodeURIComponent(recordId)}`, data, ); export const deleteProjectTableRecord = ( projectId: string, tableCode: string, recordId: string, ) => api.delete( `/data-platform/databases/${encodeURIComponent(projectId)}/tables/${encodeURIComponent(tableCode)}/records/${encodeURIComponent(recordId)}`, ); export const previewProjectTableCsvImport = ( projectId: string, tableCode: string, file: File, ) => { const form = new FormData(); form.append("file", file); return api.post( `/data-platform/databases/${encodeURIComponent(projectId)}/tables/${encodeURIComponent(tableCode)}/records/import/preview`, form, ); }; export const importProjectTableCsv = ( projectId: string, tableCode: string, file: File, ) => { const form = new FormData(); form.append("file", file); return api.post( `/data-platform/databases/${encodeURIComponent(projectId)}/tables/${encodeURIComponent(tableCode)}/records/import`, form, ); }; export const exportProjectTableCsv = ( projectId: string, tableCode: string, params?: Record, ) => api.get( `/data-platform/databases/${encodeURIComponent(projectId)}/tables/${encodeURIComponent(tableCode)}/records/export`, { params, responseType: "blob" }, ); // ── Interface Center (controlled external Data APIs) ── export type InterfaceSummary = { active_clients: number; active_credentials: number; active_policies: number; calls_24h: number; errors_24h: number; active_dbeaver_grants?: number; data_engine: string; public_base_path: string; direct_database_access: string; direct_access_enabled?: boolean; direct_access_host?: string; direct_access_port?: number; direct_access_transport?: string; ssh_tunnel_required?: boolean; ssh_host?: string; ssh_port?: number; ssh_username?: string; ssh_auth_method?: string; managed_access_enabled?: boolean; ssh_key_auto_install?: boolean; account_provisioning_ready?: boolean; ssh_host_key_fingerprint?: string; database_host?: string; database_port?: number; database_account_policy?: string; mysql_host_bind?: string; mysql_publicly_bound?: boolean; database_audit_enabled?: boolean; application_audit_enabled?: boolean; sql_console_write_enabled?: boolean; backup_enabled?: boolean; backup_encryption_required?: boolean; backup_retention_days?: number; }; export type DbeaverAccessGrant = { id: string; display_name: string; mysql_username: string; mysql_password?: string; password_shown_once?: boolean; database_id: string; database_name: string; permission_level: "read" | "write"; ssh_key_type: string; ssh_key_fingerprint: string; ssh_host?: string; ssh_port?: number; ssh_username?: string; database_host?: string; database_port?: number; created_by?: string; status: "active" | "revoked"; revoked_at?: string | null; created_at?: string; }; export type InterfaceClient = { id: string; name: string; description: string; owner: string; status: "active" | "disabled"; credential_count?: number; policy_count?: number; created_at?: string; updated_at?: string; }; export type InterfaceCredential = { id: string; client_id: string; client_name?: string; name: string; key_prefix: string; api_key?: string; expires_at?: string | null; last_used_at?: string | null; revoked_at?: string | null; created_at?: string; }; export type InterfacePolicy = { id: string; client_id: string; client_name?: string; database_id: string; database_name?: string; table_code: string; actions: string[]; readable_fields: string[]; writable_fields: string[]; row_filter: Record; status: "active" | "disabled"; created_at?: string; updated_at?: string; }; export type InterfaceCatalogField = { code: string; label: string; data_type: string; }; export type InterfaceCatalogTable = { code: string; label: string; group: string; fields: InterfaceCatalogField[]; }; export type InterfaceCatalogDatabase = { project_id: string; display_name: string; database_name: string; tables: InterfaceCatalogTable[]; }; export type InterfaceCallLog = { id: number; request_id: string; client_name?: string | null; method: string; path: string; database_id?: string | null; table_code?: string | null; action_name?: string | null; status_code: number; duration_ms: number; source_ip?: string | null; error_message?: string | null; created_at: string; }; export const getInterfaceCenterSummary = () => api.get("/interface-center/summary"); export const getInterfaceCenterCatalog = () => api.get("/interface-center/catalog"); export const listDbeaverAccessGrants = () => api.get("/interface-center/dbeaver-access"); export const issueDbeaverAccess = (data: unknown) => api.post("/interface-center/dbeaver-access", data); export const revokeDbeaverAccess = (grantId: string) => api.post(`/interface-center/dbeaver-access/${encodeURIComponent(grantId)}/revoke`, {}); export const listInterfaceClients = () => api.get("/interface-center/clients"); export const createInterfaceClient = (data: unknown) => api.post("/interface-center/clients", data); export const updateInterfaceClient = (clientId: string, data: unknown) => api.patch(`/interface-center/clients/${encodeURIComponent(clientId)}`, data); export const deleteInterfaceClient = (clientId: string) => api.delete(`/interface-center/clients/${encodeURIComponent(clientId)}`); export const listInterfaceCredentials = (clientId?: string) => api.get("/interface-center/credentials", { params: clientId ? { client_id: clientId } : undefined, }); export const createInterfaceCredential = (clientId: string, data: unknown) => api.post( `/interface-center/clients/${encodeURIComponent(clientId)}/credentials`, data, ); export const revokeInterfaceCredential = (credentialId: string) => api.post(`/interface-center/credentials/${encodeURIComponent(credentialId)}/revoke`, {}); export const listInterfacePolicies = () => api.get("/interface-center/policies"); export const createInterfacePolicy = (data: unknown) => api.post("/interface-center/policies", data); export const updateInterfacePolicy = (policyId: string, data: unknown) => api.patch(`/interface-center/policies/${encodeURIComponent(policyId)}`, data); export const deleteInterfacePolicy = (policyId: string) => api.delete(`/interface-center/policies/${encodeURIComponent(policyId)}`); export const listInterfaceCallLogs = (limit = 200) => api.get("/interface-center/logs", { params: { limit } }); export const listOntologySchemas = () => api.get("/ontology-schemas"); export const getCurrentOntologySchema = () => api.get("/ontology-schemas/current"); export const getOntologySchema = (schemaId: string | number) => api.get(`/ontology-schemas/${encodeURIComponent(String(schemaId))}`); // ── Plaza (STEP 02) ── export const plazaOverview = () => api.get("/plaza/overview"); export const plazaAmapConfig = () => api.get("/plaza/amap-config"); export const plazaWeatherHourly = () => api.get("/plaza/weather/hourly"); export const plazaMapPois = () => api.get("/plaza/map-pois"); export const plazaBusRoutes = () => api.get("/plaza/bus-routes"); export const plazaMapZones = () => api.get("/plaza/map-zones"); export const plazaMapPoiDetail = (placeId: string) => api.get(`/plaza/map-pois/${encodeURIComponent(placeId)}`); export const plazaUserQuery = (data: unknown) => api.post("/plaza/user-query", data); export const travelAssistantQuery = (data: unknown) => api.post("/travel/assistant-query", data); // ── OCR (旅行社名单录入) ── export const ocrPassport = (file: File) => { const form = new FormData(); form.append("file", file); return api.post("/ocr/passport", form, { headers: { "Content-Type": "multipart/form-data" }, }); }; export const ocrPassportByUrl = (url: string) => { const form = new FormData(); form.append("url", url); return api.post("/ocr/passport", form); }; export const graphOverview = () => api.get("/graph/overview"); export const graphQuery = (cypher: string, limit = 200) => api.post("/graph/query", { cypher, limit }); // ── User management ── export const listUsers = () => api.get("/users"); export const createUser = (data: unknown) => api.post("/users", data); export const updateUser = (id: number, data: unknown) => api.patch(`/users/${id}`, data); // ── Super Agent (autonomous KG curator) ── export const superAgentRun = () => api.post("/super-agent/run", {}); export const superAgentLatest = () => api.get("/super-agent/runs/latest"); export const superAgentGet = (id: number) => api.get(`/super-agent/runs/${id}`); export const superAgentStop = (id: number) => api.post(`/super-agent/runs/${id}/stop`); export const superAgentCoverage = () => api.get("/super-agent/coverage"); export const superAgentTasks = (runId?: number) => api.get("/super-agent/tasks", { params: runId ? { run_id: runId } : {} });