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,18 +1,12 @@
1
1
  <script setup lang="ts">
2
- import type { ModuleContext } from "../contract";
2
+ import type { ModuleSlotProps } from "../contract";
3
3
 
4
- defineProps<{ context: ModuleContext }>();
4
+ defineProps<ModuleSlotProps>();
5
5
  </script>
6
6
 
7
7
  <template>
8
- <section class="aiaji-slot aiaji-recipe">
9
- <div class="aiaji-meta">
10
- <span>WO1 WO2 WO3 W17</span>
11
- <span>42.6 Pa / 620W</span>
12
- </div>
13
- <div class="aiaji-recipe-row">
14
- <span class="aiaji-recipe-name">RCP-CU-ETCH-O7</span>
15
- <button type="button">View all</button>
16
- </div>
17
- </section>
8
+ <div class="legacy-recipe-row">
9
+ <span class="legacy-recipe-label">Recipe</span>
10
+ <span class="legacy-recipe">{{ job.recipe ?? "—" }}</span>
11
+ </div>
18
12
  </template>
@@ -0,0 +1,29 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+ import type { ModuleSlotProps, ModuleStateVisual } from "../contract";
4
+ import { tokenStateIcon } from "./format";
5
+
6
+ const props = defineProps<ModuleSlotProps>();
7
+
8
+ const visual = computed<Partial<ModuleStateVisual>>(() => {
9
+ const hit = props.config.states?.find(
10
+ (s) => s.name.toLowerCase() === (props.job.state ?? "").toLowerCase(),
11
+ );
12
+ return hit ?? { color: null, icon: tokenStateIcon(props.job.state) };
13
+ });
14
+
15
+ const chipStyle = computed<Record<string, string> | undefined>(() => {
16
+ if (!visual.value.color) return undefined;
17
+ return {
18
+ color: visual.value.color,
19
+ borderColor: `color-mix(in srgb, ${visual.value.color} 22%, #ffffff)`,
20
+ background: `color-mix(in srgb, ${visual.value.color} 12%, #ffffff)`,
21
+ };
22
+ });
23
+ </script>
24
+
25
+ <template>
26
+ <span class="legacy-chip" :style="chipStyle" :data-state="job.state ?? ''">
27
+ {{ job.state ?? "—" }}
28
+ </span>
29
+ </template>
@@ -1,22 +1,65 @@
1
1
  <script setup lang="ts">
2
- import type { ModuleContext } from "../contract";
2
+ import { computed } from "vue";
3
+ import Icons from "./Icons.vue";
4
+ import type { ModuleSlotProps, ModuleTimelineStep } from "../contract";
5
+ import { durationLabel, formatTimestamp, tokenStateIcon } from "./format";
3
6
 
4
- defineProps<{ context: ModuleContext }>();
7
+ const props = defineProps<ModuleSlotProps>();
5
8
 
6
- const steps = ["Waiting for Start", "Start", "Executing", "Finished"];
9
+ const steps = computed<ModuleTimelineStep[]>(() => {
10
+ const list = props.job.timeline ?? [];
11
+ return list.length
12
+ ? list
13
+ : [{ label: props.job.state ?? "—", state: "active" as const }];
14
+ });
15
+
16
+ const iconNames = ["clock", "list", "stop", "activity", "flag"] as const;
17
+ type IconName = (typeof iconNames)[number];
18
+
19
+ function iconFor(label: string): IconName {
20
+ const hit = props.config.states?.find(
21
+ (s) => s.name.toLowerCase() === label.toLowerCase(),
22
+ );
23
+ const name = hit?.icon ?? tokenStateIcon(label);
24
+ return (iconNames as readonly string[]).includes(name)
25
+ ? (name as IconName)
26
+ : "clock";
27
+ }
28
+
29
+ function durationBefore(idx: number): string | null {
30
+ if (idx <= 0) return null;
31
+ const prev = steps.value[idx - 1];
32
+ const current = steps.value[idx];
33
+ if (!prev || !current) return null;
34
+ if (prev.state !== "done" || current.state !== "done") return null;
35
+ return durationLabel(prev.at, current.at);
36
+ }
7
37
  </script>
8
38
 
9
39
  <template>
10
- <section class="aiaji-slot aiaji-timeline">
11
- <h3>Progress</h3>
12
- <ol>
13
- <li
14
- v-for="(step, index) in steps"
15
- :key="step"
16
- :class="{ 'aiaji-active': index === 0 }"
40
+ <ol class="legacy-timeline" :aria-label="`Timeline for ${job.name}`">
41
+ <li
42
+ v-for="(step, idx) in steps"
43
+ :key="step.label"
44
+ class="legacy-step"
45
+ :class="`legacy-step-${step.state ?? 'pending'}`"
46
+ >
47
+ <span v-if="durationBefore(idx)" class="legacy-step-duration">
48
+ {{ durationBefore(idx) }}
49
+ </span>
50
+ <span class="legacy-step-symbol">
51
+ <Icons :name="iconFor(step.label)" :size="16" />
52
+ </span>
53
+ <span class="legacy-step-label">{{ step.label }}</span>
54
+ <span
55
+ class="legacy-step-time"
56
+ :class="{ 'legacy-step-time-hoverable': !!step.at }"
17
57
  >
18
- {{ step }}
19
- </li>
20
- </ol>
21
- </section>
58
+ {{ step.at ? formatTimestamp(step.at).time : "—" }}
59
+ <span v-if="step.at" class="legacy-step-popover">
60
+ {{ formatTimestamp(step.at).date }} {{ formatTimestamp(step.at).time }}
61
+ </span>
62
+ </span>
63
+ </li>
64
+ </ol>
22
65
  </template>
@@ -0,0 +1,36 @@
1
+ /** Fixed demo jobs used when `GET /jobs` is unreachable. */
2
+
3
+ import type { ModuleJobRecord } from "../contract";
4
+
5
+ export const DEMO_JOBS: ModuleJobRecord[] = [
6
+ {
7
+ id: "job-001",
8
+ name: "ASM3GM-PLUGIN-JOB-001",
9
+ lot: "LOT-001",
10
+ state: "Executing",
11
+ carriers: { input: [{ code: "CR-01" }, { code: "CR-02" }], output: [] },
12
+ recipe: "RCP-CU-ETCH-O7",
13
+ timeline: [
14
+ { label: "Setup", at: "2026-10-18T08:00:00", state: "done" },
15
+ { label: "Select", at: "2026-10-18T08:01:05", state: "done" },
16
+ { label: "WaitingForStart", at: "2026-10-18T08:02:30", state: "active" },
17
+ { label: "Executing", at: "2026-10-18T08:05:12", state: "pending" },
18
+ { label: "Complete", at: null, state: "pending" },
19
+ ],
20
+ },
21
+ {
22
+ id: "job-002",
23
+ name: "ASM3GM-PLUGIN-JOB-002",
24
+ lot: null,
25
+ state: "Setup",
26
+ carriers: { input: [], output: [] },
27
+ recipe: "RCP-CU-ETCH-O7",
28
+ timeline: [
29
+ { label: "Setup", at: "2026-10-18T08:00:00", state: "active" },
30
+ { label: "Select", at: null, state: "pending" },
31
+ { label: "WaitingForStart", at: null, state: "pending" },
32
+ { label: "Executing", at: null, state: "pending" },
33
+ { label: "Complete", at: null, state: "pending" },
34
+ ],
35
+ },
36
+ ];
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Template defaults — shown unless `GET /config` says otherwise.
3
+ * Backend-serving only what it wants to change is fine: absent keys fall back here.
4
+ * `states[].color` is the accent used for text/border background.
5
+ */
6
+
7
+ import type { ModuleConfig } from "../contract";
8
+
9
+ export const configDefaults: Required<{
10
+ display: NonNullable<ModuleConfig["display"]>;
11
+ states: NonNullable<ModuleConfig["states"]>;
12
+ }> = {
13
+ display: {
14
+ lotId: true,
15
+ recipe: true,
16
+ inputCarriers: true,
17
+ outputCarriers: true,
18
+ timeline: true,
19
+ },
20
+ states: [
21
+ { name: "Setup", color: "#0369a1", icon: "clock" },
22
+ { name: "Select", color: "#526176", icon: "list" },
23
+ { name: "WaitingForStart", color: "#8b95a5", icon: "stop" },
24
+ { name: "Executing", color: "#1d4ed8", icon: "activity" },
25
+ { name: "Running", color: "#1d4ed8", icon: "activity" },
26
+ { name: "Complete", color: "#166534", icon: "flag" },
27
+ { name: "Finished", color: "#166534", icon: "flag" },
28
+ ],
29
+ };
30
+
31
+ export function mergeConfig(raw: Partial<ModuleConfig> | null | undefined): ModuleConfig {
32
+ return {
33
+ display: { ...configDefaults.display, ...(raw?.display ?? {}) },
34
+ states: raw?.states?.length ? raw.states : configDefaults.states,
35
+ };
36
+ }
@@ -0,0 +1,51 @@
1
+ /** Small formatting helpers shared by the built-in slots. */
2
+
3
+ /** Parse "HH:MM:SS" or any time-like string into seconds (0 on failure). */
4
+ export function secondsOf(time: string | null | undefined): number {
5
+ if (!time) return 0;
6
+ const parts = time.split(":").map(Number);
7
+ if (parts.some((n) => Number.isNaN(n))) return Number.NaN;
8
+ const [h, m, s] = [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
9
+ return h * 3600 + m * 60 + s;
10
+ }
11
+
12
+ /** Human-readable duration between two step timestamps; null when unavailable/long negative. */
13
+ export function durationLabel(from: string | null | undefined, to: string | null | undefined): string | null {
14
+ if (!from || !to) return null;
15
+ const a = new Date(from);
16
+ const b = new Date(to);
17
+ if (Number.isNaN(a.getTime()) || Number.isNaN(b.getTime())) return null;
18
+ const delta = Math.round((b.getTime() - a.getTime()) / 1000);
19
+ if (delta <= 0) return null;
20
+ if (delta < 60) return `${delta} s`;
21
+ const minutes = Math.floor(delta / 60);
22
+ if (minutes < 60) {
23
+ const seconds = delta % 60;
24
+ return seconds ? `${minutes} m ${seconds} s` : `${minutes} m`;
25
+ }
26
+ return `${Math.floor(minutes / 60)} h ${minutes % 60} m`;
27
+ }
28
+
29
+ /** ISO → { time: "08:00:00", date: "10-18" }. */
30
+ export function formatTimestamp(
31
+ value: string | null | undefined,
32
+ ): { time: string; date: string | null } {
33
+ if (!value) return { time: "—", date: null };
34
+ const date = new Date(value);
35
+ if (Number.isNaN(date.getTime())) return { time: String(value), date: null };
36
+ const pad = (n: number) => String(n).padStart(2, "0");
37
+ return {
38
+ time: `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`,
39
+ date: `${pad(date.getMonth() + 1)}-${pad(date.getDate())}`,
40
+ };
41
+ }
42
+
43
+ /** Fallback icon name when a state has no visual mapping. */
44
+ export function tokenStateIcon(label: string | null | undefined): string {
45
+ const v = (label ?? "").toLowerCase();
46
+ if (v.includes("complete") || v.includes("finish")) return "flag";
47
+ if (v.includes("waiting")) return "stop";
48
+ if (v.includes("select") || v.includes("select")) return "list";
49
+ if (v.includes("exec") || v.includes("run") || v.includes("active")) return "activity";
50
+ return "clock";
51
+ }
@@ -1,16 +1,26 @@
1
1
  import type { SlotMap } from "../contract";
2
- import Header from "./Header.vue";
2
+ import JobCard from "./JobCard.vue";
3
+ import JobHeader from "./JobHeader.vue";
4
+ import JobName from "./JobName.vue";
5
+ import LotId from "./LotId.vue";
6
+ import Status from "./Status.vue";
7
+ import JobMiddle from "./JobMiddle.vue";
3
8
  import InputCarriers from "./InputCarriers.vue";
4
- import OutputCarriers from "./OutputCarriers.vue";
5
9
  import Timeline from "./Timeline.vue";
10
+ import OutputCarriers from "./OutputCarriers.vue";
11
+ import JobBottom from "./JobBottom.vue";
6
12
  import Recipe from "./Recipe.vue";
7
13
 
8
14
  export const defaultSlots: SlotMap = {
9
- header: Header,
15
+ board: JobCard,
16
+ headerRegion: JobHeader,
17
+ jobName: JobName,
18
+ lotId: LotId,
19
+ status: Status,
20
+ middleRegion: JobMiddle,
10
21
  inputCarriers: InputCarriers,
11
- outputCarriers: OutputCarriers,
12
22
  timeline: Timeline,
23
+ outputCarriers: OutputCarriers,
24
+ bottomRegion: JobBottom,
13
25
  recipe: Recipe,
14
26
  };
15
-
16
- export const slotOrder = ["header", "inputCarriers", "outputCarriers", "timeline", "recipe"];