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
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
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";
|
|
6
|
+
|
|
7
|
+
const props = defineProps<ModuleSlotProps>();
|
|
8
|
+
|
|
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
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<template>
|
|
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 }"
|
|
57
|
+
>
|
|
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>
|
|
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
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { SlotMap } from "../contract";
|
|
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";
|
|
8
|
+
import InputCarriers from "./InputCarriers.vue";
|
|
9
|
+
import Timeline from "./Timeline.vue";
|
|
10
|
+
import OutputCarriers from "./OutputCarriers.vue";
|
|
11
|
+
import JobBottom from "./JobBottom.vue";
|
|
12
|
+
import Recipe from "./Recipe.vue";
|
|
13
|
+
|
|
14
|
+
export const defaultSlots: SlotMap = {
|
|
15
|
+
board: JobCard,
|
|
16
|
+
headerRegion: JobHeader,
|
|
17
|
+
jobName: JobName,
|
|
18
|
+
lotId: LotId,
|
|
19
|
+
status: Status,
|
|
20
|
+
middleRegion: JobMiddle,
|
|
21
|
+
inputCarriers: InputCarriers,
|
|
22
|
+
timeline: Timeline,
|
|
23
|
+
outputCarriers: OutputCarriers,
|
|
24
|
+
bottomRegion: JobBottom,
|
|
25
|
+
recipe: Recipe,
|
|
26
|
+
};
|