create-aiaui-module 0.1.0 → 0.1.2-TEST

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 (33) hide show
  1. package/README.md +84 -17
  2. package/bin/create-aiaui-module.js +86 -22
  3. package/package.json +23 -6
  4. package/runtime/__init__.py +71 -28
  5. package/runtime/_runtime/host.py +7 -15
  6. package/templates/HelloWorld/README.md +9 -1
  7. package/templates/JobInformation/CUSTOMIZATION.md +98 -148
  8. package/templates/JobInformation/README.md +17 -3
  9. package/templates/JobInformation/api.py +64 -20
  10. package/templates/JobInformation/resources/dist/modules.js +1548 -954
  11. package/templates/JobInformation/resources/src/contract.ts +109 -37
  12. package/templates/JobInformation/resources/src/customization.ts +15 -10
  13. package/templates/JobInformation/resources/src/defaults/DefaultPanel.vue +122 -15
  14. package/templates/JobInformation/resources/src/defaults/Icons.vue +49 -0
  15. package/templates/JobInformation/resources/src/defaults/InputCarriers.vue +12 -9
  16. package/templates/JobInformation/resources/src/defaults/JobBottom.vue +32 -0
  17. package/templates/JobInformation/resources/src/defaults/JobCard.vue +48 -0
  18. package/templates/JobInformation/resources/src/defaults/JobHeader.vue +41 -0
  19. package/templates/JobInformation/resources/src/defaults/JobMiddle.vue +41 -0
  20. package/templates/JobInformation/resources/src/defaults/JobName.vue +9 -0
  21. package/templates/JobInformation/resources/src/defaults/LotId.vue +14 -0
  22. package/templates/JobInformation/resources/src/defaults/OutputCarriers.vue +15 -8
  23. package/templates/JobInformation/resources/src/defaults/Recipe.vue +6 -12
  24. package/templates/JobInformation/resources/src/defaults/Status.vue +29 -0
  25. package/templates/JobInformation/resources/src/defaults/Timeline.vue +57 -14
  26. package/templates/JobInformation/resources/src/defaults/board-data.ts +36 -0
  27. package/templates/JobInformation/resources/src/defaults/config-defaults.ts +36 -0
  28. package/templates/JobInformation/resources/src/defaults/format.ts +51 -0
  29. package/templates/JobInformation/resources/src/defaults/slots.ts +16 -6
  30. package/templates/JobInformation/resources/src/defaults/styles.css +576 -47
  31. package/templates/JobInformation/resources/src/index.ts +10 -18
  32. package/templates/JobInformation/resources/src/bus.ts +0 -38
  33. package/templates/JobInformation/resources/src/defaults/Header.vue +0 -12
@@ -1,173 +1,123 @@
1
1
  # JobInformation customization
2
2
 
3
- This module ships with a built-in panel and exposes three customization
4
- levels. All of them keep the host-facing `mount(container, context)` /
5
- `unmount()` contract stable, so the Instance does not need to know which
6
- level you chose.
7
-
8
- ## Customization entry
9
-
10
- Edit `resources/src/customization.ts`. It exports a single `customization`
11
- object:
12
-
13
- ```ts
14
- import type { Customization } from "./contract";
15
-
16
- export const customization: Customization = { mode: "slots", slots: {} };
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
+ }
17
35
  ```
18
36
 
19
- ## Level 1 — default
20
-
21
- Leave `customization.ts` untouched. The module renders the built-in panel
22
- from `resources/src/defaults/`.
23
-
24
- ## Level 2 — partial (replace boxes)
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
+ ```
25
60
 
26
- Set `mode: "slots"` and provide a `slots` map. Keys are the built-in slot
27
- names; values are Vue components that receive `context` as a prop.
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 `resources/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.
28
90
 
29
91
  ```ts
30
- import MyHeader from "./custom/MyHeader.vue";
92
+ // resources/src/customization.ts
93
+ import MyJobName from "./custom/MyJobName.vue";
31
94
  import type { Customization } from "./contract";
32
95
 
33
96
  export const customization: Customization = {
34
- mode: "slots",
35
- slots: { header: MyHeader },
97
+ slots: { jobName: MyJobName },
36
98
  };
37
99
  ```
38
100
 
39
- ### Built-in slots
40
-
41
- - `header` — job/lot identity and current state
42
- - `inputCarriers` — input carriers
43
- - `outputCarriers` — output carriers
44
- - `timeline` — progress steps
45
- - `recipe` — recipe/tool summary and actions
46
-
47
- ### Slot component contract
101
+ Slot contract:
48
102
 
49
103
  ```vue
50
104
  <script setup lang="ts">
51
- import type { ModuleContext } from "../contract";
105
+ import type { ModuleSlotProps } from "../contract";
52
106
 
53
- defineProps<{ context: ModuleContext }>();
107
+ defineProps<ModuleSlotProps>();
54
108
  </script>
55
-
56
109
  <template>
57
- <section class="aiaji-slot">…</section>
110
+ <span class="my-custom">— {{ job.state }} · {{ job.name }} —</span>
58
111
  </template>
59
112
  ```
60
113
 
61
- `context.api.call(method, path, options)` reaches this module's backend
62
- router. `context.signal` is aborted when the host unmounts the module, so
63
- long-running requests should observe it.
64
-
65
-
66
- ## Communicating between slots
67
-
68
- Every slot component in one mount — built-in or custom — receives the same
69
- `context` object. On top of `api` and `signal`, `context` carries a **bus**
70
- created fresh on each `mount` and dead on `unmount` (so switching Instance
71
- gets you a clean bus automatically). It never crosses module or Instance
72
- boundaries.
73
-
74
- ### 1. Events (recommended for most cases)
75
-
76
- Decoupled one-to-many communication:
77
-
78
- ```ts
79
- // MyInput.vue — sender
80
- const props = defineProps<{ context: ModuleContext }>();
81
-
82
- function onSelect(id: string) {
83
- props.context.bus.emit("lot:selected", { id });
84
- }
85
-
86
- onBeforeUnmount(() => props.context.bus.off("lot:selected", onSelect));
87
- ```
88
-
89
- ```ts
90
- // MyOutput.vue — receiver
91
- const props = defineProps<{ context: ModuleContext }>();
92
-
93
- onMounted(() =>
94
- props.context.bus.on("lot:selected", (payload) => {
95
- // `payload` is whatever the emitter passed; type it locally if you like
96
- console.log("lot picked:", payload?.id);
97
- }),
98
- );
99
- onBeforeUnmount(() => props.context.bus.off("lot:selected", someHandler));
100
- ```
101
-
102
- - `emit` with no listeners is a no-op (safe).
103
- - Always `off` what you `on` in `onBeforeUnmount`, or old listeners fire on remount.
104
- - Type the payload where you need it; the bus signature is loose by design.
105
-
106
- ### 2. Imperative handles (when you must call into a sibling)
114
+ ## Rebuild
107
115
 
108
- For `expose/peer` — e.g. OutputCarriers telling InputCarriers to focus a lot:
109
-
110
- ```ts
111
- // MyInput.vue — publisher
112
- const handle = {
113
- focusLot(id: string) {
114
- /* imperative action inside InputCarriers */
115
- },
116
- };
117
- onMounted(() => props.context.bus.expose("inputCarriers", handle));
118
- onUnmounted(() => props.context.bus.release("inputCarriers"));
116
+ ```bash
117
+ cd resources
118
+ npm install
119
+ npm run build
119
120
  ```
120
121
 
121
- ```ts
122
- // MyOutput.vue — caller
123
- props.context.bus
124
- .peer<{ focusLot(id: string): void }>("inputCarriers")
125
- ?.focusLot("CR-01"); // always `?.` — peer returns undefined if the slot
126
- // was replaced away or hasn't mounted yet
127
- ```
128
-
129
- ### 3. Module-scope shared state (no bus needed)
130
-
131
- For pure data sync between two custom slots, a `reactive()` in a
132
- module-scope file also works with zero coupling:
133
-
134
- ```ts
135
- // resources/src/custom/shared.ts
136
- import { reactive } from "vue";
137
- export const shared = reactive({ selectedLot: null as string | null });
138
- ```
139
-
140
- ```ts
141
- // both MyInput.vue and MyOutput.vue
142
- import { shared } from "./shared";
143
- shared.selectedLot = "CR-01"; // set in one, read in the other
144
- ```
145
-
146
- Rule of thumb: defaults → events; only reach for `expose/peer` when the
147
- caller genuinely needs the peer's imperative API and accepts a
148
- not-yet-mounted/null risk.
149
-
150
-
151
- ## Level 3 — full override
152
-
153
- Set `mode: "full"` and implement both entry points. The shell skips the
154
- built-in panel entirely.
155
-
156
- ```ts
157
- export const customization: Customization = {
158
- mode: "full",
159
- mount(container, context) {
160
- // render anything you want into `container`
161
- },
162
- unmount() {
163
- // tear down your renderer
164
- },
165
- };
166
- ```
167
-
168
- ## Styling
169
-
170
- The built-in styles are bundled into `modules.js`, so no separate CSS asset
171
- is required. Slot components can reuse the built-in `aiaji-*` classes and the
172
- `--aia-*` CSS variables (`--aia-text`, `--aia-border`, `--aia-accent`), or
173
- bring their own inline styles.
122
+ For quick iteration, `npm run dev` runs `vite build --watch` and keeps
123
+ `dist/manifest.json` valid on every rebuild.
@@ -28,8 +28,16 @@ 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
43
  - `resources/src/index.ts` — the stable `mount` / `unmount` bridge the host loads.
@@ -40,6 +48,11 @@ The frontend is organized around a small customization contract:
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.
@@ -84,16 +97,17 @@ For quick iteration, `npm run dev` runs `vite build --watch` and keeps
84
97
  Call the generated entry point from your Instance's own startup method:
85
98
 
86
99
  ```python
87
- from PyAIAUI import start, stop
100
+ from PyAIAUI import AIAUI
88
101
 
89
102
 
90
- host = start(services={
103
+ aiaui = AIAUI.start(services={
91
104
  "secs_server": secs_server,
92
105
  "job_repository": job_repository,
93
106
  })
107
+ # aiaui is None when no modules are enabled
94
108
 
95
109
  # ... later, during shutdown ...
96
- stop()
110
+ aiaui.stop()
97
111
  ```
98
112
 
99
113
  `start()` returns `None` when `JobInformation` is disabled in
@@ -8,38 +8,57 @@ from fastapi import APIRouter
8
8
  def create_api(context) -> APIRouter:
9
9
  """Entry point invoked by the generated PyAIAUI host.
10
10
 
11
- The Instance injects its live objects via ``PyAIAUI.start(services={...})``
11
+ The Instance injects its live objects via ``PyAIAUI.AIAUI.start(services={...})``
12
12
  and they arrive here through ``context.services``.
13
13
  """
14
14
  router = APIRouter()
15
15
 
16
- @router.get("/server")
17
- async def server():
18
- secs_server = context.services.get("secs_server")
19
- if secs_server is None:
20
- return {"available": False, "name": None, "connected": None}
21
- driver = getattr(secs_server, "driver", None)
22
- check = getattr(driver, "is_connected", None)
23
- connected = check() if callable(check) else None
24
- if inspect.isawaitable(connected):
25
- connected = await connected
26
- return {
27
- "available": True,
28
- "name": getattr(secs_server, "name", None),
29
- "connected": connected,
30
- }
31
-
32
16
  @router.get("/jobs")
33
17
  async def jobs(name: str = "", page: int = 1, pageSize: int = 20):
18
+ """Job list for the default panel.
19
+
20
+ Each item renders one job card:
21
+ ``{ name, lot, state, carriers.input[]/output[], timeline[], recipe }``.
22
+ ``state`` drives the chip colour from ``GET /config``'s state list;
23
+ durations between timeline steps are computed by the frontend.
24
+ """
34
25
  repository = context.services.get("job_repository")
35
26
  if repository is not None:
36
27
  result = repository.list_jobs(name=name, page=page, page_size=pageSize)
37
28
  return await result if inspect.isawaitable(result) else result
38
29
 
39
- # Sample data is used only until the Instance injects a repository.
30
+ # Fixed demo data; replace when a real repository is injected.
40
31
  items = [
41
- {"id": "demo-1", "name": "DEMO-JOB-001", "state": "Running"},
42
- {"id": "demo-2", "name": "DEMO-JOB-002", "state": "Queued"},
32
+ {
33
+ "id": "job-001",
34
+ "name": "ASM3GM-PLUGIN-JOB-001",
35
+ "lot": "LOT-001",
36
+ "state": "Executing",
37
+ "carriers": {"input": [{"code": "CR-01"}, {"code": "CR-02"}], "output": []},
38
+ "recipe": "RCP-CU-ETCH-O7",
39
+ "timeline": [
40
+ {"label": "Setup", "at": "2026-10-18T08:00:00", "state": "done"},
41
+ {"label": "Select", "at": "2026-10-18T08:01:05", "state": "done"},
42
+ {"label": "WaitingForStart", "at": "2026-10-18T08:02:30", "state": "active"},
43
+ {"label": "Executing", "at": "2026-10-18T08:05:12", "state": "pending"},
44
+ {"label": "Complete", "at": None, "state": "pending"},
45
+ ],
46
+ },
47
+ {
48
+ "id": "job-002",
49
+ "name": "ASM3GM-PLUGIN-JOB-002",
50
+ "lot": None,
51
+ "state": "Setup",
52
+ "carriers": {"input": [], "output": []},
53
+ "recipe": "RCP-CU-ETCH-O7",
54
+ "timeline": [
55
+ {"label": "Setup", "at": "2026-10-18T08:00:00", "state": "active"},
56
+ {"label": "Select", "at": None, "state": "pending"},
57
+ {"label": "WaitingForStart", "at": None, "state": "pending"},
58
+ {"label": "Executing", "at": None, "state": "pending"},
59
+ {"label": "Complete", "at": None, "state": "pending"},
60
+ ],
61
+ },
43
62
  ]
44
63
  matched = [item for item in items if name.casefold() in item["name"].casefold()]
45
64
  start = (page - 1) * pageSize
@@ -50,4 +69,29 @@ def create_api(context) -> APIRouter:
50
69
  "pageSize": pageSize,
51
70
  }
52
71
 
72
+ @router.get("/config")
73
+ async def config():
74
+ """Panel appearance contract: which parts to display + the state
75
+ colour/icon map. The template already ships a built-in default copy
76
+ (``config-defaults.ts``), so this endpoint can stay absent.
77
+ """
78
+ return {
79
+ "display": {
80
+ "lotId": True,
81
+ "recipe": True,
82
+ "inputCarriers": True,
83
+ "outputCarriers": True,
84
+ "timeline": True,
85
+ },
86
+ "states": [
87
+ {"name": "Setup", "color": "#0369a1", "icon": "clock"},
88
+ {"name": "Select", "color": "#526176", "icon": "list"},
89
+ {"name": "WaitingForStart", "color": "#8b95a5", "icon": "stop"},
90
+ {"name": "Executing", "color": "#1d4ed8", "icon": "activity"},
91
+ {"name": "Running", "color": "#1d4ed8", "icon": "activity"},
92
+ {"name": "Complete", "color": "#166534", "icon": "flag"},
93
+ {"name": "Finished", "color": "#166534", "icon": "flag"},
94
+ ],
95
+ }
96
+
53
97
  return router