@hanzo/build 0.2.79 → 0.2.80
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/README.md +9 -4
- package/lib/api/agents.d.ts +37 -17
- package/lib/api/agents.js +52 -23
- package/lib/customize/agents.d.ts +3 -0
- package/lib/customize/agents.js +85 -35
- package/lib/customize/index.js +2 -4
- package/lib/host.d.ts +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -226,10 +226,15 @@ are any member's.
|
|
|
226
226
|
- Plugins: TypeScript connectors built on the platform, from source or from a
|
|
227
227
|
description of an API; a failed build says why. Browse offers building one and
|
|
228
228
|
lists what the deployment mounts.
|
|
229
|
-
- Agents: Browse is
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
229
|
+
- Agents: Browse is every agent to start from — the host's default agents
|
|
230
|
+
(`cast`) and personas (`personas`), and the platform's presets whose tool
|
|
231
|
+
calls run on the platform — searched by name and filtered by category. Use
|
|
232
|
+
makes one agent of the org's own from one template (a name taken becomes
|
|
233
|
+
`-2`, `-3`), with its face, model and instructions, and opens its profile;
|
|
234
|
+
nothing is added in bulk. Start from scratch, Clone yourself (the person
|
|
235
|
+
signed in, with writing pasted or uploaded) and Create from a person make one
|
|
236
|
+
too. Yours is the org's own — model, instructions, tools, budget — created,
|
|
237
|
+
edited (only what changed is sent) and deleted.
|
|
233
238
|
|
|
234
239
|
**New.** "What's up next?", and at the foot the composer: where the run runs
|
|
235
240
|
(Cloud is the platform's sandbox with the codebase's environment; the org's
|
package/lib/api/agents.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Agents: the org's own — a model, instructions, the tools it may call
|
|
3
|
-
* budget — and the
|
|
2
|
+
* Agents: the org's own — a model, instructions, the tools it may call, a
|
|
3
|
+
* budget and a face — and the templates one is made from.
|
|
4
4
|
*
|
|
5
5
|
* GET /v1/agent {agents: [agent]}, without instructions
|
|
6
6
|
* POST /v1/agent create → 201 agent
|
|
@@ -35,6 +35,8 @@ export interface Agent {
|
|
|
35
35
|
task: number;
|
|
36
36
|
spent: number;
|
|
37
37
|
period: Period | '';
|
|
38
|
+
/** Its picture: an https link or a data URL; '' draws its emoji or initial. */
|
|
39
|
+
avatar: string;
|
|
38
40
|
emoji: string;
|
|
39
41
|
}
|
|
40
42
|
export interface Preset {
|
|
@@ -52,6 +54,8 @@ export interface Draft {
|
|
|
52
54
|
cap: string;
|
|
53
55
|
task: string;
|
|
54
56
|
period: Period;
|
|
57
|
+
/** Sent on create only: an https link or a data URL. */
|
|
58
|
+
avatar: string;
|
|
55
59
|
}
|
|
56
60
|
/** An agent's name, as the handler takes it. */
|
|
57
61
|
export declare const NAME: RegExp;
|
|
@@ -86,29 +90,45 @@ export declare function profile(t: Target, ref: string): Promise<{
|
|
|
86
90
|
agent: Agent;
|
|
87
91
|
runs: Run[];
|
|
88
92
|
}>;
|
|
89
|
-
/**
|
|
93
|
+
/** An agent to start from: a default agent, a persona or a preset. */
|
|
90
94
|
export interface Cast {
|
|
95
|
+
/** The name it is made under. */
|
|
91
96
|
name: string;
|
|
97
|
+
/** What Browse calls it. */
|
|
98
|
+
title: string;
|
|
92
99
|
description: string;
|
|
100
|
+
/** What Browse lists it under. */
|
|
101
|
+
category: string;
|
|
93
102
|
model: string;
|
|
94
103
|
instructions: string;
|
|
95
|
-
/**
|
|
104
|
+
/** Reads the instructions, where they are a file the host serves. */
|
|
105
|
+
read?: () => Promise<string>;
|
|
106
|
+
/** Its face, an address of the host. */
|
|
96
107
|
avatar?: string;
|
|
108
|
+
tools?: string[];
|
|
97
109
|
}
|
|
98
|
-
/** What a
|
|
110
|
+
/** What an agent made from a template may spend until its owner says otherwise, in dollars: each month, and one run. */
|
|
99
111
|
export declare const CAST_CAP = "10";
|
|
100
112
|
export declare const CAST_TASK = "1";
|
|
101
|
-
/**
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
113
|
+
/**
|
|
114
|
+
* A name the API takes: accents dropped, any other run of characters a dash,
|
|
115
|
+
* from a letter or digit, up to 64. "Spärck Jones" is Sparck-Jones.
|
|
116
|
+
*/
|
|
117
|
+
export declare const handle: (name: string) => string;
|
|
118
|
+
/** `name`, or `name-2`, `name-3`… : the first the org has no agent called, in any case. */
|
|
119
|
+
export declare function free(name: string, have: readonly {
|
|
107
120
|
name: string;
|
|
108
|
-
}[]):
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
121
|
+
}[]): string;
|
|
122
|
+
/** An address of the page as an https link the API keeps, or '' (a page served over http has none). */
|
|
123
|
+
export declare const link: (at: string | undefined, base: string) => string;
|
|
124
|
+
/**
|
|
125
|
+
* Make the org's own agent from a template: under the first name the org has
|
|
126
|
+
* free (read now, so it is never one that exists), with its instructions read
|
|
127
|
+
* and its face linked. The template is not touched.
|
|
128
|
+
*/
|
|
129
|
+
export declare function use(t: Target, c: Cast, base: string): Promise<Agent>;
|
|
130
|
+
/** Anyone as a template: their name, who they are, and writing of theirs to take the voice from. */
|
|
131
|
+
export declare function person(name: string, about: string, writing: string, avatar?: string): Cast;
|
|
112
132
|
export declare function create(t: Target, d: Draft): Promise<Agent>;
|
|
113
133
|
/** The fields that differ from `was`, and nothing else: an omitted field keeps its value. */
|
|
114
134
|
export declare function changes(was: Agent, d: Draft): Record<string, unknown>;
|
|
@@ -116,5 +136,5 @@ export declare function update(t: Target, was: Agent, d: Draft): Promise<Agent>;
|
|
|
116
136
|
export declare function remove(t: Target, ref: string): Promise<void>;
|
|
117
137
|
/** The presets that make a working agent. */
|
|
118
138
|
export declare function presets(t: Target): Promise<Preset[]>;
|
|
119
|
-
/** A
|
|
120
|
-
export declare function fromPreset(p: Preset):
|
|
139
|
+
/** A preset as a template. */
|
|
140
|
+
export declare function fromPreset(p: Preset): Cast;
|
package/lib/api/agents.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Agents: the org's own — a model, instructions, the tools it may call
|
|
3
|
-
* budget — and the
|
|
2
|
+
* Agents: the org's own — a model, instructions, the tools it may call, a
|
|
3
|
+
* budget and a face — and the templates one is made from.
|
|
4
4
|
*
|
|
5
5
|
* GET /v1/agent {agents: [agent]}, without instructions
|
|
6
6
|
* POST /v1/agent create → 201 agent
|
|
@@ -47,6 +47,7 @@ export function agent(raw) {
|
|
|
47
47
|
task: num(o.max_task_micro_usd),
|
|
48
48
|
spent: num(o.consumed_micro_usd),
|
|
49
49
|
period: PERIODS.includes(period) ? period : '',
|
|
50
|
+
avatar: str(o.avatar),
|
|
50
51
|
emoji: str(o.emoji),
|
|
51
52
|
};
|
|
52
53
|
}
|
|
@@ -61,9 +62,10 @@ export function draft(a) {
|
|
|
61
62
|
cap: dollars(a.cap),
|
|
62
63
|
task: dollars(a.task),
|
|
63
64
|
period: a.period || 'month',
|
|
65
|
+
avatar: a.avatar,
|
|
64
66
|
};
|
|
65
67
|
}
|
|
66
|
-
export const EMPTY = { name: '', description: '', model: '', instructions: '', tools: [], cap: '', task: '', period: 'month' };
|
|
68
|
+
export const EMPTY = { name: '', description: '', model: '', instructions: '', tools: [], cap: '', task: '', period: 'month', avatar: '' };
|
|
67
69
|
/** Why a draft cannot be saved as it stands, or '' when it can. */
|
|
68
70
|
export function refuse(d) {
|
|
69
71
|
if (!NAME.test(d.name))
|
|
@@ -95,27 +97,52 @@ export async function profile(t, ref) {
|
|
|
95
97
|
const raw = await call(t, 'GET', `/v1/agent/${seg(ref)}`);
|
|
96
98
|
return { agent: agent(raw), runs: rows(obj(raw).recentRuns).map(run).filter((r) => r.id) };
|
|
97
99
|
}
|
|
98
|
-
/** What a
|
|
100
|
+
/** What an agent made from a template may spend until its owner says otherwise, in dollars: each month, and one run. */
|
|
99
101
|
export const CAST_CAP = '10';
|
|
100
102
|
export const CAST_TASK = '1';
|
|
101
|
-
/**
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
103
|
+
/**
|
|
104
|
+
* A name the API takes: accents dropped, any other run of characters a dash,
|
|
105
|
+
* from a letter or digit, up to 64. "Spärck Jones" is Sparck-Jones.
|
|
106
|
+
*/
|
|
107
|
+
export const handle = (name) => name
|
|
108
|
+
.normalize('NFD')
|
|
109
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
110
|
+
.replace(/[^A-Za-z0-9._-]+/g, '-')
|
|
111
|
+
.replace(/^[^A-Za-z0-9]+/, '')
|
|
112
|
+
.slice(0, 64)
|
|
113
|
+
.replace(/-+$/, '');
|
|
114
|
+
/** `name`, or `name-2`, `name-3`… : the first the org has no agent called, in any case. */
|
|
115
|
+
export function free(name, have) {
|
|
116
|
+
const taken = new Set(have.map((a) => a.name.toLowerCase()));
|
|
117
|
+
if (!taken.has(name.toLowerCase()))
|
|
118
|
+
return name;
|
|
119
|
+
for (let i = 2;; i++) {
|
|
120
|
+
const next = `${name.slice(0, 63 - String(i).length)}-${i}`;
|
|
121
|
+
if (!taken.has(next.toLowerCase()))
|
|
122
|
+
return next;
|
|
117
123
|
}
|
|
118
|
-
|
|
124
|
+
}
|
|
125
|
+
/** An address of the page as an https link the API keeps, or '' (a page served over http has none). */
|
|
126
|
+
export const link = (at, base) => {
|
|
127
|
+
if (!at)
|
|
128
|
+
return '';
|
|
129
|
+
const url = new URL(at, base).href;
|
|
130
|
+
return url.startsWith('https://') || url.startsWith('data:') ? url : '';
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Make the org's own agent from a template: under the first name the org has
|
|
134
|
+
* free (read now, so it is never one that exists), with its instructions read
|
|
135
|
+
* and its face linked. The template is not touched.
|
|
136
|
+
*/
|
|
137
|
+
export async function use(t, c, base) {
|
|
138
|
+
const [have, instructions] = await Promise.all([agents(t), c.read ? c.read() : c.instructions]);
|
|
139
|
+
return create(t, { ...EMPTY, name: free(handle(c.name), have), description: c.description, model: c.model, instructions, tools: c.tools ?? [], cap: CAST_CAP, task: CAST_TASK, avatar: link(c.avatar, base) });
|
|
140
|
+
}
|
|
141
|
+
/** Anyone as a template: their name, who they are, and writing of theirs to take the voice from. */
|
|
142
|
+
export function person(name, about, writing, avatar = '') {
|
|
143
|
+
const who = name.trim();
|
|
144
|
+
const lines = [`You are ${who}.`, about.trim(), writing.trim() && `Writing by ${who}:\n\n${writing.trim()}`];
|
|
145
|
+
return { name: who, title: who, description: about.trim(), category: 'Person', model: '', instructions: lines.filter(Boolean).join('\n\n'), avatar };
|
|
119
146
|
}
|
|
120
147
|
export async function create(t, d) {
|
|
121
148
|
const why = refuse(d);
|
|
@@ -132,6 +159,8 @@ export async function create(t, d) {
|
|
|
132
159
|
};
|
|
133
160
|
if (d.model)
|
|
134
161
|
body.model = d.model;
|
|
162
|
+
if (d.avatar)
|
|
163
|
+
body.avatar = d.avatar;
|
|
135
164
|
return agent(await call(t, 'POST', '/v1/agent', body));
|
|
136
165
|
}
|
|
137
166
|
/** The fields that differ from `was`, and nothing else: an omitted field keeps its value. */
|
|
@@ -173,7 +202,7 @@ export async function presets(t) {
|
|
|
173
202
|
.map((p) => ({ id: str(p.id), title: str(p.title), prompt: str(p.systemPrompt) }))
|
|
174
203
|
.filter((p) => p.id);
|
|
175
204
|
}
|
|
176
|
-
/** A
|
|
205
|
+
/** A preset as a template. */
|
|
177
206
|
export function fromPreset(p) {
|
|
178
|
-
return {
|
|
207
|
+
return { name: p.id, title: p.title || p.id, description: p.title, category: 'Preset', model: '', instructions: p.prompt };
|
|
179
208
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { type Cast } from '../api/agents.ts';
|
|
1
2
|
import { type Pane } from './ui.tsx';
|
|
3
|
+
/** Categories as Browse offers them: the default agents' and presets' first, as listed, then the rest by how many each holds. */
|
|
4
|
+
export declare function kinds(first: readonly Cast[], rest: readonly Cast[]): string[];
|
|
2
5
|
export declare function Agents({ view, q, adding, onAdding, onView, onCount }: Pane): import("react").JSX.Element;
|
|
3
6
|
/** One agent's page: who it is, what it runs on and reads, what it may call and spend, and what it has done. */
|
|
4
7
|
export declare function Profile({ name }: {
|
package/lib/customize/agents.js
CHANGED
|
@@ -3,10 +3,12 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
3
3
|
* Agents: the org's own — a model, instructions, the tools it may call, and
|
|
4
4
|
* what it may spend.
|
|
5
5
|
*
|
|
6
|
-
* Browse is
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* Browse is every agent there is to start from: the host's default agents
|
|
7
|
+
* (`cast`) and personas (`personas`), and the platform's presets whose tool
|
|
8
|
+
* calls run on the platform, by name and category. Use makes the org's own
|
|
9
|
+
* agent from one and opens it; nothing is added any other way, so an org starts
|
|
10
|
+
* with none. An agent is also made from scratch, from the person signed in
|
|
11
|
+
* (Clone yourself) or from anyone named. Yours lists the org's agents. Opening one is its profile page
|
|
10
12
|
* (`-/customize/agents/<name>`): its face, part, model, instructions, tools,
|
|
11
13
|
* budget and runs, edited in place, and only what changed is sent. An
|
|
12
14
|
* agent calls only the tools it names, and only those that are on for the org,
|
|
@@ -15,11 +17,11 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
15
17
|
* spend each period, and in one run.
|
|
16
18
|
*/
|
|
17
19
|
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
18
|
-
import { ArrowLeft,
|
|
20
|
+
import { ArrowLeft, Upload } from '@hanzogui/lucide-icons-2';
|
|
19
21
|
import { Button, Input, Switch, Textarea } from '@hanzo/ui';
|
|
20
22
|
import { ModelPicker } from '@hanzo/ui/models';
|
|
21
|
-
import { useEffect, useMemo, useState } from 'react';
|
|
22
|
-
import { agents, ALL, create, draft, EMPTY, fromPreset,
|
|
23
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
24
|
+
import { agents, ALL, create, draft, EMPTY, fromPreset, one, PERIODS, person, presets, profile, remove, update, use } from '../api/agents.js';
|
|
23
25
|
import { models } from '../api/models.js';
|
|
24
26
|
import { tools } from '../api/tools.js';
|
|
25
27
|
import { useHost, useTarget } from '../host.js';
|
|
@@ -27,58 +29,106 @@ import { path } from '../route.js';
|
|
|
27
29
|
import { useLoad } from './load.js';
|
|
28
30
|
import { say } from './say.js';
|
|
29
31
|
import { Add, Choice, Confirm, Empty, Failed, Field, Grid, Line, Mark, matches, mono, Part, Sheet, Soft, Tile, useCount, Visitor } from './ui.js';
|
|
32
|
+
/** How many cards Browse draws at a time. */
|
|
33
|
+
const PAGE = 60;
|
|
34
|
+
const ANY = 'All';
|
|
35
|
+
/** Categories as Browse offers them: the default agents' and presets' first, as listed, then the rest by how many each holds. */
|
|
36
|
+
export function kinds(first, rest) {
|
|
37
|
+
const lead = [...new Set(first.map((c) => c.category))];
|
|
38
|
+
const count = new Map();
|
|
39
|
+
for (const c of rest)
|
|
40
|
+
if (!lead.includes(c.category))
|
|
41
|
+
count.set(c.category, (count.get(c.category) ?? 0) + 1);
|
|
42
|
+
return [ANY, ...lead, ...[...count.keys()].sort((a, b) => count.get(b) - count.get(a) || a.localeCompare(b))];
|
|
43
|
+
}
|
|
30
44
|
const money = (m) => `$${(m / 1_000_000).toFixed(m % 10_000 ? 4 : 2).replace(/\.?0+$/, '')}`;
|
|
31
45
|
export function Agents({ view, q, adding, onAdding, onView, onCount }) {
|
|
32
46
|
const host = useHost();
|
|
33
47
|
const t = useTarget();
|
|
34
48
|
const signed = Boolean(host.person);
|
|
49
|
+
const browsing = view === 'discover';
|
|
35
50
|
const mine = useLoad(signed ? () => agents(t) : null, [], [t, signed]);
|
|
36
|
-
const offered = useLoad(
|
|
51
|
+
const offered = useLoad(browsing ? () => presets(t) : null, [], [t, view]);
|
|
52
|
+
const figures = useLoad(browsing && host.personas ? host.personas : null, [], [view]);
|
|
37
53
|
useCount(mine.value.length, onCount);
|
|
38
|
-
const [start, setStart] = useState(null);
|
|
39
54
|
const [note, setNote] = useState('');
|
|
40
|
-
const [
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
55
|
+
const [kind, setKind] = useState(ANY);
|
|
56
|
+
const [more, setMore] = useState(PAGE);
|
|
57
|
+
const [using, setUsing] = useState(null);
|
|
58
|
+
const [making, setMaking] = useState(null);
|
|
59
|
+
useEffect(() => setMore(PAGE), [q, kind]);
|
|
60
|
+
const opened = (a) => host.go(path({ kind: 'agent', name: a.name }));
|
|
61
|
+
const take = async (c) => {
|
|
62
|
+
setUsing(c);
|
|
46
63
|
setNote('');
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
64
|
+
try {
|
|
65
|
+
opened(await use(t, c, window.location.href));
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
setNote(say(e));
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
setUsing(null);
|
|
72
|
+
}
|
|
56
73
|
};
|
|
57
|
-
const editor = adding
|
|
58
|
-
|
|
74
|
+
const editor = adding ? (_jsx(Editor, { onClose: () => onAdding(false), onSaved: (a) => {
|
|
75
|
+
onAdding(false);
|
|
59
76
|
onView('yours');
|
|
60
77
|
mine.reload();
|
|
61
78
|
setNote(`${a.name} is saved`);
|
|
62
79
|
} })) : null;
|
|
63
|
-
if (
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
|
|
80
|
+
if (browsing) {
|
|
81
|
+
const first = [...(host.cast ?? []), ...offered.value.map(fromPreset)];
|
|
82
|
+
const all = [...first, ...figures.value];
|
|
83
|
+
const shown = all.filter((c) => (kind === ANY || c.category === kind) && matches(q, c.title, c.name, c.description, c.category));
|
|
84
|
+
const failed = figures.error ?? offered.error;
|
|
85
|
+
return (_jsxs(YStack, { gap: "$3", children: [signed ? (_jsxs(XStack, { gap: "$2", flexWrap: "wrap", children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => onAdding(true), children: "Start from scratch" }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => setMaking('self'), children: "Clone yourself" }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => setMaking('person'), children: "Create from a person" })] })) : null, _jsx(XStack, { overflowX: "auto", overflowY: "hidden", pb: "$1", children: _jsx(Choice, { label: "Category", value: kind, options: kinds(first, figures.value).map((id) => ({ id, label: id })), onChange: setKind }) }), _jsx(Line, { children: note }), shown.length ? (_jsx(Grid, { label: "Agents to start from", children: shown.slice(0, more).map((c) => (_jsx(Tile, { title: c.title, detail: c.description === c.title || c.description === c.category ? undefined : c.description, meta: c.category, mark: _jsx(Mark, { name: c.title, size: 40, logo: c.avatar }), action: signed ? _jsx(Add, { name: c.title, label: "Use", added: false, busy: using !== null, onPress: () => void take(c) }) : undefined }, `${c.category}/${c.name}/${c.title}`))) })) : figures.loading || offered.loading ? null : (_jsx(Soft, { children: `No agent matches “${q.trim()}”.` })), shown.length > more ? (_jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "ghost", onPress: () => setMore(more + PAGE), children: "More" }) })) : null, failed ? _jsx(Failed, { error: failed, onRetry: figures.error ? figures.reload : offered.reload }) : figures.loading || offered.loading ? _jsx(Soft, { children: "Loading\u2026" }) : null, editor, making ? _jsx(Person, { self: making === 'self', onClose: () => setMaking(null), onMade: opened }) : null] }));
|
|
67
86
|
}
|
|
68
87
|
if (!signed) {
|
|
69
88
|
return _jsx(Visitor, { children: "Sign in to see your agents." });
|
|
70
89
|
}
|
|
71
90
|
const shown = mine.value.filter((a) => matches(q, a.name, a.description, a.model));
|
|
72
|
-
return (_jsxs(YStack, { gap: "$3", children: [_jsx(Line, { children: note }), mine.error && !mine.value.length ? (_jsx(Failed, { error: mine.error, onRetry: mine.reload })) : mine.loading && !mine.value.length ? (_jsx(Soft, { children: "Loading your agents\u2026" })) : !mine.value.length ? (_jsxs(Empty, { title: "No agents yet", detail:
|
|
91
|
+
return (_jsxs(YStack, { gap: "$3", children: [_jsx(Line, { children: note }), mine.error && !mine.value.length ? (_jsx(Failed, { error: mine.error, onRetry: mine.reload })) : mine.loading && !mine.value.length ? (_jsx(Soft, { children: "Loading your agents\u2026" })) : !mine.value.length ? (_jsxs(Empty, { title: "No agents yet", detail: "", children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => onView('discover'), children: "Browse agents" }), _jsx(Button, { size: "sm", onPress: () => onAdding(true), children: "New agent" })] })) : !shown.length ? (_jsx(Soft, { children: `No agent of yours matches “${q.trim()}”.` })) : (_jsx(Grid, { label: "Your agents", children: shown.map((a) => (_jsx(Tile, { title: a.name, detail: a.description || 'No description.', meta: [a.model, `${a.runs} ${a.runs === 1 ? 'run' : 'runs'}`, reach(a), a.cap ? `${money(a.spent)} of ${money(a.cap)} this ${a.period || 'period'}` : ''].filter(Boolean).join(' · '), mark: _jsx(Face, { agent: a, cast: host.cast ?? [] }), onOpen: () => opened(a) }, a.id || a.name))) })), editor] }));
|
|
92
|
+
}
|
|
93
|
+
/** Clone yourself, or anyone named: a name, who they are, and writing of theirs, pasted or uploaded. */
|
|
94
|
+
function Person({ self, onClose, onMade }) {
|
|
95
|
+
const host = useHost();
|
|
96
|
+
const t = useTarget();
|
|
97
|
+
const me = self ? host.person : null;
|
|
98
|
+
const [name, setName] = useState(me?.name ?? '');
|
|
99
|
+
const [about, setAbout] = useState('');
|
|
100
|
+
const [writing, setWriting] = useState('');
|
|
101
|
+
const [working, setWorking] = useState(false);
|
|
102
|
+
const [note, setNote] = useState('');
|
|
103
|
+
const file = useRef(null);
|
|
104
|
+
const read = async (files) => {
|
|
105
|
+
const texts = await Promise.all([...(files ?? [])].map((f) => f.text()));
|
|
106
|
+
setWriting((w) => [w.trim(), ...texts.map((x) => x.trim())].filter(Boolean).join('\n\n'));
|
|
107
|
+
};
|
|
108
|
+
const save = async () => {
|
|
109
|
+
setWorking(true);
|
|
110
|
+
setNote('');
|
|
111
|
+
try {
|
|
112
|
+
onMade(await use(t, person(name, about, writing, me?.avatar), window.location.href));
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
setNote(say(e));
|
|
116
|
+
setWorking(false);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
return (_jsxs(Sheet, { title: self ? 'Clone yourself' : 'Create from a person', open: true, onOpenChange: (o) => !o && onClose(), children: [_jsx(Field, { label: "Name", children: _jsx(Input, { value: name, onChangeText: setName, "aria-label": "Name" }) }), _jsx(Field, { label: "Description", children: _jsx(Textarea, { value: about, onChangeText: setAbout, "aria-label": "Description", rows: 3 }) }), _jsx(Field, { label: "Writing", children: _jsx(Textarea, { value: writing, onChangeText: setWriting, "aria-label": "Writing", rows: 6 }) }), _jsxs(XStack, { children: [_jsxs(Button, { size: "sm", variant: "ghost", onPress: () => file.current?.click(), children: [_jsx(Upload, { size: 14 }), " Upload"] }), _jsx("input", { ref: file, type: "file", multiple: true, accept: ".txt,.md,.markdown,.csv,.json,.html,text/*", hidden: true, "aria-label": "Upload writing", onChange: (e) => {
|
|
120
|
+
void read(e.currentTarget.files);
|
|
121
|
+
e.currentTarget.value = '';
|
|
122
|
+
} })] }), _jsx(Line, { children: note }), _jsxs(XStack, { gap: "$2", items: "center", children: [_jsx(XStack, { flex: 1 }), _jsx(Button, { size: "sm", variant: "ghost", onPress: onClose, children: "Cancel" }), _jsx(Button, { size: "sm", disabled: working || !name.trim(), onPress: () => void save(), children: "Create" })] })] }));
|
|
73
123
|
}
|
|
74
124
|
function reach(a) {
|
|
75
125
|
if (a.tools.includes(ALL))
|
|
76
126
|
return 'every tool';
|
|
77
127
|
return a.tools.length === 1 ? '1 tool' : `${a.tools.length} tools`;
|
|
78
128
|
}
|
|
79
|
-
/** An agent's face: the
|
|
129
|
+
/** An agent's face: its picture, the default agent's of its name, its emoji, or its initial. */
|
|
80
130
|
function Face({ agent: a, cast, size = 32 }) {
|
|
81
|
-
const avatar = cast.find((c) => c.name.toLowerCase() === a.name.toLowerCase())?.avatar;
|
|
131
|
+
const avatar = a.avatar || cast.find((c) => c.name.toLowerCase() === a.name.toLowerCase())?.avatar;
|
|
82
132
|
return _jsx(Mark, { name: a.name, size: size, logo: avatar, icon: a.emoji ? _jsx(SizableText, { size: size > 40 ? '$8' : '$4', children: a.emoji }) : undefined });
|
|
83
133
|
}
|
|
84
134
|
/** One agent's page: who it is, what it runs on and reads, what it may call and spend, and what it has done. */
|
|
@@ -95,12 +145,12 @@ export function Profile({ name }) {
|
|
|
95
145
|
}, onDeleted: back })) : null] }));
|
|
96
146
|
}
|
|
97
147
|
/** Write a new agent, or change one; an existing agent is read whole first, for its instructions. */
|
|
98
|
-
function Editor({ agent,
|
|
148
|
+
function Editor({ agent, onClose, onSaved, onDeleted, }) {
|
|
99
149
|
const t = useTarget();
|
|
100
150
|
const full = useLoad(agent ? () => one(t, agent.id || agent.name) : null, null, [t, agent?.id]);
|
|
101
151
|
const catalog = useLoad(() => models(t), [], [t]);
|
|
102
152
|
const on = useLoad(() => tools(t, { activated: true }), [], [t]);
|
|
103
|
-
const [d, setD] = useState(
|
|
153
|
+
const [d, setD] = useState(EMPTY);
|
|
104
154
|
const [ready, setReady] = useState(!agent);
|
|
105
155
|
const [working, setWorking] = useState(false);
|
|
106
156
|
const [note, setNote] = useState('');
|
package/lib/customize/index.js
CHANGED
|
@@ -29,11 +29,9 @@ const LABEL = {
|
|
|
29
29
|
plugins: { title: 'Plugins', says: 'Connectors you build in TypeScript.', add: 'Build plugin', find: 'Search plugins', icon: _jsx(Puzzle, { size: 15 }) },
|
|
30
30
|
agents: { title: 'Agents', says: 'Agents with their own model, instructions and budget.', add: 'New agent', find: 'Search agents', icon: _jsx(Bot, { size: 15 }) },
|
|
31
31
|
};
|
|
32
|
-
/** Where a tab opens: an org's own agents (and the cast to add) for Agents; Browse for the rest, what there is to add being the page. */
|
|
33
|
-
const opens = (tab) => (tab === 'agents' ? 'yours' : 'discover');
|
|
34
32
|
export function Customize({ tab, view: first }) {
|
|
35
33
|
const host = useHost();
|
|
36
|
-
const [view, setView] = useState(first ??
|
|
34
|
+
const [view, setView] = useState(first ?? 'discover');
|
|
37
35
|
const [q, setQ] = useState('');
|
|
38
36
|
const [adding, setAdding] = useState(false);
|
|
39
37
|
const [count, setCount] = useState(0);
|
|
@@ -41,7 +39,7 @@ export function Customize({ tab, view: first }) {
|
|
|
41
39
|
const pick = (next) => {
|
|
42
40
|
setQ('');
|
|
43
41
|
setAdding(false);
|
|
44
|
-
setView(
|
|
42
|
+
setView('discover');
|
|
45
43
|
setCount(0);
|
|
46
44
|
host.go(path({ kind: 'customize', tab: next }));
|
|
47
45
|
};
|
package/lib/host.d.ts
CHANGED
|
@@ -107,8 +107,10 @@ export interface Host {
|
|
|
107
107
|
browser?: Browser;
|
|
108
108
|
/** The host's sidebar lists Settings' sections and Customize's tabs, so the pages draw neither. */
|
|
109
109
|
sections?: boolean;
|
|
110
|
-
/** The default agents
|
|
110
|
+
/** The default agents Customize › Agents offers to start from. Absent, none. */
|
|
111
111
|
cast?: readonly Cast[];
|
|
112
|
+
/** Every persona Customize › Agents offers to start from, read when Browse opens. Absent, none. */
|
|
113
|
+
personas?: () => Promise<readonly Cast[]>;
|
|
112
114
|
}
|
|
113
115
|
export declare function HostProvider({ host, children }: {
|
|
114
116
|
host: Host;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/build",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.80",
|
|
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.",
|