@hanzo/rooms 0.1.21 → 0.1.23

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/dist/tours.js DELETED
@@ -1,319 +0,0 @@
1
- 'use client';
2
- // The tours, one per room, and the finders they point with.
3
- //
4
- // A stop RESOLVES A LIVE ELEMENT by what it already carries — its label, its
5
- // role, its text — never a marker sprinkled for the tour. A stop whose anchor is
6
- // absent is skipped, so each tour describes the room as it is that day.
7
- import { brand } from './where.js';
8
- export const seen = (el) => {
9
- if (!el)
10
- return null;
11
- const r = el.getBoundingClientRect();
12
- if (r.width < 6 || r.height < 6)
13
- return null;
14
- const s = getComputedStyle(el);
15
- if (s.visibility === 'hidden' || s.display === 'none' || Number(s.opacity) === 0)
16
- return null;
17
- return el;
18
- };
19
- export const byLabel = (...labels) => {
20
- for (const l of labels) {
21
- const el = seen(document.querySelector(`[aria-label="${l}"]`));
22
- if (el)
23
- return el;
24
- }
25
- return null;
26
- };
27
- const byAttr = (attr, ...values) => {
28
- for (const v of values) {
29
- const el = seen(document.querySelector(`[${attr}="${v}"]`));
30
- if (el)
31
- return el;
32
- }
33
- return null;
34
- };
35
- export const byPlaceholder = (...values) => byAttr('placeholder', ...values);
36
- export const byTitle = (...values) => byAttr('title', ...values);
37
- /** The smallest element whose own text is exactly this word. */
38
- export const byText = (text, within) => {
39
- const root = within ?? document.body;
40
- let best = null;
41
- for (const el of root.querySelectorAll('button,a,[role="button"]')) {
42
- if ((el.textContent || '').trim() !== text)
43
- continue;
44
- const ok = seen(el);
45
- if (!ok)
46
- continue;
47
- if (!best || el.getBoundingClientRect().width < best.getBoundingClientRect().width)
48
- best = el;
49
- }
50
- return best;
51
- };
52
- /** A field whose placeholder starts with these words. */
53
- const byPlaceholderStart = (start) => {
54
- for (const el of document.querySelectorAll('input,textarea')) {
55
- if ((el.getAttribute('placeholder') || '').startsWith(start) && seen(el))
56
- return el;
57
- }
58
- return null;
59
- };
60
- /** The composer is the one visible textarea in the room. */
61
- export const composer = () => {
62
- const all = [...document.querySelectorAll('textarea')].map(seen).filter(Boolean);
63
- return all.sort((a, b) => b.getBoundingClientRect().width - a.getBoundingClientRect().width)[0] ?? null;
64
- };
65
- /** The Enso control on the composer: effort, and the model override inside it. */
66
- export const modelChip = () => seen(document.querySelector('[aria-label^="Enso"]'));
67
- /** A place on the rail, by the word under its icon. */
68
- const rail = (label) => {
69
- const b = byText(label);
70
- return b?.parentElement ?? b;
71
- };
72
- const CHAT = [
73
- {
74
- id: 'composer',
75
- get title() {
76
- return `What are you interested in? How can ${brand()} help you?`;
77
- },
78
- body: 'Type what you want in plain words. Paste an error, describe a feature, or drop in a file.',
79
- side: 'top',
80
- find: composer,
81
- },
82
- {
83
- id: 'model',
84
- title: 'Perfectly tuned with 500+ of the leading models',
85
- body: 'Enso reads your question and picks the best model for it. Tap here to choose one yourself.',
86
- side: 'top',
87
- find: modelChip,
88
- },
89
- {
90
- id: 'work',
91
- title: 'Audit the work-flow',
92
- body: "Show work opens the AI's steps and the tools it used. Turn it on when an answer surprises you.",
93
- side: 'top',
94
- find: () => byText('Show work') || byText('Hide work'),
95
- },
96
- {
97
- id: 'scene',
98
- title: 'More than chat',
99
- body: 'Chat with a bot, bring in the team, and take it to a call. Watch one happen.',
100
- side: 'right',
101
- scene: true,
102
- find: () => rail('Chat'),
103
- },
104
- {
105
- id: 'roster',
106
- title: 'People and agents, one list',
107
- body: 'Your team and your AI agents sit together. Click any of them to start talking. An agent answers like a person.',
108
- side: 'right',
109
- find: () => {
110
- const az = byLabel('A-Z', 'Sort A-Z');
111
- if (az?.parentElement)
112
- return az.parentElement;
113
- const dir = byText('Directory');
114
- return dir?.parentElement ?? dir;
115
- },
116
- },
117
- {
118
- id: 'find',
119
- title: 'Find anything',
120
- body: 'Press ⌘K to search chats, people, agents and every section at once.',
121
- side: 'bottom',
122
- find: () => {
123
- for (const b of document.querySelectorAll('button,[role="button"]')) {
124
- if (/^Search\b/.test((b.textContent || '').trim()) && seen(b))
125
- return b;
126
- }
127
- return null;
128
- },
129
- },
130
- {
131
- id: 'aside',
132
- title: 'Results stay beside you',
133
- body: 'Files, sources and previews collect in the side panel, so the chat stays easy to read.',
134
- side: 'bottom',
135
- find: () => byLabel('Open side panel', 'Close side panel'),
136
- },
137
- {
138
- id: 'orgs',
139
- title: 'Switch workspaces',
140
- body: 'Each icon on the far left is a workspace, with its own chats, agents, files and billing.',
141
- side: 'right',
142
- find: () => {
143
- // The rail is a column of small square marks pinned to the far edge —
144
- // that shape is what identifies it, not a label, because each mark is
145
- // labelled with its own workspace ("LUX", "ZOO", an initial) and none of
146
- // them names the rail. Take their common parent.
147
- const marks = [...document.querySelectorAll('button,[role="button"]')].filter((el) => {
148
- const r = el.getBoundingClientRect();
149
- return r.left < 64 && r.width >= 26 && r.width <= 60 && Math.abs(r.width - r.height) < 10 && seen(el);
150
- });
151
- if (marks.length < 2)
152
- return byLabel('Switch organization');
153
- let node = marks[0].parentElement;
154
- while (node && !marks.every((m) => node.contains(m)))
155
- node = node.parentElement;
156
- return seen(node) ?? marks[0];
157
- },
158
- },
159
- {
160
- id: 'account',
161
- title: 'Your plan',
162
- body: 'See your credit, manage payment and change settings. Free uses the open models. Paid unlocks the rest.',
163
- side: 'top',
164
- find: () => byLabel('Account, plan and workspace'),
165
- },
166
- ];
167
- const MEET = [
168
- {
169
- id: 'start',
170
- title: 'Start a meeting',
171
- body: 'One click opens a room. Share the link and anyone can join from a browser.',
172
- side: 'bottom',
173
- find: () => byText('Start Instant Meeting'),
174
- },
175
- {
176
- id: 'join',
177
- title: 'Join with a code',
178
- body: 'Paste a meeting code or link here to join a call someone else started.',
179
- side: 'bottom',
180
- find: () => byPlaceholder('Enter meeting code or link'),
181
- },
182
- {
183
- id: 'transcript',
184
- title: 'Every call is written down',
185
- body: 'A Hanzo bot sits in and transcribes. What everyone said is saved with the meeting.',
186
- side: 'left',
187
- find: () => {
188
- for (const el of document.querySelectorAll('div,p,section')) {
189
- if (el.childElementCount === 0 && /transcription/i.test(el.textContent || '') && seen(el))
190
- return el.parentElement ?? el;
191
- }
192
- return null;
193
- },
194
- },
195
- ];
196
- const CALENDAR = [
197
- {
198
- id: 'schedule',
199
- title: 'Add an event or a routine',
200
- body: 'A meeting, a reminder, or a job an agent runs on a schedule. Name it and pick the time.',
201
- side: 'bottom',
202
- find: () => byText('Schedule Event / Routine'),
203
- },
204
- {
205
- id: 'crons',
206
- title: 'Bots on a timer',
207
- body: 'Bot Crons lists what your bots run and when. Change a time here and the bot follows it.',
208
- side: 'bottom',
209
- find: () => byText('Bot Crons'),
210
- },
211
- {
212
- id: 'booker',
213
- title: 'Let people book you',
214
- body: 'A public page where anyone can pick a free time on your calendar.',
215
- side: 'bottom',
216
- find: () => byText('Public Booker'),
217
- },
218
- ];
219
- const CONTACTS = [
220
- {
221
- id: 'search',
222
- title: 'Everyone in one place',
223
- body: 'People and agents, side by side. Search by name to find anyone fast.',
224
- side: 'bottom',
225
- find: () => byLabel('Search contacts') || byPlaceholder('Search'),
226
- },
227
- {
228
- id: 'invite',
229
- title: 'Invite your team',
230
- body: 'Send an invite by email, or share a link anyone can use to join your organization.',
231
- side: 'bottom',
232
- find: () => byLabel('Invite') || byText('Invite'),
233
- },
234
- {
235
- id: 'agent',
236
- title: 'Make an agent',
237
- body: 'Give it a name and a job. It joins the roster next to your people and answers like one.',
238
- side: 'bottom',
239
- find: () => byText('New agent'),
240
- },
241
- ];
242
- const INBOX = [
243
- {
244
- id: 'connect',
245
- title: 'Every channel, one inbox',
246
- body: 'Connect Slack, email, Discord and the rest. Their messages land here together.',
247
- side: 'bottom',
248
- find: () => byText('Connect an app'),
249
- },
250
- {
251
- id: 'channels',
252
- title: 'Your channels',
253
- body: 'Each connected app is a channel on the left. Nothing gets lost between them.',
254
- side: 'right',
255
- find: () => byLabel('Resize inbox channel list')?.parentElement ?? null,
256
- },
257
- {
258
- id: 'reply',
259
- title: 'Reply where it came from',
260
- body: 'Answer here and it goes back on the same channel the message arrived on.',
261
- side: 'top',
262
- find: () => byPlaceholderStart('Reply on'),
263
- },
264
- ];
265
- const DRIVE = [
266
- {
267
- id: 'search',
268
- title: 'Search everything',
269
- body: 'Every file your team and your agents made, found by name or by what is inside it.',
270
- side: 'bottom',
271
- find: () => byLabel('Search Drive'),
272
- },
273
- {
274
- id: 'upload',
275
- title: 'Bring files in',
276
- body: 'Upload anything. Agents can read it, and it shows up in chat when you ask.',
277
- side: 'bottom',
278
- find: () => byTitle('Upload'),
279
- },
280
- {
281
- id: 'folder',
282
- title: 'Keep it tidy',
283
- body: 'Make folders for projects, clients or agents. Drag files between them.',
284
- side: 'bottom',
285
- find: () => byTitle('Add Folder'),
286
- },
287
- ];
288
- const BOARD = [
289
- {
290
- id: 'add',
291
- title: 'Add a card',
292
- body: 'One card per task. Give it a date, a priority, and a person or an agent to do it.',
293
- side: 'bottom',
294
- find: () => byText('Add a card') || byPlaceholder('Add an item'),
295
- },
296
- {
297
- id: 'planner',
298
- title: 'Plan by day',
299
- body: 'Planner lays the cards out by date, so today is one column and next week is a glance away.',
300
- side: 'bottom',
301
- find: () => byText('Planner'),
302
- },
303
- {
304
- id: 'filter',
305
- title: 'See only what matters now',
306
- body: 'Filter by who, by when, or by what is finished.',
307
- side: 'bottom',
308
- find: () => byText('Filter'),
309
- },
310
- ];
311
- export const TOURS = {
312
- chat: CHAT,
313
- meet: MEET,
314
- calendar: CALENDAR,
315
- contacts: CONTACTS,
316
- inbox: INBOX,
317
- drive: DRIVE,
318
- board: BOARD,
319
- };