@hanzo/build 0.2.63 → 0.2.64
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/lib/api/chat.d.ts +12 -0
- package/lib/api/chat.js +28 -0
- package/lib/api/studio.d.ts +45 -0
- package/lib/api/studio.js +76 -0
- package/lib/carry.d.ts +13 -1
- package/lib/carry.js +33 -3
- package/lib/index.d.ts +8 -1
- package/lib/index.js +7 -0
- package/lib/landing.js +31 -4
- package/lib/pdf.d.ts +139 -0
- package/lib/pdf.js +847 -0
- package/lib/plugins/index.d.ts +27 -0
- package/lib/plugins/index.js +124 -0
- package/lib/plugins/pdf.d.ts +18 -0
- package/lib/plugins/pdf.js +82 -0
- package/lib/plugins/project.d.ts +5 -0
- package/lib/plugins/project.js +65 -0
- package/lib/plugins/research.d.ts +24 -0
- package/lib/plugins/research.js +146 -0
- package/lib/plugins/sketch.d.ts +9 -0
- package/lib/plugins/sketch.js +98 -0
- package/lib/plugins/slides.d.ts +14 -0
- package/lib/plugins/slides.js +54 -0
- package/lib/plugins/template.d.ts +13 -0
- package/lib/plugins/template.js +92 -0
- package/package.json +1 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Presentations: while it is on, a message makes a deck in Hanzo Studio (the
|
|
4
|
+
* `slides` template, a PowerPoint file) and the answer is the deck: its outline,
|
|
5
|
+
* the file, and the run in Studio, where it is edited. A later message in the
|
|
6
|
+
* same conversation revises the last deck rather than starting over.
|
|
7
|
+
*/
|
|
8
|
+
import { Presentation } from '@hanzogui/lucide-icons-2';
|
|
9
|
+
import { json } from '../api/chat.js';
|
|
10
|
+
import { make } from '../api/studio.js';
|
|
11
|
+
/** The last deck each conversation made: what a later message revises. */
|
|
12
|
+
const decks = new Map();
|
|
13
|
+
/** What a deck's writing step said: its title and its slides' titles. */
|
|
14
|
+
export function outline(text) {
|
|
15
|
+
const o = json(text) ?? {};
|
|
16
|
+
const slides = (Array.isArray(o.slides) ? o.slides : [])
|
|
17
|
+
.map((s) => (s && typeof s === 'object' ? s.title : ''))
|
|
18
|
+
.map((t) => (typeof t === 'string' ? t.trim() : ''))
|
|
19
|
+
.filter(Boolean);
|
|
20
|
+
return { title: typeof o.title === 'string' ? o.title.trim() : '', slides };
|
|
21
|
+
}
|
|
22
|
+
/** The brief a revision is made from: the deck as it stands, then the change asked for. */
|
|
23
|
+
export function revision(last, ask) {
|
|
24
|
+
return `Revise this deck. Keep what the change does not touch.\n\nThe deck as it stands:\n${last.text.trim()}\n\nThe change:\n${ask}`;
|
|
25
|
+
}
|
|
26
|
+
/** The answer a made deck is: its outline, the file, and where it is edited. */
|
|
27
|
+
export function told(r) {
|
|
28
|
+
const { title, slides } = outline(r.text);
|
|
29
|
+
const file = r.outputs[0];
|
|
30
|
+
const head = `**${title || r.title || 'Your deck'}**${slides.length ? ` — ${slides.length} slides` : ''}`;
|
|
31
|
+
const links = [
|
|
32
|
+
file?.url ? `[Download the deck](${file.url}) (PowerPoint; the link works for an hour)` : '',
|
|
33
|
+
file?.origin ? `[Open it in Studio](${file.origin}) to edit it` : '',
|
|
34
|
+
].filter(Boolean);
|
|
35
|
+
const list = slides.map((s, i) => `${i + 1}. ${s}`).join('\n');
|
|
36
|
+
return [head, links.join(' · '), list, 'It is kept in your Library.'].filter(Boolean).join('\n\n');
|
|
37
|
+
}
|
|
38
|
+
export async function deck(turn, t, signal) {
|
|
39
|
+
const ask = turn.text.trim();
|
|
40
|
+
if (!ask)
|
|
41
|
+
return {};
|
|
42
|
+
const last = turn.thread ? decks.get(turn.thread) : undefined;
|
|
43
|
+
const r = await make(t, { template: 'slides', title: ask.slice(0, 80), input: { brief: last ? revision(last, ask) : ask, format: 'pptx' } }, signal);
|
|
44
|
+
if (turn.thread)
|
|
45
|
+
decks.set(turn.thread, r);
|
|
46
|
+
return { reply: told(r) };
|
|
47
|
+
}
|
|
48
|
+
export const slides = {
|
|
49
|
+
id: 'slides',
|
|
50
|
+
name: 'Presentations',
|
|
51
|
+
note: 'Create and edit decks',
|
|
52
|
+
icon: _jsx(Presentation, { size: 16 }),
|
|
53
|
+
prepare: (turn, c, signal) => deck(turn, c.target, signal),
|
|
54
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Plugin } from '../plugin.ts';
|
|
2
|
+
export interface Draft {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
body: string;
|
|
6
|
+
}
|
|
7
|
+
/** A skill's name from any words: one lowercase segment of letters, digits and hyphens. */
|
|
8
|
+
export declare function slug(text: string): string;
|
|
9
|
+
/** What a model's draft says, each part the ask's own where the draft has none. */
|
|
10
|
+
export declare function drafted(said: string, draft: string): Draft;
|
|
11
|
+
/** The SKILL.md a template is saved as: its name and description up front, then the template. */
|
|
12
|
+
export declare function document(d: Draft): string;
|
|
13
|
+
export declare const template: Plugin;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Template Creator: the ask in the composer, saved as a reusable template — one
|
|
4
|
+
* of the org's own skills (POST /v1/tool/skills), switched on as it is saved so
|
|
5
|
+
* it is under `+` › Skills from then on. A Hanzo model drafts the template from
|
|
6
|
+
* the ask (its inputs as {{placeholders}}); the person reads and edits it before
|
|
7
|
+
* it is saved. Writing a skill takes an org admin, so only an admin is offered it.
|
|
8
|
+
*/
|
|
9
|
+
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
10
|
+
import { LayoutTemplate } from '@hanzogui/lucide-icons-2';
|
|
11
|
+
import { Button, Dialog, DialogContent, DialogDescription, DialogTitle, Input, Textarea } from '@hanzo/ui';
|
|
12
|
+
import { useEffect, useState } from 'react';
|
|
13
|
+
import { ask, json } from '../api/chat.js';
|
|
14
|
+
import { NAME, refuse, tool, write } from '../api/skills.js';
|
|
15
|
+
import { toggle } from '../api/tools.js';
|
|
16
|
+
import { say } from '../customize/say.js';
|
|
17
|
+
const DRAFT = `Turn the person's request into a reusable template, saved as a skill an agent follows. Answer only JSON: {"name": "a short lowercase-hyphenated name", "description": "one line: when to use it", "body": "markdown, under 250 words: what it is for, the inputs it needs written as {{placeholders}}, then the steps"}.`;
|
|
18
|
+
/** A skill's name from any words: one lowercase segment of letters, digits and hyphens. */
|
|
19
|
+
export function slug(text) {
|
|
20
|
+
const s = text
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.normalize('NFKD')
|
|
23
|
+
.replace(/[̀-ͯ]/g, '')
|
|
24
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
25
|
+
.replace(/^-+|-+$/g, '')
|
|
26
|
+
.slice(0, 48)
|
|
27
|
+
.replace(/-+$/, '');
|
|
28
|
+
return NAME.test(s) ? s : 'template';
|
|
29
|
+
}
|
|
30
|
+
/** The first line of a text, cut to a line's length. */
|
|
31
|
+
const first = (text) => (text.trim().split('\n')[0] ?? '').slice(0, 160).trim();
|
|
32
|
+
/** What a model's draft says, each part the ask's own where the draft has none. */
|
|
33
|
+
export function drafted(said, draft) {
|
|
34
|
+
const o = json(said) ?? {};
|
|
35
|
+
const str = (v) => (typeof v === 'string' ? v.trim() : '');
|
|
36
|
+
return {
|
|
37
|
+
name: slug(str(o.name) || draft.split(/\s+/).slice(0, 6).join(' ')),
|
|
38
|
+
description: first(str(o.description) || draft),
|
|
39
|
+
body: str(o.body) || draft.trim(),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** The SKILL.md a template is saved as: its name and description up front, then the template. */
|
|
43
|
+
export function document(d) {
|
|
44
|
+
return `---\nname: ${d.name}\ndescription: ${JSON.stringify(d.description.replace(/\s+/g, ' ').trim())}\n---\n\n${d.body.trim()}\n`;
|
|
45
|
+
}
|
|
46
|
+
function Creator({ c }) {
|
|
47
|
+
const from = c.draft.trim();
|
|
48
|
+
const [d, setD] = useState({ name: from ? slug(from.split(/\s+/).slice(0, 6).join(' ')) : '', description: first(from), body: from });
|
|
49
|
+
const [drafting, setDrafting] = useState(Boolean(from));
|
|
50
|
+
const [busy, setBusy] = useState(false);
|
|
51
|
+
const [note, setNote] = useState('');
|
|
52
|
+
const [saved, setSaved] = useState('');
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (!from)
|
|
55
|
+
return;
|
|
56
|
+
const stop = new AbortController();
|
|
57
|
+
ask(c.target, DRAFT, from, stop.signal, 2000)
|
|
58
|
+
.then((said) => setD(drafted(said, from)))
|
|
59
|
+
.catch(() => undefined)
|
|
60
|
+
.finally(() => setDrafting(false));
|
|
61
|
+
return () => stop.abort();
|
|
62
|
+
}, [c.target, from]);
|
|
63
|
+
const save = async () => {
|
|
64
|
+
const content = document(d);
|
|
65
|
+
const why = refuse({ name: d.name, content });
|
|
66
|
+
if (why) {
|
|
67
|
+
setNote(why);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
setBusy(true);
|
|
71
|
+
setNote('');
|
|
72
|
+
try {
|
|
73
|
+
const s = await write(c.target, { name: d.name, description: d.description, content });
|
|
74
|
+
await toggle(c.target, [tool(s.name)]);
|
|
75
|
+
setSaved(s.name);
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
setNote(say(e));
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
setBusy(false);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
return (_jsx(Dialog, { open: true, onOpenChange: (o) => !o && c.show(null), children: _jsxs(DialogContent, { maxW: 640, width: "100%", children: [_jsx(DialogTitle, { children: "Template Creator" }), _jsx(DialogDescription, { children: saved ? `Saved as ${saved}. It is under + › Skills.` : drafting ? 'Hanzo is drafting the template from your message…' : 'A reusable template, saved as one of your skills.' }), saved ? null : (_jsxs(YStack, { gap: "$3", children: [_jsx(Input, { value: d.name, onChangeText: (name) => setD({ ...d, name }), placeholder: "weekly-update", "aria-label": "Name", autoCapitalize: "none" }), _jsx(Input, { value: d.description, onChangeText: (description) => setD({ ...d, description }), placeholder: "When to use it", "aria-label": "Description" }), _jsx(Textarea, { value: d.body, onChangeText: (body) => setD({ ...d, body }), placeholder: '# Weekly update\n\nInputs: {{team}}, {{week}}\n\n1. …', "aria-label": "Template", rows: 12, style: { maxHeight: '40vh', overflowY: 'auto' } })] })), note ? (_jsx(SizableText, { size: "$2", color: "$soft", role: "status", children: note })) : null, _jsx(XStack, { gap: "$2", justify: "flex-end", children: saved ? (_jsx(Button, { size: "sm", onPress: () => c.show(null), children: "Done" })) : (_jsxs(_Fragment, { children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => c.show(null), children: "Cancel" }), _jsx(Button, { size: "sm", disabled: busy || drafting || !d.name || !d.body.trim(), onPress: () => void save(), children: "Save" })] })) })] }) }));
|
|
85
|
+
}
|
|
86
|
+
export const template = {
|
|
87
|
+
id: 'template',
|
|
88
|
+
name: 'Template Creator',
|
|
89
|
+
note: 'Templates saved as skills',
|
|
90
|
+
icon: _jsx(LayoutTemplate, { size: 16 }),
|
|
91
|
+
open: (c) => c.show(_jsx(Creator, { c: c })),
|
|
92
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/build",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.64",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0 OR MIT",
|
|
6
6
|
"description": "Hanzo Build — say what you want, watch it built, deployed and running. The builder as a component, mounted by the Hanzo app at hanzo.ai.",
|