wysebee 0.2.0__tar.gz
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.
- wysebee-0.2.0/PKG-INFO +185 -0
- wysebee-0.2.0/README.md +166 -0
- wysebee-0.2.0/pyproject.toml +38 -0
- wysebee-0.2.0/src/wysebee/__init__.py +9 -0
- wysebee-0.2.0/src/wysebee/cli/_uv.py +31 -0
- wysebee-0.2.0/src/wysebee/cli/_vite.py +74 -0
- wysebee-0.2.0/src/wysebee/cli/desktop.py +188 -0
- wysebee-0.2.0/src/wysebee/cli/main.py +178 -0
- wysebee-0.2.0/src/wysebee/cli/server.py +147 -0
- wysebee-0.2.0/src/wysebee/cli/templates.py +180 -0
- wysebee-0.2.0/src/wysebee/desktop/filesystem.py +81 -0
- wysebee-0.2.0/src/wysebee/desktop/resource_loader.py +84 -0
- wysebee-0.2.0/src/wysebee/desktop/singleton.py +9 -0
- wysebee-0.2.0/src/wysebee/desktop/temp_helper.py +43 -0
- wysebee-0.2.0/src/wysebee/desktop/wysebee.py +129 -0
- wysebee-0.2.0/src/wysebee/desktop/wysebee_app_menu.py +58 -0
- wysebee-0.2.0/src/wysebee/desktop/wysebee_backend.py +32 -0
- wysebee-0.2.0/src/wysebee/desktop/wysebee_webengine_page.py +11 -0
- wysebee-0.2.0/src/wysebee/desktop/wysebee_webpopup.py +24 -0
- wysebee-0.2.0/src/wysebee/desktop/wysebee_webview.py +22 -0
wysebee-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wysebee
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A simple cross-platform framework for your AI project.
|
|
5
|
+
Author-email: Jeff Xu <zxu@wysebee.com>
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Dist: pyside6>=6.9.0
|
|
12
|
+
Requires-Dist: Jinja2>=3.1.2
|
|
13
|
+
Requires-Dist: typer>=0.15.2
|
|
14
|
+
Requires-Dist: colorama>=0.4.6
|
|
15
|
+
Requires-Dist: watchdog==6.0.0
|
|
16
|
+
Project-URL: Homepage, https://github.com/wysebee/wysebee
|
|
17
|
+
Project-URL: Issues, https://github.com/wysebee/wysebee/issues
|
|
18
|
+
|
|
19
|
+
# Wysebee
|
|
20
|
+
|
|
21
|
+
**Wysebee** is a cross-platform application framework for building modern apps with web-technology UIs and Python backends. Write your frontend in HTML/CSS/JS (React, Vanilla, or TypeScript via Vite) and your backend in Python — ship it as a native desktop app powered by **PySide (Qt)**, or as a **FastAPI** web service, from a single project layout.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- **One project, two targets.** Generate a desktop (PySide/Qt) or web (FastAPI) app with the same `wyse init` command.
|
|
28
|
+
- **Vite-powered frontends.** First-class support for `react`, `react-ts`, `vanilla`, and `vanilla-ts` templates.
|
|
29
|
+
- **Python ↔ JS bridge.** Desktop apps come wired with a `QWebChannel` bridge so your frontend can call Python methods and receive signals.
|
|
30
|
+
- **Native app menus.** Declare your menu bar in `menu/menu.json`; Wysebee wires each item to a backend method for you.
|
|
31
|
+
- **Drop-in backend.** Subclass `WysebeeBackend` and add `@Slot()` methods — they're instantly callable from JavaScript.
|
|
32
|
+
- **Hot reload in dev.** `wyse dev` watches your UI source and rebuilds/reloads the browser view automatically.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install wysebee
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This installs the `wyse` CLI alongside the `wysebee` Python package.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Quick start
|
|
47
|
+
|
|
48
|
+
### 1. Create a new app
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
wyse init my-app
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
You'll be prompted to choose `desktop` or `web`. To select a Vite template other than the default React:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
wyse init my-app --template vanilla-ts
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Supported templates: `react` (default), `react-ts`, `vanilla`, `vanilla-ts`.
|
|
61
|
+
|
|
62
|
+
### 2. Run it
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
cd my-app
|
|
66
|
+
python main.py
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
For live reload during development:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
wyse dev
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 3. Build the UI
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
wyse build --ui
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Generated project layout
|
|
84
|
+
|
|
85
|
+
### Desktop app
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
my-app/
|
|
89
|
+
├── main.py # Entry point (QApplication + Wysebee + WysebeeAppMenu)
|
|
90
|
+
├── menu/
|
|
91
|
+
│ └── menu.json # Declarative menu bar wired to backend methods
|
|
92
|
+
├── src/
|
|
93
|
+
│ ├── __init__.py
|
|
94
|
+
│ └── backend.py # MyBackend(WysebeeBackend) — customize Python-side logic
|
|
95
|
+
├── ui/ # Vite project (your frontend)
|
|
96
|
+
│ ├── index.html
|
|
97
|
+
│ ├── src/
|
|
98
|
+
│ └── templates/ # Built output loaded by the desktop window
|
|
99
|
+
└── requirements.txt
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Web app
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
my-app/
|
|
106
|
+
├── main.py # FastAPI app serving the built UI
|
|
107
|
+
├── ui/ # Vite project
|
|
108
|
+
│ └── templates/ # Built output mounted as static files
|
|
109
|
+
└── requirements.txt
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Wiring a native menu (desktop)
|
|
115
|
+
|
|
116
|
+
`menu/menu.json` maps menu items to methods on your backend by name:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
[
|
|
120
|
+
{
|
|
121
|
+
"label": "File",
|
|
122
|
+
"submenu": [
|
|
123
|
+
{ "label": "Open", "action": "on_open" },
|
|
124
|
+
{ "label": "Save", "action": "on_save" },
|
|
125
|
+
{ "separator": true },
|
|
126
|
+
{ "label": "Exit", "action": "on_exit" }
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Implement those methods on your backend:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from PySide6.QtWidgets import QApplication, QFileDialog
|
|
136
|
+
from PySide6.QtCore import Slot
|
|
137
|
+
from wysebee import WysebeeBackend
|
|
138
|
+
|
|
139
|
+
class MyBackend(WysebeeBackend):
|
|
140
|
+
@Slot()
|
|
141
|
+
def on_open(self):
|
|
142
|
+
file_path, _ = QFileDialog.getOpenFileName(None, "Open File")
|
|
143
|
+
# ...
|
|
144
|
+
|
|
145
|
+
@Slot()
|
|
146
|
+
def on_exit(self):
|
|
147
|
+
QApplication.quit()
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Calling Python from JavaScript (desktop)
|
|
153
|
+
|
|
154
|
+
Wysebee exposes your backend to the frontend through `QWebChannel`. The generated `index.html` initializes it and attaches the backend to `window.wysebee`:
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
window.wysebee.sendMessage("Hello from the frontend!");
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Any `@Slot(str, result=str)` method on your backend is callable this way.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## CLI reference
|
|
165
|
+
|
|
166
|
+
| Command | Description |
|
|
167
|
+
| --- | --- |
|
|
168
|
+
| `wyse init <name> [--template ...]` | Scaffold a new desktop or web app. |
|
|
169
|
+
| `wyse dev` | Build the UI and run the app in development mode (hot reload). |
|
|
170
|
+
| `wyse build --ui` | Build only the frontend (`npm run build` inside `ui/`). |
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Links
|
|
175
|
+
|
|
176
|
+
- Homepage: [wysebee.io](https://wysebee.io)
|
|
177
|
+
- Source: [github.com/wysebee/wysebee-io](https://github.com/wysebee/wysebee-io)
|
|
178
|
+
- Issues: [github.com/wysebee/wysebee-io/issues](https://github.com/wysebee/wysebee-io/issues)
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## License
|
|
183
|
+
|
|
184
|
+
MIT
|
|
185
|
+
|
wysebee-0.2.0/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Wysebee
|
|
2
|
+
|
|
3
|
+
**Wysebee** is a cross-platform application framework for building modern apps with web-technology UIs and Python backends. Write your frontend in HTML/CSS/JS (React, Vanilla, or TypeScript via Vite) and your backend in Python — ship it as a native desktop app powered by **PySide (Qt)**, or as a **FastAPI** web service, from a single project layout.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **One project, two targets.** Generate a desktop (PySide/Qt) or web (FastAPI) app with the same `wyse init` command.
|
|
10
|
+
- **Vite-powered frontends.** First-class support for `react`, `react-ts`, `vanilla`, and `vanilla-ts` templates.
|
|
11
|
+
- **Python ↔ JS bridge.** Desktop apps come wired with a `QWebChannel` bridge so your frontend can call Python methods and receive signals.
|
|
12
|
+
- **Native app menus.** Declare your menu bar in `menu/menu.json`; Wysebee wires each item to a backend method for you.
|
|
13
|
+
- **Drop-in backend.** Subclass `WysebeeBackend` and add `@Slot()` methods — they're instantly callable from JavaScript.
|
|
14
|
+
- **Hot reload in dev.** `wyse dev` watches your UI source and rebuilds/reloads the browser view automatically.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install wysebee
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
This installs the `wyse` CLI alongside the `wysebee` Python package.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
### 1. Create a new app
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
wyse init my-app
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
You'll be prompted to choose `desktop` or `web`. To select a Vite template other than the default React:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
wyse init my-app --template vanilla-ts
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Supported templates: `react` (default), `react-ts`, `vanilla`, `vanilla-ts`.
|
|
43
|
+
|
|
44
|
+
### 2. Run it
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
cd my-app
|
|
48
|
+
python main.py
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
For live reload during development:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
wyse dev
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 3. Build the UI
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
wyse build --ui
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Generated project layout
|
|
66
|
+
|
|
67
|
+
### Desktop app
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
my-app/
|
|
71
|
+
├── main.py # Entry point (QApplication + Wysebee + WysebeeAppMenu)
|
|
72
|
+
├── menu/
|
|
73
|
+
│ └── menu.json # Declarative menu bar wired to backend methods
|
|
74
|
+
├── src/
|
|
75
|
+
│ ├── __init__.py
|
|
76
|
+
│ └── backend.py # MyBackend(WysebeeBackend) — customize Python-side logic
|
|
77
|
+
├── ui/ # Vite project (your frontend)
|
|
78
|
+
│ ├── index.html
|
|
79
|
+
│ ├── src/
|
|
80
|
+
│ └── templates/ # Built output loaded by the desktop window
|
|
81
|
+
└── requirements.txt
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Web app
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
my-app/
|
|
88
|
+
├── main.py # FastAPI app serving the built UI
|
|
89
|
+
├── ui/ # Vite project
|
|
90
|
+
│ └── templates/ # Built output mounted as static files
|
|
91
|
+
└── requirements.txt
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Wiring a native menu (desktop)
|
|
97
|
+
|
|
98
|
+
`menu/menu.json` maps menu items to methods on your backend by name:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
[
|
|
102
|
+
{
|
|
103
|
+
"label": "File",
|
|
104
|
+
"submenu": [
|
|
105
|
+
{ "label": "Open", "action": "on_open" },
|
|
106
|
+
{ "label": "Save", "action": "on_save" },
|
|
107
|
+
{ "separator": true },
|
|
108
|
+
{ "label": "Exit", "action": "on_exit" }
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Implement those methods on your backend:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from PySide6.QtWidgets import QApplication, QFileDialog
|
|
118
|
+
from PySide6.QtCore import Slot
|
|
119
|
+
from wysebee import WysebeeBackend
|
|
120
|
+
|
|
121
|
+
class MyBackend(WysebeeBackend):
|
|
122
|
+
@Slot()
|
|
123
|
+
def on_open(self):
|
|
124
|
+
file_path, _ = QFileDialog.getOpenFileName(None, "Open File")
|
|
125
|
+
# ...
|
|
126
|
+
|
|
127
|
+
@Slot()
|
|
128
|
+
def on_exit(self):
|
|
129
|
+
QApplication.quit()
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Calling Python from JavaScript (desktop)
|
|
135
|
+
|
|
136
|
+
Wysebee exposes your backend to the frontend through `QWebChannel`. The generated `index.html` initializes it and attaches the backend to `window.wysebee`:
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
window.wysebee.sendMessage("Hello from the frontend!");
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Any `@Slot(str, result=str)` method on your backend is callable this way.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## CLI reference
|
|
147
|
+
|
|
148
|
+
| Command | Description |
|
|
149
|
+
| --- | --- |
|
|
150
|
+
| `wyse init <name> [--template ...]` | Scaffold a new desktop or web app. |
|
|
151
|
+
| `wyse dev` | Build the UI and run the app in development mode (hot reload). |
|
|
152
|
+
| `wyse build --ui` | Build only the frontend (`npm run build` inside `ui/`). |
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Links
|
|
157
|
+
|
|
158
|
+
- Homepage: [wysebee.io](https://wysebee.io)
|
|
159
|
+
- Source: [github.com/wysebee/wysebee-io](https://github.com/wysebee/wysebee-io)
|
|
160
|
+
- Issues: [github.com/wysebee/wysebee-io/issues](https://github.com/wysebee/wysebee-io/issues)
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
MIT
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "wysebee"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
authors = [
|
|
5
|
+
{ name="Jeff Xu", email="zxu@wysebee.com" },
|
|
6
|
+
]
|
|
7
|
+
description = "A simple cross-platform framework for your AI project."
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.9"
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"Operating System :: OS Independent",
|
|
13
|
+
]
|
|
14
|
+
license = "MIT"
|
|
15
|
+
|
|
16
|
+
dependencies = [
|
|
17
|
+
"pyside6>=6.9.0",
|
|
18
|
+
"Jinja2>=3.1.2",
|
|
19
|
+
"typer>=0.15.2",
|
|
20
|
+
"colorama>=0.4.6",
|
|
21
|
+
"watchdog==6.0.0"
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/wysebee/wysebee"
|
|
26
|
+
Issues = "https://github.com/wysebee/wysebee/issues"
|
|
27
|
+
|
|
28
|
+
[build-system]
|
|
29
|
+
requires = ["flit_core<4"]
|
|
30
|
+
build-backend = "flit_core.buildapi"
|
|
31
|
+
|
|
32
|
+
[tool.flit.module]
|
|
33
|
+
name = "wysebee"
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
wyse = "wysebee.cli.main:app"
|
|
37
|
+
wyse-web = "wysebee.cli.main:app_web"
|
|
38
|
+
wyse-desktop = "wysebee.cli.main:app_desktop"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from .desktop.wysebee import Wysebee
|
|
2
|
+
from .desktop.wysebee_webview import WysebeeWebView
|
|
3
|
+
from .desktop.wysebee_webengine_page import WysebeeWebEnginePage
|
|
4
|
+
from .desktop.wysebee_backend import WysebeeBackend
|
|
5
|
+
from .desktop.singleton import singleton
|
|
6
|
+
from .desktop.temp_helper import TempFileHelper
|
|
7
|
+
from .desktop.wysebee_webpopup import WysebeeWebPopup
|
|
8
|
+
from .desktop.wysebee_app_menu import WysebeeAppMenu
|
|
9
|
+
from .desktop.filesystem import *
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
import subprocess
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from colorama import Fore
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def ensure_uv_available() -> bool:
|
|
9
|
+
"""Return True if `uv` is on PATH, otherwise print an error and return False."""
|
|
10
|
+
if shutil.which("uv") is None:
|
|
11
|
+
print(
|
|
12
|
+
Fore.RED
|
|
13
|
+
+ "uv is not installed or not on PATH. Install it from https://docs.astral.sh/uv/ and re-run."
|
|
14
|
+
)
|
|
15
|
+
return False
|
|
16
|
+
return True
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def bootstrap_uv_project(project_dir: Path) -> bool:
|
|
20
|
+
"""Run `uv venv` and `uv sync` inside the given project directory."""
|
|
21
|
+
if not ensure_uv_available():
|
|
22
|
+
return False
|
|
23
|
+
try:
|
|
24
|
+
print(Fore.YELLOW + f"Creating virtualenv in {project_dir} with uv...")
|
|
25
|
+
subprocess.run(["uv", "venv"], cwd=project_dir, check=True)
|
|
26
|
+
print(Fore.YELLOW + f"Installing dependencies in {project_dir} with uv...")
|
|
27
|
+
subprocess.run(["uv", "sync"], cwd=project_dir, check=True)
|
|
28
|
+
return True
|
|
29
|
+
except subprocess.CalledProcessError as e:
|
|
30
|
+
print(Fore.RED + f"uv bootstrap failed: {e}")
|
|
31
|
+
return False
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Shared Vite setup helpers: fresh vite.config.js + multi-page entries."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from colorama import Fore
|
|
7
|
+
|
|
8
|
+
from .templates import (
|
|
9
|
+
render_page_html,
|
|
10
|
+
render_react_entry,
|
|
11
|
+
render_vanilla_settings_entry,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _vite_config(template: str, platform: str) -> str:
|
|
16
|
+
"""Return a fresh vite.config.js body for the given template + platform.
|
|
17
|
+
|
|
18
|
+
- Desktop uses base './' so the PySide WebView can load assets via file:// URLs.
|
|
19
|
+
- Web uses base '/' so both `/` and `/settings` can share absolute asset paths.
|
|
20
|
+
"""
|
|
21
|
+
base = "./" if platform == "desktop" else "/"
|
|
22
|
+
is_react = template in ("react", "react-ts")
|
|
23
|
+
|
|
24
|
+
plugin_import = "import react from '@vitejs/plugin-react'\n" if is_react else ""
|
|
25
|
+
plugins_line = " plugins: [react()],\n" if is_react else ""
|
|
26
|
+
|
|
27
|
+
return f"""import {{ defineConfig }} from 'vite'
|
|
28
|
+
{plugin_import}
|
|
29
|
+
// https://vitejs.dev/config/
|
|
30
|
+
export default defineConfig({{
|
|
31
|
+
{plugins_line} base: '{base}',
|
|
32
|
+
build: {{
|
|
33
|
+
outDir: 'templates',
|
|
34
|
+
emptyOutDir: true,
|
|
35
|
+
rollupOptions: {{
|
|
36
|
+
input: {{
|
|
37
|
+
main: 'index.html',
|
|
38
|
+
settings: 'settings.html',
|
|
39
|
+
}},
|
|
40
|
+
}},
|
|
41
|
+
}},
|
|
42
|
+
}})
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def setup_vite_pages(template: str, platform: str) -> None:
|
|
47
|
+
"""Write vite.config.js, index.html, settings.html, and entry modules.
|
|
48
|
+
|
|
49
|
+
Assumes the current working directory is the `ui/` folder containing the
|
|
50
|
+
just-initialized Vite project.
|
|
51
|
+
"""
|
|
52
|
+
Path("vite.config.js").write_text(_vite_config(template, platform))
|
|
53
|
+
print(Fore.YELLOW + f"Wrote vite.config.js (multi-page, base={'./'if platform == 'desktop' else '/'})")
|
|
54
|
+
|
|
55
|
+
Path("index.html").write_text(render_page_html(template, platform, "index"))
|
|
56
|
+
Path("settings.html").write_text(render_page_html(template, platform, "settings"))
|
|
57
|
+
print(Fore.YELLOW + f"Wrote index.html and settings.html for {platform} ({template})")
|
|
58
|
+
|
|
59
|
+
if template in ("react", "react-ts"):
|
|
60
|
+
ext = "tsx" if template == "react-ts" else "jsx"
|
|
61
|
+
Path(f"src/main.{ext}").write_text(render_react_entry(template, "index"))
|
|
62
|
+
Path(f"src/settings.{ext}").write_text(render_react_entry(template, "settings"))
|
|
63
|
+
print(Fore.YELLOW + f"Wrote src/main.{ext} and src/settings.{ext} with HashRouter")
|
|
64
|
+
elif template in ("vanilla", "vanilla-ts"):
|
|
65
|
+
ext = "ts" if template == "vanilla-ts" else "js"
|
|
66
|
+
Path(f"src/settings.{ext}").write_text(render_vanilla_settings_entry(template))
|
|
67
|
+
print(Fore.YELLOW + f"Wrote src/settings.{ext}")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def install_router_if_react(template: str) -> None:
|
|
71
|
+
"""Install react-router-dom if the template is a react template."""
|
|
72
|
+
if template in ("react", "react-ts"):
|
|
73
|
+
print(Fore.YELLOW + "Installing react-router-dom...")
|
|
74
|
+
subprocess.run(["npm", "install", "react-router-dom"], check=True)
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from colorama import Fore
|
|
8
|
+
|
|
9
|
+
from ._uv import bootstrap_uv_project
|
|
10
|
+
from ._vite import install_router_if_react, setup_vite_pages
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _generate_ui_template(name: str, ui_dir: str, template: str):
|
|
14
|
+
# Change to UI directory and initialize vite
|
|
15
|
+
try:
|
|
16
|
+
print(Fore.YELLOW + f"Setting up Vite in UI folder...")
|
|
17
|
+
original_dir = os.getcwd()
|
|
18
|
+
os.chdir(ui_dir)
|
|
19
|
+
if template:
|
|
20
|
+
subprocess.run(
|
|
21
|
+
["npm", "create", "vite@latest", ".", "--", "--template", template],
|
|
22
|
+
input="n\n",
|
|
23
|
+
text=True,
|
|
24
|
+
check=True,
|
|
25
|
+
)
|
|
26
|
+
else:
|
|
27
|
+
subprocess.run(
|
|
28
|
+
["npm", "create", "vite@latest", "."],
|
|
29
|
+
input="n\n",
|
|
30
|
+
text=True,
|
|
31
|
+
check=True,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
subprocess.run(["npm", "install"], check=True)
|
|
35
|
+
install_router_if_react(template)
|
|
36
|
+
|
|
37
|
+
# Write fresh vite.config.js + index/settings pages + entry modules
|
|
38
|
+
setup_vite_pages(template, platform="desktop")
|
|
39
|
+
|
|
40
|
+
subprocess.run(["npm", "run", "build"], check=True)
|
|
41
|
+
os.chdir(original_dir)
|
|
42
|
+
print(Fore.YELLOW + f"Successfully set up Vite in {name}/ui!")
|
|
43
|
+
except subprocess.CalledProcessError as e:
|
|
44
|
+
print(Fore.RED + f"Error running npm create vite: {e}")
|
|
45
|
+
os.chdir(original_dir)
|
|
46
|
+
except Exception as e:
|
|
47
|
+
print(Fore.RED + f"Unexpected error: {e}")
|
|
48
|
+
os.chdir(original_dir)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _generate_app_menu_template(desktop_dir: Path):
|
|
52
|
+
"""Create a menu folder with a sample menu.json inside the desktop folder."""
|
|
53
|
+
menu_dir = desktop_dir / "menu"
|
|
54
|
+
menu_dir.mkdir(exist_ok=True)
|
|
55
|
+
menu_file = menu_dir / "menu.json"
|
|
56
|
+
menu_file.write_text(
|
|
57
|
+
json.dumps(
|
|
58
|
+
[
|
|
59
|
+
{
|
|
60
|
+
"label": "File",
|
|
61
|
+
"submenu": [
|
|
62
|
+
{"label": "Open", "action": "on_open"},
|
|
63
|
+
{"label": "Save", "action": "on_save"},
|
|
64
|
+
{"separator": True},
|
|
65
|
+
{"label": "Exit", "action": "on_exit"},
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
indent=2,
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
print(Fore.YELLOW + f"Created menu/menu.json in {desktop_dir}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def generate_desktop_app(name: str, app_dir: Path, ui_dir: Path, template: str, project_name: Optional[str] = None) -> None:
|
|
76
|
+
"""Generate a complete desktop app: desktop/{main.py, src/, menu/, pyproject.toml}, UI at root."""
|
|
77
|
+
if project_name is None:
|
|
78
|
+
project_name = name
|
|
79
|
+
# Create the desktop/ subfolder that holds all Python files
|
|
80
|
+
desktop_dir = app_dir / "desktop"
|
|
81
|
+
desktop_dir.mkdir(exist_ok=True)
|
|
82
|
+
print(Fore.YELLOW + f"Created desktop folder in {name}")
|
|
83
|
+
|
|
84
|
+
# Create the src subfolder inside desktop/
|
|
85
|
+
src_dir = desktop_dir / "src"
|
|
86
|
+
src_dir.mkdir(exist_ok=True)
|
|
87
|
+
print(Fore.YELLOW + f"Created desktop/src folder in {name}")
|
|
88
|
+
|
|
89
|
+
# Generate menu template inside desktop/
|
|
90
|
+
_generate_app_menu_template(desktop_dir)
|
|
91
|
+
|
|
92
|
+
# Create desktop/src/__init__.py
|
|
93
|
+
init_file = src_dir / "__init__.py"
|
|
94
|
+
init_file.write_text("")
|
|
95
|
+
|
|
96
|
+
# Create desktop/src/backend.py
|
|
97
|
+
backend_file = src_dir / "backend.py"
|
|
98
|
+
backend_file.write_text(
|
|
99
|
+
"""import logging
|
|
100
|
+
from PySide6.QtWidgets import QApplication, QFileDialog
|
|
101
|
+
from PySide6.QtCore import Slot
|
|
102
|
+
from wysebee import WysebeeBackend
|
|
103
|
+
|
|
104
|
+
logger = logging.getLogger("wysebee")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class MyBackend(WysebeeBackend):
|
|
108
|
+
def __init__(self, parent):
|
|
109
|
+
super().__init__(parent)
|
|
110
|
+
|
|
111
|
+
@Slot()
|
|
112
|
+
def on_open(self):
|
|
113
|
+
file_path, _ = QFileDialog.getOpenFileName(None, "Open File")
|
|
114
|
+
if file_path:
|
|
115
|
+
logger.info(f"Opened file: {file_path}")
|
|
116
|
+
|
|
117
|
+
@Slot()
|
|
118
|
+
def on_save(self):
|
|
119
|
+
file_path, _ = QFileDialog.getSaveFileName(None, "Save File")
|
|
120
|
+
if file_path:
|
|
121
|
+
logger.info(f"Saved file: {file_path}")
|
|
122
|
+
|
|
123
|
+
@Slot()
|
|
124
|
+
def on_exit(self):
|
|
125
|
+
logger.info("Exiting application")
|
|
126
|
+
QApplication.quit()
|
|
127
|
+
"""
|
|
128
|
+
)
|
|
129
|
+
print(Fore.YELLOW + f"Created desktop/src/backend.py in {name}")
|
|
130
|
+
|
|
131
|
+
# Create desktop/main.py for desktop app. Paths are relative to this file:
|
|
132
|
+
# menu -> menu/menu.json (same dir)
|
|
133
|
+
# ui -> ../ui/templates/index.html (ui lives at project root)
|
|
134
|
+
main_file = desktop_dir / "main.py"
|
|
135
|
+
main_file.write_text(
|
|
136
|
+
"""
|
|
137
|
+
import sys
|
|
138
|
+
import os
|
|
139
|
+
from PySide6.QtWidgets import QApplication
|
|
140
|
+
from wysebee import Wysebee, WysebeeAppMenu
|
|
141
|
+
from src.backend import MyBackend
|
|
142
|
+
|
|
143
|
+
def main():
|
|
144
|
+
app = QApplication(sys.argv)
|
|
145
|
+
wysebee = Wysebee(app)
|
|
146
|
+
backend = MyBackend(app)
|
|
147
|
+
browser = wysebee.initialize_window(width=1280, height=800, backend=backend)
|
|
148
|
+
menu_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "menu/menu.json"))
|
|
149
|
+
app_menu = WysebeeAppMenu(browser, backend, menu_path)
|
|
150
|
+
html_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../ui/templates/index.html"))
|
|
151
|
+
wysebee.launch(html_path)
|
|
152
|
+
app_menu.show()
|
|
153
|
+
|
|
154
|
+
sys.exit(app.exec())
|
|
155
|
+
|
|
156
|
+
if __name__ == "__main__":
|
|
157
|
+
main()
|
|
158
|
+
"""
|
|
159
|
+
)
|
|
160
|
+
print(Fore.YELLOW + f"Created desktop/main.py file in {name}")
|
|
161
|
+
|
|
162
|
+
# Write desktop/pyproject.toml (uv-managed)
|
|
163
|
+
pyproject_file = desktop_dir / "pyproject.toml"
|
|
164
|
+
pyproject_file.write_text(
|
|
165
|
+
f"""[project]
|
|
166
|
+
name = "{project_name}-desktop"
|
|
167
|
+
version = "0.1.0"
|
|
168
|
+
requires-python = ">=3.10"
|
|
169
|
+
dependencies = [
|
|
170
|
+
"PySide6",
|
|
171
|
+
"Wysebee",
|
|
172
|
+
]
|
|
173
|
+
"""
|
|
174
|
+
)
|
|
175
|
+
print(Fore.YELLOW + f"Created desktop/pyproject.toml in {name}")
|
|
176
|
+
|
|
177
|
+
# Build the UI first (ui/ stays at project root)
|
|
178
|
+
_generate_ui_template(name, ui_dir, template)
|
|
179
|
+
|
|
180
|
+
# Bootstrap the uv venv + install dependencies
|
|
181
|
+
if bootstrap_uv_project(desktop_dir):
|
|
182
|
+
print(Fore.GREEN + f"Now you are ready!")
|
|
183
|
+
print(Fore.GREEN + f" To run the app, use:")
|
|
184
|
+
if name != ".":
|
|
185
|
+
print(Fore.GREEN + f" cd {name}/desktop")
|
|
186
|
+
else:
|
|
187
|
+
print(Fore.GREEN + f" cd desktop")
|
|
188
|
+
print(Fore.GREEN + f" uv run python main.py")
|