Integrate backend open-webui wrapper with tauri
This commit is contained in:
parent
3218c10063
commit
3794459323
2
.gitignore
vendored
2
.gitignore
vendored
@ -8,7 +8,7 @@ node_modules
|
|||||||
!.env.example
|
!.env.example
|
||||||
vite.config.js.timestamp-*
|
vite.config.js.timestamp-*
|
||||||
vite.config.ts.timestamp-*
|
vite.config.ts.timestamp-*
|
||||||
backend/.webui_secret_key
|
.webui_secret_key
|
||||||
.venv
|
.venv
|
||||||
backend/build
|
backend/build
|
||||||
backend/dist
|
backend/dist
|
||||||
|
@ -1,5 +1,22 @@
|
|||||||
|
import argparse
|
||||||
|
import uvicorn
|
||||||
from open_webui import app
|
from open_webui import app
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="GlowPath Backend Server")
|
||||||
|
parser.add_argument(
|
||||||
|
"--port", type=int, default=8080, help="Port to run the server on"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--host", type=str, default="127.0.0.1", help="Host to bind the server to"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Run the FastAPI app using uvicorn
|
||||||
|
uvicorn.run(app, host=args.host, port=args.port, log_level="info")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app()
|
main()
|
||||||
|
75
src-tauri/Cargo.lock
generated
75
src-tauri/Cargo.lock
generated
@ -1,6 +1,6 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "addr2line"
|
name = "addr2line"
|
||||||
@ -838,6 +838,15 @@ version = "1.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
|
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encoding_rs"
|
||||||
|
version = "0.8.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "endi"
|
name = "endi"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@ -1336,6 +1345,7 @@ dependencies = [
|
|||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
|
"tauri-plugin-shell",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2384,6 +2394,16 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_pipe"
|
||||||
|
version = "1.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "db335f4760b14ead6290116f2427bf33a14d4f0617d49f78a246de10c1831224"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pango"
|
name = "pango"
|
||||||
version = "0.18.3"
|
version = "0.18.3"
|
||||||
@ -3247,12 +3267,44 @@ dependencies = [
|
|||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shared_child"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c2778001df1384cf20b6dc5a5a90f48da35539885edaaefd887f8d744e939c0b"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"sigchld",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sigchld"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1219ef50fc0fdb04fcc243e6aa27f855553434ffafe4fa26554efb78b5b4bf89"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"os_pipe",
|
||||||
|
"signal-hook",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.3.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.5"
|
version = "1.4.5"
|
||||||
@ -3667,6 +3719,27 @@ dependencies = [
|
|||||||
"zbus",
|
"zbus",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-shell"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b9ffadec5c3523f11e8273465cacb3d86ea7652a28e6e2a2e9b5c182f791d25"
|
||||||
|
dependencies = [
|
||||||
|
"encoding_rs",
|
||||||
|
"log",
|
||||||
|
"open",
|
||||||
|
"os_pipe",
|
||||||
|
"regex",
|
||||||
|
"schemars 0.8.22",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"shared_child",
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
"thiserror 2.0.12",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "2.7.0"
|
version = "2.7.0"
|
||||||
|
@ -20,6 +20,6 @@ tauri-build = { version = "2", features = [] }
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "2", features = [] }
|
tauri = { version = "2", features = [] }
|
||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
|
tauri-plugin-shell = "2"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
"windows": ["main"],
|
"windows": ["main"],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"core:default",
|
"core:default",
|
||||||
"opener:default"
|
"opener:default",
|
||||||
|
"shell:allow-open",
|
||||||
|
"shell:allow-execute"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,14 @@
|
|||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use tauri::Manager;
|
||||||
|
use tauri_plugin_shell::{process::CommandEvent, ShellExt};
|
||||||
|
|
||||||
|
// Global state to track backend process and port
|
||||||
|
#[derive(Default)]
|
||||||
|
struct AppState {
|
||||||
|
backend_port: Arc<Mutex<Option<u16>>>,
|
||||||
|
backend_process: Arc<Mutex<Option<tauri_plugin_shell::process::CommandChild>>>,
|
||||||
|
}
|
||||||
|
|
||||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn greet(name: &str) -> String {
|
fn greet(name: &str) -> String {
|
||||||
@ -13,11 +24,140 @@ fn ollama_status() -> Result<String, String> {
|
|||||||
.map(|o| String::from_utf8_lossy(&o.stdout).to_string())
|
.map(|o| String::from_utf8_lossy(&o.stdout).to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn start_backend(app: tauri::AppHandle) -> Result<u16, String> {
|
||||||
|
let state = app.state::<AppState>();
|
||||||
|
|
||||||
|
// Check if backend is already running
|
||||||
|
if let Ok(port_guard) = state.backend_port.lock() {
|
||||||
|
if let Some(port) = *port_guard {
|
||||||
|
return Ok(port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find an available port
|
||||||
|
let port = find_available_port().map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
// Start the backend sidecar
|
||||||
|
let sidecar_command = app
|
||||||
|
.shell()
|
||||||
|
.sidecar("glowpath-backend")
|
||||||
|
.map_err(|e| format!("Failed to create sidecar command: {}", e))?
|
||||||
|
.args(&["serve", "--port", &port.to_string()]);
|
||||||
|
|
||||||
|
let (mut rx, child) = sidecar_command
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| format!("Failed to spawn backend process: {}", e))?;
|
||||||
|
|
||||||
|
// Store the process and port
|
||||||
|
if let Ok(mut process_guard) = state.backend_process.lock() {
|
||||||
|
*process_guard = Some(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(mut port_guard) = state.backend_port.lock() {
|
||||||
|
*port_guard = Some(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle process events in the background
|
||||||
|
let app_handle = app.clone();
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
while let Some(event) = rx.recv().await {
|
||||||
|
match event {
|
||||||
|
CommandEvent::Stdout(data) => {
|
||||||
|
println!("Backend stdout: {}", String::from_utf8_lossy(&data));
|
||||||
|
}
|
||||||
|
CommandEvent::Stderr(data) => {
|
||||||
|
eprintln!("Backend stderr: {}", String::from_utf8_lossy(&data));
|
||||||
|
}
|
||||||
|
CommandEvent::Error(error) => {
|
||||||
|
eprintln!("Backend error: {}", error);
|
||||||
|
}
|
||||||
|
CommandEvent::Terminated(payload) => {
|
||||||
|
println!("Backend terminated with code: {:?}", payload.code);
|
||||||
|
// Reset state
|
||||||
|
if let Some(state) = app_handle.try_state::<AppState>() {
|
||||||
|
if let Ok(mut port_guard) = state.backend_port.lock() {
|
||||||
|
*port_guard = None;
|
||||||
|
}
|
||||||
|
if let Ok(mut process_guard) = state.backend_process.lock() {
|
||||||
|
*process_guard = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {} // Handle any other events
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(port)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn get_backend_port(app: tauri::AppHandle) -> Result<u16, String> {
|
||||||
|
let state = app.state::<AppState>();
|
||||||
|
|
||||||
|
if let Ok(port_guard) = state.backend_port.lock() {
|
||||||
|
if let Some(port) = *port_guard {
|
||||||
|
return Ok(port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no port is set, start the backend
|
||||||
|
start_backend(app).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn stop_backend(app: tauri::AppHandle) -> Result<(), String> {
|
||||||
|
let state = app.state::<AppState>();
|
||||||
|
|
||||||
|
if let Ok(mut process_guard) = state.backend_process.lock() {
|
||||||
|
if let Some(child) = process_guard.take() {
|
||||||
|
child
|
||||||
|
.kill()
|
||||||
|
.map_err(|e| format!("Failed to kill backend process: {}", e))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(mut port_guard) = state.backend_port.lock() {
|
||||||
|
*port_guard = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_available_port() -> Result<u16, Box<dyn std::error::Error>> {
|
||||||
|
use std::net::TcpListener;
|
||||||
|
|
||||||
|
// Try to bind to port 0 to get an available port
|
||||||
|
let listener = TcpListener::bind("127.0.0.1:0")?;
|
||||||
|
let addr = listener.local_addr()?;
|
||||||
|
Ok(addr.port())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
.invoke_handler(tauri::generate_handler![greet, ollama_status])
|
.plugin(tauri_plugin_shell::init())
|
||||||
|
.manage(AppState::default())
|
||||||
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
greet,
|
||||||
|
ollama_status,
|
||||||
|
start_backend,
|
||||||
|
get_backend_port,
|
||||||
|
stop_backend
|
||||||
|
])
|
||||||
|
.setup(|app| {
|
||||||
|
// Optionally start backend on app startup
|
||||||
|
let app_handle = app.handle().clone();
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
if let Err(e) = start_backend(app_handle).await {
|
||||||
|
eprintln!("Failed to start backend on startup: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,14 @@
|
|||||||
"icons/128x128@2x.png",
|
"icons/128x128@2x.png",
|
||||||
"icons/icon.icns",
|
"icons/icon.icns",
|
||||||
"icons/icon.ico"
|
"icons/icon.ico"
|
||||||
|
],
|
||||||
|
"externalBin": [
|
||||||
|
"../backend/dist/glowpath-backend"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"shell": {
|
||||||
|
"open": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
94
src/lib/backend.ts
Normal file
94
src/lib/backend.ts
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
|
class BackendService {
|
||||||
|
private port: number | null = null;
|
||||||
|
private baseUrl: string | null = null;
|
||||||
|
|
||||||
|
async initialize(): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Get or start the backend and get its port
|
||||||
|
this.port = await invoke<number>("get_backend_port");
|
||||||
|
this.baseUrl = `http://127.0.0.1:${this.port}`;
|
||||||
|
|
||||||
|
// Wait for backend to be ready
|
||||||
|
await this.waitForBackend();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to initialize backend service:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async start(): Promise<void> {
|
||||||
|
try {
|
||||||
|
this.port = await invoke<number>("start_backend");
|
||||||
|
this.baseUrl = `http://127.0.0.1:${this.port}`;
|
||||||
|
await this.waitForBackend();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to start backend:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async stop(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await invoke("stop_backend");
|
||||||
|
this.port = null;
|
||||||
|
this.baseUrl = null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to stop backend:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async waitForBackend(maxAttempts = 30): Promise<void> {
|
||||||
|
for (let i = 0; i < maxAttempts; i++) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(this.baseUrl + "/");
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Backend not ready yet
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 30000));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Backend failed to start within timeout period");
|
||||||
|
}
|
||||||
|
|
||||||
|
getBaseUrl(): string {
|
||||||
|
if (!this.baseUrl) {
|
||||||
|
throw new Error("Backend service not initialized");
|
||||||
|
}
|
||||||
|
return this.baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetch(path: string, options?: RequestInit): Promise<Response> {
|
||||||
|
if (!this.baseUrl) {
|
||||||
|
throw new Error("Backend service not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${this.baseUrl}${path}`;
|
||||||
|
return fetch(url, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proxy method for API calls to the backend
|
||||||
|
async api(path: string, options?: RequestInit): Promise<any> {
|
||||||
|
const response = await this.fetch(`/api${path}`, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...options?.headers,
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`API request failed: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const backendService = new BackendService();
|
@ -1,20 +1,39 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { onMount } from "svelte";
|
||||||
import ModelsPane from "$lib/components/ModelsPane.svelte";
|
import ModelsPane from "$lib/components/ModelsPane.svelte";
|
||||||
|
import { backendService } from "$lib/backend";
|
||||||
|
|
||||||
let name = $state("");
|
let name = $state("");
|
||||||
let greetMsg = $state("");
|
let greetMsg = $state("");
|
||||||
|
let backendStatus = $state("Initializing...");
|
||||||
|
|
||||||
async function greet(event: Event) {
|
async function greet(event: Event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||||
greetMsg = await invoke("greet", { name });
|
greetMsg = await invoke("greet", { name });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
try {
|
||||||
|
backendStatus = "Starting backend...";
|
||||||
|
await backendService.initialize();
|
||||||
|
backendStatus = `Backend running at ${backendService.getBaseUrl()}`;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to initialize backend:", error);
|
||||||
|
backendStatus = "Backend failed to start";
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="container">
|
<main class="container">
|
||||||
<h1>Welcome to Tauri + Svelte</h1>
|
<h1>Welcome to Tauri + Svelte</h1>
|
||||||
|
|
||||||
|
<div class="backend-status">
|
||||||
|
<h2>Backend Status</h2>
|
||||||
|
<p>{backendStatus}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h1 class="text-2xl font-bold mb-4">Installed Ollama models</h1>
|
<h1 class="text-2xl font-bold mb-4">Installed Ollama models</h1>
|
||||||
<ModelsPane />
|
<ModelsPane />
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user