From 4424a9965ec1a2a56a6a669dd47f04fa9d6d5cf2 Mon Sep 17 00:00:00 2001
From: Ben Follington <5009316+bfollington@users.noreply.github.com>
Date: Mon, 3 Feb 2025 11:52:20 +1000
Subject: [PATCH 1/4] Factoring chunks out for re-use between lit and react
---
typescript/packages/common-charm/src/index.ts | 12 ++-
.../src/localBuild.ts | 0
.../packages/common-charm/src/syncRecipe.ts | 83 ++++++++++++++++++
.../test/storage.test.ts | 0
.../src/components/annotation.ts | 2 +-
.../src/components/common-spell-editor.ts | 3 +-
.../src/components/iframe-spell-ai.ts | 39 +++++++--
.../src/components/sidebar.ts | 3 +-
.../src/components/window-manager.ts | 9 +-
.../packages/lookslike-high-level/src/data.ts | 84 +------------------
.../src/recipes/annotation.tsx | 3 +-
11 files changed, 137 insertions(+), 101 deletions(-)
rename typescript/packages/{lookslike-high-level => common-charm}/src/localBuild.ts (100%)
create mode 100644 typescript/packages/common-charm/src/syncRecipe.ts
rename typescript/packages/{lookslike-high-level => common-charm}/test/storage.test.ts (100%)
diff --git a/typescript/packages/common-charm/src/index.ts b/typescript/packages/common-charm/src/index.ts
index aa35b48a8..0c9337cfc 100644
--- a/typescript/packages/common-charm/src/index.ts
+++ b/typescript/packages/common-charm/src/index.ts
@@ -1 +1,11 @@
-export { runPersistent, type Charm, addCharms, removeCharm, storage, syncCharm, charms } from "./charm.js";
+export {
+ runPersistent,
+ type Charm,
+ addCharms,
+ removeCharm,
+ storage,
+ syncCharm,
+ charms,
+} from "./charm.js";
+export { syncRecipe, saveRecipe } from "./syncRecipe.js";
+export { buildRecipe, tsToExports } from "./localBuild.js";
diff --git a/typescript/packages/lookslike-high-level/src/localBuild.ts b/typescript/packages/common-charm/src/localBuild.ts
similarity index 100%
rename from typescript/packages/lookslike-high-level/src/localBuild.ts
rename to typescript/packages/common-charm/src/localBuild.ts
diff --git a/typescript/packages/common-charm/src/syncRecipe.ts b/typescript/packages/common-charm/src/syncRecipe.ts
new file mode 100644
index 000000000..b2b661f59
--- /dev/null
+++ b/typescript/packages/common-charm/src/syncRecipe.ts
@@ -0,0 +1,83 @@
+import {
+ addRecipe,
+ getRecipe,
+ getRecipeParents,
+ getRecipeSrc,
+ getRecipeSpec,
+ getRecipeName,
+} from "@commontools/runner";
+import { buildRecipe } from "./localBuild.js";
+
+export const BLOBBY_SERVER_URL =
+ typeof window !== "undefined"
+ ? window.location.protocol + "//" + window.location.host + "/api/storage/blobby"
+ : "//api/storage/blobby";
+
+const recipesKnownToStorage = new Set();
+
+export async function syncRecipe(id: string) {
+ if (getRecipe(id)) {
+ if (recipesKnownToStorage.has(id)) return;
+ const src = getRecipeSrc(id);
+ const spec = getRecipeSpec(id);
+ const parents = getRecipeParents(id);
+ if (src) saveRecipe(id, src, spec, parents);
+ return;
+ }
+
+ const response = await fetch(`${BLOBBY_SERVER_URL}/spell-${id}`);
+ let src: string;
+ let spec: string;
+ let parents: string[];
+ try {
+ const resp = await response.json();
+ src = resp.src;
+ spec = resp.spec;
+ parents = resp.parents || [];
+ } catch (e) {
+ src = await response.text();
+ spec = "";
+ parents = [];
+ }
+
+ const { recipe, errors } = await buildRecipe(src);
+ if (errors) throw new Error(errors);
+
+ const recipeId = addRecipe(recipe!, src, spec, parents);
+ if (id !== recipeId) {
+ throw new Error(`Recipe ID mismatch: ${id} !== ${recipeId}`);
+ }
+ recipesKnownToStorage.add(recipeId);
+}
+
+export async function saveRecipe(
+ id: string,
+ src: string,
+ spec?: string,
+ parents?: string[],
+ spellbookTitle?: string,
+ spellbookTags?: string[],
+) {
+ // If the recipe is already known to storage, we don't need to save it again,
+ // unless the user is trying to attach a spellbook title or tags.
+ if (recipesKnownToStorage.has(id) && !spellbookTitle) return;
+ recipesKnownToStorage.add(id);
+
+ console.log("Saving recipe", id);
+ const response = await fetch(`${BLOBBY_SERVER_URL}/spell-${id}`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ src,
+ recipe: JSON.parse(JSON.stringify(getRecipe(id))),
+ spec,
+ parents,
+ recipeName: getRecipeName(id),
+ spellbookTitle,
+ spellbookTags,
+ }),
+ });
+ return response.ok;
+}
diff --git a/typescript/packages/lookslike-high-level/test/storage.test.ts b/typescript/packages/common-charm/test/storage.test.ts
similarity index 100%
rename from typescript/packages/lookslike-high-level/test/storage.test.ts
rename to typescript/packages/common-charm/test/storage.test.ts
diff --git a/typescript/packages/lookslike-high-level/src/components/annotation.ts b/typescript/packages/lookslike-high-level/src/components/annotation.ts
index 13bd2377d..58ded4fe4 100644
--- a/typescript/packages/lookslike-high-level/src/components/annotation.ts
+++ b/typescript/packages/lookslike-high-level/src/components/annotation.ts
@@ -2,7 +2,7 @@ import { LitElement, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ref, createRef } from "lit/directives/ref.js";
import { render } from "@commontools/html";
-import { charms, UI, annotationsEnabled } from "../data.js";
+import { annotationsEnabled } from "../data.js";
import { run, getDoc, DocImpl, getDocLinkOrValue } from "@commontools/runner";
import { annotation } from "../recipes/annotation.jsx";
diff --git a/typescript/packages/lookslike-high-level/src/components/common-spell-editor.ts b/typescript/packages/lookslike-high-level/src/components/common-spell-editor.ts
index 86d0ce4bb..72331e958 100644
--- a/typescript/packages/lookslike-high-level/src/components/common-spell-editor.ts
+++ b/typescript/packages/lookslike-high-level/src/components/common-spell-editor.ts
@@ -2,8 +2,7 @@ import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { when } from "lit/directives/when.js";
import { addRecipe, getRecipeSpec, getRecipeSrc, run } from "@commontools/runner";
-import { addCharms } from "@commontools/charm";
-import { tsToExports } from "../localBuild.js";
+import { addCharms, tsToExports } from "@commontools/charm";
import { iterate, llmTweakSpec, generateSuggestions } from "./spell-ai.js";
import { createRef, ref } from "lit/directives/ref.js";
diff --git a/typescript/packages/lookslike-high-level/src/components/iframe-spell-ai.ts b/typescript/packages/lookslike-high-level/src/components/iframe-spell-ai.ts
index 09acd46c9..f7abdb2b9 100644
--- a/typescript/packages/lookslike-high-level/src/components/iframe-spell-ai.ts
+++ b/typescript/packages/lookslike-high-level/src/components/iframe-spell-ai.ts
@@ -1,14 +1,17 @@
import { getRecipe, getRecipeSrc, addRecipe, run } from "@commontools/runner";
-import { addCharms, Charm } from "@commontools/charm";
+import { addCharms, Charm, tsToExports } from "@commontools/charm";
import { openCharm } from "../data.js";
import { LLMClient } from "@commontools/llm-client";
import { createJsonSchema, JSONSchema, TYPE } from "@commontools/builder";
-import { tsToExports } from "../localBuild.js";
import { type DocImpl } from "@commontools/runner";
import demoSrc from "./demo.html?raw";
-const SELECTED_MODEL = ["groq:llama-3.3-70b-specdec", "cerebras:llama-3.3-70b", "anthropic:claude-3-5-sonnet"];
+const SELECTED_MODEL = [
+ "groq:llama-3.3-70b-specdec",
+ "cerebras:llama-3.3-70b",
+ "anthropic:claude-3-5-sonnet",
+];
const responsePrefill =
"```html\n" +
@@ -177,7 +180,17 @@ const llmUrl =
const llm = new LLMClient(llmUrl);
-const genSrc = async ({ src, spec, newSpec, schema }: { src?: string; spec?: string; newSpec: string; schema: JSONSchema }) => {
+const genSrc = async ({
+ src,
+ spec,
+ newSpec,
+ schema,
+}: {
+ src?: string;
+ spec?: string;
+ newSpec: string;
+ schema: JSONSchema;
+}) => {
const messages = [];
if (spec && src) {
messages.push(spec);
@@ -228,7 +241,7 @@ ${newSpec}
${JSON.stringify(schema, null, 2)}
-
+
You can use the generateImage function to get a url for a generated image.`;
const payload = {
@@ -292,7 +305,12 @@ export async function iterate(charm: DocImpl | null, value: string, shift
const newSpec = shiftKey ? iframe.spec + "\n" + value : value;
- const newIFrameSrc = await genSrc({ src: iframe.src, spec: iframe.spec, newSpec, schema: iframe.argumentSchema });
+ const newIFrameSrc = await genSrc({
+ src: iframe.src,
+ spec: iframe.spec,
+ newSpec,
+ schema: iframe.argumentSchema,
+ });
const name = newIFrameSrc.match(/(.*?)<\/title>/)?.[1] ?? newSpec;
const newRecipeSrc = buildFullRecipe({ ...iframe, src: newIFrameSrc, spec: newSpec, name });
@@ -325,14 +343,19 @@ export async function iterate(charm: DocImpl | null, value: string, shift
}
}
-
export async function castNewRecipe(data: any, newSpec: string) {
const schema = createJsonSchema({}, data);
schema.description = newSpec;
const newIFrameSrc = await genSrc({ newSpec, schema });
const name = newIFrameSrc.match(/(.*?)<\/title>/)?.[1] ?? newSpec;
- const newRecipeSrc = buildFullRecipe({ src: newIFrameSrc, spec: newSpec, argumentSchema: schema, resultSchema: {}, name });
+ const newRecipeSrc = buildFullRecipe({
+ src: newIFrameSrc,
+ spec: newSpec,
+ argumentSchema: schema,
+ resultSchema: {},
+ name,
+ });
const { exports, errors } = await tsToExports(newRecipeSrc);
diff --git a/typescript/packages/lookslike-high-level/src/components/sidebar.ts b/typescript/packages/lookslike-high-level/src/components/sidebar.ts
index 6608708f1..3b317f1a0 100644
--- a/typescript/packages/lookslike-high-level/src/components/sidebar.ts
+++ b/typescript/packages/lookslike-high-level/src/components/sidebar.ts
@@ -2,7 +2,7 @@ import { css, html, LitElement, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators.js";
import { style } from "@commontools/ui";
import { when } from "lit/directives/when.js";
-import { Charm, charms, runPersistent } from "@commontools/charm";
+import { Charm, charms, runPersistent, saveRecipe } from "@commontools/charm";
import { BLOBBY_SERVER_URL, recipes } from "../data.js";
import { refer } from "merkle-reference";
@@ -20,7 +20,6 @@ import { watchCell } from "../watchCell.js";
import { createRef, ref } from "lit/directives/ref.js";
import { home } from "../recipes/home.jsx";
import { render } from "@commontools/html";
-import { saveRecipe } from "../data.js";
import { castNewRecipe } from "./iframe-spell-ai.js";
const uploadBlob = async (data: any) => {
diff --git a/typescript/packages/lookslike-high-level/src/components/window-manager.ts b/typescript/packages/lookslike-high-level/src/components/window-manager.ts
index 2549e004a..b086e8a4a 100644
--- a/typescript/packages/lookslike-high-level/src/components/window-manager.ts
+++ b/typescript/packages/lookslike-high-level/src/components/window-manager.ts
@@ -3,16 +3,14 @@ import { customElement, state } from "lit/decorators.js";
import { createRef, Ref, ref } from "lit/directives/ref.js";
import { style } from "@commontools/ui";
import { render } from "@commontools/html";
-import {
- closeCharm,
- openCharm,
- syncRecipe,
-} from "../data.js";
+import { closeCharm, openCharm } from "../data.js";
import {
syncCharm,
addCharms,
Charm,
runPersistent,
+ syncRecipe,
+ buildRecipe,
} from "@commontools/charm";
import {
@@ -28,7 +26,6 @@ import {
import { repeat } from "lit/directives/repeat.js";
import { UI, NAME, TYPE } from "@commontools/builder";
import { matchRoute, navigate } from "../router.js";
-import { buildRecipe } from "../localBuild.js";
import * as iframeSpellAi from "./iframe-spell-ai.js";
async function castSpell(value: string, openCharm: (charmId: string) => void) {
diff --git a/typescript/packages/lookslike-high-level/src/data.ts b/typescript/packages/lookslike-high-level/src/data.ts
index 600c846d5..8cff2a74a 100644
--- a/typescript/packages/lookslike-high-level/src/data.ts
+++ b/typescript/packages/lookslike-high-level/src/data.ts
@@ -8,100 +8,24 @@ import {
type DocImpl,
EntityId,
getEntityId,
- getRecipe,
- getRecipeParents,
- getRecipeSrc,
raw,
type ReactivityLog,
- getRecipeSpec,
- getRecipeName,
Action,
addAction,
removeAction,
} from "@commontools/runner";
import * as allRecipes from "./recipes/index.js";
-import { buildRecipe } from "./localBuild.js";
import { setIframeContextHandler } from "@commontools/iframe-sandbox";
import { addCharms } from "@commontools/charm";
-// Necessary, so that suggestions are indexed.
-// import "./recipes/todo-list-as-task.jsx";
-// import "./recipes/playlist.jsx";
-
-
export const BLOBBY_SERVER_URL =
typeof window !== "undefined"
? window.location.protocol + "//" + window.location.host + "/api/storage/blobby"
: "//api/storage/blobby";
-const recipesKnownToStorage = new Set();
-
-export async function syncRecipe(id: string) {
- if (getRecipe(id)) {
- if (recipesKnownToStorage.has(id)) return;
- const src = getRecipeSrc(id);
- const spec = getRecipeSpec(id);
- const parents = getRecipeParents(id);
- if (src) saveRecipe(id, src, spec, parents);
- return;
- }
-
- const response = await fetch(`${BLOBBY_SERVER_URL}/spell-${id}`);
- let src: string;
- let spec: string;
- let parents: string[];
- try {
- const resp = await response.json();
- src = resp.src;
- spec = resp.spec;
- parents = resp.parents || [];
- } catch (e) {
- src = await response.text();
- spec = "";
- parents = [];
- }
-
- const { recipe, errors } = await buildRecipe(src);
- if (errors) throw new Error(errors);
-
- const recipeId = addRecipe(recipe!, src, spec, parents);
- if (id !== recipeId) {
- throw new Error(`Recipe ID mismatch: ${id} !== ${recipeId}`);
- }
- recipesKnownToStorage.add(recipeId);
-}
-
-export async function saveRecipe(
- id: string,
- src: string,
- spec?: string,
- parents?: string[],
- spellbookTitle?: string,
- spellbookTags?: string[],
-) {
- // If the recipe is already known to storage, we don't need to save it again,
- // unless the user is trying to attach a spellbook title or tags.
- if (recipesKnownToStorage.has(id) && !spellbookTitle) return;
- recipesKnownToStorage.add(id);
-
- console.log("Saving recipe", id);
- const response = await fetch(`${BLOBBY_SERVER_URL}/spell-${id}`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- src,
- recipe: JSON.parse(JSON.stringify(getRecipe(id))),
- spec,
- parents,
- recipeName: getRecipeName(id),
- spellbookTitle,
- spellbookTags,
- }),
- });
- return response.ok;
-}
+// Necessary, so that suggestions are indexed.
+// import "./recipes/todo-list-as-task.jsx";
+// import "./recipes/playlist.jsx";
import smolIframe from "./recipes/smolIframe.js";
import complexIframe from "./recipes/complexIframe.js";
@@ -195,4 +119,4 @@ setIframeContextHandler({
unsubscribe(_context: any, receipt: any) {
removeAction(receipt);
},
-});
\ No newline at end of file
+});
diff --git a/typescript/packages/lookslike-high-level/src/recipes/annotation.tsx b/typescript/packages/lookslike-high-level/src/recipes/annotation.tsx
index 10eebb0ba..6bc469480 100644
--- a/typescript/packages/lookslike-high-level/src/recipes/annotation.tsx
+++ b/typescript/packages/lookslike-high-level/src/recipes/annotation.tsx
@@ -1,6 +1,7 @@
import { h, type VNode } from "@commontools/html";
import { recipe, lift, handler, cell, UI, NAME, TYPE, ifElse, llm } from "@commontools/builder";
-import { type Charm, openCharm } from "../data.js";
+import { type Charm } from "@commontools/charm";
+import { openCharm } from "../data.js";
import { run, getDocLinkOrValue, getEntityId, getDocByEntityId } from "@commontools/runner";
import { suggestions } from "../suggestions.js";
import { z } from "zod";
From d84e5cea41150fa714169ce011cf02dba021473a Mon Sep 17 00:00:00 2001
From: Ben Follington <5009316+bfollington@users.noreply.github.com>
Date: Mon, 3 Feb 2025 13:24:26 +1000
Subject: [PATCH 2/4] Port os-chrome to React experiment
---
.../common-os-ui/src/components/os-tab-bar.ts | 2 +-
typescript/packages/jumble/package.json | 1 +
.../jumble/src/components/CharmRunner.tsx | 6 +-
.../jumble/src/components/Sidebar.tsx | 207 ++++++++++++++++++
.../jumble/src/components/WebComponent.tsx | 13 ++
.../packages/jumble/src/hooks/use-charm.ts | 24 ++
.../jumble/src/hooks/use-web-component.tsx | 53 +++++
.../packages/jumble/src/views/Shell.tsx | 90 +++++++-
typescript/packages/jumble/src/views/main.css | 5 +
typescript/packages/jumble/src/views/state.ts | 3 +
typescript/packages/pnpm-lock.yaml | 3 +
11 files changed, 399 insertions(+), 8 deletions(-)
create mode 100644 typescript/packages/jumble/src/components/Sidebar.tsx
create mode 100644 typescript/packages/jumble/src/components/WebComponent.tsx
create mode 100644 typescript/packages/jumble/src/hooks/use-charm.ts
create mode 100644 typescript/packages/jumble/src/hooks/use-web-component.tsx
create mode 100644 typescript/packages/jumble/src/views/main.css
create mode 100644 typescript/packages/jumble/src/views/state.ts
diff --git a/typescript/packages/common-os-ui/src/components/os-tab-bar.ts b/typescript/packages/common-os-ui/src/components/os-tab-bar.ts
index dd52df6b7..7297198a7 100644
--- a/typescript/packages/common-os-ui/src/components/os-tab-bar.ts
+++ b/typescript/packages/common-os-ui/src/components/os-tab-bar.ts
@@ -115,7 +115,7 @@ export class OsTabBar extends LitElement {
return html`
- ${this.items.map(
+ ${this.items?.map(
(item) => html`