import type { AdminLoginLogsResponse, AdminLoginResponse, AdminManagedUserResponse, AdminMqttStatus, AdminRuntimeSettingsPayload, AdminRuntimeSettingsResponse, AdminUsersResponse, BotMessage, BotMessageMutationResponse, BotNode, BotNodeMutationResponse, BotNodePayload, BotSendMessagePayload, BlockingRuleResponse, DiscardDetails, ForbiddenWordBlockingRule, ForbiddenWordBlockingRulePayload, HealthStatus, HelpContentResponse, HelpPreviewResponse, IPBlockingRule, IPBlockingRulePayload, ListResponse, MapBoundsQuery, MapReport, MapTileSource, MapTileSourcePayload, MapTileSourceResponse, MapViewportResponse, MQTTForwarder, MQTTForwarderPayload, MQTTForwardMutationResponse, MQTTForwardStatusResponse, MQTTForwardTopic, MQTTForwardTopicPayload, NodeBlockingRule, NodeBlockingRulePayload, NodeInfo, PositionRecord, PublicMapTileSourceResponse, PublicMapTileSourcesResponse, SignDayCount, SignRecord, SignRecordPayload, TelemetryRecord, TextMessage, BotDirectMessage, BotDirectConversationsResponse, LLMMessage, LLMMessageStatus, LLMProvider, LLMProviderPayload, LLMProviderResponse, LLMPlatformRouterPayload, LLMPlatformRouterResponse, LLMPrimaryConfigPayload, LLMPrimaryConfigResponse, } from './types' async function requestJSON(path: string, init?: RequestInit): Promise { const response = await fetch(path, { credentials: 'same-origin', ...init }) if (!response.ok) { let message = `${response.status} ${response.statusText}` try { const data = (await response.json()) as { error?: string } if (data.error) { message = data.error } } catch { // Keep the HTTP status message when the response is not JSON. } throw new Error(message) } return response.json() as Promise } type ListQueryOptions = { nodeId?: string channelId?: string since?: string until?: string } function listPath(path: string, limit: number, offset: number, nodeIdOrOptions: string | ListQueryOptions = ''): string { const params = new URLSearchParams({ limit: String(limit), offset: String(offset) }) const options = typeof nodeIdOrOptions === 'string' ? { nodeId: nodeIdOrOptions } : nodeIdOrOptions if (options.nodeId) { params.set('node_id', options.nodeId) } if (options.channelId) { params.set('channel_id', options.channelId) } if (options.since) { params.set('since', options.since) } if (options.until) { params.set('until', options.until) } return `${path}?${params.toString()}` } function getJSON(path: string): Promise { return requestJSON(path) } function postJSON(path: string, body?: unknown): Promise { return requestJSON(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: body == null ? undefined : JSON.stringify(body), }) } function putJSON(path: string, body?: unknown): Promise { return requestJSON(path, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: body == null ? undefined : JSON.stringify(body), }) } function deleteJSON(path: string): Promise { return requestJSON(path, { method: 'DELETE' }) } export function getHealth(): Promise { return getJSON('/api/health') } export function getHelpContent(): Promise { return getJSON('/api/help') } export function getNodeInfo(limit = 500, offset = 0): Promise> { return getJSON>(listPath('/api/nodeinfo', limit, offset)) } export function getNodeInfoById(nodeId: string): Promise { return getJSON(`/api/nodeinfo/${encodeURIComponent(nodeId)}`) } export function getMapReports(limit = 500, offset = 0, bounds?: MapBoundsQuery): Promise> { const params = new URLSearchParams({ limit: String(limit), offset: String(offset) }) if (bounds) { params.set('min_lat', String(bounds.min_lat)) params.set('max_lat', String(bounds.max_lat)) params.set('min_lng', String(bounds.min_lng)) params.set('max_lng', String(bounds.max_lng)) } return getJSON>(`/api/map-reports?${params.toString()}`) } export function getMapReportById(nodeId: string): Promise { return getJSON(`/api/map-reports/${encodeURIComponent(nodeId)}`) } export function getMapReportViewport(bounds: MapBoundsQuery, zoom: number, limit = 1000): Promise { const params = new URLSearchParams({ min_lat: String(bounds.min_lat), max_lat: String(bounds.max_lat), min_lng: String(bounds.min_lng), max_lng: String(bounds.max_lng), zoom: String(zoom), limit: String(limit), }) return getJSON(`/api/map-reports/viewport?${params.toString()}`) } export function getDefaultMapSource(): Promise { return getJSON('/api/map-source/default') } export function getEnabledMapSources(): Promise { return getJSON('/api/map-source/enabled') } export function getTextMessages(limit = 100, offset = 0, nodeIdOrOptions: string | ListQueryOptions = ''): Promise> { return getJSON>(listPath('/api/text-messages', limit, offset, nodeIdOrOptions)) } export function getSignRecords(limit = 100, offset = 0, nodeIdOrOptions: string | ListQueryOptions = ''): Promise> { return getJSON>(listPath('/api/signs', limit, offset, nodeIdOrOptions)) } export function getSignDailyCounts(nodeIdOrOptions: string | ListQueryOptions = ''): Promise> { return getJSON>(listPath('/api/signs/daily', 500, 0, nodeIdOrOptions)) } export function getAdminSignRecords(limit = 100, offset = 0, nodeIdOrOptions: string | ListQueryOptions = ''): Promise> { return getJSON>(listPath('/api/admin/signs', limit, offset, nodeIdOrOptions)) } export function createAdminSignRecord(payload: SignRecordPayload): Promise<{ item: SignRecord }> { return postJSON<{ item: SignRecord }>('/api/admin/signs', payload) } export function updateAdminSignRecord(id: number, payload: SignRecordPayload): Promise<{ item: SignRecord }> { return putJSON<{ item: SignRecord }>(`/api/admin/signs/${id}`, payload) } export function deleteAdminSignRecord(id: number): Promise<{ status: string }> { return deleteJSON<{ status: string }>(`/api/admin/signs/${id}`) } export function deleteTextMessage(id: number): Promise<{ status: string }> { return deleteJSON<{ status: string }>(`/api/admin/text-messages/${id}`) } export function deleteNode(nodeId: string): Promise<{ status: string }> { return deleteJSON<{ status: string }>(`/api/admin/nodes/${encodeURIComponent(nodeId)}`) } // 「删除节点」:除节点信息和地图上报外,再清理聊天消息以及 position/telemetry/routing/traceroute 等数据包记录。 export function purgeNode(nodeId: string): Promise<{ status: string }> { return deleteJSON<{ status: string }>(`/api/admin/nodes/${encodeURIComponent(nodeId)}/purge`) } export function getPositions(limit = 500, offset = 0, nodeIdOrOptions: string | ListQueryOptions = ''): Promise> { return getJSON>(listPath('/api/positions', limit, offset, nodeIdOrOptions)) } export function getDiscardDetails(limit = 100, offset = 0): Promise> { return getJSON>(listPath('/api/discard-details', limit, offset)) } export function getTelemetry(limit = 500, offset = 0, nodeId = ''): Promise> { return getJSON>(listPath('/api/telemetry', limit, offset, nodeId)) } export function adminLogin(username: string, password: string): Promise { return postJSON('/api/admin/login', { username, password }) } export function adminLogout(): Promise<{ status: string }> { return postJSON<{ status: string }>('/api/admin/logout') } export function getAdminMe(): Promise { return getJSON('/api/admin/me') } export function getAdminMqttStatus(): Promise { return getJSON('/api/admin/mqtt/status') } // 一键断开 MQTT 客户端并把它的远端 IP 加入屏蔽表。 export function disconnectAndBlockMqttClient( clientId: string, payload: { reason?: string } = {}, ): Promise<{ status: string client_id: string ip_value: string disconnected: boolean ip_rule_created: boolean ip_rule_id?: number }> { return postJSON(`/api/admin/mqtt/clients/${encodeURIComponent(clientId)}/disconnect-and-block`, payload) } export function getAdminRuntimeSettings(): Promise { return getJSON('/api/admin/runtime-settings') } export function updateAdminRuntimeSettings(payload: AdminRuntimeSettingsPayload): Promise { return putJSON('/api/admin/runtime-settings', payload) } export function getAdminHelpContent(): Promise { return getJSON('/api/admin/help') } export function saveAdminHelpContent(markdown: string): Promise { return postJSON('/api/admin/help', { markdown }) } export function previewAdminHelpContent(markdown: string): Promise { return postJSON('/api/admin/help/preview', { markdown }) } export function getAdminUsers(): Promise { return getJSON('/api/admin/users') } export function createAdminUser(username: string, password: string): Promise { return postJSON('/api/admin/users', { username, password }) } export function updateAdminUserPassword(id: number, password: string): Promise { return putJSON(`/api/admin/users/${id}/password`, { password }) } export function getAdminLoginLogs(limit = 100, offset = 0): Promise { return getJSON(`/api/admin/log/login?limit=${limit}&offset=${offset}`) } export function getAdminMapSources(limit = 100, offset = 0): Promise> { return getJSON>(listPath('/api/admin/map-source', limit, offset)) } export function createAdminMapSource(payload: MapTileSourcePayload): Promise { return postJSON('/api/admin/map-source', payload) } export function updateAdminMapSource(id: number, payload: MapTileSourcePayload): Promise { return putJSON(`/api/admin/map-source/${id}`, payload) } export function deleteAdminMapSource(id: number): Promise<{ status: string }> { return deleteJSON<{ status: string }>(`/api/admin/map-source/${id}`) } export function setDefaultAdminMapSource(id: number): Promise { return postJSON(`/api/admin/map-source/${id}/default`) } export function getNodeBlockingRules(limit = 100, offset = 0): Promise> { return getJSON>(listPath('/api/admin/blocking/nodes', limit, offset)) } export function createNodeBlockingRule(payload: NodeBlockingRulePayload): Promise> { return postJSON>('/api/admin/blocking/nodes', payload) } export function updateNodeBlockingRule(id: number, payload: NodeBlockingRulePayload): Promise> { return putJSON>(`/api/admin/blocking/nodes/${id}`, payload) } export function deleteNodeBlockingRule(id: number): Promise<{ status: string }> { return deleteJSON<{ status: string }>(`/api/admin/blocking/nodes/${id}`) } export function getIPBlockingRules(limit = 100, offset = 0): Promise> { return getJSON>(listPath('/api/admin/blocking/ips', limit, offset)) } export function createIPBlockingRule(payload: IPBlockingRulePayload): Promise> { return postJSON>('/api/admin/blocking/ips', payload) } export function updateIPBlockingRule(id: number, payload: IPBlockingRulePayload): Promise> { return putJSON>(`/api/admin/blocking/ips/${id}`, payload) } export function deleteIPBlockingRule(id: number): Promise<{ status: string }> { return deleteJSON<{ status: string }>(`/api/admin/blocking/ips/${id}`) } export function getForbiddenWordBlockingRules(limit = 100, offset = 0): Promise> { return getJSON>(listPath('/api/admin/blocking/words', limit, offset)) } export function createForbiddenWordBlockingRule(payload: ForbiddenWordBlockingRulePayload): Promise> { return postJSON>('/api/admin/blocking/words', payload) } export function updateForbiddenWordBlockingRule(id: number, payload: ForbiddenWordBlockingRulePayload): Promise> { return putJSON>(`/api/admin/blocking/words/${id}`, payload) } export function deleteForbiddenWordBlockingRule(id: number): Promise<{ status: string }> { return deleteJSON<{ status: string }>(`/api/admin/blocking/words/${id}`) } export function getMQTTForwarders(limit = 100, offset = 0): Promise> { return getJSON>(listPath('/api/admin/mqtt-forward/forwarders', limit, offset)) } export function createMQTTForwarder(payload: MQTTForwarderPayload): Promise> { return postJSON>('/api/admin/mqtt-forward/forwarders', payload) } export function updateMQTTForwarder(id: number, payload: MQTTForwarderPayload): Promise> { return putJSON>(`/api/admin/mqtt-forward/forwarders/${id}`, payload) } export function deleteMQTTForwarder(id: number): Promise<{ status: string }> { return deleteJSON<{ status: string }>(`/api/admin/mqtt-forward/forwarders/${id}`) } export function restartMQTTForwarder(id: number): Promise<{ status: string }> { return postJSON<{ status: string }>(`/api/admin/mqtt-forward/forwarders/${id}/restart`) } export function getMQTTForwardTopics(forwarderId: number, limit = 100, offset = 0): Promise> { return getJSON>(listPath(`/api/admin/mqtt-forward/forwarders/${forwarderId}/topics`, limit, offset)) } export function createMQTTForwardTopic(forwarderId: number, payload: MQTTForwardTopicPayload): Promise> { return postJSON>(`/api/admin/mqtt-forward/forwarders/${forwarderId}/topics`, payload) } export function updateMQTTForwardTopic(id: number, payload: MQTTForwardTopicPayload): Promise> { return putJSON>(`/api/admin/mqtt-forward/topics/${id}`, payload) } export function deleteMQTTForwardTopic(id: number): Promise<{ status: string }> { return deleteJSON<{ status: string }>(`/api/admin/mqtt-forward/topics/${id}`) } export function getMQTTForwardStatus(): Promise { return getJSON('/api/admin/mqtt-forward/status') } export function getBotNodes(limit = 100, offset = 0): Promise> { return getJSON>(listPath('/api/admin/bot/nodes', limit, offset)) } export function createBotNode(payload: BotNodePayload): Promise { return postJSON('/api/admin/bot/nodes', payload) } export function updateBotNode(id: number, payload: BotNodePayload): Promise { return putJSON(`/api/admin/bot/nodes/${id}`, payload) } export function deleteBotNode(id: number): Promise<{ status: string }> { return deleteJSON<{ status: string }>(`/api/admin/bot/nodes/${id}`) } export function broadcastBotNodeInfo(id: number): Promise { return postJSON(`/api/admin/bot/nodes/${id}/nodeinfo`) } export function regenerateBotNodeKeys(id: number): Promise { return postJSON(`/api/admin/bot/nodes/${id}/keys/regenerate`) } export function getBotMessages(botId = 0, limit = 100, offset = 0): Promise> { const params = new URLSearchParams({ limit: String(limit), offset: String(offset) }) if (botId > 0) { params.set('bot_id', String(botId)) } return getJSON>(`/api/admin/bot/messages?${params.toString()}`) } export function getBotDirectMessages(botId: number, targetNodeNum: number, limit = 100, offset = 0, direction = ''): Promise> { const params = new URLSearchParams({ bot_id: String(botId), target_node_num: String(targetNodeNum), limit: String(limit), offset: String(offset) }) if (direction) { params.set('direction', direction) } return getJSON>(`/api/admin/bot/direct-messages?${params.toString()}`) } export function getBotConversations(botId: number, limit = 100, offset = 0): Promise { const params = new URLSearchParams({ bot_id: String(botId), limit: String(limit), offset: String(offset) }) return getJSON(`/api/admin/bot/conversations?${params.toString()}`) } export function markBotDirectMessagesRead(botId: number, peerNodeNum: number): Promise<{ updated: number }> { return postJSON<{ updated: number }>('/api/admin/bot/direct-messages/read', { bot_id: botId, peer_node_num: peerNodeNum }) } // 静默使用未导出类型,避免 TS6133(未使用的导入)。 export type { BotDirectConversation } from './types' export function sendBotMessage(payload: BotSendMessagePayload): Promise { return postJSON('/api/admin/bot/messages', payload) } // LLM 消息队列 API export function getLLMMessages(limit = 100, offset = 0, botId?: number, includeDeleted = false): Promise> { const params = new URLSearchParams({ limit: String(limit), offset: String(offset) }) if (botId !== undefined && botId > 0) { params.set('bot_id', String(botId)) } if (includeDeleted) { params.set('include_deleted', 'true') } return getJSON>(`/api/admin/llm/messages?${params.toString()}`) } export function getLLMMessage(id: number): Promise<{ item: LLMMessage }> { return getJSON<{ item: LLMMessage }>(`/api/admin/llm/messages/${id}`) } export function updateLLMMessageStatus(id: number, status: LLMMessageStatus, error?: string): Promise<{ status: string }> { return putJSON<{ status: string }>(`/api/admin/llm/messages/${id}/status`, { status, error }) } export function deleteLLMMessage(id: number): Promise<{ status: string }> { return deleteJSON<{ status: string }>(`/api/admin/llm/messages/${id}`) } export function deleteLLMMessagesByBot(botId: number): Promise<{ status: string }> { return deleteJSON<{ status: string }>(`/api/admin/llm/bots/${botId}/messages`) } export function cleanupDeletedLLMMessages(days = 7): Promise<{ status: string; deleted_count: number }> { return postJSON<{ status: string; deleted_count: number }>('/api/admin/llm/messages/cleanup', { days }) } // LLM Provider API export function getLLMProviders(includeInactive = false): Promise> { const params = new URLSearchParams() if (includeInactive) { params.set('include_inactive', 'true') } const query = params.toString() ? `?${params.toString()}` : '' return getJSON>(`/api/admin/llm/providers${query}`) } export function getLLMProvider(name: string): Promise { return getJSON(`/api/admin/llm/providers/${encodeURIComponent(name)}`) } export function createLLMProvider(payload: LLMProviderPayload): Promise { return postJSON('/api/admin/llm/providers', payload) } export function updateLLMProvider(name: string, payload: Partial): Promise { return putJSON(`/api/admin/llm/providers/${encodeURIComponent(name)}`, payload) } export function deleteLLMProvider(name: string): Promise<{ status: string }> { return deleteJSON<{ status: string }>(`/api/admin/llm/providers/${encodeURIComponent(name)}`) } // LLM Tool Router API export function getLLMToolRouter(): Promise { return getJSON('/api/admin/llm/tool-router') } export function updateLLMToolRouter(payload: Partial): Promise { return putJSON('/api/admin/llm/tool-router', payload) } // LLM Primary Config API - 主 AI 回复配置 export function getLLMPrimaryConfig(): Promise { return getJSON('/api/admin/llm/primary-config') } export function updateLLMPrimaryConfig(payload: Partial): Promise { return putJSON('/api/admin/llm/primary-config', payload) } // 静默使用未导出类型,避免 TS6133(未使用的导入)。 export type { LLMMessage, LLMMessageStatus, LLMProvider, LLMPlatformRouter, LLMPrimaryConfig } from './types'