@ryanfw/prompt-orchestration-pipeline 0.5.0 → 0.7.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.
Files changed (67) hide show
  1. package/README.md +1 -2
  2. package/package.json +1 -2
  3. package/src/api/validators/json.js +39 -0
  4. package/src/components/DAGGrid.jsx +392 -303
  5. package/src/components/JobCard.jsx +14 -12
  6. package/src/components/JobDetail.jsx +54 -51
  7. package/src/components/JobTable.jsx +72 -23
  8. package/src/components/Layout.jsx +145 -42
  9. package/src/components/LiveText.jsx +47 -0
  10. package/src/components/PageSubheader.jsx +75 -0
  11. package/src/components/TaskDetailSidebar.jsx +216 -0
  12. package/src/components/TimerText.jsx +82 -0
  13. package/src/components/UploadSeed.jsx +0 -70
  14. package/src/components/ui/Logo.jsx +16 -0
  15. package/src/components/ui/RestartJobModal.jsx +140 -0
  16. package/src/components/ui/toast.jsx +138 -0
  17. package/src/config/models.js +322 -0
  18. package/src/config/statuses.js +119 -0
  19. package/src/core/config.js +4 -34
  20. package/src/core/file-io.js +13 -28
  21. package/src/core/module-loader.js +54 -40
  22. package/src/core/pipeline-runner.js +65 -26
  23. package/src/core/status-writer.js +213 -58
  24. package/src/core/symlink-bridge.js +57 -0
  25. package/src/core/symlink-utils.js +94 -0
  26. package/src/core/task-runner.js +321 -437
  27. package/src/llm/index.js +258 -86
  28. package/src/pages/Code.jsx +351 -0
  29. package/src/pages/PipelineDetail.jsx +124 -15
  30. package/src/pages/PromptPipelineDashboard.jsx +20 -88
  31. package/src/providers/anthropic.js +83 -69
  32. package/src/providers/base.js +52 -0
  33. package/src/providers/deepseek.js +20 -21
  34. package/src/providers/gemini.js +226 -0
  35. package/src/providers/openai.js +36 -106
  36. package/src/providers/zhipu.js +136 -0
  37. package/src/ui/client/adapters/job-adapter.js +42 -28
  38. package/src/ui/client/api.js +134 -0
  39. package/src/ui/client/hooks/useJobDetailWithUpdates.js +65 -179
  40. package/src/ui/client/index.css +15 -0
  41. package/src/ui/client/index.html +2 -1
  42. package/src/ui/client/main.jsx +19 -14
  43. package/src/ui/client/time-store.js +161 -0
  44. package/src/ui/config-bridge.js +15 -24
  45. package/src/ui/config-bridge.node.js +15 -24
  46. package/src/ui/dist/assets/{index-CxcrauYR.js → index-DqkbzXZ1.js} +2132 -1086
  47. package/src/ui/dist/assets/style-DBF9NQGk.css +62 -0
  48. package/src/ui/dist/index.html +4 -3
  49. package/src/ui/job-reader.js +0 -108
  50. package/src/ui/public/favicon.svg +12 -0
  51. package/src/ui/server.js +252 -0
  52. package/src/ui/sse-enhancer.js +0 -1
  53. package/src/ui/transformers/list-transformer.js +32 -12
  54. package/src/ui/transformers/status-transformer.js +29 -42
  55. package/src/utils/dag.js +8 -4
  56. package/src/utils/duration.js +13 -19
  57. package/src/utils/formatters.js +27 -0
  58. package/src/utils/geometry-equality.js +83 -0
  59. package/src/utils/pipelines.js +5 -1
  60. package/src/utils/time-utils.js +40 -0
  61. package/src/utils/token-cost-calculator.js +294 -0
  62. package/src/utils/ui.jsx +18 -20
  63. package/src/components/ui/select.jsx +0 -27
  64. package/src/lib/utils.js +0 -6
  65. package/src/ui/client/hooks/useTicker.js +0 -26
  66. package/src/ui/config-bridge.browser.js +0 -149
  67. package/src/ui/dist/assets/style-D6K_oQ12.css +0 -62
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Duration policy utilities for consistent time display across components
3
3
  */
4
+ import { TaskState, normalizeTaskState } from "../config/statuses.js";
4
5
 
5
6
  /**
6
7
  * Normalizes task state names to canonical values
@@ -8,21 +9,15 @@
8
9
  * @returns {string} Normalized state
9
10
  */
10
11
  export function normalizeState(state) {
11
- switch (state) {
12
- case "done":
13
- return "completed";
14
- case "failed":
15
- case "error":
16
- return "error";
17
- case "pending":
18
- case "running":
19
- case "current":
20
- case "completed":
21
- case "rejected":
22
- return state;
23
- default:
24
- return state; // Pass through unknown states
12
+ // Use centralized normalization, then map to duration-specific canonical forms
13
+ const canonicalState = normalizeTaskState(state);
14
+
15
+ // Duration utilities use "completed" instead of "done" for legacy compatibility
16
+ if (canonicalState === TaskState.DONE) {
17
+ return "completed";
25
18
  }
19
+
20
+ return canonicalState;
26
21
  }
27
22
 
28
23
  /**
@@ -36,18 +31,17 @@ export function taskDisplayDurationMs(task, now = Date.now()) {
36
31
  const normalizedState = normalizeState(state);
37
32
 
38
33
  switch (normalizedState) {
39
- case "pending":
34
+ case TaskState.PENDING:
40
35
  return 0;
41
36
 
42
- case "running":
43
- case "current":
37
+ case TaskState.RUNNING:
44
38
  if (!startedAt) {
45
39
  return 0;
46
40
  }
47
41
  const startTime = Date.parse(startedAt);
48
42
  return Math.max(0, now - startTime);
49
43
 
50
- case "completed":
44
+ case "completed": // Duration utilities still use "completed" for legacy compatibility
51
45
  // Prefer executionTimeMs or executionTime if available, even without startedAt
52
46
  const execTime =
53
47
  executionTimeMs != null ? executionTimeMs : executionTime;
@@ -63,7 +57,7 @@ export function taskDisplayDurationMs(task, now = Date.now()) {
63
57
  const endTime = endedAt ? Date.parse(endedAt) : now;
64
58
  return Math.max(0, endTime - completedStartTime);
65
59
 
66
- case "rejected":
60
+ case TaskState.FAILED:
67
61
  return 0;
68
62
 
69
63
  default:
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Format currency with 4 decimal places, trimming trailing zeros
3
+ * @param {number} x - The number to format
4
+ * @returns {string} Formatted currency string
5
+ */
6
+ export function formatCurrency4(x) {
7
+ if (typeof x !== "number" || x === 0) return "$0.0000";
8
+ const formatted = x.toFixed(4);
9
+ // Trim trailing zeros and unnecessary decimal point
10
+ return `$${formatted.replace(/\.?0+$/, "")}`;
11
+ }
12
+
13
+ /**
14
+ * Format tokens in compact notation (k, M suffixes)
15
+ * @param {number} n - The number of tokens to format
16
+ * @returns {string} Formatted tokens string
17
+ */
18
+ export function formatTokensCompact(n) {
19
+ if (typeof n !== "number" || n === 0) return "0 tok";
20
+
21
+ if (n >= 1000000) {
22
+ return `${(n / 1000000).toFixed(1).replace(/\.0$/, "")}M tokens`;
23
+ } else if (n >= 1000) {
24
+ return `${(n / 1000).toFixed(1).replace(/\.0$/, "")}k tokens`;
25
+ }
26
+ return `${n} tokens`;
27
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Compare two geometry snapshots for layout-relevant changes using tolerance.
3
+ * @param {Object} prev - Previous geometry snapshot
4
+ * @param {Object} next - New geometry snapshot
5
+ * @param {number} epsilon - Tolerance in pixels for floating-point differences (default: 0.5)
6
+ * @returns {boolean} true if geometries are effectively equal for rendering purposes
7
+ */
8
+ export function areGeometriesEqual(prev, next, epsilon = 0.5) {
9
+ // Strict equality shortcut
10
+ if (prev === next) return true;
11
+ if (!prev || !next) return false;
12
+
13
+ // Compare top-level scalars
14
+ if (prev.itemsLength !== next.itemsLength) return false;
15
+ if (prev.effectiveCols !== next.effectiveCols) return false;
16
+
17
+ // Compare overlay box numeric fields only (DOMRect may have non-numeric props)
18
+ if (!areOverlayBoxesEqual(prev.overlayBox, next.overlayBox, epsilon)) {
19
+ return false;
20
+ }
21
+
22
+ // Compare boxes array length and each box's layout-relevant fields
23
+ const prevBoxes = prev.boxes;
24
+ const nextBoxes = next.boxes;
25
+ if (prevBoxes.length !== nextBoxes.length) return false;
26
+
27
+ for (let i = 0; i < prevBoxes.length; i++) {
28
+ if (!areBoxesEqual(prevBoxes[i], nextBoxes[i], epsilon)) {
29
+ return false;
30
+ }
31
+ }
32
+
33
+ return true;
34
+ }
35
+
36
+ /**
37
+ * Compare overlay box numeric fields with tolerance.
38
+ * @param {DOMRect|Object} a
39
+ * @param {DOMRect|Object} b
40
+ * @param {number} epsilon
41
+ * @returns {boolean}
42
+ */
43
+ function areOverlayBoxesEqual(a, b, epsilon) {
44
+ return (
45
+ areNumbersClose(a.left, b.left, epsilon) &&
46
+ areNumbersClose(a.top, b.top, epsilon) &&
47
+ areNumbersClose(a.width, b.width, epsilon) &&
48
+ areNumbersClose(a.height, b.height, epsilon) &&
49
+ areNumbersClose(a.right, b.right, epsilon) &&
50
+ areNumbersClose(a.bottom, b.bottom, epsilon)
51
+ );
52
+ }
53
+
54
+ /**
55
+ * Compare individual card box layout fields with tolerance.
56
+ * @param {Object} a - box object with left/top/width/height/right/bottom/headerMidY
57
+ * @param {Object} b - box object with same shape
58
+ * @param {number} epsilon
59
+ * @returns {boolean}
60
+ */
61
+ function areBoxesEqual(a, b, epsilon) {
62
+ if (!a || !b) return a === b;
63
+ return (
64
+ areNumbersClose(a.left, b.left, epsilon) &&
65
+ areNumbersClose(a.top, b.top, epsilon) &&
66
+ areNumbersClose(a.width, b.width, epsilon) &&
67
+ areNumbersClose(a.height, b.height, epsilon) &&
68
+ areNumbersClose(a.right, b.right, epsilon) &&
69
+ areNumbersClose(a.bottom, b.bottom, epsilon) &&
70
+ areNumbersClose(a.headerMidY, b.headerMidY, epsilon)
71
+ );
72
+ }
73
+
74
+ /**
75
+ * Numeric comparison with tolerance.
76
+ * @param {number} a
77
+ * @param {number} b
78
+ * @param {number} epsilon
79
+ * @returns {boolean}
80
+ */
81
+ function areNumbersClose(a, b, epsilon) {
82
+ return Math.abs(a - b) <= epsilon;
83
+ }
@@ -33,8 +33,12 @@ export function derivePipelineMetadata(source = {}) {
33
33
  ? pipelineSlugFromSource
34
34
  : null);
35
35
 
36
+ // Also return string pipeline value directly if it's a string
37
+ const stringPipeline =
38
+ typeof pipelineValue === "string" ? pipelineValue : null;
39
+
36
40
  return {
37
- pipeline,
41
+ pipeline: pipeline || stringPipeline,
38
42
  pipelineSlug:
39
43
  typeof pipelineSlugFromSource === "string"
40
44
  ? pipelineSlugFromSource
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Time utilities for handling timestamp conversions
3
+ */
4
+
5
+ /**
6
+ * Converts a timestamp string or number to milliseconds since epoch
7
+ * @param {string|number|null|undefined} timestamp - ISO string, milliseconds, or null/undefined
8
+ * @returns {number|null} Milliseconds since epoch, or null if input is invalid
9
+ */
10
+ export function toMilliseconds(timestamp) {
11
+ if (timestamp === null || timestamp === undefined) {
12
+ return null;
13
+ }
14
+
15
+ // If it's already a number, return as-is
16
+ if (typeof timestamp === "number") {
17
+ return isNaN(timestamp) ? null : timestamp;
18
+ }
19
+
20
+ // If it's a string, try to parse it as an ISO date
21
+ if (typeof timestamp === "string") {
22
+ const parsed = Date.parse(timestamp);
23
+ return isNaN(parsed) ? null : parsed;
24
+ }
25
+
26
+ // Invalid type
27
+ return null;
28
+ }
29
+
30
+ /**
31
+ * Safely converts startedAt/endedAt timestamps for TimerText components
32
+ * @param {Object} task - Task object with startedAt and/or endedAt
33
+ * @returns {Object} Object with startMs and endMs as numbers or null
34
+ */
35
+ export function taskToTimerProps(task) {
36
+ return {
37
+ startMs: toMilliseconds(task?.startedAt),
38
+ endMs: toMilliseconds(task?.endedAt),
39
+ };
40
+ }
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Token usage and cost calculation utilities
3
+ *
4
+ * This module provides functions to calculate costs from token usage data
5
+ * by cross-referencing with LLM model pricing configuration.
6
+ */
7
+
8
+ import { MODEL_CONFIG } from "../config/models.js";
9
+
10
+ /**
11
+ * Calculate cost for a single token usage entry
12
+ * @param {Array} tokenUsageEntry - [modelKey, inputTokens, outputTokens]
13
+ * @param {Object} modelsConfig - LLM models configuration
14
+ * @returns {Object} Cost calculation result
15
+ */
16
+ export function calculateSingleTokenCost(tokenUsageEntry, modelsConfig = null) {
17
+ if (!Array.isArray(tokenUsageEntry) || tokenUsageEntry.length < 3) {
18
+ return {
19
+ modelKey: null,
20
+ inputTokens: 0,
21
+ outputTokens: 0,
22
+ totalTokens: 0,
23
+ inputCost: 0,
24
+ outputCost: 0,
25
+ totalCost: 0,
26
+ };
27
+ }
28
+
29
+ const [modelKey, inputTokens, outputTokens] = tokenUsageEntry;
30
+
31
+ // Get models config if not provided
32
+ const config = modelsConfig || MODEL_CONFIG;
33
+ const modelConfig = config[modelKey];
34
+
35
+ if (!modelConfig) {
36
+ console.warn(
37
+ `[token-cost-calculator] Model configuration not found for: ${modelKey}`
38
+ );
39
+ return {
40
+ modelKey,
41
+ inputTokens: Number(inputTokens) || 0,
42
+ outputTokens: Number(outputTokens) || 0,
43
+ totalTokens: (Number(inputTokens) || 0) + (Number(outputTokens) || 0),
44
+ inputCost: 0,
45
+ outputCost: 0,
46
+ totalCost: 0,
47
+ };
48
+ }
49
+
50
+ const inputCost =
51
+ ((Number(inputTokens) || 0) * (modelConfig.tokenCostInPerMillion || 0)) /
52
+ 1_000_000;
53
+ const outputCost =
54
+ ((Number(outputTokens) || 0) * (modelConfig.tokenCostOutPerMillion || 0)) /
55
+ 1_000_000;
56
+ const totalCost = inputCost + outputCost;
57
+
58
+ return {
59
+ modelKey,
60
+ inputTokens: Number(inputTokens) || 0,
61
+ outputTokens: Number(outputTokens) || 0,
62
+ totalTokens: (Number(inputTokens) || 0) + (Number(outputTokens) || 0),
63
+ inputCost: Math.round(inputCost * 10000) / 10000, // Round to 4 decimal places
64
+ outputCost: Math.round(outputCost * 10000) / 10000,
65
+ totalCost: Math.round(totalCost * 10000) / 10000,
66
+ provider: modelConfig.provider,
67
+ model: modelConfig.model,
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Calculate costs for multiple token usage entries
73
+ * @param {Array} tokenUsageArray - Array of [modelKey, inputTokens, outputTokens] entries
74
+ * @param {Object} modelsConfig - LLM models configuration
75
+ * @returns {Object} Aggregated cost calculation
76
+ */
77
+ export function calculateMultipleTokenCosts(
78
+ tokenUsageArray,
79
+ modelsConfig = null
80
+ ) {
81
+ if (!Array.isArray(tokenUsageArray) || tokenUsageArray.length === 0) {
82
+ return {
83
+ entries: [],
84
+ summary: {
85
+ totalInputTokens: 0,
86
+ totalOutputTokens: 0,
87
+ totalTokens: 0,
88
+ totalInputCost: 0,
89
+ totalOutputCost: 0,
90
+ totalCost: 0,
91
+ modelBreakdown: {},
92
+ },
93
+ };
94
+ }
95
+
96
+ const entries = tokenUsageArray.map((entry) =>
97
+ calculateSingleTokenCost(entry, modelsConfig)
98
+ );
99
+
100
+ // Aggregate totals
101
+ const summary = entries.reduce(
102
+ (acc, entry) => {
103
+ acc.totalInputTokens += entry.inputTokens;
104
+ acc.totalOutputTokens += entry.outputTokens;
105
+ acc.totalTokens += entry.totalTokens;
106
+ acc.totalInputCost += entry.inputCost;
107
+ acc.totalOutputCost += entry.outputCost;
108
+ acc.totalCost += entry.totalCost;
109
+
110
+ // Model breakdown
111
+ const modelKey = entry.modelKey;
112
+ if (!acc.modelBreakdown[modelKey]) {
113
+ acc.modelBreakdown[modelKey] = {
114
+ provider: entry.provider,
115
+ model: entry.model,
116
+ inputTokens: 0,
117
+ outputTokens: 0,
118
+ totalTokens: 0,
119
+ inputCost: 0,
120
+ outputCost: 0,
121
+ totalCost: 0,
122
+ requestCount: 0,
123
+ };
124
+ }
125
+
126
+ const breakdown = acc.modelBreakdown[modelKey];
127
+ breakdown.inputTokens += entry.inputTokens;
128
+ breakdown.outputTokens += entry.outputTokens;
129
+ breakdown.totalTokens += entry.totalTokens;
130
+ breakdown.inputCost += entry.inputCost;
131
+ breakdown.outputCost += entry.outputCost;
132
+ breakdown.totalCost += entry.totalCost;
133
+ breakdown.requestCount += 1;
134
+
135
+ return acc;
136
+ },
137
+ {
138
+ totalInputTokens: 0,
139
+ totalOutputTokens: 0,
140
+ totalTokens: 0,
141
+ totalInputCost: 0,
142
+ totalOutputCost: 0,
143
+ totalCost: 0,
144
+ modelBreakdown: {},
145
+ }
146
+ );
147
+
148
+ // Round all cost values in summary
149
+ summary.totalInputCost = Math.round(summary.totalInputCost * 10000) / 10000;
150
+ summary.totalOutputCost = Math.round(summary.totalOutputCost * 10000) / 10000;
151
+ summary.totalCost = Math.round(summary.totalCost * 10000) / 10000;
152
+
153
+ // Round model breakdown costs
154
+ Object.values(summary.modelBreakdown).forEach((breakdown) => {
155
+ breakdown.inputCost = Math.round(breakdown.inputCost * 10000) / 10000;
156
+ breakdown.outputCost = Math.round(breakdown.outputCost * 10000) / 10000;
157
+ breakdown.totalCost = Math.round(breakdown.totalCost * 10000) / 10000;
158
+ });
159
+
160
+ return { entries, summary };
161
+ }
162
+
163
+ /**
164
+ * Extract and calculate token usage costs from tasks-status.json data
165
+ * @param {Object} tasksStatus - The tasks-status.json content
166
+ * @param {string} taskName - Optional specific task name to calculate for
167
+ * @returns {Object} Cost calculation for entire job or specific task
168
+ */
169
+ export function calculateJobCosts(tasksStatus, taskName = null) {
170
+ if (!tasksStatus || typeof tasksStatus !== "object") {
171
+ return {
172
+ jobLevel: {
173
+ entries: [],
174
+ summary: {
175
+ totalInputTokens: 0,
176
+ totalOutputTokens: 0,
177
+ totalTokens: 0,
178
+ totalInputCost: 0,
179
+ totalOutputCost: 0,
180
+ totalCost: 0,
181
+ modelBreakdown: {},
182
+ },
183
+ },
184
+ tasksLevel: {},
185
+ };
186
+ }
187
+
188
+ const tasks = tasksStatus.tasks || {};
189
+
190
+ // If specific task requested, calculate only for that task
191
+ if (taskName && tasks[taskName]) {
192
+ const taskTokenUsage = tasks[taskName].tokenUsage || [];
193
+ const taskCosts = calculateMultipleTokenCosts(taskTokenUsage);
194
+
195
+ return {
196
+ jobLevel: taskCosts,
197
+ tasksLevel: {
198
+ [taskName]: taskCosts,
199
+ },
200
+ };
201
+ }
202
+
203
+ // Calculate for all tasks
204
+ const tasksLevel = {};
205
+ const allEntries = [];
206
+ const allTuples = [];
207
+
208
+ for (const [currentTaskName, taskData] of Object.entries(tasks)) {
209
+ const taskTokenUsage = taskData.tokenUsage || [];
210
+ const taskCosts = calculateMultipleTokenCosts(taskTokenUsage);
211
+ tasksLevel[currentTaskName] = taskCosts;
212
+ allEntries.push(...taskCosts.entries);
213
+ allTuples.push(...taskTokenUsage);
214
+ }
215
+
216
+ // Calculate job-level aggregation from raw tuples
217
+ const jobLevel = calculateMultipleTokenCosts(allTuples);
218
+
219
+ return {
220
+ jobLevel,
221
+ tasksLevel,
222
+ };
223
+ }
224
+
225
+ /**
226
+ * Format cost data for API response
227
+ * @param {Object} costData - Cost calculation data
228
+ * @returns {Object} Formatted cost data for API
229
+ */
230
+ export function formatCostDataForAPI(costData) {
231
+ const { jobLevel, tasksLevel } = costData;
232
+
233
+ return {
234
+ summary: {
235
+ totalInputTokens: jobLevel.summary.totalInputTokens,
236
+ totalOutputTokens: jobLevel.summary.totalOutputTokens,
237
+ totalTokens: jobLevel.summary.totalTokens,
238
+ totalInputCost: jobLevel.summary.totalInputCost,
239
+ totalOutputCost: jobLevel.summary.totalOutputCost,
240
+ totalCost: jobLevel.summary.totalCost,
241
+ },
242
+ modelBreakdown: jobLevel.summary.modelBreakdown,
243
+ taskBreakdown: Object.entries(tasksLevel).reduce(
244
+ (acc, [taskName, taskData]) => {
245
+ acc[taskName] = {
246
+ summary: taskData.summary,
247
+ entries: taskData.entries,
248
+ };
249
+ return acc;
250
+ },
251
+ {}
252
+ ),
253
+ };
254
+ }
255
+
256
+ /**
257
+ * Get model pricing information
258
+ * @param {string} modelKey - Model key (e.g., "openai:gpt-5-mini")
259
+ * @returns {Object|null} Model pricing information
260
+ */
261
+ export function getModelPricing(modelKey) {
262
+ const modelConfig = MODEL_CONFIG[modelKey];
263
+
264
+ if (!modelConfig) {
265
+ return null;
266
+ }
267
+
268
+ return {
269
+ modelKey,
270
+ provider: modelConfig.provider,
271
+ model: modelConfig.model,
272
+ inputCostPerMillion: modelConfig.tokenCostInPerMillion,
273
+ outputCostPerMillion: modelConfig.tokenCostOutPerMillion,
274
+ };
275
+ }
276
+
277
+ /**
278
+ * Get all available model pricing information
279
+ * @returns {Object} All model pricing information
280
+ */
281
+ export function getAllModelPricing() {
282
+ const pricing = {};
283
+ for (const [modelKey, modelConfig] of Object.entries(MODEL_CONFIG)) {
284
+ pricing[modelKey] = {
285
+ modelKey,
286
+ provider: modelConfig.provider,
287
+ model: modelConfig.model,
288
+ inputCostPerMillion: modelConfig.tokenCostInPerMillion,
289
+ outputCostPerMillion: modelConfig.tokenCostOutPerMillion,
290
+ };
291
+ }
292
+
293
+ return pricing;
294
+ }
package/src/utils/ui.jsx CHANGED
@@ -1,31 +1,31 @@
1
1
  import React from "react";
2
2
  import { Badge } from "../components/ui/badge.jsx";
3
3
  import { CheckCircle2, Loader2, AlertTriangle, Circle } from "lucide-react";
4
+ import { TaskState } from "../config/statuses.js";
4
5
 
5
6
  export const statusBadge = (status) => {
6
7
  switch (status) {
7
- case "running":
8
+ case TaskState.RUNNING:
8
9
  return (
9
- <Badge variant="info" aria-label="Running">
10
+ <Badge intent="blue" aria-label="Running">
10
11
  Running
11
12
  </Badge>
12
13
  );
13
- case "failed":
14
+ case TaskState.FAILED:
14
15
  return (
15
- <Badge variant="error" aria-label="Failed">
16
+ <Badge intent="red" aria-label="Failed">
16
17
  Failed
17
18
  </Badge>
18
19
  );
19
- case "completed":
20
- case "complete":
20
+ case TaskState.DONE:
21
21
  return (
22
- <Badge variant="success" aria-label="Completed">
22
+ <Badge intent="green" aria-label="Completed">
23
23
  Completed
24
24
  </Badge>
25
25
  );
26
- case "pending":
26
+ case TaskState.PENDING:
27
27
  return (
28
- <Badge variant="default" aria-label="Pending">
28
+ <Badge intent="gray" aria-label="Pending">
29
29
  Pending
30
30
  </Badge>
31
31
  );
@@ -36,12 +36,11 @@ export const statusBadge = (status) => {
36
36
 
37
37
  export const taskStatusIcon = (state) => {
38
38
  switch (state) {
39
- case "completed":
40
- case "complete":
39
+ case TaskState.DONE:
41
40
  return <CheckCircle2 className="h-4 w-4 text-success" aria-hidden />;
42
- case "running":
41
+ case TaskState.RUNNING:
43
42
  return <Loader2 className="h-4 w-4 animate-spin text-info" aria-hidden />;
44
- case "failed":
43
+ case TaskState.FAILED:
45
44
  return <AlertTriangle className="h-4 w-4 text-destructive" aria-hidden />;
46
45
  default:
47
46
  return <Circle className="h-4 w-4 text-slate-500" aria-hidden />;
@@ -50,11 +49,11 @@ export const taskStatusIcon = (state) => {
50
49
 
51
50
  export const progressClasses = (status) => {
52
51
  switch (status) {
53
- case "running":
52
+ case TaskState.RUNNING:
54
53
  return "bg-info/20 [&>div]:bg-info";
55
- case "failed":
54
+ case TaskState.FAILED:
56
55
  return "bg-destructive/20 [&>div]:bg-destructive";
57
- case "completed":
56
+ case TaskState.DONE:
58
57
  return "bg-success/20 [&>div]:bg-success";
59
58
  default:
60
59
  return "bg-muted [&>div]:bg-muted-foreground";
@@ -63,12 +62,11 @@ export const progressClasses = (status) => {
63
62
 
64
63
  export const barColorForState = (state) => {
65
64
  switch (state) {
66
- case "running":
65
+ case TaskState.RUNNING:
67
66
  return "bg-info";
68
- case "failed":
67
+ case TaskState.FAILED:
69
68
  return "bg-destructive";
70
- case "completed":
71
- case "complete":
69
+ case TaskState.DONE:
72
70
  return "bg-success";
73
71
  default:
74
72
  return "bg-muted-foreground";
@@ -1,27 +0,0 @@
1
- import React from "react";
2
- export function Select({ value, onValueChange, children, className = "" }) {
3
- return (
4
- <select
5
- className={[
6
- "h-9 rounded-md border px-3 text-sm bg-white",
7
- className,
8
- ].join(" ")}
9
- value={value}
10
- onChange={(e) => onValueChange?.(e.target.value)}
11
- >
12
- {children}
13
- </select>
14
- );
15
- }
16
- export function SelectItem({ value, children }) {
17
- return <option value={value}>{children}</option>;
18
- }
19
- export function SelectTrigger({ children, ...p }) {
20
- return <>{children}</>;
21
- } // keep API compatible
22
- export function SelectContent({ children }) {
23
- return <>{children}</>;
24
- }
25
- export function SelectValue({ placeholder }) {
26
- return <>{placeholder}</>;
27
- }
package/src/lib/utils.js DELETED
@@ -1,6 +0,0 @@
1
- import { clsx } from "clsx";
2
- import { twMerge } from "tailwind-merge";
3
-
4
- export function cn(...inputs) {
5
- return twMerge(clsx(inputs));
6
- }
@@ -1,26 +0,0 @@
1
- import { useState, useEffect, useRef } from "react";
2
-
3
- /**
4
- * Reactive ticker hook that provides updating timestamp
5
- * @param {number} intervalMs - Update interval in milliseconds (default: 1000)
6
- * @returns {number} Current timestamp that updates on interval
7
- */
8
- export function useTicker(intervalMs = 1000) {
9
- const [now, setNow] = useState(() => Date.now());
10
-
11
- useEffect(() => {
12
- // Set up interval to update timestamp
13
- const intervalId = setInterval(() => {
14
- setNow(Date.now());
15
- }, intervalMs);
16
-
17
- // Cleanup interval on unmount
18
- return () => {
19
- clearInterval(intervalId);
20
- };
21
- }, [intervalMs]);
22
-
23
- return now;
24
- }
25
-
26
- export default useTicker;