@gui-chat-plugin/sequence 0.2.0 → 0.3.0
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/CHANGELOG.md +12 -0
- package/README.md +29 -6
- package/dist/core/definitions.d.ts +15 -5
- package/dist/core/host.d.ts +7 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/slideHtml.d.ts +23 -0
- package/dist/core-B1EsmU-q.cjs +46 -0
- package/dist/{core-qe3zK8TI.js → core-BALd7sgj.js} +219 -151
- package/dist/index.cjs +1 -1
- package/dist/index.js +2 -2
- package/dist/style.css +1 -1
- package/dist/vue.cjs +1 -1
- package/dist/vue.js +91 -33
- package/package.json +1 -1
- package/dist/core-AQFO5Vcb.cjs +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
- **HTML slides**: `presentSlide` takes `html` instead of `imagePrompt` for a slide designed in HTML
|
|
6
|
+
(words, numbers, lists, comparisons, diagrams), styled with Tailwind CSS v4 and animated, so it
|
|
7
|
+
builds up while the model explains it. The model picks per slide; a slideshow can mix them. The
|
|
8
|
+
View shows it at 1280x720, scaled to fit, in a sandboxed iframe (`sandbox="allow-scripts"`, no
|
|
9
|
+
network: `connect-src 'none'`, images from `data:`/`blob:` only; the slide's own scripts, event
|
|
10
|
+
handlers, `<meta>` refreshes and links don't run, since they could navigate the frame to a URL
|
|
11
|
+
carrying the slide). Nothing is drawn, so it needs
|
|
12
|
+
no image backend. `slideHtmlDocument(html)` makes the page, for a host that shows one elsewhere.
|
|
13
|
+
Records and results carry `html`; `imagePrompt` is `""` and `imagePath` absent for these slides.
|
|
14
|
+
|
|
3
15
|
## 0.2.0
|
|
4
16
|
|
|
5
17
|
- **State per conversation**: the slideshow in progress, the steps and panels waiting for the
|
package/README.md
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
Tools that show things one step at a time, as pictures, for
|
|
4
4
|
[GUI Chat Protocol](https://github.com/receptron/gui-chat-protocol) hosts:
|
|
5
5
|
|
|
6
|
-
| Tool | Shows
|
|
7
|
-
| ------------------ |
|
|
8
|
-
| `presentSlide` | One slide of a spoken slideshow (mode `"presentation"`), or one step of a step-by-step guide the user follows along with (mode `"steps"`) |
|
|
9
|
-
| `defineStoryboard` | A story's cast: a reference sheet for each recurring character
|
|
10
|
-
| `presentPanel` | One panel of the story, with its characters drawn as in their sheets; an interactive story offers choices
|
|
6
|
+
| Tool | Shows |
|
|
7
|
+
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
8
|
+
| `presentSlide` | One slide of a spoken slideshow (mode `"presentation"`), or one step of a step-by-step guide the user follows along with (mode `"steps"`): a generated picture, or a slide in HTML |
|
|
9
|
+
| `defineStoryboard` | A story's cast: a reference sheet for each recurring character |
|
|
10
|
+
| `presentPanel` | One panel of the story, with its characters drawn as in their sheets; an interactive story offers choices |
|
|
11
11
|
|
|
12
12
|
From MulmoChat and MulmoGlass, tested there by voice on OpenAI Realtime, Gemini Live and Grok, and
|
|
13
13
|
in text chat.
|
|
@@ -60,11 +60,34 @@ overlap (a host that runs `execute()` on a server gets them as concurrent reques
|
|
|
60
60
|
claimed before anything is awaited, so an identical one waits for it and a later step is held while
|
|
61
61
|
it is drawn.
|
|
62
62
|
|
|
63
|
+
## HTML slides
|
|
64
|
+
|
|
65
|
+
A slide is a generated picture (`imagePrompt`) or HTML (`html`), the model's choice per slide: a
|
|
66
|
+
picture for a scene or an object, HTML for words, numbers, lists, comparisons and diagrams, which
|
|
67
|
+
picture models draw badly. The model writes the inside of `<body>` for a 1280x720 canvas, with
|
|
68
|
+
Tailwind CSS v4 classes and these entrance animations: `animate-fade-in`, `animate-fade-up`,
|
|
69
|
+
`animate-fade-down`, `animate-slide-in-left`, `animate-slide-in-right`, `animate-zoom-in`,
|
|
70
|
+
`animate-pop` and `animate-draw` (an SVG path drawing itself), staggered with
|
|
71
|
+
`[animation-delay:…]`. The slide's own scripts don't run: its animations are CSS.
|
|
72
|
+
|
|
73
|
+
`slideHtmlDocument(html)` (core entry) makes the page: Tailwind's browser build from jsDelivr (one
|
|
74
|
+
version, checked by its hash), the animations, and a Content Security Policy that lets it send
|
|
75
|
+
nothing out: `connect-src 'none'`; images, media and fonts only from `data:`/`blob:` URLs and
|
|
76
|
+
Google Fonts; and no script but the page's own and Tailwind's, so nothing can navigate the frame to
|
|
77
|
+
a URL carrying the page (a sandbox allows that, and CSP can't forbid it). The slide's `<meta>`,
|
|
78
|
+
`<base>` and `<link>` tags are made plain text (a refresh would navigate), and a link doesn't
|
|
79
|
+
navigate when clicked. The View shows it in an iframe with `sandbox="allow-scripts"` and no
|
|
80
|
+
`allow-same-origin`, so the page can't reach the host's pages, storage or cookies, scaled to fit. Tailwind is loaded from the network: offline, the slide
|
|
81
|
+
appears unstyled.
|
|
82
|
+
|
|
83
|
+
A host that sends `currentResult` by its saved picture's path only (as MulmoChat does) can't tell
|
|
84
|
+
that an HTML step is on the screen; a guide step asked for again is then shown again.
|
|
85
|
+
|
|
63
86
|
## Records
|
|
64
87
|
|
|
65
88
|
Saved through `files.artifacts`, with their pictures' paths:
|
|
66
89
|
|
|
67
|
-
- `slideshows/<id>.json`: `{ id, title, mode, totalSlides, slides: { "1": { title, imagePrompt, imagePath } } }`
|
|
90
|
+
- `slideshows/<id>.json`: `{ id, title, mode, totalSlides, slides: { "1": { title, imagePrompt, imagePath } } }`; an HTML slide has `html`, an `imagePrompt` of `""` and no `imagePath`
|
|
68
91
|
- `storyboards/<id>.json`: `{ id, title, style, totalPanels, interactive, characters: [{ name, description, imagePath }], panels: { "1": { caption, characters, imagePrompt, imagePath, choices } } }`
|
|
69
92
|
|
|
70
93
|
IDs are 12 hex digits. The results name them, so a later tool call can refer to a slideshow or a
|
|
@@ -13,12 +13,17 @@ export interface SlideArgs {
|
|
|
13
13
|
slide: number;
|
|
14
14
|
totalSlides: number;
|
|
15
15
|
title: string;
|
|
16
|
+
/** The picture's prompt; "" for an HTML slide. */
|
|
16
17
|
imagePrompt: string;
|
|
18
|
+
/** An HTML slide's body; absent for a picture. */
|
|
19
|
+
html?: string;
|
|
17
20
|
mode: SlideMode;
|
|
18
21
|
}
|
|
19
|
-
export declare const SLIDE_ARGS_ERROR = "presentSlide needs slide and totalSlides (whole numbers, slide from 1 to totalSlides) and an imagePrompt";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
export declare const SLIDE_ARGS_ERROR = "presentSlide needs slide and totalSlides (whole numbers, slide from 1 to totalSlides), and either an imagePrompt or html";
|
|
23
|
+
export declare const SLIDE_HTML_TOO_LONG = "presentSlide's html is too long: keep a slide under 60000 characters";
|
|
24
|
+
/** The slide arguments, or what is wrong with them (said to the model). A
|
|
25
|
+
* slide with html is an HTML slide, whatever imagePrompt says. */
|
|
26
|
+
export declare function parseSlideArgs(args: Record<string, unknown>): SlideArgs | string;
|
|
22
27
|
/** What the model does once a slide is on the screen. */
|
|
23
28
|
export declare const slideShownInstructions: ({ slide, totalSlides, title, mode, }: SlideArgs) => string;
|
|
24
29
|
/** A slideshow as saved in artifacts/slideshows/<id>.json. */
|
|
@@ -30,14 +35,19 @@ export interface Slideshow {
|
|
|
30
35
|
totalSlides: number;
|
|
31
36
|
slides: Record<string, {
|
|
32
37
|
title: string;
|
|
38
|
+
/** "" for an HTML slide. */
|
|
33
39
|
imagePrompt: string;
|
|
34
40
|
imagePath?: string;
|
|
41
|
+
/** An HTML slide's body (slideHtmlDocument makes it a page). */
|
|
42
|
+
html?: string;
|
|
35
43
|
}>;
|
|
36
44
|
}
|
|
37
|
-
/** A shown slide's result data. */
|
|
45
|
+
/** A shown slide's result data: a picture, or an HTML slide's body. */
|
|
38
46
|
export interface SlideData {
|
|
39
|
-
imageData
|
|
47
|
+
imageData?: string;
|
|
40
48
|
imagePath?: string;
|
|
49
|
+
html?: string;
|
|
50
|
+
/** The picture's prompt; "" for an HTML slide. */
|
|
41
51
|
prompt: string;
|
|
42
52
|
slideshowId: string;
|
|
43
53
|
slide: number;
|
package/dist/core/host.d.ts
CHANGED
|
@@ -11,9 +11,15 @@ export declare function imageOf(result: ToolResult | null | undefined): {
|
|
|
11
11
|
imageData?: string;
|
|
12
12
|
imagePath?: string;
|
|
13
13
|
};
|
|
14
|
+
/** An HTML slide's body, when the result is one. */
|
|
15
|
+
export declare function htmlOf(result: ToolResult | null | undefined): string;
|
|
16
|
+
/** Whether a result shows something: a picture or an HTML slide. */
|
|
17
|
+
export declare const shownContent: (result: ToolResult) => boolean;
|
|
14
18
|
/** Whether two results show the same picture: by its saved path when both
|
|
15
19
|
* have one (a host may send the current result without its image data),
|
|
16
|
-
* else by the picture itself.
|
|
20
|
+
* else by the picture itself, or the same HTML slide. A host that sends the
|
|
21
|
+
* current result by its path only can't tell an HTML slide is on the
|
|
22
|
+
* screen: one asked for again is shown again. */
|
|
17
23
|
export declare function samePicture(a: ToolResult | null | undefined, b: ToolResult | null | undefined): boolean;
|
|
18
24
|
/** When the user spoke after `time` (ms since the epoch). A host that doesn't
|
|
19
25
|
* say when the user spoke holds nothing. */
|
package/dist/core/index.d.ts
CHANGED
|
@@ -4,5 +4,6 @@ export declare const presentSlidePluginCore: SequencePluginCore;
|
|
|
4
4
|
export declare const defineStoryboardPluginCore: SequencePluginCore;
|
|
5
5
|
export declare const presentPanelPluginCore: SequencePluginCore;
|
|
6
6
|
export * from "./definitions";
|
|
7
|
+
export { MAX_SLIDE_HTML, SLIDE_ANIMATIONS, SLIDE_HEIGHT, SLIDE_WIDTH, slideHtmlDocument, } from "./slideHtml";
|
|
7
8
|
export { SLIDESHOWS_DIR, STORYBOARDS_DIR, loadRecord } from "./records";
|
|
8
9
|
export type { SequenceContext } from "./host";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const SLIDE_WIDTH = 1280;
|
|
2
|
+
export declare const SLIDE_HEIGHT = 720;
|
|
3
|
+
/** The most HTML a slide may have, in characters. */
|
|
4
|
+
export declare const MAX_SLIDE_HTML = 60000;
|
|
5
|
+
/** The animations every slide has, as `animate-<name>` classes. They keep
|
|
6
|
+
* their first frame while delayed (fill mode both), so an element with a
|
|
7
|
+
* delay stays hidden until its turn. */
|
|
8
|
+
export declare const SLIDE_ANIMATIONS: readonly ["fade-in", "fade-up", "fade-down", "slide-in-left", "slide-in-right", "zoom-in", "pop", "draw"];
|
|
9
|
+
/** PAGE_SCRIPT's hash, which the policy allows (checked by a test). */
|
|
10
|
+
export declare const PAGE_SCRIPT_HASH = "sha256-MZyL5rvWmIVxl3WUuDU5BqzPJGR2/fV/YPh01MNyUoM=";
|
|
11
|
+
/** The slide's HTML, made safe to show:
|
|
12
|
+
* - the tags that act on the page from outside the body are plain text:
|
|
13
|
+
* <meta> (a refresh navigates the frame, a policy of its own), <base> and
|
|
14
|
+
* <link> (prefetches). No tag can be rebuilt by the replacement, which only
|
|
15
|
+
* adds "<".
|
|
16
|
+
* - no declarative shadow root (<template shadowrootmode>, the only way to
|
|
17
|
+
* make one without a script): a click inside one reaches the page's link
|
|
18
|
+
* guard retargeted to its host, so a link there would navigate. Every
|
|
19
|
+
* "shadowroot" is renamed, which leaves no attribute that makes one.
|
|
20
|
+
* Everything stays in the light DOM, where the guard sees it. */
|
|
21
|
+
export declare const neutralizeSlideHtml: (html: string) => string;
|
|
22
|
+
/** The page an HTML slide is shown as. */
|
|
23
|
+
export declare function slideHtmlDocument(html: string): string;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
let e=require("gui-chat-protocol");var t=1280,n=720,r=6e4,i=`https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.3.3/dist/index.global.js`,a=`sha384-2ql948lIdLcGEE0/qxNiudyTjgauA3RDJERu5xW75kFCvSl5a9odyQYCb6tEjnmB`,o=[`fade-in`,`fade-up`,`fade-down`,`slide-in-left`,`slide-in-right`,`zoom-in`,`pop`,`draw`],s=`
|
|
2
|
+
@theme {
|
|
3
|
+
--animate-fade-in: fade-in 0.8s ease-out both;
|
|
4
|
+
--animate-fade-up: fade-up 0.8s ease-out both;
|
|
5
|
+
--animate-fade-down: fade-down 0.8s ease-out both;
|
|
6
|
+
--animate-slide-in-left: slide-in-left 0.8s ease-out both;
|
|
7
|
+
--animate-slide-in-right: slide-in-right 0.8s ease-out both;
|
|
8
|
+
--animate-zoom-in: zoom-in 0.8s ease-out both;
|
|
9
|
+
--animate-pop: pop 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) both;
|
|
10
|
+
--animate-draw: draw 1.5s ease-in-out both;
|
|
11
|
+
@keyframes fade-in { from { opacity: 0 } to { opacity: 1 } }
|
|
12
|
+
@keyframes fade-up { from { opacity: 0; transform: translateY(40px) } to { opacity: 1; transform: none } }
|
|
13
|
+
@keyframes fade-down { from { opacity: 0; transform: translateY(-40px) } to { opacity: 1; transform: none } }
|
|
14
|
+
@keyframes slide-in-left { from { opacity: 0; transform: translateX(-80px) } to { opacity: 1; transform: none } }
|
|
15
|
+
@keyframes slide-in-right { from { opacity: 0; transform: translateX(80px) } to { opacity: 1; transform: none } }
|
|
16
|
+
@keyframes zoom-in { from { opacity: 0; transform: scale(0.8) } to { opacity: 1; transform: none } }
|
|
17
|
+
@keyframes pop { from { opacity: 0; transform: scale(0.5) } to { opacity: 1; transform: none } }
|
|
18
|
+
@keyframes draw { from { stroke-dashoffset: var(--draw-length, 1000) } to { stroke-dashoffset: 0 } }
|
|
19
|
+
}`,c=`
|
|
20
|
+
(() => {
|
|
21
|
+
const show = () => document.documentElement.classList.add("ready");
|
|
22
|
+
const ready = () => requestAnimationFrame(() => requestAnimationFrame(show));
|
|
23
|
+
const check = () =>
|
|
24
|
+
document.querySelector("style:not([type]):not(#slide-base)") ? ready() : setTimeout(check, 30);
|
|
25
|
+
addEventListener("DOMContentLoaded", check);
|
|
26
|
+
setTimeout(show, 1000);
|
|
27
|
+
addEventListener("click", (event) => {
|
|
28
|
+
if (event.target instanceof Element && event.target.closest("a, area")) event.preventDefault();
|
|
29
|
+
}, true);
|
|
30
|
+
})();`,l=[`default-src 'none'`,`script-src 'sha256-MZyL5rvWmIVxl3WUuDU5BqzPJGR2/fV/YPh01MNyUoM=' ${i}`,`style-src 'unsafe-inline' https://fonts.googleapis.com`,`font-src https://fonts.gstatic.com data:`,`img-src data: blob:`,`media-src data: blob:`,`connect-src 'none'`,`form-action 'none'`,`base-uri 'none'`].join(`; `),u=e=>e.replace(/<(?=(meta|base|link)\b)/gi,`<`).replace(/shadowroot/gi,`data-no-shadow-root`),d=`
|
|
31
|
+
html, body { margin: 0; width: ${t}px; height: 720px; overflow: hidden; }
|
|
32
|
+
html:not(.ready) body { visibility: hidden; }
|
|
33
|
+
html:not(.ready) body * { animation-play-state: paused !important; }`;function f(e){return`<!doctype html>
|
|
34
|
+
<html lang="en">
|
|
35
|
+
<head>
|
|
36
|
+
<meta charset="utf-8">
|
|
37
|
+
<meta http-equiv="Content-Security-Policy" content="${l}">
|
|
38
|
+
<style id="slide-base">${d}</style>
|
|
39
|
+
<script>${c}<\/script>
|
|
40
|
+
<script defer src="${i}" integrity="${a}" crossorigin="anonymous"><\/script>
|
|
41
|
+
<style type="text/tailwindcss">${s}</style>
|
|
42
|
+
</head>
|
|
43
|
+
<body class="bg-white text-slate-900 font-sans antialiased">
|
|
44
|
+
${u(e)}
|
|
45
|
+
</body>
|
|
46
|
+
</html>`}var p=`presentSlide`,m=`defineStoryboard`,h=`presentPanel`,ee=4,te=12,ne=3,g=/^[0-9a-f]{12}$/,_=e=>typeof e==`object`&&!!e&&!Array.isArray(e),v=e=>typeof e==`string`?e.trim():``,y=(e,t)=>e.toLowerCase()===t.toLowerCase(),b=e=>typeof e==`number`&&Number.isInteger(e),x=`presentSlide needs slide and totalSlides (whole numbers, slide from 1 to totalSlides), and either an imagePrompt or html`,re=`presentSlide's html is too long: keep a slide under ${r} characters`;function ie(e){let{slide:t,totalSlides:n,title:r,imagePrompt:i}=e,a=v(e.html);return!b(t)||!b(n)||t<1||n<t||!a&&!v(i)?x:a.length>6e4?re:{slide:t,totalSlides:n,title:typeof r==`string`?r:``,imagePrompt:a?``:String(i),...a&&{html:a},mode:e.mode===`steps`?`steps`:`presentation`}}var S=({slide:e,totalSlides:t,title:n,mode:r})=>{let i=n?` ("${n}")`:``;if(r===`steps`){let n=`Step ${e} of ${t}${i}`;return e<t?`${n} is now on the screen. Explain what to do in this step, then stop and wait while the user does it. When they say they're ready (next, done, go on), call presentSlide for step ${e+1} with mode "steps". If they ask to go back or to see a step again, call presentSlide for that step.`:`${n}, the last one, is now on the screen. Explain what to do, then wrap up the guide once the user is done.`}let a=`Slide ${e} of ${t}${i}`;return e<t?`${a} is now on the screen. Explain it, then, in this same reply and without waiting for the user, call presentSlide for slide ${e+1}. If the user has asked you to stop, or asked something else, since the slideshow began, answer them instead of going on.`:`${a}, the last one, is now on the screen. Explain it, then wrap up the slideshow.`},ae=[`For an HTML slide, instead of imagePrompt: the slide's content, the inside of <body> only (no <html>, <head> or <body> tags), for a ${t}x720 px canvas, which it should fill (start with a <div class="w-full h-full ...">). Style it with Tailwind CSS v4 classes, which are loaded for you: a clear layout, a large title (text-6xl or so), few words in large type, a color scheme, and inline SVG or emoji for icons and diagrams. There is no network: no <img> from URLs, no links to follow.`,`Animate it so it builds up as you explain it: these classes are defined, ${o.map(e=>`animate-${e}`).join(`, `)} (an SVG path drawing itself: set stroke-dasharray and --draw-length to its length), plus Tailwind's animate-pulse, animate-bounce, animate-spin and animate-ping, and transitions. Stagger the parts with a delay, [animation-delay:600ms], so they appear one after another over a few seconds. A <style> with your own @keyframes is allowed too (and Google Fonts by @import). Scripts don't run: animate with CSS only.`].join(` `),C=`When the user asks for a slideshow (or to explain something with slides), plan four to six slides, then show them one at a time with presentSlide: slide 1 first, and each next slide only after you have explained the one on the screen. Go on to the last slide without asking whether to continue. A slide is a generated picture (imagePrompt) or a designed slide in HTML (html): use a picture for a scene, an object or a place, and HTML for words, numbers, lists, comparisons, timelines and diagrams, which a picture model draws badly; a slideshow can mix them. When the user wants to be shown how to do something they will do along with you (cooking, folding, fixing, an exercise), make it a step-by-step guide instead: mode "steps", one slide per step, and after each step wait for the user to say they are ready. Use generateImage for a single picture, not for slides.`,w={type:`function`,name:p,prompt:C,description:`Show one slide of a slideshow, full screen: a picture generated from imagePrompt, or a slide written in HTML (html). Call it once per slide, in order, starting with slide 1. With mode "steps" it is a step-by-step guide: one slide per step, and the next step waits for the user.`,parameters:{type:`object`,properties:{slide:{type:`integer`,description:`This slide's number, starting at 1.`},totalSlides:{type:`integer`,description:`How many slides the slideshow has. Keep it the same for every slide.`},title:{type:`string`,description:`The slide's title.`},imagePrompt:{type:`string`,description:`For a picture slide: the picture, a clear illustration of its point, with the title as its only large text. Be concrete. For a step, show the action: hands, tools and the object. Leave it out for an HTML slide.`},html:{type:`string`,description:ae},mode:{type:`string`,enum:[`presentation`,`steps`],description:`"presentation" (the default) goes on by itself; "steps" is a how-to the user does along with you, and waits for them after each step. Keep it the same for every slide.`}},required:[`slide`,`totalSlides`,`title`]}};function T(e){let{totalPanels:t}=e;if(!b(t)||t<1||t>12)return`totalPanels must be a whole number from 1 to 12`;let n=[];for(let t of Array.isArray(e.characters)?e.characters:[]){let e=_(t)?v(t.name):``,r=_(t)?v(t.description):``;if(!e||!r)return`every character needs a name and a description`;if(n.some(t=>y(t.name,e)))return`two characters are named ${e}`;n.push({name:e,description:r})}return n.length<1||n.length>4?`a storyboard has 1 to 4 characters`:{title:v(e.title),style:v(e.style),totalPanels:t,characters:n,interactive:e.interactive===!0}}var E=({title:e,id:t})=>`The cast of "${e}" is now on the screen. Introduce the characters briefly, then, in this same reply and without waiting for the user, call presentPanel for panel 1 with storyboardId "${t}". If the user has asked you to stop, or asked something else, answer them instead of going on.`,D=`presentPanel needs a storyboardId, a panel number (from 1) and an imagePrompt`;function O(e){let{panel:t}=e,n=v(e.storyboardId),r=v(e.imagePrompt);if(!n||!r||!b(t)||t<1)return null;let i=e=>(Array.isArray(e)?e:[]).map(v).filter(Boolean);return{storyboardId:n,panel:t,characters:i(e.characters),imagePrompt:r,caption:v(e.caption),choices:i(e.choices).slice(0,3)}}function k(e,t,n){let{id:r,totalPanels:i}=e;return n.length?`Panel ${t} of ${i} is now on the screen, with the user's choices: ${n.map((e,t)=>`${t+1}. ${e}`).join(` `)} Tell this part of the story, then read the choices out and ask the user which one they pick, and wait for their answer. Then call presentPanel for panel ${t+1} with storyboardId "${r}", going on the way they chose.`:t<i?`Panel ${t} of ${i} is now on the screen. Tell this part of the story, then, in this same reply and without waiting for the user, call presentPanel for panel ${t+1} with storyboardId "${r}". If the user has asked you to stop, or asked something else, since the story began, answer them instead of going on.`:`Panel ${t} of ${i}, the last one, is now on the screen. Tell this part of the story, then bring it to an end.`}var A=`When the user asks for a story, a picture book, a comic or a storyboard, tell it in pictures: first call defineStoryboard with the art style and the characters who appear more than once (it draws a reference sheet for each, so they look the same in every panel), then show the panels one at a time with presentPanel, naming the characters in each, and tell each part of the story when its panel appears. Go on to the last panel without asking whether to continue, unless the story is interactive. When the user wants to decide what happens (an adventure, a story for a child who wants to choose), set interactive, and give two or three choices on two or three of the panels: the story waits there for the user's pick and goes on their way; it still ends at the last panel whichever way it goes. Use presentSlide to explain a topic, not to tell a story.`,j={type:`function`,name:m,prompt:A,description:`Start a story told in pictures: set its art style and draw a reference sheet for each recurring character, shown as the cast. Then call presentPanel for each panel, in order.`,parameters:{type:`object`,properties:{title:{type:`string`,description:`The story's title.`},style:{type:`string`,description:`The art style of every picture, in one sentence (for example: soft watercolor, children's picture book).`},totalPanels:{type:`integer`,description:`How many panels the story has, at most 12; four to eight is usual.`},interactive:{type:`boolean`,description:`The user chooses what happens: panels can offer choices, and the story waits for the pick.`},characters:{type:`array`,description:`The characters who appear in more than one panel, 1 to 4.`,items:{type:`object`,properties:{name:{type:`string`,description:`A short name, used in presentPanel.`},description:{type:`string`,description:`How they look: age, build, face, hair, clothes and colors. Be concrete: it is drawn once and reused in every panel.`}},required:[`name`,`description`]}}},required:[`title`,`style`,`totalPanels`,`characters`]}},M={type:`function`,name:h,description:`Show one panel of a storyboard made with defineStoryboard: a picture of the scene with its characters drawn as in their reference sheets, and a caption. Call it once per panel, in order, starting with panel 1.`,parameters:{type:`object`,properties:{storyboardId:{type:`string`,description:`The storyboardId defineStoryboard returned.`},panel:{type:`integer`,description:`This panel's number, starting at 1.`},characters:{type:`array`,items:{type:`string`},description:`The names (as in defineStoryboard) of the characters in this panel.`},imagePrompt:{type:`string`,description:`What the panel shows: the setting, what happens, expressions and the camera angle. Refer to characters by name; don't describe their looks again.`},caption:{type:`string`,description:`One short line shown under the picture.`},choices:{type:`array`,items:{type:`string`},description:`Only in an interactive story, and not on the last panel: two or three short options for what happens next. The story waits for the user's pick.`}},required:[`storyboardId`,`panel`,`characters`,`imagePrompt`]}},N=e=>typeof e==`object`&&!!e&&!Array.isArray(e),oe=e=>N(e)&&typeof e.message==`string`;function P(e){let t=N(e?.data)?e.data:{};return{...typeof t.imageData==`string`&&{imageData:t.imageData},...typeof t.imagePath==`string`&&{imagePath:t.imagePath}}}function F(e){let t=N(e?.data)?e.data:{};return typeof t.html==`string`?t.html:``}var se=e=>!!P(e).imageData||!!F(e);function ce(e,t){let n=P(e),r=P(t);if(n.imagePath&&r.imagePath)return n.imagePath===r.imagePath;if(n.imageData)return n.imageData===r.imageData;let i=F(e);return!!i&&i===F(t)}var I=(e,t)=>e.userSpokeAt===void 0||e.userSpokeAt>t,L=()=>crypto.randomUUID().replace(/-/g,``).slice(0,12),le=`artifacts/`;async function R(e,t){let n=e.files?.artifacts;if(!e.app?.editImages||!n)return[];let r=[];for(let e of t)if(e.startsWith(le))try{await n.exists(e.slice(10))&&r.push(e)}catch{}return r}async function z(e,t,n=[]){let r=e.app,i=r?.editImages,a=r?.generateImage;try{let e;if(n.length&&i)e=await i(t,[...n]);else if(a)e=await a(t);else return{message:`image generation isn't available`};return oe(e)?e:{message:`image generation returned an unrecognized result`}}catch(e){return{message:`image generation failed: ${e instanceof Error?e.message:String(e)}`}}}var ue=50;function B(e){let t=new Map;return n=>{let r=n.conversationId??``,i=t.get(r);if(i===void 0?i=e():t.delete(r),t.set(r,i),t.size>ue){let e=t.keys().next().value;e!==void 0&&t.delete(e)}return i}}var V=`slideshows`,H=`storyboards`,U={},W=new WeakMap,G=new WeakMap;function K(e){let t=W.get(e);return t||W.set(e,t=new Map),t}function de(t,n){let r=G.get(t);r||G.set(t,r=new Map);let i=r.get(n);return i||r.set(n,i=(0,e.createSerialLock)()),i}var q=(e,t)=>`${e}/${t}.json`;async function J(e,t){let n=K(e??U),r=n.get(t);if(r)return r;if(!e)return null;try{let r=JSON.parse(await e.read(t));return n.set(t,r),r}catch{return null}}async function Y(e,t,n){return g.test(n)?J(e,q(t,n)):null}async function X(e,t,n,r){let i=q(t,n),a=e??U;return de(a,i)(async()=>{let t=r(await J(e,i));if(K(a).set(i,t),!e)return!1;try{return await e.write(i,JSON.stringify(t,null,2)),!0}catch(e){return console.warn(`[sequence] could not save`,i,e),!1}})}var Z=e=>`the ${e} could not be saved, so it lasts only until the app or server restarts`,fe=6e4;function pe(){let e=new Map;return{claim:t=>{let n=Date.now();for(let[t,r]of e)n-r.at>fe&&e.delete(t);let r=e.get(t);if(r)return{earlier:r.shown};let i=()=>void 0,a=new Promise(e=>i=e);return e.set(t,{at:n,shown:a}),{settle:n=>{!n&&e.get(t)?.shown===a&&e.delete(t),i(n)}}}}}function me({slide:e,totalSlides:t,mode:n}){return n===`steps`?{step:e,total:t,kind:`guide`,label:`Step ${e} of ${t}`,onShown:`explain it`,nextCall:`call presentSlide for step ${e+1} of ${t} with mode "steps"`,waitsForUser:!0}:{step:e,total:t,kind:`slideshow`,label:`Slide ${e} of ${t}`,onShown:`explain it`,nextCall:`call presentSlide for slide ${e+1} of ${t}`}}var he=(e,{slide:t,totalSlides:n,title:r,imagePrompt:i,html:a})=>JSON.stringify([e,t,n,r,i,a]),ge=B(()=>({latest:{presentation:null,steps:null},lastShown:null,repeats:pe()}));function _e(e,t){let n=e.latest[t.mode],r=n?.totalSlides===t.totalSlides&&(t.slide!==1||n.firstTitle===t.title);if(n&&r)return n;let i={id:L(),mode:t.mode,totalSlides:t.totalSlides,firstTitle:t.slide===1?t.title:``,slides:new Map,current:0,shownAt:-1/0};return e.latest[t.mode]=i,i}function ve(e,t,n,r){t.current=n.slide,t.shownAt=Date.now(),e.lastShown=t;let i=n.mode===`steps`?`step`:`slide`,{uuid:a,...o}=r;return{...o,message:`${i} ${n.slide} of ${n.totalSlides} is on the screen again, as it was`,instructions:S(n),instructionsRequired:!0,sequence:me(n)}}function ye(e,t){if(e.mode!==`steps`)return e.title?`A presentation slide titled "${e.title}". ${e.imagePrompt}`:e.imagePrompt;let n=e.title?`, "${e.title}"`:``;return[`An instructional illustration of step ${e.slide} of ${e.totalSlides}${n}. ${e.imagePrompt}`,t?`The reference image is the previous step: keep the same objects, hands, tools and setting, and show what this step changes.`:``,`Show the action clearly, with little or no text.`].filter(Boolean).join(` `)}async function be(e,t,n){if(n.html)return{html:n.html,prompt:``};let r=t.slides.get(n.slide-1),i=n.mode===`steps`?P(r).imagePath:void 0,a=i?await R(e,[i]):[],o=ye(n,a.length>0),s=await z(e,o,a),{imageData:c,imagePath:l}=P(s);return c?{imageData:c,...l&&{imagePath:l},prompt:o}:{failed:{...s,sequence:null}}}async function xe(e,t,n){let r=await be(e,t,n);if(`failed`in r)return r.failed;let{imageData:i,imagePath:a,html:o,prompt:s}=r,c=t.id,l=await X(e.files?.artifacts,V,c,e=>{let t=e??{id:c,title:``,mode:n.mode,totalSlides:n.totalSlides,slides:{}};return n.slide===1&&(t.title=n.title),t.slides[String(n.slide)]={title:n.title,imagePrompt:n.imagePrompt,...a&&{imagePath:a},...o&&{html:o}},t}),u={...i&&{imageData:i},...a&&{imagePath:a},...o&&{html:o},prompt:s,slideshowId:c,slide:n.slide,totalSlides:n.totalSlides,title:n.title,mode:n.mode},d=n.mode===`steps`?`step`:`slide`;return{data:u,title:n.title||`Slide ${n.slide}`,message:[`${d} ${n.slide} of ${n.totalSlides} of slideshow "${c}" is on the screen`,a?`saved to ${a}`:``,l?``:Z(`slideshow`)].filter(Boolean).join(`; `),instructions:S(n),instructionsRequired:!0,sequence:me(n)}}async function Q(e,t){let n=ie(t);if(typeof n==`string`)return{message:n,sequence:null};let r=ge(e),i=_e(r,n),a=n.mode===`steps`;if(a&&n.slide>i.current&&i.current>0&&!I(e,i.shownAt)){console.info(`[sequence] holding step ${n.slide} for the user`);let e=i.shownAt===1/0?`is still being drawn`:`is still on the screen`;return{message:`step ${n.slide} was not shown: the user hasn't said they're ready yet, and step ${i.current} ${e}. Wait for them before calling presentSlide for step ${n.slide}.`,cancelled:!0}}let o=a?i.slides.get(n.slide):void 0;if(o)return ce(e.currentResult,o)?{message:`step ${n.slide} of ${n.totalSlides} is already on the screen`,cancelled:!0}:ve(r,i,n,o);let s=r.repeats.claim(he(i.id,n));if(`earlier`in s){if(!await s.earlier)return Q(e,t);let a=i.slides.get(n.slide);return a&&r.lastShown!==i?ve(r,i,n,a):{message:`slide ${n.slide} of ${n.totalSlides} is already on the screen`,cancelled:!0}}let{settle:c}=s,l={current:i.current,shownAt:i.shownAt};i.current=n.slide,i.shownAt=1/0;let u=!1;try{let t=await xe(e,i,n);return u=se(t),u&&(i.slides.set(n.slide,t),i.current=n.slide,i.shownAt=Date.now(),r.lastShown=i),t}finally{c(u),!u&&i.current===n.slide&&(i.current=l.current,i.shownAt=l.shownAt)}}var Se=({id:e,title:t,totalPanels:n})=>({step:0,total:n,kind:`story`,label:`The cast of "${t}"`,onShown:`introduce the characters`,nextCall:`call presentPanel for panel 1 of ${n} with storyboardId "${e}"`}),Ce=({id:e,totalPanels:t},n,r)=>({step:n,total:t,kind:`story`,label:`Panel ${n} of ${t}`,onShown:`tell that part of the story`,nextCall:`call presentPanel for panel ${n+1} of ${t} with storyboardId "${e}"`,...r.length>0&&{waitsForUser:!0}}),we=(e,t)=>e?`${e}. ${t}`:t,Te=(e,{name:t,description:n})=>we(e,`Character reference sheet of ${t}: ${n}. Full body, front, side and back views, on a plain white background. No text or labels.`);async function Ee(e,t){let n=T(t);if(typeof n==`string`)return{message:n,sequence:null};let r=await Promise.all(n.characters.map(t=>z(e,Te(n.style,t)))),i=r.map(P),a=r.find((e,t)=>!i[t]?.imageData);if(i.every(({imageData:e})=>!e)&&a)return{...a,message:`the character sheets couldn't be made: ${a.message}`,sequence:null};let o={id:L(),...n,characters:n.characters.map((e,t)=>{let n=i[t]?.imagePath;return{...e,...n&&{imagePath:n}}}),panels:{}},s=await X(e.files?.artifacts,H,o.id,()=>o),c=o.characters.filter(e=>e.imagePath).map(e=>`${e.name}: ${e.imagePath}`),l=n.characters.map(({name:e},t)=>i[t]?.imageData?null:`${e} (${r[t]?.message??``})`).filter(e=>!!e),u=i.find(e=>e.imageData)?.imageData;return{data:{storyboardId:o.id,title:o.title,totalPanels:o.totalPanels,...u&&{imageData:u},characters:o.characters.map(({name:e,imagePath:t},n)=>{let r=i[n]?.imageData;return{name:e,...r&&{imageData:r},...t&&{imagePath:t}}})},title:o.title,message:[`storyboard "${o.id}" is ready with ${o.totalPanels} panels; the cast is on the screen`,c.length?`character sheets saved to ${c.join(`, `)}`:``,l.length?`no sheet for ${l.join(`; `)}: they will be drawn from their description`:``,s?``:Z(`storyboard`)].filter(Boolean).join(`; `),instructions:E(o),instructionsRequired:!0,sequence:Se(o)}}function De(e,t,n){let r=[we(e.style,`Panel ${t.panel} of ${e.totalPanels} of the illustrated story "${e.title}". ${t.imagePrompt}`)],i=0,a=n.map(({character:e,hasSheet:t})=>t?`${e.name} is the character in reference image ${++i}`:`${e.name}: ${e.description}`);return a.length&&r.push(`${a.join(`. `)}.`),i&&r.push(`Draw each character exactly as in their reference image: the same face, hair, clothes and colors. Use the reference images only for how the characters look, not for their pose or the background.`),r.push(`No captions, speech bubbles or other text in the picture.`),r.join(` `)}async function Oe(e,t,n){let r=[],i=[];for(let a of n.characters){let n=t.characters.find(e=>y(e.name,a));if(!n)r.push(a);else if(!i.some(e=>e.character===n)){let[t]=n.imagePath?await R(e,[n.imagePath]):[];i.push({character:n,sheet:t??null})}}let a=i.map(({sheet:e})=>e).filter(e=>!!e),o=De(t,n,i.map(({character:e,sheet:t})=>({character:e,hasSheet:!!t}))),s=await z(e,o,a),{imageData:c,imagePath:l}=P(s);if(!c)return{...s,sequence:null};let u=n.panel<t.totalPanels&&n.choices.length>=2?n.choices:[],d=await X(e.files?.artifacts,H,t.id,e=>{let r=e??t;return u.length&&(r.interactive=!0),r.panels[String(n.panel)]={caption:n.caption,characters:i.map(({character:e})=>e.name),imagePrompt:n.imagePrompt,...l&&{imagePath:l},...u.length&&{choices:u}},r});return{data:{imageData:c,...l&&{imagePath:l},prompt:o,storyboardId:t.id,panel:n.panel,totalPanels:t.totalPanels,caption:n.caption,...u.length&&{choices:u}},title:n.caption||`Panel ${n.panel}`,message:[`panel ${n.panel} of ${t.totalPanels} is on the screen`,l?`saved to ${l}`:``,r.length?`not in the cast, so drawn from the prompt alone: ${r.join(`, `)}`:``,d?``:Z(`storyboard`)].filter(Boolean).join(`; `),instructions:k(t,n.panel,u),instructionsRequired:!0,sequence:Ce(t,n.panel,u)}}var ke=B(()=>({repeats:pe(),awaitingChoice:new Map}));async function $(e,t){let n=O(t);if(!n)return{message:D,sequence:null};let{storyboardId:r,panel:i}=n,{repeats:a,awaitingChoice:o}=ke(e),s=o.get(r);if(s&&i>s.panel&&!I(e,s.shownAt))return console.info(`[sequence] holding panel ${i} for the user`),{message:s.shownAt===1/0?`panel ${i} was not shown: panel ${s.panel} is still being drawn. Wait for it before going on.`:`panel ${i} was not shown: the user hasn't picked a choice yet, and panel ${s.panel} is still on the screen. Wait for their answer.`,cancelled:!0};let c=a.claim(JSON.stringify(n));if(`earlier`in c)return await c.earlier?{message:`panel ${i} is already on the screen`,cancelled:!0}:$(e,t);let{settle:l}=c,u={panel:i,shownAt:1/0},d=o.get(r);o.set(r,u);let f=()=>o.get(r)===u,p=!1;try{let t=await Y(e.files?.artifacts,H,r);if(!t)return{message:`there is no storyboard "${r}"; call defineStoryboard first`,sequence:null};if(i>t.totalPanels)return{message:`storyboard "${t.id}" has ${t.totalPanels} panels`,sequence:null};let a=await Oe(e,t,n);p=!!P(a).imageData;let s=a.data;return p&&f()&&(s?.choices?.length?o.set(r,{panel:i,shownAt:Date.now()}):o.delete(r)),a}finally{l(p),!p&&f()&&(d?o.set(r,d):o.delete(r))}}var Ae={toolDefinition:w,generatingMessage:`Making the slide...`,isEnabled:()=>!0,execute:(e,t)=>Q(e,t)},je={toolDefinition:j,generatingMessage:`Drawing the characters...`,isEnabled:()=>!0,execute:(e,t)=>Ee(e,t)},Me={toolDefinition:M,generatingMessage:`Drawing the panel...`,isEnabled:()=>!0,execute:(e,t)=>$(e,t)};Object.defineProperty(exports,"A",{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,"C",{enumerable:!0,get:function(){return k}}),Object.defineProperty(exports,"D",{enumerable:!0,get:function(){return y}}),Object.defineProperty(exports,"E",{enumerable:!0,get:function(){return T}}),Object.defineProperty(exports,"M",{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,"N",{enumerable:!0,get:function(){return f}}),Object.defineProperty(exports,"O",{enumerable:!0,get:function(){return S}}),Object.defineProperty(exports,"S",{enumerable:!0,get:function(){return E}}),Object.defineProperty(exports,"T",{enumerable:!0,get:function(){return ie}}),Object.defineProperty(exports,"_",{enumerable:!0,get:function(){return C}}),Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return H}}),Object.defineProperty(exports,"b",{enumerable:!0,get:function(){return re}}),Object.defineProperty(exports,"c",{enumerable:!0,get:function(){return j}}),Object.defineProperty(exports,"d",{enumerable:!0,get:function(){return te}}),Object.defineProperty(exports,"f",{enumerable:!0,get:function(){return D}}),Object.defineProperty(exports,"g",{enumerable:!0,get:function(){return w}}),Object.defineProperty(exports,"h",{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return V}}),Object.defineProperty(exports,"j",{enumerable:!0,get:function(){return n}}),Object.defineProperty(exports,"k",{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,"l",{enumerable:!0,get:function(){return ee}}),Object.defineProperty(exports,"m",{enumerable:!0,get:function(){return M}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return Me}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return Y}}),Object.defineProperty(exports,"p",{enumerable:!0,get:function(){return h}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return Ae}}),Object.defineProperty(exports,"s",{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return je}}),Object.defineProperty(exports,"u",{enumerable:!0,get:function(){return ne}}),Object.defineProperty(exports,"v",{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,"w",{enumerable:!0,get:function(){return O}}),Object.defineProperty(exports,"x",{enumerable:!0,get:function(){return A}}),Object.defineProperty(exports,"y",{enumerable:!0,get:function(){return x}});
|