create-aiaui-module 0.1.1-TEST → 0.2.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 +13 -12
- package/bin/create-aiaui-module.js +24 -32
- package/package.json +4 -3
- package/runtime/_runtime/host.py +7 -15
- package/runtime/_runtime/registry.py +4 -1
- package/templates/JobDashboard/CUSTOMIZATION.md +123 -0
- package/templates/{JobInformation → JobDashboard}/README.md +22 -9
- package/templates/JobDashboard/backend/api.py +144 -0
- package/templates/JobDashboard/backend/config.json +46 -0
- package/templates/JobDashboard/client/dist/modules.js +3513 -0
- package/templates/JobDashboard/client/src/contract.ts +146 -0
- package/templates/JobDashboard/client/src/customization.ts +30 -0
- package/templates/JobDashboard/client/src/defaults/DefaultPanel.vue +129 -0
- package/templates/JobDashboard/client/src/defaults/Icons.vue +49 -0
- package/templates/JobDashboard/client/src/defaults/InputCarriers.vue +18 -0
- package/templates/JobDashboard/client/src/defaults/JobBottom.vue +32 -0
- package/templates/JobDashboard/client/src/defaults/JobCard.vue +48 -0
- package/templates/JobDashboard/client/src/defaults/JobHeader.vue +41 -0
- package/templates/JobDashboard/client/src/defaults/JobMiddle.vue +41 -0
- package/templates/JobDashboard/client/src/defaults/JobName.vue +9 -0
- package/templates/JobDashboard/client/src/defaults/LotId.vue +14 -0
- package/templates/JobDashboard/client/src/defaults/OutputCarriers.vue +21 -0
- package/templates/JobDashboard/client/src/defaults/Recipe.vue +12 -0
- package/templates/JobDashboard/client/src/defaults/Status.vue +29 -0
- package/templates/JobDashboard/client/src/defaults/Timeline.vue +65 -0
- package/templates/JobDashboard/client/src/defaults/board-data.ts +36 -0
- package/templates/JobDashboard/client/src/defaults/config-defaults.ts +36 -0
- package/templates/JobDashboard/client/src/defaults/format.ts +51 -0
- package/templates/JobDashboard/client/src/defaults/slots.ts +26 -0
- package/templates/JobDashboard/client/src/defaults/styles.css +619 -0
- package/templates/{JobInformation/resources → JobDashboard/client}/src/index.ts +10 -18
- package/templates/JobInformation/CUSTOMIZATION.md +0 -173
- package/templates/JobInformation/api.py +0 -53
- package/templates/JobInformation/resources/dist/modules.js +0 -2919
- package/templates/JobInformation/resources/src/bus.ts +0 -38
- package/templates/JobInformation/resources/src/contract.ts +0 -74
- package/templates/JobInformation/resources/src/customization.ts +0 -25
- package/templates/JobInformation/resources/src/defaults/DefaultPanel.vue +0 -22
- package/templates/JobInformation/resources/src/defaults/Header.vue +0 -12
- package/templates/JobInformation/resources/src/defaults/InputCarriers.vue +0 -15
- package/templates/JobInformation/resources/src/defaults/OutputCarriers.vue +0 -14
- package/templates/JobInformation/resources/src/defaults/Recipe.vue +0 -18
- package/templates/JobInformation/resources/src/defaults/Timeline.vue +0 -22
- package/templates/JobInformation/resources/src/defaults/slots.ts +0 -16
- package/templates/JobInformation/resources/src/defaults/styles.css +0 -90
- /package/templates/{JobInformation/resources → JobDashboard/client}/build.mjs +0 -0
- /package/templates/{JobInformation/resources → JobDashboard/client}/dist/manifest.json +0 -0
- /package/templates/{JobInformation/resources → JobDashboard/client}/package-lock.json +0 -0
- /package/templates/{JobInformation/resources → JobDashboard/client}/package.json +0 -0
- /package/templates/{JobInformation/resources → JobDashboard/client}/src/vite-env.d.ts +0 -0
- /package/templates/{JobInformation/resources → JobDashboard/client}/vite.config.mjs +0 -0
- /package/templates/{JobInformation → JobDashboard}/contractVersion +0 -0
package/README.md
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
| 步骤 | 命令 | 说明 |
|
|
8
8
|
| --- | --- | --- |
|
|
9
9
|
| 查看可用模块 | `npx create-aiaui-module@latest --list` | 列出模块 ID 与 contractVersion |
|
|
10
|
-
| 生成模块 | `npx create-aiaui-module@latest
|
|
11
|
-
| 机器可读 | `npx create-aiaui-module@latest
|
|
10
|
+
| 生成模块 | `npx create-aiaui-module@latest JobDashboard` | 在实例根目录生成 `PyAIAUI/JobDashboard` |
|
|
11
|
+
| 机器可读 | `npx create-aiaui-module@latest JobDashboard --json` | 生成结果输出 JSON(接 CI) |
|
|
12
12
|
|
|
13
13
|
## 环境要求
|
|
14
14
|
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
在 Instance 根目录(含 `Config/` 的目录)下执行:
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
npx create-aiaui-module@latest
|
|
23
|
+
npx create-aiaui-module@latest JobDashboard
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
生成后 `PyAIAUI/
|
|
26
|
+
生成后 `PyAIAUI/JobDashboard/` 与 `PyAIAUI/_runtime/` 就位,`Config/AIAUIProtocol.json` 中登记该模块(默认 `enabled: false`、`port: null`)。
|
|
27
27
|
|
|
28
28
|
启用前需要调整的配置:
|
|
29
29
|
|
|
@@ -32,7 +32,7 @@ npx create-aiaui-module@latest JobInformation
|
|
|
32
32
|
"schemaVersion": 1,
|
|
33
33
|
"port": 18901,
|
|
34
34
|
"modules": [
|
|
35
|
-
{ "name": "
|
|
35
|
+
{ "name": "JobDashboard", "enabled": true }
|
|
36
36
|
]
|
|
37
37
|
}
|
|
38
38
|
```
|
|
@@ -62,10 +62,11 @@ if aiaui is not None:
|
|
|
62
62
|
├── Config/AIAUIProtocol.json # 运行协议:端口 + 模块启停(生成器只登记,不配置)
|
|
63
63
|
└── PyAIAUI/
|
|
64
64
|
├── __init__.py # 入口:AIAUI 类(AIAUI.start / aiaui.stop)
|
|
65
|
-
├──
|
|
66
|
-
│ ├── api.py
|
|
67
|
-
│ ├──
|
|
68
|
-
│ ├──
|
|
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 重出)
|
|
69
70
|
│ └── {README.md, CUSTOMIZATION.md}# 模块说明与定制手册
|
|
70
71
|
└── _runtime/ # 内嵌模块宿主(零外部依赖)
|
|
71
72
|
```
|
|
@@ -76,9 +77,9 @@ if aiaui is not None:
|
|
|
76
77
|
|
|
77
78
|
| 模块 ID | 说明 | 组成 |
|
|
78
79
|
| --- | --- | --- |
|
|
79
|
-
| `
|
|
80
|
+
| `JobDashboard` | 工单 / 载具 / 工序信息面板 | FastAPI 后端(`backend/api.py` + `backend/config.json`)+ Vue 前端(`client/`);实现 `GET /jobs`(工单数据)+ `GET /config`(显示开关 + 状态色/图标,config.json 读取)即可;分粒度槽位(`board`/`headerRegion`/`middleRegion`/`bottomRegion` + 叶子)可替换任意局部 |
|
|
80
81
|
| `HelloWorld` | 最小前端模块示例 | 自定义元素;验证挂载链路,无后端 |
|
|
81
|
-
自定义通过 `
|
|
82
|
+
自定义通过 `client/src/customization.ts` 完成,槽位通信通过 `context.bus`(事件 / expose+peer),详见生成目录里的 `CUSTOMIZATION.md`。
|
|
82
83
|
|
|
83
84
|
## UIService 集成
|
|
84
85
|
|
|
@@ -97,6 +98,6 @@ export AIA_UI_MODULE_TEMPLATE_ROOT="$(npm root -g)/create-aiaui-module"
|
|
|
97
98
|
|
|
98
99
|
## 维护者(发布新模板)
|
|
99
100
|
|
|
100
|
-
- 新增模块:在 `templates/<ModuleId>/` 下放实现(含 `contractVersion`、`README.md`、`
|
|
101
|
+
- 新增模块:在 `templates/<ModuleId>/` 下放实现(含 `contractVersion`、`README.md`、`backend/api.py`、`client/`)
|
|
101
102
|
- 前端模板发布前在 `resources/` 跑 `npm install && npm run build`,把产物一起随包发布;发布自动执行 `verify-dist` 校验每个模板的 `contractVersion`、入口、`dist/` 产物
|
|
102
103
|
- 发版流程:`npm test` → 改 `package.json` 版本号 → `npm publish`
|
|
@@ -35,7 +35,7 @@ function briefDescription(templateId) {
|
|
|
35
35
|
|
|
36
36
|
function printTemplateList(entries) {
|
|
37
37
|
console.log();
|
|
38
|
-
console.log("AIAUI
|
|
38
|
+
console.log("AIAUI module templates");
|
|
39
39
|
console.log(DIVIDER);
|
|
40
40
|
if (entries.length === 0) console.log(" (none)");
|
|
41
41
|
for (const entry of entries) {
|
|
@@ -44,43 +44,35 @@ function printTemplateList(entries) {
|
|
|
44
44
|
console.log(` ${briefDescription(entry.id)}`);
|
|
45
45
|
}
|
|
46
46
|
console.log(DIVIDER);
|
|
47
|
-
console.log("
|
|
47
|
+
console.log("usage: npx create-aiaui-module <ModuleId> [--list] [--json] [--instance-root <path>]");
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
function printSuccess(result) {
|
|
51
51
|
console.log();
|
|
52
|
-
console.log("
|
|
53
|
-
console.log("
|
|
54
|
-
console.log("
|
|
52
|
+
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
53
|
+
console.log(" [AIAUI] module created");
|
|
54
|
+
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
55
55
|
console.log();
|
|
56
|
-
console.log(`
|
|
57
|
-
console.log(`
|
|
56
|
+
console.log(` done module ${result.moduleId}`);
|
|
57
|
+
console.log(` done path ${result.path}`);
|
|
58
|
+
console.log(` done files ${result.filesCopied}`);
|
|
59
|
+
console.log(` done protocol ${result.configPath}`);
|
|
60
|
+
console.log(` enabled: ${result.enabled}, port: ${result.port ?? "unset"}`);
|
|
58
61
|
console.log();
|
|
59
|
-
console.log("
|
|
60
|
-
console.log("
|
|
61
|
-
console.log(
|
|
62
|
-
console.log(
|
|
63
|
-
console.log("
|
|
64
|
-
console.log();
|
|
65
|
-
console.log("
|
|
66
|
-
console.log("
|
|
67
|
-
console.log(
|
|
68
|
-
console.log("
|
|
69
|
-
|
|
70
|
-
console.log("
|
|
71
|
-
console.log("
|
|
72
|
-
console.log("
|
|
73
|
-
console.log(" · 关闭时: aiaui.stop()");
|
|
74
|
-
console.log(" 4. 打开 UIClient 对应页面查看效果(JobInformation → /jobs)");
|
|
75
|
-
console.log();
|
|
76
|
-
console.log("❌ 没有做的(请确认)");
|
|
77
|
-
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
|
78
|
-
console.log(" · 未启动服务(由 Instance 生命周期调用 AIAUI.start)");
|
|
79
|
-
console.log(" · 未安装依赖(模块运行需要 pip install fastapi uvicorn)");
|
|
80
|
-
console.log(" · 未选择/配置端口");
|
|
81
|
-
console.log(" · 未手动启用任何模块");
|
|
82
|
-
console.log();
|
|
83
|
-
console.log(" 初始化目录已就绪,按上面的顺序操作即可上线。");
|
|
62
|
+
console.log("next: edit your Config & wire the module (you must do this)");
|
|
63
|
+
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
64
|
+
console.log(' 1. Set a free loopback port in AIAUIProtocol.json ("port", e.g. 18900-18999 range)');
|
|
65
|
+
console.log(' and flip "enabled" to true for your module');
|
|
66
|
+
console.log(" 2. Wire the module into your Instance lifecycle");
|
|
67
|
+
console.log(" from PyAIAUI import AIAUI");
|
|
68
|
+
console.log(" aiaui = AIAUI.start(services={...}) # at Instance startup");
|
|
69
|
+
console.log(" aiaui.stop() # at shutdown");
|
|
70
|
+
console.log(' 3. Mount its data: backend/api.py implements create_api(context) -> APIRouter.');
|
|
71
|
+
console.log(" (many templates ship backend/config.json for appearance settings — see README)");
|
|
72
|
+
|
|
73
|
+
console.log(" 4. Rebuild the frontend if you edited it:");
|
|
74
|
+
console.log(" npm install && npm run build # from <module>/client");
|
|
75
|
+
console.log(" no starting services, auto-required deps, or a running Instance is triggered");
|
|
84
76
|
console.log();
|
|
85
77
|
}
|
|
86
78
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-aiaui-module",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "AIAUI Instance module scaffolder:
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "AIAUI Instance module scaffolder: npx generates an editable module",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"aiaui",
|
|
7
7
|
"scaffolding",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"vue",
|
|
10
10
|
"fastapi",
|
|
11
11
|
"module",
|
|
12
|
-
"instance"
|
|
12
|
+
"instance",
|
|
13
|
+
"sharetek"
|
|
13
14
|
],
|
|
14
15
|
"type": "module",
|
|
15
16
|
"bin": {
|
package/runtime/_runtime/host.py
CHANGED
|
@@ -12,9 +12,6 @@ OWNER = "aiaui-proxy"
|
|
|
12
12
|
|
|
13
13
|
class ProxyHost:
|
|
14
14
|
def __init__(self, instance_root: Path, port: int, modules: list[ModuleDescriptor], context):
|
|
15
|
-
import fcntl
|
|
16
|
-
import os
|
|
17
|
-
|
|
18
15
|
import uvicorn
|
|
19
16
|
from fastapi import FastAPI
|
|
20
17
|
|
|
@@ -24,17 +21,13 @@ class ProxyHost:
|
|
|
24
21
|
self.closed = False
|
|
25
22
|
self.thread: threading.Thread | None = None
|
|
26
23
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
except BlockingIOError as error:
|
|
35
|
-
self._lock_file.close()
|
|
36
|
-
raise ValueError("Another process already hosts this Instance") from error
|
|
37
|
-
|
|
24
|
+
# No file lock: the loopback bind in `name:port` (the protocol's port)
|
|
25
|
+
# already enforces "one host per Instance". A second host for the
|
|
26
|
+
# same Instance fails fast with a uvicorn bind error. If two hosts
|
|
27
|
+
# for the same Instance ever end up on different ports, the second
|
|
28
|
+
# one silently serves alongside the first; this is an edge case the
|
|
29
|
+
# caller avoids by keeping AIAUIProtocol.json as the single source
|
|
30
|
+
# of truth.
|
|
38
31
|
self._app = FastAPI(title=f"AIAUI modules for {self.root.name}")
|
|
39
32
|
for module in modules:
|
|
40
33
|
router = load_api(module, context)
|
|
@@ -85,4 +78,3 @@ class ProxyHost:
|
|
|
85
78
|
if self.thread is not None:
|
|
86
79
|
self.server.should_exit = True
|
|
87
80
|
self.thread.join(timeout=5)
|
|
88
|
-
self._lock_file.close()
|
|
@@ -19,7 +19,10 @@ class ModuleDescriptor:
|
|
|
19
19
|
self.id = module_id
|
|
20
20
|
self.directory = directory
|
|
21
21
|
self.api_file = directory / "api.py"
|
|
22
|
-
|
|
22
|
+
client_dist = directory / "client" / "dist"
|
|
23
|
+
self.frontend_entry = (
|
|
24
|
+
client_dist if client_dist.is_dir() else directory / "resources" / "dist"
|
|
25
|
+
)
|
|
23
26
|
|
|
24
27
|
@property
|
|
25
28
|
def has_backend(self) -> bool:
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# JobDashboard customization
|
|
2
|
+
|
|
3
|
+
This module ships a built-in job-information panel that reads two local
|
|
4
|
+
endpoints and renders one card per job. Every part of a card can be replaced
|
|
5
|
+
without touching the platform code; the host contract
|
|
6
|
+
(`mount(container, context)` / `unmount()`) never changes.
|
|
7
|
+
|
|
8
|
+
## Data contract (implement these two endpoints, get a working panel)
|
|
9
|
+
|
|
10
|
+
`GET /jobs` — one entry per job (device + carriers + timeline + recipe):
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"items": [
|
|
15
|
+
{
|
|
16
|
+
"id": "job-001",
|
|
17
|
+
"name": "ASM3GM-PLUGIN-JOB-001",
|
|
18
|
+
"lot": "LOT-001",
|
|
19
|
+
"state": "Executing",
|
|
20
|
+
"carriers": { "input": [{"code": "CR-01"}], "output": [] },
|
|
21
|
+
"recipe": "RCP-CU-ETCH-O7",
|
|
22
|
+
"timeline": [
|
|
23
|
+
{ "label": "Setup", "at": "2026-10-18T08:00:00", "state": "done" },
|
|
24
|
+
{ "label": "Select", "at": "2026-10-18T08:01:05", "state": "done" },
|
|
25
|
+
{ "label": "WaitingForStart", "at": "2026-10-18T08:02:30", "state": "active" },
|
|
26
|
+
{ "label": "Executing", "at": "2026-10-18T08:05:12", "state": "pending" },
|
|
27
|
+
{ "label": "Complete", "at": null, "state": "pending" }
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"total": 1,
|
|
32
|
+
"page": 1,
|
|
33
|
+
"pageSize": 10
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- `{ id, name }` is the minimum; `lot/state/carriers/timeline/recipe` are all
|
|
38
|
+
optional and degrade to `—` / `No progress data`.
|
|
39
|
+
- Durations between completed steps are computed on the frontend.
|
|
40
|
+
- If `GET /jobs` fails, the module falls back to a built-in demo set.
|
|
41
|
+
|
|
42
|
+
`GET /config` — the appearance contract:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"display": {
|
|
47
|
+
"lotId": true,
|
|
48
|
+
"recipe": true,
|
|
49
|
+
"inputCarriers": true,
|
|
50
|
+
"outputCarriers": true,
|
|
51
|
+
"timeline": true
|
|
52
|
+
},
|
|
53
|
+
"states": [
|
|
54
|
+
{ "name": "Executing", "color": "#1d4ed8", "icon": "activity" },
|
|
55
|
+
{ "name": "Setup", "color": "#0369a1", "icon": "clock" },
|
|
56
|
+
{ "name": "Complete", "color": "#166534", "icon": "flag" }
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- Missing keys fall back to the built-in `config-defaults.ts` (all parts
|
|
62
|
+
visible, a sensible default state palette). Fields are merged shallowly, so
|
|
63
|
+
a backend may send only the fragments it wants to change.
|
|
64
|
+
- The palette drives the status chip and the timeline step icons
|
|
65
|
+
(`clock / list / stop / activity / flag`). Unknown icons fall back to
|
|
66
|
+
`clock`.
|
|
67
|
+
|
|
68
|
+
## Slots (what you can replace)
|
|
69
|
+
|
|
70
|
+
Edit `client/src/customization.ts`. Keys per card:
|
|
71
|
+
|
|
72
|
+
| Slot | Replaces | Renders inside |
|
|
73
|
+
| --- | --- | --- |
|
|
74
|
+
| `board` | the entire job card | (replaces header + middle + bottom) |
|
|
75
|
+
| `headerRegion` | the job-name / lot-id / state row | each card |
|
|
76
|
+
| `jobName` | job name (italic + leading dot) | `headerRegion` |
|
|
77
|
+
| `lotId` | `Lot ID <value>` | `headerRegion` |
|
|
78
|
+
| `status` | the state chip | `headerRegion` |
|
|
79
|
+
| `middleRegion` | the input-carrier / timeline / output-carrier row | each card |
|
|
80
|
+
| `inputCarriers` | the input-cards list | `middleRegion` |
|
|
81
|
+
| `timeline` | the Setup → … → Complete stepper | `middleRegion` |
|
|
82
|
+
| `outputCarriers` | the output-cards list | `middleRegion` |
|
|
83
|
+
| `bottomRegion` | the recipe sub-card + footer | each card |
|
|
84
|
+
| `recipe` | the `Recipe` + name line | `bottomRegion` |
|
|
85
|
+
|
|
86
|
+
Every slot component receives `{ context, job, config }` as props; leaf
|
|
87
|
+
slots ignore the region around them (which stays platform-owned). Replacing
|
|
88
|
+
`board` (or any `*Region`) replaces the whole region — including its
|
|
89
|
+
sub-slots if set.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
// client/src/customization.ts
|
|
93
|
+
import MyJobName from "./custom/MyJobName.vue";
|
|
94
|
+
import type { Customization } from "./contract";
|
|
95
|
+
|
|
96
|
+
export const customization: Customization = {
|
|
97
|
+
slots: { jobName: MyJobName },
|
|
98
|
+
};
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Slot contract:
|
|
102
|
+
|
|
103
|
+
```vue
|
|
104
|
+
<script setup lang="ts">
|
|
105
|
+
import type { ModuleSlotProps } from "../contract";
|
|
106
|
+
|
|
107
|
+
defineProps<ModuleSlotProps>();
|
|
108
|
+
</script>
|
|
109
|
+
<template>
|
|
110
|
+
<span class="my-custom">— {{ job.state }} · {{ job.name }} —</span>
|
|
111
|
+
</template>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Rebuild
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
cd resources
|
|
118
|
+
npm install
|
|
119
|
+
npm run build
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
For quick iteration, `npm run dev` runs `vite build --watch` and keeps
|
|
123
|
+
`dist/manifest.json` valid on every rebuild.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# JobDashboard module
|
|
2
2
|
|
|
3
3
|
This template scaffolds an editable "job information" UI module. The
|
|
4
4
|
generator only produces scaffolding plus an inlined runtime
|
|
@@ -19,7 +19,7 @@ def create_api(context) -> APIRouter:
|
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
The generated host calls `create_api(context)` once and mounts the returned
|
|
22
|
-
router at `/modules/
|
|
22
|
+
router at `/modules/JobDashboard`. You receive:
|
|
23
23
|
|
|
24
24
|
- `context.instance_root` — the Instance root directory.
|
|
25
25
|
- `context.services` — a dict of the live objects your Instance passes in at
|
|
@@ -28,25 +28,38 @@ router at `/modules/JobInformation`. You receive:
|
|
|
28
28
|
|
|
29
29
|
Everything else about how to fetch or present data is yours to implement.
|
|
30
30
|
|
|
31
|
+
## 前端契约(默认面板与数据)
|
|
32
|
+
|
|
33
|
+
默认面板消费模块自带两个接口:`GET /jobs`(返回每张卡片的数据,任选可选
|
|
34
|
+
字段,最少 `{id, name}`)和 `GET /config`(面板显示开关 + 状态颜色/图标;缺
|
|
35
|
+
失字段回退内置 `config-defaults.ts`,后端失败则显示 demo 数据)。实现这两个
|
|
36
|
+
接口 + `AIAUI.start()` 即可,无需改前端文件。
|
|
37
|
+
|
|
31
38
|
## Frontend
|
|
32
39
|
|
|
40
|
+
|
|
33
41
|
The frontend is organized around a small customization contract:
|
|
34
42
|
|
|
35
|
-
- `
|
|
36
|
-
- `
|
|
37
|
-
- `
|
|
38
|
-
- `
|
|
39
|
-
- `
|
|
43
|
+
- `client/src/index.ts` — the stable `mount` / `unmount` bridge the host loads.
|
|
44
|
+
- `client/src/contract.ts` — the shared types for the module context, bus, and customization.
|
|
45
|
+
- `client/src/customization.ts` — the only file you normally edit to customize.
|
|
46
|
+
- `client/src/bus.ts` — a per-mount communication bus shared by all slot components.
|
|
47
|
+
- `client/src/defaults/` — the built-in panel and its replaceable boxes.
|
|
40
48
|
|
|
41
49
|
Slot components also communicate through `context.bus` (events or imperative handles); see CUSTOMIZATION.md for the patterns.
|
|
42
50
|
|
|
51
|
+
Slot components receive `{ context, job }`; the default layout only
|
|
52
|
+
allows replacing individual regions (`header`, `inputCarriers`,
|
|
53
|
+
`outputCarriers`, `timeline`, `bottom` [nested `recipe`]). Use `full` to
|
|
54
|
+
replace the whole board.
|
|
55
|
+
|
|
43
56
|
Three levels are supported without changing `index.ts`:
|
|
44
57
|
|
|
45
58
|
1. **Default** — do nothing; the built-in panel renders.
|
|
46
59
|
2. **Partial** — replace one or more named boxes:
|
|
47
60
|
|
|
48
61
|
```ts
|
|
49
|
-
//
|
|
62
|
+
// client/src/customization.ts
|
|
50
63
|
import MyHeader from "./custom/MyHeader.vue";
|
|
51
64
|
import type { Customization } from "./contract";
|
|
52
65
|
|
|
@@ -97,7 +110,7 @@ aiaui = AIAUI.start(services={
|
|
|
97
110
|
aiaui.stop()
|
|
98
111
|
```
|
|
99
112
|
|
|
100
|
-
`start()` returns `None` when `
|
|
113
|
+
`start()` returns `None` when `JobDashboard` is disabled in
|
|
101
114
|
`Config/AIAUIProtocol.json`; otherwise it returns the running host.
|
|
102
115
|
|
|
103
116
|
## Dependencies (reminder, not installed automatically)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Editable JobDashboard example, mounted under /modules/JobDashboard."""
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from fastapi import APIRouter
|
|
9
|
+
|
|
10
|
+
CONFIG_PATH = Path(__file__).resolve().parent / "config.json"
|
|
11
|
+
|
|
12
|
+
DEFAULT_CONFIG = {
|
|
13
|
+
"display": {
|
|
14
|
+
"lotId": true,
|
|
15
|
+
"recipe": true,
|
|
16
|
+
"inputCarriers": true,
|
|
17
|
+
"outputCarriers": true,
|
|
18
|
+
"timeline": true
|
|
19
|
+
},
|
|
20
|
+
"states": [
|
|
21
|
+
{
|
|
22
|
+
"name": "Setup",
|
|
23
|
+
"color": "#0369a1",
|
|
24
|
+
"icon": "clock"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "Select",
|
|
28
|
+
"color": "#526176",
|
|
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
|
+
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def default_config() -> dict:
|
|
62
|
+
"""Merge ``config.json`` over the shipped default palette."""
|
|
63
|
+
try:
|
|
64
|
+
raw = json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
|
|
65
|
+
except OSError:
|
|
66
|
+
return DEFAULT_CONFIG
|
|
67
|
+
if not isinstance(raw, dict):
|
|
68
|
+
return DEFAULT_CONFIG
|
|
69
|
+
return {**DEFAULT_CONFIG, **raw}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def create_api(context) -> APIRouter:
|
|
73
|
+
"""Entry point invoked by the generated PyAIAUI host.
|
|
74
|
+
|
|
75
|
+
The Instance injects its live objects via ``PyAIAUI.AIAUI.start(services={...})``
|
|
76
|
+
and they arrive here through ``context.services``.
|
|
77
|
+
"""
|
|
78
|
+
router = APIRouter()
|
|
79
|
+
|
|
80
|
+
@router.get("/jobs")
|
|
81
|
+
async def jobs(name: str = "", page: int = 1, pageSize: int = 20):
|
|
82
|
+
"""Job list for the default panel.
|
|
83
|
+
|
|
84
|
+
Each item renders one job card:
|
|
85
|
+
``{ name, lot, state, carriers.input[]/output[], timeline[], recipe }``.
|
|
86
|
+
``state`` drives the chip colour from ``GET /config``'s state list;
|
|
87
|
+
durations between timeline steps are computed by the frontend.
|
|
88
|
+
"""
|
|
89
|
+
repository = context.services.get("job_repository")
|
|
90
|
+
if repository is not None:
|
|
91
|
+
result = repository.list_jobs(name=name, page=page, page_size=pageSize)
|
|
92
|
+
return await result if inspect.isawaitable(result) else result
|
|
93
|
+
|
|
94
|
+
# Fixed demo data; replace when a real repository is injected.
|
|
95
|
+
items = [
|
|
96
|
+
{
|
|
97
|
+
"id": "job-001",
|
|
98
|
+
"name": "ASM3GM-PLUGIN-JOB-001",
|
|
99
|
+
"lot": "LOT-001",
|
|
100
|
+
"state": "Executing",
|
|
101
|
+
"carriers": {"input": [{"code": "CR-01"}, {"code": "CR-02"}], "output": []},
|
|
102
|
+
"recipe": "RCP-CU-ETCH-O7",
|
|
103
|
+
"timeline": [
|
|
104
|
+
{"label": "Setup", "at": "2026-10-18T08:00:00", "state": "done"},
|
|
105
|
+
{"label": "Select", "at": "2026-10-18T08:01:05", "state": "done"},
|
|
106
|
+
{"label": "WaitingForStart", "at": "2026-10-18T08:02:30", "state": "active"},
|
|
107
|
+
{"label": "Executing", "at": "2026-10-18T08:05:12", "state": "pending"},
|
|
108
|
+
{"label": "Complete", "at": None, "state": "pending"},
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"id": "job-002",
|
|
113
|
+
"name": "ASM3GM-PLUGIN-JOB-002",
|
|
114
|
+
"lot": None,
|
|
115
|
+
"state": "Setup",
|
|
116
|
+
"carriers": {"input": [], "output": []},
|
|
117
|
+
"recipe": "RCP-CU-ETCH-O7",
|
|
118
|
+
"timeline": [
|
|
119
|
+
{"label": "Setup", "at": "2026-10-18T08:00:00", "state": "active"},
|
|
120
|
+
{"label": "Select", "at": None, "state": "pending"},
|
|
121
|
+
{"label": "WaitingForStart", "at": None, "state": "pending"},
|
|
122
|
+
{"label": "Executing", "at": None, "state": "pending"},
|
|
123
|
+
{"label": "Complete", "at": None, "state": "pending"},
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
]
|
|
127
|
+
matched = [item for item in items if name.casefold() in item["name"].casefold()]
|
|
128
|
+
start = (page - 1) * pageSize
|
|
129
|
+
return {
|
|
130
|
+
"items": matched[start : start + pageSize],
|
|
131
|
+
"total": len(matched),
|
|
132
|
+
"page": page,
|
|
133
|
+
"pageSize": pageSize,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@router.get("/config")
|
|
137
|
+
async def config():
|
|
138
|
+
"""Panel appearance contract, read from ``config.json`` (sitting next
|
|
139
|
+
to this file). Any missing key is merged over the built-in default
|
|
140
|
+
palette; unreadable/malformed content falls back to the default.
|
|
141
|
+
"""
|
|
142
|
+
return default_config()
|
|
143
|
+
|
|
144
|
+
return router
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"display": {
|
|
3
|
+
"lotId": true,
|
|
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
|
+
}
|