create-tether-app 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +729 -0
- package/package.json +59 -0
- package/template/.env.example +18 -0
- package/template/README.md.template +123 -0
- package/template/backend/app/__init__.py.template +5 -0
- package/template/backend/app/main.py +66 -0
- package/template/backend/app/routes/__init__.py +3 -0
- package/template/backend/app/routes/chat.py +151 -0
- package/template/backend/app/routes/health.py +28 -0
- package/template/backend/app/routes/models.py +126 -0
- package/template/backend/app/services/__init__.py +3 -0
- package/template/backend/app/services/llm.py +526 -0
- package/template/backend/pyproject.toml.template +34 -0
- package/template/backend/scripts/build.py +112 -0
- package/template/frontend/App.css +58 -0
- package/template/frontend/App.tsx +62 -0
- package/template/frontend/components/Chat.css +220 -0
- package/template/frontend/components/Chat.tsx +284 -0
- package/template/frontend/components/ChatMessage.css +206 -0
- package/template/frontend/components/ChatMessage.tsx +62 -0
- package/template/frontend/components/ModelStatus.css +62 -0
- package/template/frontend/components/ModelStatus.tsx +103 -0
- package/template/frontend/hooks/useApi.ts +334 -0
- package/template/frontend/index.css +92 -0
- package/template/frontend/main.tsx +10 -0
- package/template/frontend/vite-env.d.ts +1 -0
- package/template/index.html.template +13 -0
- package/template/package.json.template +33 -0
- package/template/postcss.config.js.template +6 -0
- package/template/public/tether.svg +15 -0
- package/template/src-tauri/.cargo/config.toml +66 -0
- package/template/src-tauri/Cargo.lock +4764 -0
- package/template/src-tauri/Cargo.toml +24 -0
- package/template/src-tauri/build.rs +3 -0
- package/template/src-tauri/capabilities/default.json +40 -0
- package/template/src-tauri/icons/128x128.png +0 -0
- package/template/src-tauri/icons/128x128@2x.png +0 -0
- package/template/src-tauri/icons/32x32.png +0 -0
- package/template/src-tauri/icons/icon.icns +0 -0
- package/template/src-tauri/icons/icon.ico +0 -0
- package/template/src-tauri/src/main.rs +65 -0
- package/template/src-tauri/src/sidecar.rs +110 -0
- package/template/src-tauri/tauri.conf.json.template +44 -0
- package/template/tailwind.config.js.template +19 -0
- package/template/tsconfig.json +21 -0
- package/template/tsconfig.node.json +11 -0
- package/template/vite.config.ts +27 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "tether-app"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A Tether App"
|
|
5
|
+
authors = ["you"]
|
|
6
|
+
edition = "2021"
|
|
7
|
+
|
|
8
|
+
[build-dependencies]
|
|
9
|
+
tauri-build = { version = "2", features = [] }
|
|
10
|
+
|
|
11
|
+
[dependencies]
|
|
12
|
+
tauri = { version = "2", features = [] }
|
|
13
|
+
tauri-plugin-shell = "2"
|
|
14
|
+
serde = { version = "1", features = ["derive"] }
|
|
15
|
+
serde_json = "1"
|
|
16
|
+
tokio = { version = "1", features = ["sync"] }
|
|
17
|
+
portpicker = "0.1"
|
|
18
|
+
|
|
19
|
+
[features]
|
|
20
|
+
default = ["custom-protocol"]
|
|
21
|
+
custom-protocol = ["tauri/custom-protocol"]
|
|
22
|
+
# Enable devtools in development (adds ~20% to compile time)
|
|
23
|
+
# Use: cargo build --features devtools
|
|
24
|
+
devtools = ["tauri/devtools"]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://schemas.tauri.app/capability/schema.json",
|
|
3
|
+
"identifier": "default",
|
|
4
|
+
"description": "Default capabilities for the app",
|
|
5
|
+
"windows": ["main"],
|
|
6
|
+
"permissions": [
|
|
7
|
+
"core:default",
|
|
8
|
+
{
|
|
9
|
+
"identifier": "shell:allow-spawn",
|
|
10
|
+
"allow": [
|
|
11
|
+
{
|
|
12
|
+
"name": "binaries/api",
|
|
13
|
+
"sidecar": true,
|
|
14
|
+
"args": [
|
|
15
|
+
"--port",
|
|
16
|
+
{
|
|
17
|
+
"validator": "\\d+"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"identifier": "shell:allow-execute",
|
|
25
|
+
"allow": [
|
|
26
|
+
{
|
|
27
|
+
"name": "binaries/api",
|
|
28
|
+
"sidecar": true,
|
|
29
|
+
"args": [
|
|
30
|
+
"--port",
|
|
31
|
+
{
|
|
32
|
+
"validator": "\\d+"
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"shell:allow-kill"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Prevents additional console window on Windows in release
|
|
2
|
+
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
|
3
|
+
|
|
4
|
+
mod sidecar;
|
|
5
|
+
|
|
6
|
+
use sidecar::SidecarManager;
|
|
7
|
+
use std::sync::Arc;
|
|
8
|
+
use tauri::Manager;
|
|
9
|
+
use tokio::sync::Mutex;
|
|
10
|
+
|
|
11
|
+
#[tauri::command]
|
|
12
|
+
async fn get_api_port(state: tauri::State<'_, Arc<Mutex<SidecarManager>>>) -> Result<u16, String> {
|
|
13
|
+
let manager = state.lock().await;
|
|
14
|
+
Ok(manager.port())
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#[tauri::command]
|
|
18
|
+
async fn restart_backend(
|
|
19
|
+
state: tauri::State<'_, Arc<Mutex<SidecarManager>>>,
|
|
20
|
+
) -> Result<String, String> {
|
|
21
|
+
let mut manager = state.lock().await;
|
|
22
|
+
manager.restart().await
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fn main() {
|
|
26
|
+
tauri::Builder::default()
|
|
27
|
+
.plugin(tauri_plugin_shell::init())
|
|
28
|
+
.setup(|app| {
|
|
29
|
+
let app_handle = app.handle().clone();
|
|
30
|
+
|
|
31
|
+
// Find an available port
|
|
32
|
+
let port = portpicker::pick_unused_port().expect("No available port");
|
|
33
|
+
|
|
34
|
+
// Create sidecar manager
|
|
35
|
+
let manager = Arc::new(Mutex::new(SidecarManager::new(port)));
|
|
36
|
+
|
|
37
|
+
// Store in app state
|
|
38
|
+
app.manage(manager.clone());
|
|
39
|
+
|
|
40
|
+
// Start the sidecar
|
|
41
|
+
tauri::async_runtime::spawn(async move {
|
|
42
|
+
let mut manager = manager.lock().await;
|
|
43
|
+
if let Err(e) = manager.start(&app_handle).await {
|
|
44
|
+
eprintln!("Failed to start API server: {}", e);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
Ok(())
|
|
49
|
+
})
|
|
50
|
+
.on_window_event(|window, event| {
|
|
51
|
+
if let tauri::WindowEvent::CloseRequested { .. } = event {
|
|
52
|
+
let app_handle = window.app_handle().clone();
|
|
53
|
+
tauri::async_runtime::block_on(async {
|
|
54
|
+
let state = app_handle.state::<Arc<Mutex<SidecarManager>>>();
|
|
55
|
+
let mut manager = state.lock().await;
|
|
56
|
+
if let Err(e) = manager.stop() {
|
|
57
|
+
eprintln!("Error stopping API server: {}", e);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
.invoke_handler(tauri::generate_handler![get_api_port, restart_backend])
|
|
63
|
+
.run(tauri::generate_context!())
|
|
64
|
+
.expect("error while running tauri application");
|
|
65
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
//! Python sidecar process management.
|
|
2
|
+
|
|
3
|
+
use std::process::Command as StdCommand;
|
|
4
|
+
use tauri::AppHandle;
|
|
5
|
+
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
|
|
6
|
+
use tauri_plugin_shell::ShellExt;
|
|
7
|
+
|
|
8
|
+
/// Manages the Python sidecar process.
|
|
9
|
+
pub struct SidecarManager {
|
|
10
|
+
child: Option<CommandChild>,
|
|
11
|
+
port: u16,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
impl SidecarManager {
|
|
15
|
+
/// Create a new sidecar manager with the specified port.
|
|
16
|
+
pub fn new(port: u16) -> Self {
|
|
17
|
+
Self { child: None, port }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// Get the port the sidecar is running on.
|
|
21
|
+
pub fn port(&self) -> u16 {
|
|
22
|
+
self.port
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// Start the sidecar process.
|
|
26
|
+
pub async fn start(&mut self, app: &AppHandle) -> Result<String, String> {
|
|
27
|
+
if self.child.is_some() {
|
|
28
|
+
return Ok("API server is already running".into());
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
println!("Starting API server on port {}...", self.port);
|
|
32
|
+
|
|
33
|
+
let shell = app.shell();
|
|
34
|
+
let (mut rx, child) = shell
|
|
35
|
+
.sidecar("api")
|
|
36
|
+
.map_err(|e| format!("Failed to create sidecar command: {}", e))?
|
|
37
|
+
.args(["--port", &self.port.to_string()])
|
|
38
|
+
.spawn()
|
|
39
|
+
.map_err(|e| format!("Failed to spawn API server: {}", e))?;
|
|
40
|
+
|
|
41
|
+
// Spawn a task to handle sidecar output
|
|
42
|
+
tauri::async_runtime::spawn(async move {
|
|
43
|
+
while let Some(event) = rx.recv().await {
|
|
44
|
+
match event {
|
|
45
|
+
CommandEvent::Stdout(line) => println!("API: {}", String::from_utf8_lossy(&line)),
|
|
46
|
+
CommandEvent::Stderr(line) => {
|
|
47
|
+
eprintln!("API Error: {}", String::from_utf8_lossy(&line))
|
|
48
|
+
}
|
|
49
|
+
CommandEvent::Error(error) => eprintln!("API Process Error: {}", error),
|
|
50
|
+
CommandEvent::Terminated(status) => {
|
|
51
|
+
println!("API Process Terminated with status: {:?}", status)
|
|
52
|
+
}
|
|
53
|
+
_ => {}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
self.child = Some(child);
|
|
59
|
+
println!("API server started successfully on port {}", self.port);
|
|
60
|
+
Ok(format!("API server started on port {}", self.port))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Stop the sidecar process.
|
|
64
|
+
pub fn stop(&mut self) -> Result<String, String> {
|
|
65
|
+
if let Some(child) = self.child.take() {
|
|
66
|
+
println!("Stopping API server...");
|
|
67
|
+
|
|
68
|
+
let pid = child.pid();
|
|
69
|
+
|
|
70
|
+
// Kill child processes first
|
|
71
|
+
#[cfg(unix)]
|
|
72
|
+
{
|
|
73
|
+
let _ = StdCommand::new("pkill")
|
|
74
|
+
.args(["-P", &pid.to_string()])
|
|
75
|
+
.output();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#[cfg(windows)]
|
|
79
|
+
{
|
|
80
|
+
let _ = StdCommand::new("taskkill")
|
|
81
|
+
.args(["/F", "/T", "/PID", &pid.to_string()])
|
|
82
|
+
.output();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Kill the main process
|
|
86
|
+
child
|
|
87
|
+
.kill()
|
|
88
|
+
.map_err(|e| format!("Failed to stop API server: {}", e))?;
|
|
89
|
+
|
|
90
|
+
println!("API server stopped");
|
|
91
|
+
Ok("API server stopped".into())
|
|
92
|
+
} else {
|
|
93
|
+
Ok("API server is not running".into())
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Restart the sidecar process.
|
|
98
|
+
pub async fn restart(&mut self) -> Result<String, String> {
|
|
99
|
+
// We can't restart without an app handle, so this is a placeholder
|
|
100
|
+
// In practice, you'd store the app handle or use a different approach
|
|
101
|
+
self.stop()?;
|
|
102
|
+
Err("Restart requires app handle - use Tauri commands".into())
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
impl Drop for SidecarManager {
|
|
107
|
+
fn drop(&mut self) {
|
|
108
|
+
let _ = self.stop();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://schema.tauri.app/config/2",
|
|
3
|
+
"productName": "{{PROJECT_NAME}}",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"identifier": "com.tether.{{PROJECT_NAME}}",
|
|
6
|
+
"build": {
|
|
7
|
+
"beforeDevCommand": "pnpm dev",
|
|
8
|
+
"devUrl": "http://localhost:1420",
|
|
9
|
+
"beforeBuildCommand": "pnpm build:all",
|
|
10
|
+
"frontendDist": "../dist"
|
|
11
|
+
},
|
|
12
|
+
"app": {
|
|
13
|
+
"windows": [
|
|
14
|
+
{
|
|
15
|
+
"title": "{{PROJECT_NAME}}",
|
|
16
|
+
"width": 1024,
|
|
17
|
+
"height": 768,
|
|
18
|
+
"resizable": true,
|
|
19
|
+
"fullscreen": false,
|
|
20
|
+
"dragDropEnabled": false
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"security": {
|
|
24
|
+
"csp": null
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"bundle": {
|
|
28
|
+
"active": true,
|
|
29
|
+
"targets": "all",
|
|
30
|
+
"icon": [
|
|
31
|
+
"icons/32x32.png",
|
|
32
|
+
"icons/128x128.png",
|
|
33
|
+
"icons/128x128@2x.png",
|
|
34
|
+
"icons/icon.icns",
|
|
35
|
+
"icons/icon.ico"
|
|
36
|
+
],
|
|
37
|
+
"externalBin": ["binaries/api"]
|
|
38
|
+
},
|
|
39
|
+
"plugins": {
|
|
40
|
+
"shell": {
|
|
41
|
+
"open": true
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** @type {import('tailwindcss').Config} */
|
|
2
|
+
export default {
|
|
3
|
+
content: [
|
|
4
|
+
"./index.html",
|
|
5
|
+
"./frontend/**/*.{js,ts,jsx,tsx}",
|
|
6
|
+
],
|
|
7
|
+
theme: {
|
|
8
|
+
extend: {
|
|
9
|
+
colors: {
|
|
10
|
+
surface: 'var(--color-surface)',
|
|
11
|
+
'surface-hover': 'var(--color-surface-hover)',
|
|
12
|
+
border: 'var(--color-border)',
|
|
13
|
+
primary: 'var(--color-primary)',
|
|
14
|
+
'primary-hover': 'var(--color-primary-hover)',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
plugins: [],
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"jsx": "react-jsx",
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true
|
|
18
|
+
},
|
|
19
|
+
"include": ["frontend"],
|
|
20
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
21
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import react from "@vitejs/plugin-react";
|
|
3
|
+
|
|
4
|
+
// https://vitejs.dev/config/
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [react()],
|
|
7
|
+
// Prevent vite from obscuring rust errors
|
|
8
|
+
clearScreen: false,
|
|
9
|
+
server: {
|
|
10
|
+
port: 1420,
|
|
11
|
+
strictPort: true,
|
|
12
|
+
watch: {
|
|
13
|
+
// Ignore the src-tauri and backend directories
|
|
14
|
+
ignored: ["**/src-tauri/**", "**/backend/**"],
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
// Env variables starting with TAURI_ are exposed to the frontend
|
|
18
|
+
envPrefix: ["VITE_", "TAURI_"],
|
|
19
|
+
build: {
|
|
20
|
+
// Tauri uses Chromium on Windows and WebKit on macOS and Linux
|
|
21
|
+
target: process.env.TAURI_PLATFORM === "windows" ? "chrome105" : "safari13",
|
|
22
|
+
// Don't minify for debug builds
|
|
23
|
+
minify: !process.env.TAURI_DEBUG ? "esbuild" : false,
|
|
24
|
+
// Produce sourcemaps for debug builds
|
|
25
|
+
sourcemap: !!process.env.TAURI_DEBUG,
|
|
26
|
+
},
|
|
27
|
+
});
|