create-aiaui-module 0.2.0 → 0.4.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/README.md +53 -73
- package/package.json +1 -1
- package/runtime/_runtime/registry.py +5 -1
- package/templates/JobDashboard/backend/api.py +28 -50
- package/templates/JobDashboard/backend/config.json +2 -45
- package/templates/JobDashboard/client/package-lock.json +2 -2
- package/templates/JobDashboard/client/package.json +1 -1
- package/templates/JobDashboard/client/src/defaults/styles.css +0 -1
package/README.md
CHANGED
|
@@ -1,103 +1,83 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AIAUI module templates
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Scaffold an editable AIAUI Instance module — a FastAPI `backend/` router
|
|
4
|
+
plus a Vue `client/` frontend — with a single `npx` command. No global
|
|
5
|
+
install, no repository checkout, no `aiaui` package required.
|
|
6
6
|
|
|
7
|
-
|
|
|
7
|
+
| Step | Command | Description |
|
|
8
8
|
| --- | --- | --- |
|
|
9
|
-
|
|
|
10
|
-
|
|
|
11
|
-
|
|
|
9
|
+
| List available templates | `npx create-aiaui-module@latest --list` | Allowed template IDs and their contract version |
|
|
10
|
+
| Create a module | `npx create-aiaui-module@latest JobDashboard` | Generates `PyAIAUI/JobDashboard/` in the Instance root |
|
|
11
|
+
| Machine-readable output | `npx create-aiaui-module@latest JobDashboard --json` | Structured result (CI hook) |
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Requirements
|
|
14
14
|
|
|
15
|
-
-
|
|
16
|
-
-
|
|
15
|
+
- To run the generator: Node.js >= 20
|
|
16
|
+
- To run the generated module: Python + `fastapi`, `uvicorn` on the Instance
|
|
17
|
+
side (`pip install fastapi uvicorn`)
|
|
17
18
|
|
|
18
|
-
##
|
|
19
|
+
## Quick start
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
From an Instance root (a directory containing `Config/`):
|
|
21
22
|
|
|
22
23
|
```bash
|
|
23
24
|
npx create-aiaui-module@latest JobDashboard
|
|
24
25
|
```
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
Produces `PyAIAUI/JobDashboard/` (module code) and `PyAIAUI/_runtime/` (the
|
|
28
|
+
embedded runtime that loads it), and registers `JobDashboard` (disabled,
|
|
29
|
+
`port: null`) in `Config/AIAUIProtocol.json`.
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
The generated module:
|
|
29
32
|
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
```
|
|
34
|
+
<Instance root>/
|
|
35
|
+
├── Config/AIAUIProtocol.json # port + module enable/disable
|
|
36
|
+
└── PyAIAUI/
|
|
37
|
+
├── __init__.py # AIAUI class entry (start/stop)
|
|
38
|
+
├── JobDashboard/ # one module = one directory
|
|
39
|
+
│ ├── backend/api.py # create_api(context) -> APIRouter
|
|
40
|
+
│ ├── backend/config.json # panel appearance (display + state color/icon)
|
|
41
|
+
│ ├── client/src/ # Vue frontend source
|
|
42
|
+
│ ├── client/dist/ # prebuilt bundle (manifest.json)
|
|
43
|
+
│ └── README.md / CUSTOMIZATION.md
|
|
44
|
+
└── _runtime/ # embedded runtime (no external deps)
|
|
38
45
|
```
|
|
39
46
|
|
|
40
|
-
|
|
47
|
+
Enable the module before first boot: pick a free loopback `port` for
|
|
48
|
+
`AIAUIProtocol.json` and flip `enabled` to `true`. Then wire the Instance
|
|
49
|
+
lifecycle:
|
|
41
50
|
|
|
42
51
|
```python
|
|
43
52
|
from PyAIAUI import AIAUI
|
|
44
53
|
|
|
45
|
-
# 业务对象就绪后
|
|
46
54
|
aiaui = AIAUI.start(services={"job_repository": repo})
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
# ... Instance 正常运行 ...
|
|
50
|
-
|
|
51
|
-
# 关闭前
|
|
52
|
-
if aiaui is not None:
|
|
53
|
-
aiaui.stop()
|
|
55
|
+
...
|
|
56
|
+
aiaui.stop()
|
|
54
57
|
```
|
|
55
58
|
|
|
56
|
-
`AIAUI.start()`
|
|
59
|
+
`AIAUI.start()` is idempotent; it returns `None` when no modules are enabled.
|
|
57
60
|
|
|
58
|
-
|
|
61
|
+
After enabling, the module answers locally under
|
|
62
|
+
`/api/v1/services/<id>/modules/JobDashboard/...` (via UIService) — `GET /jobs`
|
|
63
|
+
(feed the panel), `GET /config` (panel appearance from `backend/config.json`),
|
|
64
|
+
and any custom routes you add to `create_api(context)`.
|
|
59
65
|
|
|
60
|
-
|
|
61
|
-
<Instance root>/
|
|
62
|
-
├── Config/AIAUIProtocol.json # 运行协议:端口 + 模块启停(生成器只登记,不配置)
|
|
63
|
-
└── PyAIAUI/
|
|
64
|
-
├── __init__.py # 入口:AIAUI 类(AIAUI.start / aiaui.stop)
|
|
65
|
-
├── JobDashboard/ # 模块目录,目录名 == 模块 ID
|
|
66
|
-
│ ├── backend/api.py # 后端契约:create_api(context) -> APIRouter
|
|
67
|
-
│ ├── backend/config.json # 外观配置(可无:/config 端点读它)
|
|
68
|
-
│ ├── client/src/ # Vue 前端源码
|
|
69
|
-
│ ├── client/dist/ # 预构建产物(开箱即跑,改源码后 npm run build 重出)
|
|
70
|
-
│ └── {README.md, CUSTOMIZATION.md}# 模块说明与定制手册
|
|
71
|
-
└── _runtime/ # 内嵌模块宿主(零外部依赖)
|
|
72
|
-
```
|
|
66
|
+
## Built-in templates
|
|
73
67
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
## 内置模块清单
|
|
77
|
-
|
|
78
|
-
| 模块 ID | 说明 | 组成 |
|
|
68
|
+
| ID | Description | Contents |
|
|
79
69
|
| --- | --- | --- |
|
|
80
|
-
| `JobDashboard` |
|
|
81
|
-
| `HelloWorld` |
|
|
82
|
-
自定义通过 `client/src/customization.ts` 完成,槽位通信通过 `context.bus`(事件 / expose+peer),详见生成目录里的 `CUSTOMIZATION.md`。
|
|
83
|
-
|
|
84
|
-
## UIService 集成
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
npm i -g create-aiaui-module
|
|
88
|
-
export AIA_UI_MODULE_TEMPLATE_ROOT="$(npm root -g)/create-aiaui-module"
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
此后 UIService 调用包内 `bin/create-aiaui-module.js`(与 `npx` 行为一致),或在业务机上直接 `npx`。
|
|
92
|
-
|
|
93
|
-
## 常用命令
|
|
70
|
+
| `JobDashboard` | Job information / carrier / process panel | `backend/api.py` + `backend/config.json` + Vue `client/`. Implement `GET /jobs` (job data) and `GET /config` (display switches + state color/icon), or adjust nothing at all — a built-in default panel ships and falls back to demo data. Slots are individually replaceable (`board` / `headerRegion` / `middleRegion` / `bottomRegion` + leaf slots like `jobName` / `status` / `timeline` / `inputCarriers` / `outputCarriers` / `recipe`) without touching the panel shell. |
|
|
71
|
+
| `HelloWorld` | Minimal frontend-only example | Custom element for probing the module lifecycle, no backend |
|
|
94
72
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
73
|
+
Customize a card's layout by editing `client/src/customization.ts`; every slot
|
|
74
|
+
component receives `{ context, job, config }` as props. See
|
|
75
|
+
`CUSTOMIZATION.md` inside the generated `JobDashboard/` directory for the full
|
|
76
|
+
slot table and contract.
|
|
98
77
|
|
|
99
|
-
##
|
|
78
|
+
## What the generator does NOT do
|
|
100
79
|
|
|
101
|
-
-
|
|
102
|
-
|
|
103
|
-
-
|
|
80
|
+
- It does not choose a port, install dependencies, start any process, or
|
|
81
|
+
overwrite an existing `PyAIAUI/<ModuleId>` directory.
|
|
82
|
+
- An invalid existing `AIAUIProtocol.json` is refused without leaving a new
|
|
83
|
+
module directory behind.
|
package/package.json
CHANGED
|
@@ -18,7 +18,11 @@ class ModuleDescriptor:
|
|
|
18
18
|
def __init__(self, module_id: str, directory: Path):
|
|
19
19
|
self.id = module_id
|
|
20
20
|
self.directory = directory
|
|
21
|
-
|
|
21
|
+
# New templates ship backend/api.py, legacy ones api.py at module root.
|
|
22
|
+
backend_api = directory / "backend" / "api.py"
|
|
23
|
+
self.api_file = (
|
|
24
|
+
backend_api if backend_api.is_file() else directory / "api.py"
|
|
25
|
+
)
|
|
22
26
|
client_dist = directory / "client" / "dist"
|
|
23
27
|
self.frontend_entry = (
|
|
24
28
|
client_dist if client_dist.is_dir() else directory / "resources" / "dist"
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Editable JobDashboard example, mounted under /modules/JobDashboard."""
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
-
|
|
5
4
|
import json
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
|
|
@@ -11,69 +10,49 @@ CONFIG_PATH = Path(__file__).resolve().parent / "config.json"
|
|
|
11
10
|
|
|
12
11
|
DEFAULT_CONFIG = {
|
|
13
12
|
"display": {
|
|
14
|
-
"lotId":
|
|
15
|
-
"recipe":
|
|
16
|
-
"inputCarriers":
|
|
17
|
-
"outputCarriers":
|
|
18
|
-
"timeline":
|
|
13
|
+
"lotId": True,
|
|
14
|
+
"recipe": True,
|
|
15
|
+
"inputCarriers": True,
|
|
16
|
+
"outputCarriers": True,
|
|
17
|
+
"timeline": True,
|
|
19
18
|
},
|
|
20
19
|
"states": [
|
|
21
|
-
{
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"icon": "list"
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
"name": "WaitingForStart",
|
|
33
|
-
"color": "#8b95a5",
|
|
34
|
-
"icon": "stop"
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
"name": "Executing",
|
|
38
|
-
"color": "#1d4ed8",
|
|
39
|
-
"icon": "activity"
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
"name": "Running",
|
|
43
|
-
"color": "#1d4ed8",
|
|
44
|
-
"icon": "activity"
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
"name": "Complete",
|
|
48
|
-
"color": "#166534",
|
|
49
|
-
"icon": "flag"
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
"name": "Finished",
|
|
53
|
-
"color": "#166534",
|
|
54
|
-
"icon": "flag"
|
|
55
|
-
}
|
|
56
|
-
]
|
|
57
|
-
|
|
20
|
+
{"name": "Setup", "color": "#0369a1", "icon": "clock"},
|
|
21
|
+
{"name": "Select", "color": "#526176", "icon": "list"},
|
|
22
|
+
{"name": "WaitingForStart", "color": "#8b95a5", "icon": "stop"},
|
|
23
|
+
{"name": "Executing", "color": "#1d4ed8", "icon": "activity"},
|
|
24
|
+
{"name": "Running", "color": "#1d4ed8", "icon": "activity"},
|
|
25
|
+
{"name": "Complete", "color": "#166534", "icon": "flag"},
|
|
26
|
+
{"name": "Finished", "color": "#166534", "icon": "flag"},
|
|
27
|
+
],
|
|
58
28
|
}
|
|
59
29
|
|
|
60
30
|
|
|
61
31
|
def default_config() -> dict:
|
|
62
|
-
"""Merge ``config.json`` over the shipped default palette.
|
|
32
|
+
"""Merge ``config.json`` over the shipped default palette.
|
|
33
|
+
|
|
34
|
+
Any missing key is merged over the built-in defaults; unreadable or
|
|
35
|
+
non-object content falls back to the shipped defaults, so the endpoint
|
|
36
|
+
can be left ``{}`` to keep everything visible by default.
|
|
37
|
+
"""
|
|
63
38
|
try:
|
|
64
39
|
raw = json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
|
|
65
40
|
except OSError:
|
|
66
41
|
return DEFAULT_CONFIG
|
|
67
42
|
if not isinstance(raw, dict):
|
|
68
43
|
return DEFAULT_CONFIG
|
|
69
|
-
|
|
44
|
+
merged: dict = {**DEFAULT_CONFIG, **raw}
|
|
45
|
+
if isinstance(raw.get("display"), dict):
|
|
46
|
+
merged["display"] = {**DEFAULT_CONFIG["display"], **raw["display"]}
|
|
47
|
+
if isinstance(raw.get("states"), list) and raw["states"]:
|
|
48
|
+
merged["states"] = raw["states"]
|
|
49
|
+
return merged
|
|
70
50
|
|
|
71
51
|
|
|
72
52
|
def create_api(context) -> APIRouter:
|
|
73
53
|
"""Entry point invoked by the generated PyAIAUI host.
|
|
74
54
|
|
|
75
|
-
The Instance injects its live objects via ``PyAIAUI.AIAUI.start(services={...})``
|
|
76
|
-
and they arrive here through ``context.services``.
|
|
55
|
+
The Instance injects its live objects via ``PyAIAUI.AIAUI.start(services={...})`` and they arrive here through ``context.services``.
|
|
77
56
|
"""
|
|
78
57
|
router = APIRouter()
|
|
79
58
|
|
|
@@ -135,9 +114,8 @@ def create_api(context) -> APIRouter:
|
|
|
135
114
|
|
|
136
115
|
@router.get("/config")
|
|
137
116
|
async def config():
|
|
138
|
-
"""Panel appearance contract, read from ``config.json`` (
|
|
139
|
-
|
|
140
|
-
palette; unreadable/malformed content falls back to the default.
|
|
117
|
+
"""Panel appearance contract, read from ``config.json`` (this file's
|
|
118
|
+
sibling). Missing keys fall back to the built-in DEFAULT_CONFIG.
|
|
141
119
|
"""
|
|
142
120
|
return default_config()
|
|
143
121
|
|
|
@@ -1,46 +1,3 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
"recipe": true,
|
|
5
|
-
"inputCarriers": true,
|
|
6
|
-
"outputCarriers": true,
|
|
7
|
-
"timeline": true
|
|
8
|
-
},
|
|
9
|
-
"states": [
|
|
10
|
-
{
|
|
11
|
-
"name": "Setup",
|
|
12
|
-
"color": "#0369a1",
|
|
13
|
-
"icon": "clock"
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"name": "Select",
|
|
17
|
-
"color": "#526176",
|
|
18
|
-
"icon": "list"
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"name": "WaitingForStart",
|
|
22
|
-
"color": "#8b95a5",
|
|
23
|
-
"icon": "stop"
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"name": "Executing",
|
|
27
|
-
"color": "#1d4ed8",
|
|
28
|
-
"icon": "activity"
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"name": "Running",
|
|
32
|
-
"color": "#1d4ed8",
|
|
33
|
-
"icon": "activity"
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
"name": "Complete",
|
|
37
|
-
"color": "#166534",
|
|
38
|
-
"icon": "flag"
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
"name": "Finished",
|
|
42
|
-
"color": "#166534",
|
|
43
|
-
"icon": "flag"
|
|
44
|
-
}
|
|
45
|
-
]
|
|
46
|
-
}
|
|
2
|
+
"display": {}
|
|
3
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "aiaui-job-
|
|
2
|
+
"name": "aiaui-job-dashboard-template",
|
|
3
3
|
"lockfileVersion": 3,
|
|
4
4
|
"requires": true,
|
|
5
5
|
"packages": {
|
|
6
6
|
"": {
|
|
7
|
-
"name": "aiaui-job-
|
|
7
|
+
"name": "aiaui-job-dashboard-template",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"vue": "^3.5.0"
|
|
10
10
|
},
|