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.
Files changed (48) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +729 -0
  3. package/package.json +59 -0
  4. package/template/.env.example +18 -0
  5. package/template/README.md.template +123 -0
  6. package/template/backend/app/__init__.py.template +5 -0
  7. package/template/backend/app/main.py +66 -0
  8. package/template/backend/app/routes/__init__.py +3 -0
  9. package/template/backend/app/routes/chat.py +151 -0
  10. package/template/backend/app/routes/health.py +28 -0
  11. package/template/backend/app/routes/models.py +126 -0
  12. package/template/backend/app/services/__init__.py +3 -0
  13. package/template/backend/app/services/llm.py +526 -0
  14. package/template/backend/pyproject.toml.template +34 -0
  15. package/template/backend/scripts/build.py +112 -0
  16. package/template/frontend/App.css +58 -0
  17. package/template/frontend/App.tsx +62 -0
  18. package/template/frontend/components/Chat.css +220 -0
  19. package/template/frontend/components/Chat.tsx +284 -0
  20. package/template/frontend/components/ChatMessage.css +206 -0
  21. package/template/frontend/components/ChatMessage.tsx +62 -0
  22. package/template/frontend/components/ModelStatus.css +62 -0
  23. package/template/frontend/components/ModelStatus.tsx +103 -0
  24. package/template/frontend/hooks/useApi.ts +334 -0
  25. package/template/frontend/index.css +92 -0
  26. package/template/frontend/main.tsx +10 -0
  27. package/template/frontend/vite-env.d.ts +1 -0
  28. package/template/index.html.template +13 -0
  29. package/template/package.json.template +33 -0
  30. package/template/postcss.config.js.template +6 -0
  31. package/template/public/tether.svg +15 -0
  32. package/template/src-tauri/.cargo/config.toml +66 -0
  33. package/template/src-tauri/Cargo.lock +4764 -0
  34. package/template/src-tauri/Cargo.toml +24 -0
  35. package/template/src-tauri/build.rs +3 -0
  36. package/template/src-tauri/capabilities/default.json +40 -0
  37. package/template/src-tauri/icons/128x128.png +0 -0
  38. package/template/src-tauri/icons/128x128@2x.png +0 -0
  39. package/template/src-tauri/icons/32x32.png +0 -0
  40. package/template/src-tauri/icons/icon.icns +0 -0
  41. package/template/src-tauri/icons/icon.ico +0 -0
  42. package/template/src-tauri/src/main.rs +65 -0
  43. package/template/src-tauri/src/sidecar.rs +110 -0
  44. package/template/src-tauri/tauri.conf.json.template +44 -0
  45. package/template/tailwind.config.js.template +19 -0
  46. package/template/tsconfig.json +21 -0
  47. package/template/tsconfig.node.json +11 -0
  48. 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,3 @@
1
+ fn main() {
2
+ tauri_build::build()
3
+ }
@@ -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
+ }
@@ -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,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "allowSyntheticDefaultImports": true,
8
+ "strict": true
9
+ },
10
+ "include": ["vite.config.ts"]
11
+ }
@@ -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
+ });