Skip to content

Commit 164419d

Browse files
committed
Merge lookslike prototype with wasm modules
1 parent f42e6e2 commit 164419d

25 files changed

+1418
-6
lines changed

typescript/package-lock.json

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

typescript/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,8 @@
5656
"./common/runtime:clean"
5757
]
5858
}
59+
},
60+
"dependencies": {
61+
"@commontools/runtime": "^0.0.1"
5962
}
6063
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export function fetchApiKey() {
2+
let apiKey = localStorage.getItem("apiKey");
3+
4+
if (!apiKey) {
5+
// Prompt the user for the API key if it doesn't exist
6+
const userApiKey = prompt("Please enter your API key:");
7+
8+
if (userApiKey) {
9+
// Save the API key in localStorage
10+
localStorage.setItem("apiKey", userApiKey);
11+
apiKey = userApiKey;
12+
} else {
13+
// Handle the case when the user cancels or doesn't provide an API key
14+
alert("API key not provided. Some features may not work.");
15+
}
16+
}
17+
18+
return apiKey;
19+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import {LitElement, html, css} from 'lit-element'
2+
import {customElement} from 'lit/decorators.js'
3+
import {base} from '../styles'
4+
5+
const styles = css`
6+
:host {
7+
--sidebar-width: calc(var(--unit) * 60);
8+
display: grid;
9+
grid-template-columns: var(--sidebar-width) 1fr;
10+
grid-template-areas: "sidebar main";
11+
min-height: 100dvh;
12+
}
13+
14+
15+
com-app-grid-sidebar {
16+
grid-area: sidebar;
17+
background-color: var(--color-secondary-background);
18+
}
19+
20+
com-app-grid-main {
21+
grid-area: main;
22+
container-type: inline-size;
23+
}
24+
`
25+
26+
@customElement('com-app-grid')
27+
export class ComAppGrid extends LitElement {
28+
static styles = [base, styles]
29+
30+
render() {
31+
return html`
32+
<com-app-grid-main>
33+
<slot name="main"></slot>
34+
</com-app-grid-main>
35+
<com-app-grid-sidebar>
36+
<slot name="sidebar"></slot>
37+
</com-app-grid-sidebar>
38+
`
39+
}
40+
}
41+
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
import { LitElement, html } from 'lit-element'
2+
import { customElement, state } from 'lit/decorators.js'
3+
import { base } from '../styles'
4+
5+
import { Recipe, emptyGraph, todoAppMockup, RecipeNode } from '../data'
6+
import { doLLM, grabJson } from '../llm'
7+
import { collectSymbols } from '../graph'
8+
import { listKeys } from '../state'
9+
10+
const codePrompt = `
11+
Your task is to take a user description or request and produce a series of nodes for a computation graph. Nodes can be code blocks or UI components and they communicate with named ports.
12+
you will provide the required edges to connect data from the environment to the inputs of the node. The keys of \`in\` are the names of local inputs and the values are NodePaths (of the form [context, nodeId], where context is typically '.' meaning local namespace).
13+
14+
"Fetch my todos" ->
15+
16+
\`\`\`json
17+
[
18+
{
19+
"id": "todos",
20+
"messages": [
21+
{
22+
"role": "user",
23+
"content": "get my todos"
24+
},
25+
{
26+
"role": "assistant",
27+
"content": "..."
28+
}
29+
],
30+
"contentType": "text/javascript",
31+
"in": {},
32+
"outputType": {
33+
"$id": "https://common.tools/stream.schema.json",
34+
"type": {
35+
"$id": "https://common.tools/todos.json"
36+
}
37+
},
38+
"body": "function() { return read('todos'); }"
39+
}
40+
]
41+
\`\`\`
42+
43+
Tasks that take no inputs require no edges.
44+
45+
---
46+
47+
"Remind me to water the plants" ->
48+
49+
\`\`\`json
50+
[
51+
{
52+
"id": "addReminder",
53+
"contentType": "text/javascript",
54+
"in": {},
55+
"outputType": {},
56+
"body": "async function() { const todos = await system.get('todos'); const newTodo = { label: 'water the plants', checked: false }; await system.set('todos', [...todos, newTodo]); return newTodo; }"
57+
}
58+
]
59+
\`\`\`
60+
61+
Tasks that take no inputs require no edges.
62+
63+
---
64+
65+
"Take the existing todos and filter to unchecked" ->
66+
\`\`\`json
67+
[
68+
{
69+
"id": "filteredTodos",
70+
"contentType": "text/javascript",
71+
"in": {
72+
"todos": [".", "todos"]
73+
},
74+
"outputType": {
75+
"type": "array",
76+
"items": {
77+
"type": "object",
78+
"properties": {
79+
"label": { "type": "string" },
80+
"checked": { "type": "boolean" }
81+
}
82+
}
83+
},
84+
"body": "function() { return inputs.todos.filter(todo => todo.checked); }"
85+
}
86+
]
87+
\`\`\`
88+
89+
Tasks that filter other data must pipe the data through the edges.
90+
91+
ContentType should be "text/javascript" for code.
92+
Always respond with code, even for static data. Wrap your response in a json block. Respond with nothing else.
93+
94+
render my todos" ->
95+
96+
\`\`\`json
97+
[
98+
{
99+
"id": "todoUi",
100+
"contentType": "application/json+vnd.common.ui",
101+
"in": {
102+
"todos": [".", "todos"]
103+
},
104+
"outputType": {
105+
"$id": "https://common.tools/ui.schema.json"
106+
},
107+
"body": {
108+
"tag": "ul",
109+
"props": {
110+
"className": "todo"
111+
},
112+
"children": {
113+
"type": "repeat",
114+
"binding": "todos",
115+
"template": {
116+
"tag": "li",
117+
"props": {},
118+
"children": [
119+
{
120+
"tag": "input",
121+
"props": {
122+
"type": "checkbox",
123+
"checked": { type: 'boolean', binding: 'checked' }
124+
}
125+
},
126+
{
127+
"tag": "span",
128+
"props": {
129+
"className": "todo-label"
130+
},
131+
"children": [
132+
{ type: 'string', binding: 'label' }
133+
]
134+
}
135+
]
136+
}
137+
}
138+
}
139+
}
140+
]
141+
\`\`\`
142+
143+
ContentType should be "application/json+vnd.common.ui" for UI. UI trees cannot use any javascript methods, code blocks must prepare the data for the UI to consume.
144+
145+
notalk;justgo
146+
`
147+
148+
@customElement('com-app')
149+
export class ComApp extends LitElement {
150+
static styles = [base]
151+
152+
@state() graph: Recipe = emptyGraph
153+
@state() userInput = ''
154+
155+
async appendMessage() {
156+
const newGraph = [...this.graph]
157+
// TODO: let GPT name the node
158+
const id = 'new' + (Math.floor(Math.random() * 1000))
159+
const input = `${this.userInput}`
160+
161+
const newNode: RecipeNode = {
162+
id,
163+
messages: [
164+
{
165+
role: 'user',
166+
content: input
167+
}
168+
],
169+
// TODO: generate these
170+
in: {},
171+
outputType: {},
172+
contentType: 'text/javascript',
173+
body: ''
174+
}
175+
176+
const symbols = collectSymbols(this.graph);
177+
symbols.reverse();
178+
179+
this.graph = newGraph;
180+
this.userInput = '';
181+
182+
const localContext = `
183+
The following nodes are available in the current context, these can be referenced by name when wiring the graph.
184+
185+
\`\`\`json
186+
${JSON.stringify(symbols, null, 2)}
187+
\`\`\`
188+
189+
The keys at the top of the list are the most recently created by the user, prefer these.
190+
`
191+
192+
const systemContext = `
193+
The current keys available in the system DB are:
194+
195+
\`\`\`json
196+
${JSON.stringify((await listKeys()).map(k => `system.get('${k}')`), null, 2)}
197+
\`\`\`
198+
`
199+
200+
const result = await doLLM(input + localContext, codePrompt + systemContext, null)
201+
const message = result?.choices[0]?.message
202+
if (message) {
203+
const data = grabJson(message?.content)
204+
for (const node of data) {
205+
node.messages = []
206+
newGraph.push(node)
207+
}
208+
209+
// newNode.id = data.id || id
210+
// newNode.contentType = data.contentType || 'text/javascript'
211+
// newNode.outputType = data.outputType || {}
212+
// newNode.body = data.body || '...'
213+
// newNode.in = data.in || {}
214+
// newNode.messages = data.messages || newNode.messages
215+
}
216+
217+
this.graph = JSON.parse(JSON.stringify(newGraph));
218+
console.log('graph updated', this.graph);
219+
}
220+
221+
render() {
222+
const setUserInput = (input: string) => {
223+
this.userInput = input
224+
}
225+
226+
return html`
227+
<com-app-grid>
228+
<com-chat slot="main">
229+
<com-thread slot="main" .graph=${this.graph}></com-thread>
230+
<div slot="footer">
231+
<com-unibox>
232+
<com-editor slot="main" .value=${this.userInput} .setValue=${setUserInput}></com-editor>
233+
<com-button slot="end" .action=${() => this.appendMessage()}>Send</com-button>
234+
</com-unibox>
235+
</div>
236+
</com-chat>
237+
<div slot="sidebar">
238+
239+
</div>
240+
</com-app-grid>
241+
`
242+
}
243+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { LitElement, html, css } from 'lit-element'
2+
import { customElement, property } from 'lit/decorators.js'
3+
import { base } from '../styles'
4+
5+
const styles = css`
6+
:host {
7+
display: inline-block;
8+
}
9+
10+
.button {
11+
--color-button: var(--color-green);
12+
--color-button-text: var(--color-green-2);
13+
--height: calc(var(--unit) * 11);
14+
appearance: none;
15+
background-color: var(--color-button);
16+
border: 0;
17+
border-radius: calc(var(--height) / 2);
18+
color: var(--color-button-text);
19+
display: block;
20+
font-size: var(--body-size);
21+
font-weight: bold;
22+
height: var(--height);
23+
line-height: var(--height);
24+
padding: 0 calc(var(--unit) * 5);
25+
}
26+
`
27+
28+
@customElement('com-button')
29+
export class ComButton extends LitElement {
30+
static styles = [base, styles]
31+
32+
@property({ type: Function }) action = () => { }
33+
34+
render() {
35+
return html`<button class="button" @click=${this.action}><slot></slot></button>`
36+
}
37+
}

0 commit comments

Comments
 (0)