@hanzo/rooms 0.1.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/LICENSE-APACHE +205 -0
- package/LICENSE-MIT +21 -0
- package/README.md +23 -0
- package/dist/Account.d.ts +8 -0
- package/dist/Account.js +58 -0
- package/dist/Beluga.d.ts +12 -0
- package/dist/Beluga.js +152 -0
- package/dist/Billing.d.ts +1 -0
- package/dist/Billing.js +31 -0
- package/dist/Board.d.ts +9 -0
- package/dist/Board.js +447 -0
- package/dist/Boxes.d.ts +1 -0
- package/dist/Boxes.js +173 -0
- package/dist/CalScheduler.d.ts +15 -0
- package/dist/CalScheduler.js +196 -0
- package/dist/Chat.d.ts +35 -0
- package/dist/Chat.js +1049 -0
- package/dist/Column.d.ts +15 -0
- package/dist/Column.js +88 -0
- package/dist/Contacts.d.ts +1 -0
- package/dist/Contacts.js +301 -0
- package/dist/Directory.d.ts +24 -0
- package/dist/Directory.js +356 -0
- package/dist/Drive.d.ts +2 -0
- package/dist/Drive.js +466 -0
- package/dist/Find.d.ts +7 -0
- package/dist/Find.js +82 -0
- package/dist/Guide.d.ts +6 -0
- package/dist/Guide.js +272 -0
- package/dist/Home.d.ts +39 -0
- package/dist/Home.js +306 -0
- package/dist/Inbox.d.ts +9 -0
- package/dist/Inbox.js +193 -0
- package/dist/Market.d.ts +3 -0
- package/dist/Market.js +149 -0
- package/dist/Me.d.ts +27 -0
- package/dist/Me.js +251 -0
- package/dist/Meet.d.ts +9 -0
- package/dist/Meet.js +370 -0
- package/dist/ModeMark.d.ts +8 -0
- package/dist/ModeMark.js +18 -0
- package/dist/OrgSwitcher.d.ts +31 -0
- package/dist/OrgSwitcher.js +120 -0
- package/dist/Orgs.d.ts +10 -0
- package/dist/Orgs.js +582 -0
- package/dist/Profile.d.ts +4 -0
- package/dist/Profile.js +67 -0
- package/dist/Prose.d.ts +5 -0
- package/dist/Prose.js +111 -0
- package/dist/ProviderMark.d.ts +17 -0
- package/dist/ProviderMark.js +277 -0
- package/dist/Recents.d.ts +10 -0
- package/dist/Recents.js +91 -0
- package/dist/RightPane.d.ts +13 -0
- package/dist/RightPane.js +426 -0
- package/dist/Room.d.ts +8 -0
- package/dist/Room.js +50 -0
- package/dist/Scene.d.ts +12 -0
- package/dist/Scene.js +105 -0
- package/dist/Settings.d.ts +46 -0
- package/dist/Settings.js +321 -0
- package/dist/Setup.d.ts +1 -0
- package/dist/Setup.js +36 -0
- package/dist/Share.d.ts +3 -0
- package/dist/Share.js +130 -0
- package/dist/Shared.d.ts +1 -0
- package/dist/Shared.js +113 -0
- package/dist/Shell.d.ts +63 -0
- package/dist/Shell.js +1225 -0
- package/dist/Start.d.ts +1 -0
- package/dist/Start.js +96 -0
- package/dist/Support.d.ts +14 -0
- package/dist/Support.js +122 -0
- package/dist/Talk.d.ts +4 -0
- package/dist/Talk.js +107 -0
- package/dist/Upgrade.d.ts +11 -0
- package/dist/Upgrade.js +75 -0
- package/dist/Usage.d.ts +1 -0
- package/dist/Usage.js +21 -0
- package/dist/Vibe.d.ts +2 -0
- package/dist/Vibe.js +188 -0
- package/dist/Work.d.ts +12 -0
- package/dist/Work.js +256 -0
- package/dist/bots/Bots.d.ts +2 -0
- package/dist/bots/Bots.js +456 -0
- package/dist/bots/Lights.d.ts +8 -0
- package/dist/bots/Lights.js +13 -0
- package/dist/bots/api.d.ts +120 -0
- package/dist/bots/api.js +75 -0
- package/dist/bots/face.d.ts +5 -0
- package/dist/bots/face.js +13 -0
- package/dist/bots/ink.d.ts +7 -0
- package/dist/bots/ink.js +29 -0
- package/dist/bots/kit.d.ts +113 -0
- package/dist/bots/kit.js +43 -0
- package/dist/bots/runtime.d.ts +4 -0
- package/dist/bots/runtime.js +326 -0
- package/dist/card.d.ts +52 -0
- package/dist/card.js +52 -0
- package/dist/cast.d.ts +143 -0
- package/dist/cast.js +251 -0
- package/dist/connect.d.ts +29 -0
- package/dist/connect.js +64 -0
- package/dist/contacts-store.d.ts +111 -0
- package/dist/contacts-store.js +276 -0
- package/dist/conversations.d.ts +54 -0
- package/dist/conversations.js +98 -0
- package/dist/copy.d.ts +23 -0
- package/dist/copy.js +39 -0
- package/dist/crew.d.ts +36 -0
- package/dist/crew.js +304 -0
- package/dist/dev.d.ts +3 -0
- package/dist/dev.js +102 -0
- package/dist/enso.d.ts +70 -0
- package/dist/enso.js +112 -0
- package/dist/failure.d.ts +57 -0
- package/dist/failure.js +143 -0
- package/dist/ground.d.ts +32 -0
- package/dist/ground.js +32 -0
- package/dist/host.d.ts +76 -0
- package/dist/host.js +76 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +12 -0
- package/dist/kept.d.ts +5 -0
- package/dist/kept.js +27 -0
- package/dist/lib/account.d.ts +49 -0
- package/dist/lib/account.js +229 -0
- package/dist/lib/ai.d.ts +46 -0
- package/dist/lib/ai.js +140 -0
- package/dist/lib/api.d.ts +43 -0
- package/dist/lib/api.js +62 -0
- package/dist/lib/bare.d.ts +9 -0
- package/dist/lib/bare.js +7 -0
- package/dist/lib/coding.d.ts +105 -0
- package/dist/lib/coding.js +87 -0
- package/dist/lib/destination.d.ts +102 -0
- package/dist/lib/destination.js +260 -0
- package/dist/lib/durable.d.ts +66 -0
- package/dist/lib/durable.js +107 -0
- package/dist/lib/first.d.ts +11 -0
- package/dist/lib/first.js +23 -0
- package/dist/lib/home.d.ts +61 -0
- package/dist/lib/home.js +54 -0
- package/dist/lib/host.d.ts +104 -0
- package/dist/lib/host.js +132 -0
- package/dist/lib/hostname.d.ts +1 -0
- package/dist/lib/hostname.js +14 -0
- package/dist/lib/hydrated.d.ts +1 -0
- package/dist/lib/hydrated.js +20 -0
- package/dist/lib/integrations.d.ts +21 -0
- package/dist/lib/integrations.js +1 -0
- package/dist/lib/intent.d.ts +6 -0
- package/dist/lib/intent.js +71 -0
- package/dist/lib/invite.d.ts +155 -0
- package/dist/lib/invite.js +245 -0
- package/dist/lib/limits.d.ts +55 -0
- package/dist/lib/limits.js +119 -0
- package/dist/lib/list.d.ts +9 -0
- package/dist/lib/list.js +11 -0
- package/dist/lib/mark.d.ts +13 -0
- package/dist/lib/mark.js +85 -0
- package/dist/lib/mix.d.ts +22 -0
- package/dist/lib/mix.js +22 -0
- package/dist/lib/models.d.ts +27 -0
- package/dist/lib/models.js +106 -0
- package/dist/lib/org.d.ts +109 -0
- package/dist/lib/org.js +140 -0
- package/dist/lib/path.d.ts +9 -0
- package/dist/lib/path.js +29 -0
- package/dist/lib/plans.d.ts +193 -0
- package/dist/lib/plans.js +310 -0
- package/dist/lib/prose.d.ts +13 -0
- package/dist/lib/prose.js +62 -0
- package/dist/lib/reach.d.ts +17 -0
- package/dist/lib/reach.js +70 -0
- package/dist/lib/rooms.json +23 -0
- package/dist/lib/served.d.ts +20 -0
- package/dist/lib/served.js +92 -0
- package/dist/lib/session.d.ts +211 -0
- package/dist/lib/session.js +493 -0
- package/dist/lib/share.d.ts +131 -0
- package/dist/lib/share.js +216 -0
- package/dist/lib/signout.d.ts +14 -0
- package/dist/lib/signout.js +40 -0
- package/dist/lib/tags.d.ts +27 -0
- package/dist/lib/tags.js +46 -0
- package/dist/lib/tap.d.ts +17 -0
- package/dist/lib/tap.js +9 -0
- package/dist/lib/tier.d.ts +115 -0
- package/dist/lib/tier.js +207 -0
- package/dist/lib/tiers.d.ts +9 -0
- package/dist/lib/tiers.js +141 -0
- package/dist/lib/todo.d.ts +125 -0
- package/dist/lib/todo.js +379 -0
- package/dist/look.d.ts +1 -0
- package/dist/look.js +21 -0
- package/dist/meters.d.ts +8 -0
- package/dist/meters.js +31 -0
- package/dist/model.d.ts +4 -0
- package/dist/model.js +44 -0
- package/dist/network.d.ts +64 -0
- package/dist/network.js +216 -0
- package/dist/open.d.ts +81 -0
- package/dist/open.js +149 -0
- package/dist/pane.d.ts +92 -0
- package/dist/pane.js +231 -0
- package/dist/progress.d.ts +46 -0
- package/dist/progress.js +51 -0
- package/dist/recent.d.ts +67 -0
- package/dist/recent.js +161 -0
- package/dist/rooms.d.ts +131 -0
- package/dist/rooms.js +195 -0
- package/dist/roster.d.ts +28 -0
- package/dist/roster.js +70 -0
- package/dist/span.d.ts +14 -0
- package/dist/span.js +50 -0
- package/dist/speech.d.ts +44 -0
- package/dist/speech.js +229 -0
- package/dist/stars.d.ts +16 -0
- package/dist/stars.js +75 -0
- package/dist/tabs.d.ts +26 -0
- package/dist/tabs.js +25 -0
- package/dist/team.d.ts +11 -0
- package/dist/team.gen.d.ts +10 -0
- package/dist/team.gen.js +11 -0
- package/dist/team.js +30 -0
- package/dist/tools.d.ts +35 -0
- package/dist/tools.js +143 -0
- package/dist/tours.d.ts +24 -0
- package/dist/tours.js +311 -0
- package/dist/waiting.d.ts +15 -0
- package/dist/waiting.js +17 -0
- package/dist/where.d.ts +54 -0
- package/dist/where.js +14 -0
- package/package.json +108 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified contacts & social directory store for People, Custom Agents, and Pretrained Agents.
|
|
3
|
+
*
|
|
4
|
+
* WhatsApp-style Contacts architecture:
|
|
5
|
+
* - People (friends, teammates, colleagues)
|
|
6
|
+
* - Custom Agents (user-created AI personas)
|
|
7
|
+
* - Pretrained Agents (specialized ready-to-use Hanzo agents)
|
|
8
|
+
* - Conversation Threads (Group chats, Group calls, Direct messages)
|
|
9
|
+
*
|
|
10
|
+
* Critical requirement:
|
|
11
|
+
* Group chats and calls are created as conversation threads stored in the CHAT system,
|
|
12
|
+
* NOT inside the contacts roster.
|
|
13
|
+
*/
|
|
14
|
+
export interface ContactPerson {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
email: string;
|
|
18
|
+
role?: string;
|
|
19
|
+
status?: string;
|
|
20
|
+
avatar?: string;
|
|
21
|
+
online?: boolean;
|
|
22
|
+
lastSeen?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ContactAgent {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
model: string;
|
|
28
|
+
role: string;
|
|
29
|
+
description: string;
|
|
30
|
+
emoji?: string;
|
|
31
|
+
avatar?: string;
|
|
32
|
+
isPretrained?: boolean;
|
|
33
|
+
installed?: boolean;
|
|
34
|
+
capabilities?: string[];
|
|
35
|
+
}
|
|
36
|
+
export interface CustomThread {
|
|
37
|
+
id: string;
|
|
38
|
+
title: string;
|
|
39
|
+
type: 'direct' | 'group' | 'call';
|
|
40
|
+
participants: {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
isAgent?: boolean;
|
|
44
|
+
avatar?: string;
|
|
45
|
+
}[];
|
|
46
|
+
updatedAt: number;
|
|
47
|
+
lastMessage?: string;
|
|
48
|
+
callActive?: boolean;
|
|
49
|
+
}
|
|
50
|
+
export declare const PRETRAINED_AGENTS: ContactAgent[];
|
|
51
|
+
export declare const DEFAULT_PEOPLE: ContactPerson[];
|
|
52
|
+
/** Read stored people, falling back to defaults */
|
|
53
|
+
export declare function getStoredPeople(): ContactPerson[];
|
|
54
|
+
/** Save updated people to storage */
|
|
55
|
+
export declare function saveStoredPeople(people: ContactPerson[]): void;
|
|
56
|
+
/**
|
|
57
|
+
* Read the agents this browser stored. NOTHING IS SEEDED: this used to write
|
|
58
|
+
* three invented agents into every new browser and the roster drew them as the
|
|
59
|
+
* org's own, so an org with no agents looked staffed. The org's agents come
|
|
60
|
+
* from /v1/agents; this is only what a reader added here.
|
|
61
|
+
*/
|
|
62
|
+
export declare function getStoredAgents(): ContactAgent[];
|
|
63
|
+
/** Save updated custom agents to storage */
|
|
64
|
+
export declare function saveStoredAgents(agents: ContactAgent[]): void;
|
|
65
|
+
/** Read stored custom conversation threads (Group chats, Group calls, etc.) */
|
|
66
|
+
export declare function getCustomThreads(): CustomThread[];
|
|
67
|
+
/** Save updated conversation threads */
|
|
68
|
+
export declare function saveCustomThreads(threads: CustomThread[]): void;
|
|
69
|
+
/**
|
|
70
|
+
* Creates a new Group Chat thread and stores it in the CHAT system.
|
|
71
|
+
* Returns the created thread ID.
|
|
72
|
+
*/
|
|
73
|
+
export declare function createGroupChat(params: {
|
|
74
|
+
title: string;
|
|
75
|
+
participants: {
|
|
76
|
+
id: string;
|
|
77
|
+
name: string;
|
|
78
|
+
isAgent?: boolean;
|
|
79
|
+
avatar?: string;
|
|
80
|
+
}[];
|
|
81
|
+
}): string;
|
|
82
|
+
/**
|
|
83
|
+
* Creates a new Group Call thread and stores it in the CHAT system.
|
|
84
|
+
* Returns the created thread ID.
|
|
85
|
+
*/
|
|
86
|
+
export declare function createGroupCall(params: {
|
|
87
|
+
title: string;
|
|
88
|
+
participants: {
|
|
89
|
+
id: string;
|
|
90
|
+
name: string;
|
|
91
|
+
isAgent?: boolean;
|
|
92
|
+
avatar?: string;
|
|
93
|
+
}[];
|
|
94
|
+
}): string;
|
|
95
|
+
/**
|
|
96
|
+
* Creates or opens a 1-on-1 direct chat thread.
|
|
97
|
+
* Returns the thread ID.
|
|
98
|
+
*/
|
|
99
|
+
export declare function createDirectChat(target: {
|
|
100
|
+
id: string;
|
|
101
|
+
name: string;
|
|
102
|
+
isAgent?: boolean;
|
|
103
|
+
avatar?: string;
|
|
104
|
+
}): string;
|
|
105
|
+
/**
|
|
106
|
+
* The org's members, from IAM. `/v1/iam/account` names the caller's org and
|
|
107
|
+
* `/v1/iam/users?owner=<org>` lists who is in it; both answer for any member.
|
|
108
|
+
* What comes back replaces the stored roster, so the store is a cache of the
|
|
109
|
+
* platform and never a fixture.
|
|
110
|
+
*/
|
|
111
|
+
export declare function fetchOrgPeople(baseUrl: string, token: string): Promise<ContactPerson[]>;
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { scope } from './lib/session.js';
|
|
3
|
+
// Pretrained Agents catalog
|
|
4
|
+
export const PRETRAINED_AGENTS = [
|
|
5
|
+
{
|
|
6
|
+
id: 'pretrained-coder',
|
|
7
|
+
name: 'Hanzo Coder',
|
|
8
|
+
model: 'enso',
|
|
9
|
+
role: 'Full-Stack Software Engineer',
|
|
10
|
+
description: 'Autonomous code generation, debugging, refactoring, PR reviews, and Git operations.',
|
|
11
|
+
avatar: '/agents/dev.png',
|
|
12
|
+
emoji: '💻',
|
|
13
|
+
isPretrained: true,
|
|
14
|
+
installed: true,
|
|
15
|
+
capabilities: ['TypeScript & Next.js', 'Go & Rust', 'Bug Fixing & Testing', 'Architecture Review'],
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: 'pretrained-researcher',
|
|
19
|
+
name: 'Hanzo Researcher',
|
|
20
|
+
model: 'claude-3-5-sonnet',
|
|
21
|
+
role: 'Deep Intelligence & Research Specialist',
|
|
22
|
+
description: 'Deep web search, arXiv paper extraction, competitive intelligence, and synthesis.',
|
|
23
|
+
emoji: '🔬',
|
|
24
|
+
isPretrained: true,
|
|
25
|
+
installed: true,
|
|
26
|
+
capabilities: ['Academic Paper Extraction', 'Market Research', 'Data Synthesis', 'Web Fact-Checking'],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'pretrained-designer',
|
|
30
|
+
name: 'Hanzo Designer',
|
|
31
|
+
model: 'gpt-4o',
|
|
32
|
+
role: 'UI/UX & Design Systems Architect',
|
|
33
|
+
description: 'Modern component design, responsive Tailwind/CSS layouts, Figma token synchronization.',
|
|
34
|
+
avatar: '/agents/des.png',
|
|
35
|
+
emoji: '🎨',
|
|
36
|
+
isPretrained: true,
|
|
37
|
+
installed: true,
|
|
38
|
+
capabilities: ['Figma to React', 'Design Systems', 'Micro-Interactions', 'Dark Mode Theming'],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'pretrained-quant',
|
|
42
|
+
name: 'Hanzo Quant',
|
|
43
|
+
model: 'deepseek-r1',
|
|
44
|
+
role: 'Quantitative Finance & Market Analyst',
|
|
45
|
+
description: 'Market data analysis, predictive financial modeling, risk calculations, and algorithmic strategies.',
|
|
46
|
+
avatar: '/agents/zach.jpg',
|
|
47
|
+
emoji: '📈',
|
|
48
|
+
isPretrained: true,
|
|
49
|
+
installed: false,
|
|
50
|
+
capabilities: ['Financial Modeling', 'Risk Assessment', 'Statistical Arbitrage', 'Portfolio Analysis'],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'pretrained-copywriter',
|
|
54
|
+
name: 'Hanzo Copywriter',
|
|
55
|
+
model: 'llama-3.3',
|
|
56
|
+
role: 'Technical Writer & Content Strategist',
|
|
57
|
+
description: 'High-impact technical documentation, release notes, developer blogs, and launch copy.',
|
|
58
|
+
avatar: '/agents/creative.png',
|
|
59
|
+
emoji: '✍️',
|
|
60
|
+
isPretrained: true,
|
|
61
|
+
installed: false,
|
|
62
|
+
capabilities: ['API Documentation', 'Launch Announcements', 'SEO Optimization', 'Whitepapers'],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'pretrained-devops',
|
|
66
|
+
name: 'Hanzo DevOps',
|
|
67
|
+
model: 'enso',
|
|
68
|
+
role: 'Cloud Infrastructure & SRE Specialist',
|
|
69
|
+
description: 'Kubernetes orchestration, Docker build pipelines, CI/CD automation, and incident mitigation.',
|
|
70
|
+
avatar: '/agents/vi.png',
|
|
71
|
+
emoji: '⚙️',
|
|
72
|
+
isPretrained: true,
|
|
73
|
+
installed: false,
|
|
74
|
+
capabilities: ['Kubernetes & Helm', 'GitHub Actions', 'GCP & AWS Cloud', 'Docker Containerization'],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'pretrained-legal',
|
|
78
|
+
name: 'Hanzo Legal & Compliance',
|
|
79
|
+
model: 'enso',
|
|
80
|
+
role: 'Commercial Legal & IP Counsel',
|
|
81
|
+
description: 'Commercial contract review, NDAs, SaaS service agreements, GDPR compliance, and IP protection.',
|
|
82
|
+
avatar: '/agents/einstein.png',
|
|
83
|
+
emoji: '🛡️',
|
|
84
|
+
isPretrained: true,
|
|
85
|
+
installed: false,
|
|
86
|
+
capabilities: ['Contract Review', 'SaaS Master Agreements', 'Privacy & GDPR', 'IP Licensing'],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 'pretrained-assistant',
|
|
90
|
+
name: 'Hanzo Executive Assistant',
|
|
91
|
+
model: 'gpt-4o',
|
|
92
|
+
role: 'Operations & Executive Coordinator',
|
|
93
|
+
description: 'Daily briefing generation, task prioritization, agenda prep, and team communication sync.',
|
|
94
|
+
avatar: '/agents/maya.png',
|
|
95
|
+
emoji: '👔',
|
|
96
|
+
isPretrained: true,
|
|
97
|
+
installed: false,
|
|
98
|
+
capabilities: ['Meeting Agendas', 'Executive Briefings', 'Email Drafts', 'Task Prioritization'],
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
// The roster is the org's own members, read from IAM. Nothing is invented for
|
|
102
|
+
// an org that has none yet: an empty roster reads empty.
|
|
103
|
+
export const DEFAULT_PEOPLE = [];
|
|
104
|
+
const PEOPLE_STORAGE_KEY = 'hanzo_contacts_people_v1';
|
|
105
|
+
const AGENTS_STORAGE_KEY = 'hanzo_contacts_agents_v1';
|
|
106
|
+
const THREADS_STORAGE_KEY = 'hanzo_custom_threads_v1';
|
|
107
|
+
/** Read stored people, falling back to defaults */
|
|
108
|
+
export function getStoredPeople() {
|
|
109
|
+
if (typeof window === 'undefined')
|
|
110
|
+
return DEFAULT_PEOPLE;
|
|
111
|
+
try {
|
|
112
|
+
const raw = localStorage.getItem(PEOPLE_STORAGE_KEY);
|
|
113
|
+
if (!raw)
|
|
114
|
+
return DEFAULT_PEOPLE;
|
|
115
|
+
return JSON.parse(raw);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return DEFAULT_PEOPLE;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** Save updated people to storage */
|
|
122
|
+
export function saveStoredPeople(people) {
|
|
123
|
+
if (typeof window === 'undefined')
|
|
124
|
+
return;
|
|
125
|
+
try {
|
|
126
|
+
localStorage.setItem(PEOPLE_STORAGE_KEY, JSON.stringify(people));
|
|
127
|
+
window.dispatchEvent(new Event('hanzo_contacts_changed'));
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
console.warn('Failed to save contacts:', err);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Read the agents this browser stored. NOTHING IS SEEDED: this used to write
|
|
135
|
+
* three invented agents into every new browser and the roster drew them as the
|
|
136
|
+
* org's own, so an org with no agents looked staffed. The org's agents come
|
|
137
|
+
* from /v1/agents; this is only what a reader added here.
|
|
138
|
+
*/
|
|
139
|
+
export function getStoredAgents() {
|
|
140
|
+
if (typeof window === 'undefined')
|
|
141
|
+
return [];
|
|
142
|
+
try {
|
|
143
|
+
const raw = localStorage.getItem(AGENTS_STORAGE_KEY);
|
|
144
|
+
return raw ? JSON.parse(raw) : [];
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/** Save updated custom agents to storage */
|
|
151
|
+
export function saveStoredAgents(agents) {
|
|
152
|
+
if (typeof window === 'undefined')
|
|
153
|
+
return;
|
|
154
|
+
try {
|
|
155
|
+
localStorage.setItem(AGENTS_STORAGE_KEY, JSON.stringify(agents));
|
|
156
|
+
window.dispatchEvent(new Event('hanzo_agents_changed'));
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
console.warn('Failed to save agents:', err);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/** Read stored custom conversation threads (Group chats, Group calls, etc.) */
|
|
163
|
+
export function getCustomThreads() {
|
|
164
|
+
if (typeof window === 'undefined')
|
|
165
|
+
return [];
|
|
166
|
+
try {
|
|
167
|
+
const raw = localStorage.getItem(THREADS_STORAGE_KEY);
|
|
168
|
+
if (!raw)
|
|
169
|
+
return [];
|
|
170
|
+
return JSON.parse(raw);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/** Save updated conversation threads */
|
|
177
|
+
export function saveCustomThreads(threads) {
|
|
178
|
+
if (typeof window === 'undefined')
|
|
179
|
+
return;
|
|
180
|
+
try {
|
|
181
|
+
localStorage.setItem(THREADS_STORAGE_KEY, JSON.stringify(threads));
|
|
182
|
+
window.dispatchEvent(new Event('hanzo_threads_changed'));
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
console.warn('Failed to save threads:', err);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Creates a new Group Chat thread and stores it in the CHAT system.
|
|
190
|
+
* Returns the created thread ID.
|
|
191
|
+
*/
|
|
192
|
+
export function createGroupChat(params) {
|
|
193
|
+
const threadId = `group_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
|
194
|
+
const newThread = {
|
|
195
|
+
id: threadId,
|
|
196
|
+
title: params.title || `Group (${params.participants.length})`,
|
|
197
|
+
type: 'group',
|
|
198
|
+
participants: params.participants,
|
|
199
|
+
updatedAt: Date.now(),
|
|
200
|
+
lastMessage: `Group created with ${params.participants.map((p) => p.name).join(', ')}`,
|
|
201
|
+
};
|
|
202
|
+
const existing = getCustomThreads();
|
|
203
|
+
saveCustomThreads([newThread, ...existing]);
|
|
204
|
+
return threadId;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Creates a new Group Call thread and stores it in the CHAT system.
|
|
208
|
+
* Returns the created thread ID.
|
|
209
|
+
*/
|
|
210
|
+
export function createGroupCall(params) {
|
|
211
|
+
const threadId = `call_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
|
212
|
+
const newThread = {
|
|
213
|
+
id: threadId,
|
|
214
|
+
title: params.title ? `Group Call: ${params.title}` : `Group Call (${params.participants.length})`,
|
|
215
|
+
type: 'call',
|
|
216
|
+
participants: params.participants,
|
|
217
|
+
updatedAt: Date.now(),
|
|
218
|
+
lastMessage: `Started group call with ${params.participants.map((p) => p.name).join(', ')}`,
|
|
219
|
+
callActive: true,
|
|
220
|
+
};
|
|
221
|
+
const existing = getCustomThreads();
|
|
222
|
+
saveCustomThreads([newThread, ...existing]);
|
|
223
|
+
return threadId;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Creates or opens a 1-on-1 direct chat thread.
|
|
227
|
+
* Returns the thread ID.
|
|
228
|
+
*/
|
|
229
|
+
export function createDirectChat(target) {
|
|
230
|
+
const existing = getCustomThreads();
|
|
231
|
+
const found = existing.find((t) => t.type === 'direct' && t.participants.some((p) => p.id === target.id));
|
|
232
|
+
if (found)
|
|
233
|
+
return found.id;
|
|
234
|
+
const threadId = `direct_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
|
235
|
+
const newThread = {
|
|
236
|
+
id: threadId,
|
|
237
|
+
title: target.name,
|
|
238
|
+
type: 'direct',
|
|
239
|
+
participants: [target],
|
|
240
|
+
updatedAt: Date.now(),
|
|
241
|
+
lastMessage: `Conversation opened with ${target.name}`,
|
|
242
|
+
};
|
|
243
|
+
saveCustomThreads([newThread, ...existing]);
|
|
244
|
+
return threadId;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* The org's members, from IAM. `/v1/iam/account` names the caller's org and
|
|
248
|
+
* `/v1/iam/users?owner=<org>` lists who is in it; both answer for any member.
|
|
249
|
+
* What comes back replaces the stored roster, so the store is a cache of the
|
|
250
|
+
* platform and never a fixture.
|
|
251
|
+
*/
|
|
252
|
+
export async function fetchOrgPeople(baseUrl, token) {
|
|
253
|
+
const headers = { ...scope(), Authorization: `Bearer ${token}` };
|
|
254
|
+
const me = (await (await fetch(`${baseUrl}/v1/iam/account`, { headers })).json());
|
|
255
|
+
const org = me?.owner;
|
|
256
|
+
if (!org)
|
|
257
|
+
return getStoredPeople();
|
|
258
|
+
const res = await fetch(`${baseUrl}/v1/iam/users?owner=${encodeURIComponent(org)}`, { headers });
|
|
259
|
+
if (!res.ok)
|
|
260
|
+
return getStoredPeople();
|
|
261
|
+
const body = (await res.json());
|
|
262
|
+
const rows = (Array.isArray(body) ? body : body?.data) ?? [];
|
|
263
|
+
const people = rows
|
|
264
|
+
.filter((u) => typeof u.name === 'string')
|
|
265
|
+
.map((u) => ({
|
|
266
|
+
id: `${u.owner}/${u.name}`,
|
|
267
|
+
name: u.displayName || u.name,
|
|
268
|
+
email: u.email || '',
|
|
269
|
+
role: u.isAdmin ? 'Admin' : undefined,
|
|
270
|
+
avatar: u.avatar || undefined,
|
|
271
|
+
online: Boolean(u.isOnline),
|
|
272
|
+
}));
|
|
273
|
+
if (people.length)
|
|
274
|
+
saveStoredPeople(people);
|
|
275
|
+
return people;
|
|
276
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useInbox } from '@hanzo/ai/react';
|
|
2
|
+
import type { AiClient, Thread } from '@hanzo/ai';
|
|
3
|
+
import type { Member } from './roster.js';
|
|
4
|
+
/** An arrival room, as `@hanzo/ai` groups one. Named so this file can speak of
|
|
5
|
+
* it without every caller importing the hook's return type. */
|
|
6
|
+
type Arrival = ReturnType<typeof useInbox>['rooms'][number];
|
|
7
|
+
/** One conversation another network carries. */
|
|
8
|
+
export interface Conversation {
|
|
9
|
+
key: string;
|
|
10
|
+
/** What to call it in a list. */
|
|
11
|
+
title: string;
|
|
12
|
+
/** The network it arrived on. */
|
|
13
|
+
channel: string;
|
|
14
|
+
/** Unix MILLIseconds of the last thing said. The arrival plane stamps
|
|
15
|
+
* SECONDS, and this is the one place that is reconciled. */
|
|
16
|
+
lastAt: number;
|
|
17
|
+
/** The last thing said, on one line. */
|
|
18
|
+
preview: string;
|
|
19
|
+
arrival: Arrival;
|
|
20
|
+
}
|
|
21
|
+
/** The key for an arrival. `useInbox` already keys a room; this qualifies it. */
|
|
22
|
+
export declare const netKey: (arrival: Arrival) => string;
|
|
23
|
+
/**
|
|
24
|
+
* Every conversation this reader has been sent, newest first.
|
|
25
|
+
*
|
|
26
|
+
* The read already runs in this workspace; this shapes it rather than fetching
|
|
27
|
+
* anything of its own, so the list costs no request that was not already made.
|
|
28
|
+
*/
|
|
29
|
+
export declare function useConversations(): {
|
|
30
|
+
conversations: Conversation[];
|
|
31
|
+
loading: boolean;
|
|
32
|
+
reload: () => void;
|
|
33
|
+
};
|
|
34
|
+
/** The conversation with that key, or null for none open. */
|
|
35
|
+
export declare function conversationOf(all: Conversation[], key: string | null): Conversation | null;
|
|
36
|
+
export declare const opening: (members: Member[]) => string;
|
|
37
|
+
/**
|
|
38
|
+
* Writes the room and answers its id.
|
|
39
|
+
*
|
|
40
|
+
* The agents in it go in AHEAD of the opening line as the roster turn
|
|
41
|
+
* (cast.tsx `roster`), which is how a conversation keeps who is in it: /chat
|
|
42
|
+
* seats that room when the thread opens and reads each agent's brief fresh.
|
|
43
|
+
* One agent, several, people, or a mix: all open by this one call.
|
|
44
|
+
*/
|
|
45
|
+
export declare function start(ai: AiClient, members: Member[]): Promise<string>;
|
|
46
|
+
/**
|
|
47
|
+
* When a conversation last named them, in unix milliseconds, or 0 for never.
|
|
48
|
+
*
|
|
49
|
+
* The title is the only place a member is written down, so this is what
|
|
50
|
+
* "recently" can honestly mean about a contact. The day a thread carries its
|
|
51
|
+
* members, this reads them instead.
|
|
52
|
+
*/
|
|
53
|
+
export declare function spokenAt(threads: Thread[], label: string): number;
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
// WHAT ARRIVED, IN ONE LIST.
|
|
3
|
+
//
|
|
4
|
+
// The messages other networks carry in (`/v1/channels/inbox`, read by
|
|
5
|
+
// `@hanzo/ai`'s `useInbox`), as the Inbox lists them. A room of this platform
|
|
6
|
+
// is not an arrival: it is a place the org talks in, read and answered on
|
|
7
|
+
// /home (`rooms.ts`), and each kind keeps its own reader because a thread you
|
|
8
|
+
// REPLY to and a room you POST into are not the same act.
|
|
9
|
+
//
|
|
10
|
+
// `key` is the ONE id the workspace selects by, and it names its own source:
|
|
11
|
+
// `net:<channel> <roomId>` here, `team:<space>/<id>` for a room. Prefixed
|
|
12
|
+
// rather than hashed, because a key a person can read in a URL is one they can
|
|
13
|
+
// report in a bug, and a lookup that misses is a bug rather than a category.
|
|
14
|
+
import { useMemo } from 'react';
|
|
15
|
+
import { useInbox } from '@hanzo/ai/react';
|
|
16
|
+
import { roster } from './cast.js';
|
|
17
|
+
/** What to call a member in a room. The roster carries one name; this is it. */
|
|
18
|
+
const label = (m) => m.name;
|
|
19
|
+
/** "Ada", "Ada and Mel", "Ada, Mel and Sam". */
|
|
20
|
+
const named = (names) => names.length <= 1
|
|
21
|
+
? (names[0] ?? '')
|
|
22
|
+
: `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`;
|
|
23
|
+
/** The key for an arrival. `useInbox` already keys a room; this qualifies it. */
|
|
24
|
+
export const netKey = (arrival) => `net:${arrival.key}`;
|
|
25
|
+
/**
|
|
26
|
+
* Every conversation this reader has been sent, newest first.
|
|
27
|
+
*
|
|
28
|
+
* The read already runs in this workspace; this shapes it rather than fetching
|
|
29
|
+
* anything of its own, so the list costs no request that was not already made.
|
|
30
|
+
*/
|
|
31
|
+
export function useConversations() {
|
|
32
|
+
const { rooms: arrivals, loading, reload } = useInbox({ limit: 100 });
|
|
33
|
+
const conversations = useMemo(() => arrivals
|
|
34
|
+
.map((arrival) => ({
|
|
35
|
+
key: netKey(arrival),
|
|
36
|
+
// The handle we hold, which is the only thing that tells two senders
|
|
37
|
+
// on one network apart. See Inbox's `speaker`.
|
|
38
|
+
title: arrival.last.sender ? `@${arrival.last.sender}` : arrival.channel,
|
|
39
|
+
channel: arrival.channel,
|
|
40
|
+
lastAt: arrival.lastAt * 1000,
|
|
41
|
+
preview: arrival.last.text,
|
|
42
|
+
arrival,
|
|
43
|
+
}))
|
|
44
|
+
// Newest first, then by title, so two identical reads order the same.
|
|
45
|
+
.sort((a, b) => b.lastAt - a.lastAt || a.title.localeCompare(b.title)), [arrivals]);
|
|
46
|
+
return { conversations, loading, reload };
|
|
47
|
+
}
|
|
48
|
+
/** The conversation with that key, or null for none open. */
|
|
49
|
+
export function conversationOf(all, key) {
|
|
50
|
+
return key ? (all.find((one) => one.key === key) ?? null) : null;
|
|
51
|
+
}
|
|
52
|
+
// ── Starting one, and when one last named someone ──────────────────────────
|
|
53
|
+
//
|
|
54
|
+
// These came across the rewrite of how rooms are READ, because they are not
|
|
55
|
+
// about reading: `start` opens a conversation with people the directory picked,
|
|
56
|
+
// and `spokenAt` says when one last named a person, which is how the directory
|
|
57
|
+
// sorts. `Member` belongs to the roster now, so they take it from there.
|
|
58
|
+
export const opening = (members) => `With ${named(members.map(label))}`;
|
|
59
|
+
/**
|
|
60
|
+
* Writes the room and answers its id.
|
|
61
|
+
*
|
|
62
|
+
* The agents in it go in AHEAD of the opening line as the roster turn
|
|
63
|
+
* (cast.tsx `roster`), which is how a conversation keeps who is in it: /chat
|
|
64
|
+
* seats that room when the thread opens and reads each agent's brief fresh.
|
|
65
|
+
* One agent, several, people, or a mix: all open by this one call.
|
|
66
|
+
*/
|
|
67
|
+
export async function start(ai, members) {
|
|
68
|
+
if (members.length === 0)
|
|
69
|
+
throw new Error('A conversation needs somebody in it.');
|
|
70
|
+
const agents = members.filter((one) => one.agent).map((one) => one.name);
|
|
71
|
+
const id = await ai.threads.record([
|
|
72
|
+
...(agents.length ? [{ role: 'system', content: roster(agents) }] : []),
|
|
73
|
+
{ role: 'user', content: opening(members) },
|
|
74
|
+
]);
|
|
75
|
+
// `record` answers "" where the body carried no id, which is a write that did
|
|
76
|
+
// not happen — never a room to open.
|
|
77
|
+
if (!id)
|
|
78
|
+
throw new Error('The conversation store answered without an id.');
|
|
79
|
+
return id;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* When a conversation last named them, in unix milliseconds, or 0 for never.
|
|
83
|
+
*
|
|
84
|
+
* The title is the only place a member is written down, so this is what
|
|
85
|
+
* "recently" can honestly mean about a contact. The day a thread carries its
|
|
86
|
+
* members, this reads them instead.
|
|
87
|
+
*/
|
|
88
|
+
export function spokenAt(threads, label) {
|
|
89
|
+
let last = 0;
|
|
90
|
+
for (const one of threads) {
|
|
91
|
+
if (!one.title?.includes(label))
|
|
92
|
+
continue;
|
|
93
|
+
const at = one.updatedAt ? new Date(one.updatedAt).getTime() : 0;
|
|
94
|
+
if (at > last)
|
|
95
|
+
last = at;
|
|
96
|
+
}
|
|
97
|
+
return last;
|
|
98
|
+
}
|
package/dist/copy.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Take something away with you.
|
|
3
|
+
*
|
|
4
|
+
* It copies the TEXT IT IS GIVEN, which for a message is the source rather than
|
|
5
|
+
* what is on screen: an answer is markdown — fences, lists, a table — and the
|
|
6
|
+
* rendered text has thrown all of that away, so pasting it into an editor gives
|
|
7
|
+
* you the words with the shape stripped off. The bytes the model wrote are the
|
|
8
|
+
* ones worth having.
|
|
9
|
+
*
|
|
10
|
+
* ALWAYS PRESENT, not hover-only. A control that exists only under a pointer is
|
|
11
|
+
* one a keyboard and a thumb cannot reach; this one rests dim and comes up on
|
|
12
|
+
* hover or focus, so it is quiet without being conditional. Given a `label` it
|
|
13
|
+
* stops resting — a word in a row of controls is not decoration and is read at
|
|
14
|
+
* full weight.
|
|
15
|
+
*/
|
|
16
|
+
export declare function Take({ text, says, label, }: {
|
|
17
|
+
/** What lands on the clipboard. */
|
|
18
|
+
text: string;
|
|
19
|
+
/** What it is, for the reader who hears the control rather than sees it. */
|
|
20
|
+
says?: string;
|
|
21
|
+
/** A word beside the glyph, where the control sits among named ones. */
|
|
22
|
+
label?: string;
|
|
23
|
+
}): import("react").JSX.Element;
|
package/dist/copy.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// Copying something out of the app, in one control.
|
|
4
|
+
//
|
|
5
|
+
// Two places want it and they want the same thing: an answer you are taking
|
|
6
|
+
// into an editor, and the address of a site you are sending to somebody. So the
|
|
7
|
+
// glyph, the tick, and how long the tick stays are decided here rather than
|
|
8
|
+
// twice — a control that confirms for 1400ms in one pane and 800ms in another
|
|
9
|
+
// reads as two different controls doing two different things.
|
|
10
|
+
import { useEffect, useState } from 'react';
|
|
11
|
+
import { Check, Copy } from 'lucide-react';
|
|
12
|
+
import { Box, Text, XStack } from '@hanzo/ui';
|
|
13
|
+
/**
|
|
14
|
+
* Take something away with you.
|
|
15
|
+
*
|
|
16
|
+
* It copies the TEXT IT IS GIVEN, which for a message is the source rather than
|
|
17
|
+
* what is on screen: an answer is markdown — fences, lists, a table — and the
|
|
18
|
+
* rendered text has thrown all of that away, so pasting it into an editor gives
|
|
19
|
+
* you the words with the shape stripped off. The bytes the model wrote are the
|
|
20
|
+
* ones worth having.
|
|
21
|
+
*
|
|
22
|
+
* ALWAYS PRESENT, not hover-only. A control that exists only under a pointer is
|
|
23
|
+
* one a keyboard and a thumb cannot reach; this one rests dim and comes up on
|
|
24
|
+
* hover or focus, so it is quiet without being conditional. Given a `label` it
|
|
25
|
+
* stops resting — a word in a row of controls is not decoration and is read at
|
|
26
|
+
* full weight.
|
|
27
|
+
*/
|
|
28
|
+
export function Take({ text, says = 'this', label, }) {
|
|
29
|
+
const [took, setTook] = useState(false);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!took)
|
|
32
|
+
return;
|
|
33
|
+
const t = setTimeout(() => setTook(false), 1400);
|
|
34
|
+
return () => clearTimeout(t);
|
|
35
|
+
}, [took]);
|
|
36
|
+
return (_jsx(Box, { render: "button", onClick: () => {
|
|
37
|
+
void navigator.clipboard?.writeText(text).then(() => setTook(true)).catch(() => { });
|
|
38
|
+
}, "aria-label": took ? 'Copied' : `Copy ${says}`, borderWidth: 0, bg: "transparent", p: "$1", rounded: "$2", opacity: label || took ? 1 : 0.35, hoverStyle: { opacity: 1, bg: '$hover' }, focusStyle: { opacity: 1 }, children: _jsxs(XStack, { items: "center", gap: "$1", children: [took ? _jsx(Check, { size: 13, "aria-hidden": true }) : _jsx(Copy, { size: 13, "aria-hidden": true }), label ? (_jsx(Text, { fontSize: "$2", color: "$ink", children: took ? 'Copied' : label })) : null] }) }));
|
|
39
|
+
}
|
package/dist/crew.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useAgents } from '@hanzo/ai/react';
|
|
2
|
+
/**
|
|
3
|
+
* The org's agents, read again whenever the sheet makes one. `useAgents` holds
|
|
4
|
+
* one list per caller, so the strip, the chat header and the sheet each hold
|
|
5
|
+
* their own; the event is how a new agent reaches every one of them.
|
|
6
|
+
*/
|
|
7
|
+
export declare function useCrew(): ReturnType<typeof useAgents>;
|
|
8
|
+
/** The house crew's names, which a reply may be split by even after one has left the room. */
|
|
9
|
+
export declare const HOUSE: readonly string[];
|
|
10
|
+
/**
|
|
11
|
+
* `to` is the chat a press opens, for a strip on a page that is not one. Absent,
|
|
12
|
+
* the strip sits under a chat and a press changes who is in it.
|
|
13
|
+
*/
|
|
14
|
+
export declare function Crew({ to }?: {
|
|
15
|
+
to?: string;
|
|
16
|
+
}): import("react").JSX.Element;
|
|
17
|
+
/**
|
|
18
|
+
* The sheet that makes an agent.
|
|
19
|
+
*
|
|
20
|
+
* A name, a mind and a spending limit are what the agents API requires; what it
|
|
21
|
+
* does becomes its instructions; the likeness is whichever the maker gives — a
|
|
22
|
+
* photo uploaded onto the round picture (pressed, or dropped on), or a face
|
|
23
|
+
* described and drawn by the image model. Both go through `mark`, the one crop
|
|
24
|
+
* every saved face takes, so a drawn face and an uploaded one are the same kind
|
|
25
|
+
* of picture. The row keeps at most one of a picture and an emoji, so choosing
|
|
26
|
+
* one clears the other here too.
|
|
27
|
+
*
|
|
28
|
+
* Create stays off until the required fields are filled in the API's own
|
|
29
|
+
* terms, and a refusal is said in plain words under the field it concerns.
|
|
30
|
+
*
|
|
31
|
+
* EXPORTED, because Contacts adds agents too and there is one way to make one.
|
|
32
|
+
*/
|
|
33
|
+
export declare function Hire({ onClose, onMade }: {
|
|
34
|
+
onClose: () => void;
|
|
35
|
+
onMade: (name: string) => void;
|
|
36
|
+
}): import("react").JSX.Element;
|