refactor: move routes under /tori/ prefix and add /api/obj mount

Routes now live at /tori/api, /ws/tori, and static files at /tori/.
Root / redirects to /tori/. Object storage mounted at /api/obj.
Dev proxy updated accordingly.
This commit is contained in:
Fam Zheng 2026-03-04 11:46:52 +00:00
parent 84779a0527
commit ae72e699f4
5 changed files with 40 additions and 7 deletions

View File

@ -5,7 +5,9 @@ mod kb;
mod llm; mod llm;
mod exec; mod exec;
pub mod state; pub mod state;
mod template;
mod timer; mod timer;
mod tools;
mod ws; mod ws;
use std::sync::Arc; use std::sync::Arc;
@ -19,6 +21,7 @@ pub struct AppState {
pub config: Config, pub config: Config,
pub agent_mgr: Arc<agent::AgentManager>, pub agent_mgr: Arc<agent::AgentManager>,
pub kb: Option<Arc<kb::KbManager>>, pub kb: Option<Arc<kb::KbManager>>,
pub obj_root: String,
} }
#[derive(Debug, Clone, serde::Deserialize)] #[derive(Debug, Clone, serde::Deserialize)]
@ -83,17 +86,28 @@ async fn main() -> anyhow::Result<()> {
// Resume incomplete workflows after restart // Resume incomplete workflows after restart
resume_workflows(database.pool.clone(), agent_mgr.clone()).await; resume_workflows(database.pool.clone(), agent_mgr.clone()).await;
let obj_root = std::env::var("OBJ_ROOT").unwrap_or_else(|_| "/data/obj".to_string());
let state = Arc::new(AppState { let state = Arc::new(AppState {
db: database, db: database,
config: config.clone(), config: config.clone(),
agent_mgr: agent_mgr.clone(), agent_mgr: agent_mgr.clone(),
kb: kb_arc, kb: kb_arc,
obj_root: obj_root.clone(),
}); });
let app = Router::new() let app = Router::new()
.nest("/api", api::router(state)) .nest("/tori/api", api::router(state))
.nest("/ws", ws::router(agent_mgr)) .nest("/api/obj", api::obj::router(obj_root.clone()))
.fallback_service(ServeDir::new("web/dist").fallback(ServeFile::new("web/dist/index.html"))) .route("/api/obj/", axum::routing::get({
let r = obj_root;
move || api::obj::root_listing(r)
}))
.nest("/ws/tori", ws::router(agent_mgr))
.nest_service("/tori", ServeDir::new("web/dist").fallback(ServeFile::new("web/dist/index.html")))
.route("/", axum::routing::get(|| async {
axum::response::Redirect::permanent("/tori/")
}))
.layer(CorsLayer::permissive()); .layer(CorsLayer::permissive());
let addr = format!("{}:{}", &config.server.host, config.server.port); let addr = format!("{}:{}", &config.server.host, config.server.port);

8
web/package-lock.json generated
View File

@ -1127,6 +1127,7 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.11.0.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.11.0.tgz",
"integrity": "sha512-fPxQqz4VTgPI/IQ+lj9r0h+fDR66bzoeMGHp8ASee+32OSGIkeASsoZuJixsQoVef1QJbeubcPBxKk22QVoWdw==", "integrity": "sha512-fPxQqz4VTgPI/IQ+lj9r0h+fDR66bzoeMGHp8ASee+32OSGIkeASsoZuJixsQoVef1QJbeubcPBxKk22QVoWdw==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"undici-types": "~7.16.0" "undici-types": "~7.16.0"
} }
@ -1324,6 +1325,7 @@
"version": "11.1.2", "version": "11.1.2",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.2.tgz", "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.2.tgz",
"integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==", "integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==",
"peer": true,
"dependencies": { "dependencies": {
"@chevrotain/cst-dts-gen": "11.1.2", "@chevrotain/cst-dts-gen": "11.1.2",
"@chevrotain/gast": "11.1.2", "@chevrotain/gast": "11.1.2",
@ -1374,6 +1376,7 @@
"version": "3.33.1", "version": "3.33.1",
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz",
"integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==",
"peer": true,
"engines": { "engines": {
"node": ">=0.10" "node": ">=0.10"
} }
@ -1740,6 +1743,7 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
} }
@ -2136,6 +2140,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true, "dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@ -2314,6 +2319,7 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true, "devOptional": true,
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@ -2350,6 +2356,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.27.0", "esbuild": "^0.27.0",
"fdir": "^6.5.0", "fdir": "^6.5.0",
@ -2466,6 +2473,7 @@
"version": "3.5.29", "version": "3.5.29",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.29.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.29.tgz",
"integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==", "integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==",
"peer": true,
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.5.29", "@vue/compiler-dom": "3.5.29",
"@vue/compiler-sfc": "3.5.29", "@vue/compiler-sfc": "3.5.29",

View File

@ -1,6 +1,6 @@
import type { Project, Workflow, ExecutionLogEntry, Comment, Timer, KbArticle, KbArticleSummary, PlanStepInfo, LlmCallLogEntry } from './types' import type { Project, Workflow, ExecutionLogEntry, Comment, Timer, KbArticle, KbArticleSummary, PlanStepInfo, LlmCallLogEntry } from './types'
const BASE = '/api' const BASE = `${import.meta.env.BASE_URL.replace(/\/$/, '')}/api`
async function request<T>(path: string, options?: RequestInit): Promise<T> { async function request<T>(path: string, options?: RequestInit): Promise<T> {
const res = await fetch(`${BASE}${path}`, { const res = await fetch(`${BASE}${path}`, {
@ -102,4 +102,12 @@ export const api = {
deleteArticle: (id: string) => deleteArticle: (id: string) =>
request<boolean>(`/kb/articles/${id}`, { method: 'DELETE' }), request<boolean>(`/kb/articles/${id}`, { method: 'DELETE' }),
getSettings: () => request<Record<string, string>>('/settings'),
putSetting: (key: string, value: string) =>
request<Record<string, string>>(`/settings/${key}`, {
method: 'PUT',
body: JSON.stringify({ value }),
}),
} }

View File

@ -51,7 +51,8 @@ export type WsHandler = (msg: WsMessage) => void
export function connectWs(projectId: string, onMessage: WsHandler): { close: () => void } { export function connectWs(projectId: string, onMessage: WsHandler): { close: () => void } {
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:' const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'
const url = `${proto}//${location.host}/ws/${projectId}` const wsBase = import.meta.env.BASE_URL.replace(/\/$/, '')
const url = `${proto}//${location.host}/ws${wsBase}/${projectId}`
let ws: WebSocket | null = null let ws: WebSocket | null = null
let reconnectTimer: ReturnType<typeof setTimeout> | null = null let reconnectTimer: ReturnType<typeof setTimeout> | null = null
let closed = false let closed = false

View File

@ -2,11 +2,13 @@ import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
export default defineConfig({ export default defineConfig({
base: process.env.VITE_BASE_PATH || '/tori/',
plugins: [vue()], plugins: [vue()],
server: { server: {
proxy: { proxy: {
'/api': 'http://localhost:3000', '/tori/api': 'http://localhost:3000',
'/ws': { '/api/obj': 'http://localhost:3000',
'/ws/tori': {
target: 'ws://localhost:3000', target: 'ws://localhost:3000',
ws: true, ws: true,
}, },