Skip to content

Commit 538b9cb

Browse files
committed
WIP interpreted graph
1 parent 24564e1 commit 538b9cb

File tree

2 files changed

+346
-0
lines changed

2 files changed

+346
-0
lines changed

static/interpreted-graph/graph.js

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
import { BehaviorSubject, combineLatest } from 'https://cdn.jsdelivr.net/npm/rxjs@7.8.1/+esm';
2+
import Ajv from 'https://cdn.jsdelivr.net/npm/ajv@8.14.0/+esm'
3+
import Mustache from 'https://cdn.jsdelivr.net/npm/mustache@4.2.0/+esm'
4+
5+
export function start() { }
6+
7+
function coerce(val) {
8+
if (val == 'true') return true;
9+
if (val == 'false') return false;
10+
if (!isNaN(val)) return parseFloat(val);
11+
return val;
12+
}
13+
14+
// Function to create the RxJS network from JSON document
15+
function createRxJSNetworkFromJson(jsonDocument) {
16+
const ajv = new Ajv();
17+
const cells = {};
18+
const validators = {};
19+
20+
21+
const system = {
22+
get: (key) => {
23+
if (key == 'todos') {
24+
return [
25+
{ label: 'Buy groceries', checked: false },
26+
{ label: 'Vacuum house', checked: true },
27+
{ label: 'Learn RxJS', checked: false }
28+
]
29+
}
30+
}
31+
};
32+
33+
// Create subjects and validators for each cell
34+
jsonDocument.conversation.forEach(conversation => {
35+
conversation.messages.forEach(message => {
36+
if (message.role === 'assistant') {
37+
const cellName = message.name || generateUniqueId();
38+
39+
cells[cellName] = new BehaviorSubject(null);
40+
41+
// if (message.inputs) {
42+
43+
// for (const [key, schema] of Object.entries(message.inputs)) {
44+
// const inputName = key;
45+
// validators[inputName] = ajv.compile(message.input);
46+
// }
47+
48+
// if schema has not been compiled, compile it
49+
50+
// }
51+
52+
// // If there is an output schema, validate the output
53+
// if (message.output && message.output.schema) {
54+
// validators[`${cellName}_output`] = ajv.compile(message.output.schema);
55+
// }
56+
}
57+
});
58+
});
59+
60+
// Define a unique ID generator
61+
function generateUniqueId() {
62+
return '_' + Math.random().toString(36).substr(2, 9);
63+
}
64+
65+
// Process messages and set up reactive bindings
66+
jsonDocument.conversation.forEach(conversation => {
67+
conversation.messages.forEach(message => {
68+
if (message.role === 'assistant') {
69+
const cellName = message.name || generateUniqueId();
70+
71+
if (message.contentType === 'text/javascript') {
72+
// Evaluate the JavaScript content and bind it to the subject
73+
const func = new Function('system', ...message.content.args, message.content.body);
74+
const result = func(system, {
75+
get: (key) => cells[key].getValue(),
76+
set: (key, value) => cells[key].next(value)
77+
});
78+
79+
// Validate the result against the output schema
80+
if (validators[`${cellName}_output`] && !validators[`${cellName}_output`](result)) {
81+
console.error(`Output validation failed for cell ${cellName}`, validators[`${cellName}_output`].errors);
82+
} else {
83+
cells[cellName].next(result);
84+
}
85+
} else if (message.contentType === 'text/vnd.common.template') {
86+
// Set up template rendering
87+
const { inputs } = message;
88+
const inputObservables = [];
89+
90+
for (const [key, schema] of Object.entries(inputs)) {
91+
const inputName = key;
92+
if (cells[inputName]) {
93+
inputObservables.push(cells[inputName]);
94+
}
95+
}
96+
97+
if (message.tag) {
98+
// register as web component
99+
customElements.define(message.tag, class extends HTMLElement {
100+
constructor() {
101+
super();
102+
this.attachShadow({ mode: 'open' });
103+
}
104+
105+
connectedCallback() {
106+
const attrs = Object.fromEntries(Object.entries(this.attributes).map(([key, attr]) => ([attr.name, coerce(attr.value)])));
107+
108+
attrs.alert = () => {
109+
alert('clicked');
110+
};
111+
112+
const renderedTemplate = Mustache.render(message.content, attrs);
113+
this.shadowRoot.innerHTML = renderedTemplate;
114+
}
115+
});
116+
} else {
117+
combineLatest(inputObservables).subscribe(values => {
118+
const inputValues = values.reduce((acc, value, index) => {
119+
const key = Object.keys(inputs)[index];
120+
acc[key] = value;
121+
return acc;
122+
}, {});
123+
124+
const renderedTemplate = Mustache.render(message.content, inputValues);
125+
cells[cellName]?.next(renderedTemplate);
126+
});
127+
}
128+
}
129+
}
130+
});
131+
});
132+
133+
return cells;
134+
}
135+
136+
// Example JSON document
137+
const jsonDocument = {
138+
"conversation": [
139+
{
140+
"id": "",
141+
"messages": [
142+
{
143+
"role": "user",
144+
"content": "Write a title"
145+
},
146+
{
147+
"role": "assistant",
148+
"contentType": "text/javascript",
149+
"name": "title",
150+
"inputs": {},
151+
"output": {
152+
"schema": {
153+
"type": "string",
154+
}
155+
},
156+
"content": {
157+
args: [],
158+
body: "return 'hello'"
159+
}
160+
}
161+
]
162+
},
163+
{
164+
"id": "",
165+
"messages": [
166+
{
167+
"role": "user",
168+
"content": "Get my todos"
169+
},
170+
{
171+
"role": "assistant",
172+
"contentType": "text/javascript",
173+
"name": "todos",
174+
"inputs": {},
175+
"output": {
176+
"schema": {
177+
"type": "array",
178+
"items": {
179+
"type": "object",
180+
"properties": {
181+
"label": {
182+
"type": "string"
183+
},
184+
"checked": {
185+
"type": "boolean",
186+
"default": false
187+
}
188+
},
189+
"required": ["label"]
190+
}
191+
}
192+
},
193+
"content": {
194+
args: [],
195+
body: "return system.get('todos')"
196+
}
197+
}
198+
]
199+
},
200+
{
201+
"id": "",
202+
"messages": [
203+
{
204+
"role": "user",
205+
"content": "I would like a todo item component, e.g. <todo-item checked>vacuum house</todo-item>"
206+
},
207+
{
208+
"role": "assistant",
209+
"contentType": "text/vnd.common.template",
210+
"tag": "todo-item",
211+
"imports": {
212+
"label": "common:ui/label.component.json",
213+
"checkbox": "common:ui/checkbox.component.json"
214+
},
215+
"inputs": {
216+
"checked": {
217+
"type": "boolean",
218+
"default": false
219+
},
220+
"label": {
221+
"type": "string"
222+
}
223+
},
224+
"output": {
225+
"schema": {
226+
"type": "template"
227+
}
228+
},
229+
"content": "<label onclick={{alert}}><input type=checkbox {{#checked}}checked={{checked}}{{/checked}}> {{label}}</label>"
230+
}
231+
]
232+
},
233+
{
234+
"id": "",
235+
"messages": [
236+
{
237+
"role": "user",
238+
"content": "Ok, now make a task list using that todo item."
239+
},
240+
{
241+
"role": "assistant",
242+
"contentType": "text/vnd.common.template",
243+
"name": "ui",
244+
"inputs": {
245+
"title": {
246+
"type": "string",
247+
},
248+
"todos": {
249+
"type": "array",
250+
"items": {
251+
"type": "object",
252+
"properties": {
253+
"label": {
254+
"type": "string"
255+
},
256+
"checked": {
257+
"type": "boolean",
258+
"default": false
259+
}
260+
},
261+
"required": ["label"]
262+
},
263+
"default": []
264+
}
265+
},
266+
"output": {
267+
"schema": {
268+
"type": "template"
269+
}
270+
},
271+
"imports": {
272+
"task": "todo-item"
273+
},
274+
"content": "<h1>{{title}}</h1><ul>{{#todos}}<li><todo-item label={{label}} checked={{checked}}></todo-item></li>{{/todos}}</ul>"
275+
}
276+
]
277+
}
278+
]
279+
};
280+
281+
// Create the RxJS network
282+
const subjects = createRxJSNetworkFromJson(jsonDocument);
283+
284+
// Example usage: Adding a new todo item
285+
// subjects['todos'].next([
286+
// { label: 'Buy groceries', checked: false },
287+
// { label: 'Vacuum house', checked: true },
288+
// { label: 'Learn RxJS', checked: false }
289+
// ]);
290+
291+
// Example usage: Updating the title
292+
subjects['title'].next('My Updated Task List');
293+
294+
console.log(subjects)
295+
296+
// subjects['ui'].subscribe(console.log);
297+
subjects['todos'].subscribe(console.log);
298+
subjects['ui'].subscribe(html => {
299+
document.getElementById('app').innerHTML = html;
300+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<title>rxjs</title>
5+
</head>
6+
<style type="text/css">
7+
.debug {
8+
font-size: 8px;
9+
max-height: 128px;
10+
max-width: 50vw;
11+
overflow-y: auto;
12+
background-color: #f0f0f0;
13+
border: 1px solid #ccc;
14+
padding: 4px;
15+
border-radius: 4px;
16+
}
17+
18+
#workflow {
19+
display: flex;
20+
flex-direction: column;
21+
flex-wrap: wrap;
22+
gap: 8px;
23+
}
24+
25+
#workflow > * {
26+
}
27+
28+
.columns {
29+
display: flex;
30+
gap: 16px;
31+
}
32+
33+
.columns > * {
34+
flex: 1;
35+
}
36+
</style>
37+
<body>
38+
<button id="startWorkflow">Start Workflow</button>
39+
<div id="app">
40+
</div>
41+
42+
<script type="module">
43+
import { start } from "./graph.js";
44+
</script>
45+
</body>
46+
</html>

0 commit comments

Comments
 (0)