@web-applets/sdk 0.1.2 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
package/README.md CHANGED
@@ -1,29 +1,44 @@
1
1
  # Web Applets
2
2
 
3
- > An open spec & SDK for creating apps that agents can use.
3
+ > An open spec & SDK for creating web apps that agents can use.
4
4
 
5
- 💌 [Mailing List](https://groups.google.com/a/unternet.co/g/community)
5
+ 👾 [Community Discord](https://discord.gg/2aUvMe8HrC) | 💌 [Mailing List](https://groups.google.com/a/unternet.co/g/community)
6
6
 
7
- ## What is it?
7
+ [![Mozilla builders logo](docs/assets/builders.png)](https://builders.mozilla.org/)
8
+
9
+ Web Applets is a [Mozilla Builders](https://builders.mozilla.org/) project.
8
10
 
9
- **Web Applets is an open specification for building software that both humans and AI can understand and use together.** Instead of forcing AI to operate traditional point-and-click apps built for humans, Web Applets creates a new kind of software designed for human-AI collaboration.
11
+ ## What is it?
10
12
 
11
- Think of them a bit like Claude artifacts, but they _do stuff_ & _work anywhere_!
13
+ **Web Applets is an open specification for building software that both humans and AI can understand and use together.** Instead of forcing AI to operate traditional point-and-click apps built for humans, Web Applets creates a new kind of web software designed for human-AI collaboration.
12
14
 
13
15
  ![Demo of a web applets chatbot](./docs/assets/applets-chat-demo.gif)
14
16
 
15
- Think of any web software you use today - maps, documents, shopping, calendars - and imagine if instead of visiting these as separate websites, you could pull them into your own environment where both you and AI could use them together seamlessly. Web applets can do that!
17
+ ## Why?
18
+
19
+ [Unternet](https://unternet.co) is building a new, intelligent user agent that can do things for you across the web. As part of that effort, we needed a way to actuate an embedded web app. You can do this with a computer use model, but for many use cases it's not suitable to point and click around in a virtual browser. Why make a computer talk to another computer via a clumsy web interface when they can just talk directly?
20
+
21
+ Web Applets lets you define a simple, computer-readable API for a web app running in a browser, webview, or iframe. You can send it actions as JSON objects, which an LLM can easily create (see [OpenAI's structured JSON endpoint](https://openai.com/index/introducing-structured-outputs-in-the-api/)), and they can update their UI instantly in-place. Plus, you can expose the internal state of the applets to the model so you can do cool stuff like chat to a map.
22
+
23
+ We wanted anyone to be able to build these actions into their own third-party applets and distribute them. So, we extended the web & made it available to everyone!
24
+
25
+ ## Getting started
26
+
27
+ Create a new web app using our CLI:
28
+
29
+ ```bash
30
+ npx @web-applets/create
31
+ ```
32
+
33
+ Inside the generated folder, you'll find a basic web app setup:
16
34
 
17
- - **Built on Web Standards:** Create applets using familiar web technologies (HTML, CSS, JavaScript, React, Vue, etc.)
18
- - **AI-Native Protocol:** Applets expose their state and actions in a way AI can understand and use
19
- - **Rich Interfaces:** Full support for complex graphical UIs, not just text
20
- - **Local-First:** Runs in your environment, keeping your data under your control
21
- - **Composable:** Applets can work together, sharing context and state
22
- - **Open Standard:** Designed for interoperability across clients, not platform lock-in
35
+ - `public/manifest.json`: A web app manifest, where you can define initial actions, add icons, etc.
36
+ - `index.html`: Much like a website, this holds the main page for your applet
37
+ - `src/main.ts`: Declares functions that respond to each action, and a render function that updates the view based on state
23
38
 
24
- ## Example
39
+ > Want to use React? Svelte? Vue? – No problem, just install the dependencies and create an app the way you normally would in a website. So long as you're receiving the action events, it will all just work.
25
40
 
26
- Let's say we have a simple website that says hello. It might look something like this:
41
+ Now let's build out a basic web applet that will say hello when we send it an action:
27
42
 
28
43
  `index.html`:
29
44
 
@@ -37,7 +52,30 @@ Let's say we have a simple website that says hello. It might look something like
37
52
  </html>
38
53
  ```
39
54
 
40
- Let's add some Web Applets functionality, so this can respond to a `set_name` message:
55
+ Let's add some Web Applets functionality, so this can respond to a `set_name` action. You can do this by adding actions that a model can call, with each on accepting a parameters object that we can describe using JSONSchema.
56
+
57
+ `public/manifest.json`:
58
+
59
+ ```js
60
+ {
61
+ // ...
62
+ "actions": [
63
+ {
64
+ "id": "set_name",
65
+ "description": "Sets the name of the user.",
66
+ "parameters": {
67
+ "type": "object",
68
+ "properties": {
69
+ "name": {
70
+ "type": "string"
71
+ }
72
+ },
73
+ "required": ["name"]
74
+ }
75
+ }
76
+ ]
77
+ }
78
+ ```
41
79
 
42
80
  `main.js`:
43
81
 
@@ -47,60 +85,30 @@ import { applets } from '@web-applets/sdk';
47
85
  const context = applets.getContext();
48
86
 
49
87
  // Define a 'set_name' action, and make it update the shared data object with the new name
50
- context.defineAction('set_name', {
51
- params: {
52
- name: {
53
- type: string,
54
- description: 'The name of the person to be greeted.',
55
- },
56
- },
57
- handler: ({ name }) => applet.data = { name };
88
+ context.setActionHandler('set_name', ({ name }) => {
89
+ context.data = { name };
58
90
  });
59
91
 
60
92
  // Whenever the data is updated, update the view
61
93
  context.ondata = () => {
62
- document.getElementById('name').innerText = applet.data.name;
94
+ const nameElement = document.getElementById('name');
95
+ if (nameElement) {
96
+ nameElement.innerText = context.data.name;
97
+ }
63
98
  };
64
99
  ```
65
100
 
66
- Done! If you load this up in the inspector and introduce yourself, it will respond by greeting you.
67
-
68
- To use this applet, we need to load it in our host web app using the SDK. Assuming the applet lives in our public directory, here's what that might look like:
69
-
70
- ```js
71
- const applet = await applets.load('/helloworld.applet');
72
- applet.onstateupdated = (state) => console.log(state);
73
- applet.dispatchAction('set_name', { name: 'Web Applets' });
74
- // { name: 'Web Applets' }
75
- ```
76
-
77
- For a live example you can download and play with now, check out the [applets chat demo](https://github.com/unternet-co/applets-chat).
78
-
79
- ## Getting started
80
-
81
- Create a new web app with the applets SDK installed. You can do this quickly using our CLI:
82
-
83
- ```bash
84
- npx @web-applets/create
85
- ```
86
-
87
- Inside the generated folder, you'll find a basic web app setup:
88
-
89
- - `public/manifest.json`: A web app manifest, useful when publishing your applet, adding icons, etc.
90
- - `index.html`: Much like a website, this holds the main page for your applet
91
- - `src/main.ts`: Declares functions that respond to each action, and a render function that updates the view based on state
92
-
93
- > Want to use React? Svelte? Vue? – No problem, just install the dependencies and create an app the way you normally would in a website. So long as you're receiving the action events, it will all just work.
94
-
95
- Now if you run `npx @web-applets/inspector`, you should be able to test out your new applet directly. This applet will now work in any environment where the SDK is installed.
101
+ To test out this applet, first start the dev server with `npm run dev`, and take note of the dev server URL. Then, fire up the Web Applets inspector by running `npx @web-applets/inspector`, and enter the dev URL into the URL bar up the top.
96
102
 
97
103
  ![A screenshot showing the 'playground' editing UI, with a web applets showing 'Hello, Web Applets'](docs/assets/web-applets-inspector.png)
98
104
 
105
+ You can build this applet, by running `npm run build`, and host it on any static site host. This applet will now work in any environment where the SDK is installed.
106
+
99
107
  ## Integrating Web Applets into your client
100
108
 
101
- Using Web Applets is just as easy as creating them!
109
+ In order to run, web applets need to be embedded in an environment that supports the Web Applets protocol. This might look like a browser (email me if you're interested!), or an electron app with `<webview>` tags, or sometthing as simple as a web-based AI chat client using iframes.
102
110
 
103
- Install & import the applets client in your app:
111
+ First, install & import the applets SDK in your client app:
104
112
 
105
113
  ```bash
106
114
  npm install @web-applets/sdk
@@ -110,12 +118,12 @@ npm install @web-applets/sdk
110
118
  import { applets } from '@web-applets/sdk';
111
119
  ```
112
120
 
113
- Now you can import your applets from wherever they're being served from (note – you can also host them anywhere on the web):
121
+ Now you can import your applets from wherever they're being served from (note – you can also host them locally, or anywhere on the web):
114
122
 
115
123
  ```js
116
- const applet = await applets.load('/helloworld.applet'); // replace with an https URL if hosted remotely
124
+ const applet = await applets.load('https://applets.unternet.co/maps');
117
125
  applet.ondata = (e) => console.log(e.data);
118
- applet.dispatchAction('set_name', { name: 'Web Applets' });
126
+ applet.dispatchAction('set_name', { name: 'Web Applets' }); // console.log: { name: "Ada Lovelace" }
119
127
  ```
120
128
 
121
129
  The above applet is actually running headless, but we can get it to display by attaching it to a container. For the loading step, instead run:
@@ -129,8 +137,7 @@ const applet = await applets.load(`/helloworld.applet`, container);
129
137
  To load pre-existing saved data into an applet, simply set the data property:
130
138
 
131
139
  ```js
132
- applet.data = { name: 'Ada Lovelace' };
133
- // console.log: { name: "Ada Lovelace" }
140
+ applet.data = { name: 'Ada Lovelace' }; // console.log: { name: "Ada Lovelace" }
134
141
  ```
135
142
 
136
143
  ## Feedback & Community
@@ -42,7 +42,7 @@ export async function load(url, container
42
42
  // } else {
43
43
  // container.setAttribute('sandbox', 'allow-scripts allow-forms');
44
44
  // }
45
- container.setAttribute('sandbox', 'allow-scripts allow-forms');
45
+ container.setAttribute('sandbox', 'allow-scripts allow-forms allow-same-origin');
46
46
  container.src = url;
47
47
  const applet = new Applet(manifest, container.contentWindow);
48
48
  return new Promise((resolve) => {
@@ -1,4 +1,4 @@
1
- import { ActionParams, AppletDataEvent, AppletLoadEvent, AppletReadyEvent, JSONSchemaProperties, AppletManifest, AppletAction, AppletMessageRelay } from './shared';
1
+ import { ActionParams, AppletDataEvent, AppletLoadEvent, AppletReadyEvent, JSONSchema, AppletManifest, AppletAction, AppletMessageRelay } from './shared';
2
2
  export type ActionHandler<T extends ActionParams> = (params: T) => void | Promise<void>;
3
3
  export type ActionHandlerDict = {
4
4
  [key: string]: ActionHandler<any>;
@@ -25,7 +25,7 @@ export declare class AppletContext extends EventTarget {
25
25
  ondata(event: AppletDataEvent): void;
26
26
  }
27
27
  interface ActionDefinition<T> extends Omit<AppletAction, 'id'> {
28
- params?: JSONSchemaProperties;
28
+ parameters?: JSONSchema;
29
29
  handler?: ActionHandler<T>;
30
30
  }
31
31
  export declare function getContext(): AppletContext;
@@ -32,7 +32,6 @@ export class AppletContext extends EventTarget {
32
32
  // Document not yet loaded, we'll add an event listener to call when it does
33
33
  window.addEventListener('DOMContentLoaded', this.initialize.bind(this));
34
34
  }
35
- this.createResizeObserver();
36
35
  this.attachListeners();
37
36
  }
38
37
  async initialize() {
@@ -60,6 +59,7 @@ export class AppletContext extends EventTarget {
60
59
  this.dispatchEvent(readyEvent);
61
60
  if (typeof this.onready === 'function')
62
61
  this.onready(readyEvent);
62
+ this.createResizeObserver();
63
63
  }
64
64
  createResizeObserver() {
65
65
  const resizeObserver = new ResizeObserver((entries) => {
@@ -18,13 +18,17 @@ export interface AppletAction {
18
18
  id: string;
19
19
  name?: string;
20
20
  description?: string;
21
- params?: JSONSchemaProperties;
21
+ parameters?: JSONSchema;
22
22
  }
23
- export type JSONSchemaProperties = Record<string, {
24
- type: string;
23
+ export interface JSONSchema {
24
+ type: 'object' | 'string' | 'number' | 'integer' | 'array' | 'boolean' | 'null';
25
25
  description?: string;
26
- properties?: JSONSchemaProperties;
27
- }>;
26
+ properties?: {
27
+ [key: string]: JSONSchema;
28
+ };
29
+ required?: string[];
30
+ additionalProperties?: boolean;
31
+ }
28
32
  export type ActionParams = Record<string, any>;
29
33
  export declare function loadManifest(pageUrl: string): Promise<AppletManifest>;
30
34
  interface SendMessageOptions {
@@ -50,6 +50,8 @@ export class AppletMessageRelay {
50
50
  return;
51
51
  if (messageEvent.data.type !== messageType)
52
52
  return;
53
+ if (messageEvent.source !== this.target)
54
+ return;
53
55
  const message = new AppletMessage(messageEvent.data.type, messageEvent.data);
54
56
  // Wait for the callback to complete, then send a 'resolve' event
55
57
  // with the message ID.
@@ -0,0 +1,9 @@
1
+ export interface JSONSchema {
2
+ type: 'object' | 'string' | 'number' | 'integer' | 'array' | 'boolean' | 'null';
3
+ description?: string;
4
+ properties?: {
5
+ [key: string]: JSONSchema;
6
+ };
7
+ required?: string[];
8
+ additionalProperties?: boolean;
9
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/utils.d.ts CHANGED
@@ -1,17 +1 @@
1
- import { AppletAction } from './core/shared';
2
1
  export declare function parseUrl(url: string, base?: string): string;
3
- export declare function createOpenAISchemaForAction(action: AppletAction): {
4
- strict: boolean;
5
- name: string;
6
- schema: {
7
- type: string;
8
- required: string[];
9
- properties: {
10
- id: {
11
- type: string;
12
- };
13
- params: import("./core/shared").JSONSchemaProperties;
14
- };
15
- additionalProperties: boolean;
16
- };
17
- };
package/dist/utils.js CHANGED
@@ -26,19 +26,3 @@ function trimTrailingSlash(url) {
26
26
  }
27
27
  return url;
28
28
  }
29
- // Creates an OpenAI-compatible schema declaration for an action
30
- export function createOpenAISchemaForAction(action) {
31
- return {
32
- strict: true,
33
- name: 'action_schema',
34
- schema: {
35
- type: 'object',
36
- required: Object.keys(action),
37
- properties: {
38
- id: { type: 'string' },
39
- params: action.params,
40
- },
41
- additionalProperties: false,
42
- },
43
- };
44
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@web-applets/sdk",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "The Web Applets SDK, for creating & hosting Web Applets.",
5
5
  "author": "Rupert Manfredi <rupert@unternet.co>",
6
6
  "license": "MIT",
@@ -23,9 +23,5 @@
23
23
  },
24
24
  "devDependencies": {
25
25
  "typescript": "^5.6.2"
26
- },
27
- "dependencies": {
28
- "marked": "^14.1.3",
29
- "vite": "^5.4.7"
30
26
  }
31
27
  }