fix(router): support /aura basename deployment

main
sp mac bookpro 2605 2026-05-28 16:17:27 +08:00
parent 83e4fab788
commit 604d549a79
5 changed files with 20 additions and 13 deletions

View File

@ -1,10 +1,13 @@
import axios from 'axios'; import axios from 'axios';
const AURA_API_BASE = 'https://api.redhare.cc/aura/v1'; const API_BASE_URL = 'https://api.hoyidata.com/aura/v1';
const APP_BASE = (import.meta.env.BASE_URL || '/').replace(/\/$/, '');
const withAppBase = (path: string) => `${APP_BASE}${path.startsWith('/') ? path : `/${path}`}`;
const withApiBase = (path: string) => `${API_BASE_URL}${path.startsWith('/') ? path : `/${path}`}`;
const isMockAuth = () => typeof localStorage !== 'undefined' && localStorage.getItem('mock-auth') === '1'; const isMockAuth = () => typeof localStorage !== 'undefined' && localStorage.getItem('mock-auth') === '1';
export const api = axios.create({ export const api = axios.create({
baseURL: 'https://api.hoyidata.com/aura/v1', baseURL: API_BASE_URL,
timeout: 90000, timeout: 90000,
withCredentials: true // 关键:跨域请求带 cookie withCredentials: true // 关键:跨域请求带 cookie
}); });
@ -13,9 +16,10 @@ export const api = axios.create({
api.interceptors.response.use( api.interceptors.response.use(
(r) => r, (r) => r,
(err) => { (err) => {
if (err?.response?.status === 401 && !location.pathname.startsWith('/login') && !isMockAuth()) { const isLoginPage = location.pathname === '/login' || location.pathname === withAppBase('/login');
if (err?.response?.status === 401 && !isLoginPage && !isMockAuth()) {
const next = encodeURIComponent(location.pathname + location.search); const next = encodeURIComponent(location.pathname + location.search);
location.href = `/login?next=${next}`; location.href = `${withAppBase('/login')}?next=${next}`;
} }
return Promise.reject(err); return Promise.reject(err);
} }
@ -309,7 +313,7 @@ export const SessionAPI = {
.then((r) => r.data), .then((r) => r.data),
/** 导出会话为文件并触发浏览器下载 */ /** 导出会话为文件并触发浏览器下载 */
exportSession: (agentId: string, sessionId: string, format: 'md' | 'json' = 'md') => { exportSession: (agentId: string, sessionId: string, format: 'md' | 'json' = 'md') => {
const url = `/api/agents/${agentId}/sessions/${sessionId}/export?format=${format}`; const url = withApiBase(`/agents/${agentId}/sessions/${sessionId}/export?format=${format}`);
const a = document.createElement('a'); const a = document.createElement('a');
a.href = url; a.href = url;
a.rel = 'noopener'; a.rel = 'noopener';
@ -747,7 +751,7 @@ export function streamWorkflowRun(
} }
): () => void { ): () => void {
const qs = input ? `?input=${encodeURIComponent(JSON.stringify(input))}` : ''; const qs = input ? `?input=${encodeURIComponent(JSON.stringify(input))}` : '';
const url = `/api/workflows/${workflowId}/run-stream${qs}`; const url = withApiBase(`/workflows/${workflowId}/run-stream${qs}`);
const es = new EventSource(url, { withCredentials: true } as any); const es = new EventSource(url, { withCredentials: true } as any);
es.addEventListener('ready', (e: any) => handlers.onReady?.(JSON.parse(e.data))); es.addEventListener('ready', (e: any) => handlers.onReady?.(JSON.parse(e.data)));
es.addEventListener('step_start', (e: any) => handlers.onStepStart?.(JSON.parse(e.data))); es.addEventListener('step_start', (e: any) => handlers.onStepStart?.(JSON.parse(e.data)));

View File

@ -124,7 +124,7 @@ function ThemeProvider({ children }: { children: React.ReactNode }) {
ReactDOM.createRoot(document.getElementById('root')!).render( ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode> <React.StrictMode>
<ThemeProvider> <ThemeProvider>
<BrowserRouter> <BrowserRouter basename={import.meta.env.BASE_URL}>
<App /> <App />
</BrowserRouter> </BrowserRouter>
</ThemeProvider> </ThemeProvider>

View File

@ -16,6 +16,7 @@ export default function SharedSessionPage() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [err, setErr] = useState<string>(''); const [err, setErr] = useState<string>('');
const { message } = AntApp.useApp(); const { message } = AntApp.useApp();
const loginHref = `${(import.meta.env.BASE_URL || '/').replace(/\/$/, '')}/login`;
useEffect(() => { useEffect(() => {
if (!token) return; if (!token) return;
@ -121,7 +122,7 @@ export default function SharedSessionPage() {
marginTop: 40 marginTop: 40
}} }}
> >
Agent Studio · <a href="/login" style={{ color: 'var(--color-brand)' }}> agent</a> Agent Studio · <a href={loginHref} style={{ color: 'var(--color-brand)' }}> agent</a>
</div> </div>
</div> </div>
</div> </div>

1
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@ -1,13 +1,14 @@
import { defineConfig, loadEnv } from 'vite'; import { defineConfig, loadEnv } from 'vite';
import react from '@vitejs/plugin-react'; import react from '@vitejs/plugin-react';
// 默认走 Go 后端 :4001要回退 Node 后端就 set VITE_API_TARGET=http://localhost:4000 // 默认走 Go 后端 :4001要回退 Node 后端就 set VITE_API_TARGET=http://localhost:4000
export default defineConfig(({ mode }) => { export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, process.cwd(), ''); const env = loadEnv(mode, process.cwd(), '');
const target = env.VITE_API_TARGET || 'https://api.hoyidata.com'; const target = env.VITE_API_TARGET || 'https://api.hoyidata.com';
return { return {
base: '/aura/', // <-- 关键修改:设置构建的基础路径 // 本地开发走根路径,生产构建部署到 /aura 子路径
base: command === 'serve' ? '/' : '/aura/',
plugins: [react()], plugins: [react()],
server: { server: {
port: 3001, port: 3001,