feat(electron): adapt renderer API and WebSocket layer for dynamic backend URL

This commit is contained in:
DrSmoothl
2026-03-03 00:54:14 +08:00
parent b5cc361ce9
commit fc394f4412
6 changed files with 55 additions and 27 deletions

View File

@@ -1,8 +1,19 @@
import axios from 'axios'
import { getApiBaseUrl } from './api-base'
const apiClient = axios.create({
baseURL: import.meta.env.DEV ? 'http://localhost:8000' : '',
baseURL: '', // 统一为空,通过拦截器动态设置
timeout: 10000,
})
// Electron 端:动态注入后端 URL浏览器端 getApiBaseUrl() 返回空字符串,行为不变
apiClient.interceptors.request.use(async (config) => {
const baseUrl = await getApiBaseUrl()
if (baseUrl && !config.baseURL) {
config.baseURL = baseUrl
}
return config
})
export default apiClient

View File

@@ -1,5 +1,20 @@
import { getApiBaseUrl } from './api-base'
import { isElectron } from './runtime'
// 带自动认证处理的 fetch 封装
/**
* 将相对路径在 Electron 端转换为绝对路径
* 浏览器端直接返回原始 input行为不变
*/
async function resolveUrl(input: RequestInfo | URL): Promise<RequestInfo | URL> {
if (isElectron() && typeof input === 'string' && input.startsWith('/')) {
const base = await getApiBaseUrl()
return base ? `${base}${input}` : input
}
return input
}
/**
* 增强的 fetch 函数,自动处理 401 错误并跳转到登录页
* 使用 HttpOnly Cookie 进行认证,自动携带 credentials
@@ -25,7 +40,7 @@ export async function fetchWithAuth(
headers,
}
const response = await fetch(input, config)
const response = await fetch(await resolveUrl(input), config)
// 检测 401 未授权错误
if (response.status === 401) {
@@ -54,7 +69,7 @@ export function getAuthHeaders(): HeadersInit {
*/
export async function logout(): Promise<void> {
try {
await fetch('/api/webui/auth/logout', {
await fetch(await resolveUrl('/api/webui/auth/logout'), {
method: 'POST',
credentials: 'include',
})
@@ -70,7 +85,7 @@ export async function logout(): Promise<void> {
*/
export async function checkAuthStatus(): Promise<boolean> {
try {
const response = await fetch('/api/webui/auth/check', {
const response = await fetch(await resolveUrl('/api/webui/auth/check'), {
method: 'GET',
credentials: 'include',
})

View File

@@ -2,7 +2,16 @@
* 知识库 API
*/
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api/webui'
import { getApiBaseUrl } from './api-base'
import { isElectron } from './runtime'
async function getKnowledgeApiBase(): Promise<string> {
if (isElectron()) {
const base = await getApiBaseUrl()
return base ? `${base}/api/webui` : '/api/webui'
}
return import.meta.env.VITE_API_BASE_URL || '/api/webui'
}
export interface KnowledgeNode {
id: string
@@ -35,7 +44,7 @@ export interface KnowledgeStats {
* 获取知识图谱数据
*/
export async function getKnowledgeGraph(limit: number = 100, nodeType: 'all' | 'entity' | 'paragraph' = 'all'): Promise<KnowledgeGraph> {
const url = `${API_BASE_URL}/knowledge/graph?limit=${limit}&node_type=${nodeType}`
const url = `${await getKnowledgeApiBase()}/knowledge/graph?limit=${limit}&node_type=${nodeType}`
const response = await fetch(url)
@@ -50,7 +59,7 @@ export async function getKnowledgeGraph(limit: number = 100, nodeType: 'all' | '
* 获取知识图谱统计信息
*/
export async function getKnowledgeStats(): Promise<KnowledgeStats> {
const response = await fetch(`${API_BASE_URL}/knowledge/stats`)
const response = await fetch(`${await getKnowledgeApiBase()}/knowledge/stats`)
if (!response.ok) {
throw new Error('获取知识图谱统计信息失败')
}
@@ -61,7 +70,7 @@ export async function getKnowledgeStats(): Promise<KnowledgeStats> {
* 搜索知识节点
*/
export async function searchKnowledgeNode(query: string): Promise<KnowledgeNode[]> {
const response = await fetch(`${API_BASE_URL}/knowledge/search?query=${encodeURIComponent(query)}`)
const response = await fetch(`${await getKnowledgeApiBase()}/knowledge/search?query=${encodeURIComponent(query)}`)
if (!response.ok) {
throw new Error('搜索知识节点失败')
}

View File

@@ -7,6 +7,8 @@ import { checkAuthStatus } from './fetch-with-auth'
import { getSetting } from './settings-manager'
import { createReconnectingWebSocket } from './ws-utils'
import { getWsBaseUrl } from '@/lib/api-base'
export interface LogEntry {
id: string
timestamp: string
@@ -54,18 +56,9 @@ class LogWebSocketManager {
/**
* 获取 WebSocket URL不含 token 参数)
*/
private getWebSocketUrl(): string {
let baseUrl: string
if (import.meta.env.DEV) {
// 开发模式:连接到 WebUI 后端服务器
baseUrl = 'ws://127.0.0.1:8001/ws/logs'
} else {
// 生产模式:使用当前页面的 host
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
const host = window.location.host
baseUrl = `${protocol}//${host}/ws/logs`
}
return baseUrl
private async getWebSocketUrl(): Promise<string> {
const wsBase = await getWsBaseUrl()
return `${wsBase}/ws/logs`
}
/**
@@ -85,7 +78,7 @@ class LogWebSocketManager {
return
}
const wsUrl = this.getWebSocketUrl()
const wsUrl = await this.getWebSocketUrl()
// 使用 ws-utils 创建 WebSocket
this.wsControl = createReconnectingWebSocket(wsUrl, {

View File

@@ -1,9 +1,9 @@
import type { ApiResponse } from '@/types/api'
import type { PluginInfo } from '@/types/plugin'
import { getWsBaseUrl } from '@/lib/api-base'
import { fetchWithAuth } from '@/lib/fetch-with-auth'
import { parseResponse } from '@/lib/api-helpers'
import type { GitStatus, MaimaiVersion } from './types'
/**
@@ -213,9 +213,8 @@ export async function connectPluginProgressWebSocket(
onProgress: (progress: import('./types').PluginLoadProgress) => void,
onError?: (error: Event) => void
): Promise<WebSocket | null> {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
const host = window.location.host
const wsUrl = `${protocol}//${host}/api/webui/ws/plugin-progress`
const wsBase = await getWsBaseUrl()
const wsUrl = `${wsBase}/api/webui/ws/plugin-progress`
// 使用 ws-utils 创建 WebSocket
const { createReconnectingWebSocket } = await import('@/lib/ws-utils')

View File

@@ -5,6 +5,7 @@ import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { ScrollArea } from '@/components/ui/scroll-area'
import { useToast } from '@/hooks/use-toast'
import { getWsBaseUrl } from '@/lib/api-base'
import { fetchWithAuth } from '@/lib/fetch-with-auth'
import { cn } from '@/lib/utils'
import { Bot, Edit2, Loader2, RefreshCw, User, Send, Wifi, WifiOff, UserCircle2 } from 'lucide-react'
@@ -299,7 +300,7 @@ export function ChatPage() {
return
}
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
const wsBase = await getWsBaseUrl()
const params = new URLSearchParams()
// 添加 token 到参数
@@ -320,7 +321,7 @@ export function ChatPage() {
params.append('user_name', userName)
}
const wsUrl = `${protocol}//${window.location.host}/api/chat/ws?${params.toString()}`
const wsUrl = `${wsBase}/api/chat/ws?${params.toString()}`
console.log(`[Tab ${tabId}] 正在连接 WebSocket:`, wsUrl)
try {