@juggernautlabs/views 0.0.1 → 0.0.2
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 +195 -0
- package/dist/cli.js +3 -1
- package/dist/index.js +67 -10
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
|
|
2
|
+
# Juggernaut Labs React View SDK
|
|
3
|
+
|
|
4
|
+
The **Juggernaut Labs React View SDK** (`@juggernautlabs/views`) is the bridge between your platform’s backend logic and rich, interactive user experiences.
|
|
5
|
+
|
|
6
|
+
It empowers developers to inject custom React interfaces directly into specific platform contexts—such as **Process Job Results** or **Database Records**. Instead of viewing raw JSON or static tables, users interact with dynamic UIs that can visualize data and trigger complex downstream workflows.
|
|
7
|
+
|
|
8
|
+
## Core Value Proposition
|
|
9
|
+
|
|
10
|
+
* **Contextual Leverage:** Build views that are instantly aware of where they are running. Automatically hydrate your UI with data from a specific Job or Database Record without complex API fetching logic.
|
|
11
|
+
* **Human-in-the-Loop Interactivity:** Transform static outputs into active decision points. Review AI-generated content, validate data, and trigger follow-up processes directly from the view.
|
|
12
|
+
* **Dynamic Visualization:** Create meaningful visual layers for your data. Turn a standard database row into a rich profile, dashboard, or media player based on the record type.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Installation & Setup
|
|
17
|
+
|
|
18
|
+
### 1. Install the Package
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @juggernautlabs/views
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 2. Configure Your Project
|
|
26
|
+
|
|
27
|
+
Add the following scripts to your `package.json`. The SDK includes a CLI that handles the heavy lifting for local development and deployment.
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
"scripts": {
|
|
31
|
+
"dev": "npx @juggernautlabs/views dev index.tsx",
|
|
32
|
+
"build": "vite build",
|
|
33
|
+
"deploy": "npx @juggernautlabs/views deploy ./index.tsx"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Usage: Process Jobs
|
|
41
|
+
|
|
42
|
+
Build interactive interfaces for the results of a Process Job. This is essential for workflows requiring manual review or distinct "next steps" based on the job's output.
|
|
43
|
+
|
|
44
|
+
### The Hooks
|
|
45
|
+
|
|
46
|
+
* **`useJuggernautJob()`**: Returns the context of the current job, including all tasks and generated outputs.
|
|
47
|
+
* **`useJuggernautActions()`**: Returns an object containing available follow-up actions.
|
|
48
|
+
|
|
49
|
+
### Implementation Example
|
|
50
|
+
|
|
51
|
+
Below is an example of a "Curriculum Review" interface where a user reviews generated content and triggers an approval process.
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { useJuggernautJob, useJuggernautActions } from '@juggernautlabs/views';
|
|
55
|
+
|
|
56
|
+
export const JobResultView = () => {
|
|
57
|
+
const job = useJuggernautJob();
|
|
58
|
+
const actions = useJuggernautActions();
|
|
59
|
+
|
|
60
|
+
// "approve_curriculum" corresponds to the API Name of the action
|
|
61
|
+
// defined in your Juggernaut output settings.
|
|
62
|
+
const handleApprove = () => {
|
|
63
|
+
if (actions.approve_curriculum) {
|
|
64
|
+
actions.approve_curriculum.execute({
|
|
65
|
+
// 'inputs' are passed to the triggered process
|
|
66
|
+
inputs: {
|
|
67
|
+
jobId: job.id,
|
|
68
|
+
status: 'approved',
|
|
69
|
+
reviewerNotes: 'Curriculum meets all standards.'
|
|
70
|
+
},
|
|
71
|
+
// 'metadata' (optional) helps with logging and audit trails
|
|
72
|
+
metadata: {
|
|
73
|
+
source: 'reviewer_dashboard_v1'
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div className="review-container">
|
|
81
|
+
<h1>Reviewing: {job.title}</h1>
|
|
82
|
+
<pre>{JSON.stringify(job.output, null, 2)}</pre>
|
|
83
|
+
|
|
84
|
+
<div className="footer">
|
|
85
|
+
<button onClick={handleApprove}>
|
|
86
|
+
Approve & Publish
|
|
87
|
+
</button>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
> **Configuration Note:** For an action to appear in the `useJuggernautActions` hook, it must be explicitly added to the **Trigger Actions** section within the **Outputs** tab of your Process configuration.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Usage: Database Records
|
|
100
|
+
|
|
101
|
+
Create a custom visual layer for your database records. This allows you to render distinct UIs based on the record's content.
|
|
102
|
+
|
|
103
|
+
### The Hook
|
|
104
|
+
|
|
105
|
+
* **`useJuggernautRecord()`**: Returns the full data object for the current database record context.
|
|
106
|
+
|
|
107
|
+
### Implementation Example
|
|
108
|
+
|
|
109
|
+
Transforming a standard "Movie" database row into a rich media profile.
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
import { useJuggernautRecord } from '@juggernautlabs/views';
|
|
113
|
+
|
|
114
|
+
export const MovieRecordView = () => {
|
|
115
|
+
const movie = useJuggernautRecord();
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div className="movie-card">
|
|
119
|
+
<div className="header" style={{ backgroundImage: `url(${movie.backdrop})` }}>
|
|
120
|
+
<h1>{movie.title}</h1>
|
|
121
|
+
</div>
|
|
122
|
+
<div className="content">
|
|
123
|
+
<p>{movie.description}</p>
|
|
124
|
+
{/* Conditional rendering based on record data */}
|
|
125
|
+
{movie.trailerId && (
|
|
126
|
+
<iframe src={`https://youtube.com/embed/${movie.trailerId}`} />
|
|
127
|
+
)}
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Local Development (Mocking Data)
|
|
138
|
+
|
|
139
|
+
You cannot effectively develop UI without data. The SDK allows you to simulate the platform environment locally using a JSON file.
|
|
140
|
+
|
|
141
|
+
1. Create a `.demo.json` file in the root of your component directory.
|
|
142
|
+
2. Populate it with mock data. You can copy JSON from a successful job run or database query in the platform.
|
|
143
|
+
|
|
144
|
+
**`.demo.json` Structure:**
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"job": {
|
|
149
|
+
"id": "job_123",
|
|
150
|
+
"status": "completed",
|
|
151
|
+
"output": { "module_1": "Intro to React" }
|
|
152
|
+
},
|
|
153
|
+
"record": {
|
|
154
|
+
"id": "rec_456",
|
|
155
|
+
"title": "Inception",
|
|
156
|
+
"trailerId": "8hP9D6kZseM"
|
|
157
|
+
},
|
|
158
|
+
"actions": {
|
|
159
|
+
"approve_curriculum": {
|
|
160
|
+
"type": "process_trigger",
|
|
161
|
+
"label": "Approve"
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
When you run `npm run dev`, the CLI will read this file and automatically hydrate `useJuggernautJob`, `useJuggernautRecord`, and `useJuggernautActions` with your mock data.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Deployment
|
|
173
|
+
|
|
174
|
+
When your view is ready, deploy it directly to the Juggernaut Labs platform using the CLI command configured in your scripts:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
npm run deploy
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
This bundles your React code and uploads it to the view configuration specified in your `index.tsx`.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
### API Reference: Action Execution
|
|
186
|
+
|
|
187
|
+
When using `useJuggernautActions`, the returned object keys match the **API Name** of the action. To trigger an action, call the `.execute()` method on that key.
|
|
188
|
+
|
|
189
|
+
**Method Signature:**
|
|
190
|
+
`action.execute(payload: { inputs: object, metadata?: object })`
|
|
191
|
+
|
|
192
|
+
| Property | Type | Description |
|
|
193
|
+
| --- | --- | --- |
|
|
194
|
+
| **inputs** | `object` | Key-value pairs matching the input variables defined in the target process. |
|
|
195
|
+
| **metadata** | `object` | *(Optional)* Additional context data useful for logging or auditing. |
|
package/dist/cli.js
CHANGED
|
@@ -77,6 +77,7 @@ class TemplateUploader {
|
|
|
77
77
|
try {
|
|
78
78
|
const fileContent = await fs2.readFileSync(filePath, "utf-8");
|
|
79
79
|
const metadata = JSON52.parse(fileContent) || {};
|
|
80
|
+
const isValidSettings = metadata.settings && typeof metadata.settings === "object";
|
|
80
81
|
return {
|
|
81
82
|
name: metadata.name || "Untitled Template",
|
|
82
83
|
description: metadata.description || "",
|
|
@@ -85,7 +86,8 @@ class TemplateUploader {
|
|
|
85
86
|
role: metadata.target?.role,
|
|
86
87
|
resource: metadata.target?.resource,
|
|
87
88
|
resourceType: metadata.target?.resourceType
|
|
88
|
-
}
|
|
89
|
+
},
|
|
90
|
+
settings: isValidSettings ? metadata.settings : undefined
|
|
89
91
|
};
|
|
90
92
|
} catch (e) {
|
|
91
93
|
throw new Error("Invalid JSON in metadata file");
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,56 @@ class ContextManager {
|
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// src/events.js
|
|
20
|
+
var ActionEvents = {
|
|
21
|
+
START_JOB: "start:job",
|
|
22
|
+
SCHEDULE_JOB: "schedule:job",
|
|
23
|
+
OPEN_RESOURCE: "open:resource",
|
|
24
|
+
START_PLUGIN: "start:plugin",
|
|
25
|
+
DATABASE_OPERATION: "database:operation",
|
|
26
|
+
TRIGGER_ACTION: "trigger:action",
|
|
27
|
+
CONTEXT_REQUEST: "juggernaut:context:request",
|
|
28
|
+
CONTEXT_RESPONSE: "juggernaut:context:response",
|
|
29
|
+
CONTEXT_UPDATE: "juggernaut:context:update"
|
|
30
|
+
};
|
|
31
|
+
var generateTriggerActionEventKey = (eventKey) => {
|
|
32
|
+
return `${ActionEvents.TRIGGER_ACTION}-${eventKey}`;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// src/resource.js
|
|
36
|
+
class JuggernautAction {
|
|
37
|
+
constructor(options = { name: "", label: "", apiName: "", description: "", allowUserInput }, handler) {
|
|
38
|
+
this.name = options.name;
|
|
39
|
+
this.label = options.label;
|
|
40
|
+
this.apiName = options.apiName;
|
|
41
|
+
this.description = options.description;
|
|
42
|
+
this.handler = handler;
|
|
43
|
+
this.allowUserInput = options.allowUserInput || true;
|
|
44
|
+
}
|
|
45
|
+
get resourceType() {
|
|
46
|
+
return "Action";
|
|
47
|
+
}
|
|
48
|
+
execute(params) {
|
|
49
|
+
if (this.handler) {
|
|
50
|
+
return this.handler(params);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
static resolveActions(actionsData = []) {
|
|
54
|
+
let actions = {};
|
|
55
|
+
for (let actionData of actionsData) {
|
|
56
|
+
actions[actionData.apiName] = new JuggernautAction({
|
|
57
|
+
name: actionData.name,
|
|
58
|
+
label: actionData.label,
|
|
59
|
+
apiName: actionData.apiName,
|
|
60
|
+
description: actionData.description
|
|
61
|
+
}, (params) => {
|
|
62
|
+
return sendJuggernautActionEvent(generateTriggerActionEventKey(actionData.key), params);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return actions;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
19
69
|
// src/hooks.jsx
|
|
20
70
|
var IS_DEV_MODE = import.meta.env ? import.meta.env.DEV : process?.env.NODE_ENV === "development";
|
|
21
71
|
var DEMO_FILE_PATH = "/.demo.json";
|
|
@@ -25,7 +75,7 @@ var handleContextWindowEvents = (event) => {
|
|
|
25
75
|
const { type, payload } = event.data || {};
|
|
26
76
|
if (!type || !payload)
|
|
27
77
|
return;
|
|
28
|
-
const { resourceType, resource } = payload;
|
|
78
|
+
const { resourceType, resource, actions = [] } = payload;
|
|
29
79
|
switch (type) {
|
|
30
80
|
case "juggernaut:context:request":
|
|
31
81
|
if (ContextManager[STORE_KEY] && ContextManager[STORE_KEY].hasOwnProperty(resourceType)) {
|
|
@@ -39,6 +89,7 @@ var handleContextWindowEvents = (event) => {
|
|
|
39
89
|
case "juggernaut:context:update":
|
|
40
90
|
if (ContextManager[STORE_KEY]) {
|
|
41
91
|
ContextManager[STORE_KEY][resourceType] = resource;
|
|
92
|
+
ContextManager[STORE_KEY]["actions"] = JuggernautAction.resolveActions(actions);
|
|
42
93
|
subscribers.forEach((callback) => callback(resourceType, resource));
|
|
43
94
|
}
|
|
44
95
|
break;
|
|
@@ -76,6 +127,10 @@ var useJuggernautResource = (resourceType) => {
|
|
|
76
127
|
if (ContextManager[STORE_KEY]) {
|
|
77
128
|
ContextManager[STORE_KEY][resourceType] = demoData[resourceType];
|
|
78
129
|
}
|
|
130
|
+
if (resourceType === "actions") {
|
|
131
|
+
const actions = JuggernautAction.resolveActions(demoData.actions || []);
|
|
132
|
+
ContextManager[STORE_KEY]["actions"] = actions;
|
|
133
|
+
}
|
|
79
134
|
setResource(demoData[resourceType]);
|
|
80
135
|
return true;
|
|
81
136
|
}
|
|
@@ -133,7 +188,9 @@ var useJuggernautRecord = () => useJuggernautResource("record");
|
|
|
133
188
|
var useJuggernautData = () => useJuggernautResource("data");
|
|
134
189
|
var useJuggernautPlugin = () => useJuggernautResource("plugin");
|
|
135
190
|
var useJuggernautProcess = () => useJuggernautResource("process");
|
|
136
|
-
var useJuggernautActions = () =>
|
|
191
|
+
var useJuggernautActions = () => {
|
|
192
|
+
return ContextManager[STORE_KEY]["actions"] || {};
|
|
193
|
+
};
|
|
137
194
|
var useJuggernautSystem = () => useJuggernautResource("system");
|
|
138
195
|
var FILE_EVENTS = {
|
|
139
196
|
GET_FILE: "juggernaut:file:get",
|
|
@@ -203,7 +260,7 @@ var useJuggernautFileSystem = () => {
|
|
|
203
260
|
};
|
|
204
261
|
// src/actions.jsx
|
|
205
262
|
import { useEffect as useEffect2, useCallback as useCallback2, useRef as useRef2 } from "react";
|
|
206
|
-
var
|
|
263
|
+
var ActionEvents2 = {
|
|
207
264
|
START_JOB: "start:job",
|
|
208
265
|
SCHEDULE_JOB: "schedule:job",
|
|
209
266
|
OPEN_RESOURCE: "open:resource",
|
|
@@ -262,22 +319,22 @@ var useBridge = () => {
|
|
|
262
319
|
});
|
|
263
320
|
}, []);
|
|
264
321
|
const startJob = useCallback2((processId, inputs, metadata = {}) => {
|
|
265
|
-
return sendEvent(
|
|
322
|
+
return sendEvent(ActionEvents2.START_JOB, { processId, inputs, metadata });
|
|
266
323
|
}, [sendEvent]);
|
|
267
324
|
const scheduleJob = useCallback2((processId, inputs, schedule, metadata = {}) => {
|
|
268
|
-
return sendEvent(
|
|
325
|
+
return sendEvent(ActionEvents2.SCHEDULE_JOB, { processId, inputs, schedule, metadata });
|
|
269
326
|
}, [sendEvent]);
|
|
270
327
|
const openResource = useCallback2((type, id, extra = {}) => {
|
|
271
|
-
return sendEvent(
|
|
328
|
+
return sendEvent(ActionEvents2.OPEN_RESOURCE, { resourceType: type, resourceId: id, ...extra });
|
|
272
329
|
}, [sendEvent]);
|
|
273
|
-
const triggerAction = useCallback2((payload) => sendEvent(
|
|
330
|
+
const triggerAction = useCallback2((eventKey, payload) => sendEvent(`${ActionEvents2.TRIGGER_ACTION}-${eventKey}`, payload), [sendEvent]);
|
|
274
331
|
const launchProcess = (id, options = {}) => openResource("process", id, options);
|
|
275
332
|
const launchJob = (id, options = {}) => openResource("job", id, options);
|
|
276
333
|
const launchPlugin = (id, options = {}) => openResource("plugin", id, options);
|
|
277
334
|
const launchDatabase = (id, options = {}) => openResource("database", id, options);
|
|
278
335
|
const launchRecord = (dbId, recordId, options = {}) => openResource("database:record", recordId, { database: dbId, ...options });
|
|
279
|
-
const startPlugin = useCallback2((payload) => sendEvent(
|
|
280
|
-
const databaseOperation = useCallback2((payload) => sendEvent(
|
|
336
|
+
const startPlugin = useCallback2((payload) => sendEvent(ActionEvents2.START_PLUGIN, payload), [sendEvent]);
|
|
337
|
+
const databaseOperation = useCallback2((payload) => sendEvent(ActionEvents2.DATABASE_OPERATION, payload), [sendEvent]);
|
|
281
338
|
return {
|
|
282
339
|
startJob,
|
|
283
340
|
scheduleJob,
|
|
@@ -304,5 +361,5 @@ export {
|
|
|
304
361
|
useBridge,
|
|
305
362
|
sendJuggernautActionEvent,
|
|
306
363
|
listenForJuggernautPlatformEvents,
|
|
307
|
-
ActionEvents
|
|
364
|
+
ActionEvents2 as ActionEvents
|
|
308
365
|
};
|
package/package.json
CHANGED