@hanzo/rooms 0.1.72 → 0.1.74
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/assets/personas/ada.md +187 -0
- package/assets/personas/adi_shamir.md +155 -0
- package/assets/personas/al_khwarizmi.md +168 -0
- package/assets/personas/al_kindi.md +172 -0
- package/assets/personas/alan_kay.md +154 -0
- package/assets/personas/analyst.md +138 -0
- package/assets/personas/andrew_yao.md +155 -0
- package/assets/personas/auguste_kerckhoffs.md +168 -0
- package/assets/personas/barbara_liskov.md +154 -0
- package/assets/personas/butler_lampson.md +156 -0
- package/assets/personas/charles_babbage.md +175 -0
- package/assets/personas/creative.md +141 -0
- package/assets/personas/curry.md +169 -0
- package/assets/personas/cynthia_dwork.md +158 -0
- package/assets/personas/david_chaum.md +157 -0
- package/assets/personas/david_parnas.md +153 -0
- package/assets/personas/dennis_ritchie.md +153 -0
- package/assets/personas/dijkstra.md +187 -0
- package/assets/personas/donald_knuth.md +156 -0
- package/assets/personas/douglas_engelbart.md +175 -0
- package/assets/personas/edgar_codd.md +151 -0
- package/assets/personas/engineer.md +140 -0
- package/assets/personas/feynman.md +178 -0
- package/assets/personas/finney.md +187 -0
- package/assets/personas/fred_brooks.md +152 -0
- package/assets/personas/geoffrey_hinton.md +194 -0
- package/assets/personas/herbert_simon.md +190 -0
- package/assets/personas/hickey.md +169 -0
- package/assets/personas/hopper.md +191 -0
- package/assets/personas/hubert_dreyfus.md +184 -0
- package/assets/personas/humberto_maturana.md +156 -0
- package/assets/personas/index.json +1 -0
- package/assets/personas/jcr_licklider.md +178 -0
- package/assets/personas/jobs.md +177 -0
- package/assets/personas/joe_armstrong.md +155 -0
- package/assets/personas/john_backus.md +179 -0
- package/assets/personas/john_mccarthy.md +177 -0
- package/assets/personas/jon_postel.md +149 -0
- package/assets/personas/joseph_weizenbaum.md +185 -0
- package/assets/personas/judea_pearl.md +192 -0
- package/assets/personas/karen_sparck_jones.md +154 -0
- package/assets/personas/konrad_zuse.md +175 -0
- package/assets/personas/kunihiko_fukushima.md +152 -0
- package/assets/personas/leslie_lamport.md +156 -0
- package/assets/personas/markov.md +171 -0
- package/assets/personas/mentor.md +141 -0
- package/assets/personas/nancy_lynch.md +154 -0
- package/assets/personas/niklaus_wirth.md +154 -0
- package/assets/personas/norbert_wiener.md +179 -0
- package/assets/personas/north-star.md +138 -0
- package/assets/personas/peter_naur.md +151 -0
- package/assets/personas/pike.md +165 -0
- package/assets/personas/radia_perlman.md +156 -0
- package/assets/personas/raj_reddy.md +154 -0
- package/assets/personas/ralph_merkle.md +152 -0
- package/assets/personas/richard_sutton.md +156 -0
- package/assets/personas/robin_milner.md +149 -0
- package/assets/personas/shafi_goldwasser.md +156 -0
- package/assets/personas/shannon.md +184 -0
- package/assets/personas/stafford_beer.md +157 -0
- package/assets/personas/taher_elgamal.md +180 -0
- package/assets/personas/tesla.md +181 -0
- package/assets/personas/tony_hoare.md +153 -0
- package/assets/personas/torvalds.md +188 -0
- package/assets/personas/tpope.md +163 -0
- package/assets/personas/turing.md +188 -0
- package/assets/personas/whitfield_diffie.md +159 -0
- package/assets/personas/zeekay.md +151 -0
- package/dist/App.js +26 -22
- package/dist/Chat.js +51 -38
- package/dist/Column.d.ts +3 -3
- package/dist/Column.js +5 -27
- package/dist/Me.d.ts +8 -3
- package/dist/Me.js +79 -108
- package/dist/Palette.d.ts +18 -0
- package/dist/Palette.js +131 -0
- package/dist/Rail.js +3 -15
- package/dist/Recents.d.ts +1 -2
- package/dist/Recents.js +9 -9
- package/dist/Shell.d.ts +7 -0
- package/dist/Shell.js +65 -46
- package/dist/Top.d.ts +17 -0
- package/dist/Top.js +58 -0
- package/dist/agents.d.ts +64 -0
- package/dist/agents.js +62 -0
- package/dist/crew.d.ts +26 -5
- package/dist/crew.js +208 -83
- package/dist/help.d.ts +43 -0
- package/dist/help.js +58 -0
- package/dist/lib/session.d.ts +5 -10
- package/dist/lib/session.js +6 -10
- package/dist/lib/web.d.ts +6 -0
- package/dist/lib/web.js +39 -7
- package/dist/recent.d.ts +2 -2
- package/dist/recent.js +3 -5
- package/dist/seek.d.ts +37 -0
- package/dist/seek.js +55 -0
- package/dist/team.js +3 -11
- package/dist/thread.js +7 -2
- package/dist/view.d.ts +44 -0
- package/dist/view.js +98 -0
- package/package.json +7 -7
- package/dist/Account.d.ts +0 -12
- package/dist/Account.js +0 -65
- package/dist/Find.d.ts +0 -7
- package/dist/Find.js +0 -82
- package/dist/Profile.d.ts +0 -4
- package/dist/Profile.js +0 -67
- package/dist/Standing.d.ts +0 -4
- package/dist/Standing.js +0 -16
package/dist/lib/web.js
CHANGED
|
@@ -41,6 +41,9 @@ export const PAGES = 3;
|
|
|
41
41
|
/** Results a search lists, and how many of them it reads. */
|
|
42
42
|
const LISTED = 6;
|
|
43
43
|
const READ = 2;
|
|
44
|
+
/** What Research lists and reads instead. */
|
|
45
|
+
const DEEP_LISTED = 10;
|
|
46
|
+
const DEEP_READ = 5;
|
|
44
47
|
/** Characters of a page a search quotes, and a named page quotes. */
|
|
45
48
|
const PAGE_CHARS = 3500;
|
|
46
49
|
const CRAWL_CHARS = 6000;
|
|
@@ -53,6 +56,21 @@ const GATE = 8192;
|
|
|
53
56
|
const SEARCH_MS = 20_000;
|
|
54
57
|
const SKIM_MS = 10_000;
|
|
55
58
|
const READ_MS = 25_000;
|
|
59
|
+
/**
|
|
60
|
+
* Research: beside `WEB`, a turn that carries this reads deeper — ten results
|
|
61
|
+
* listed, five pages read — whatever its shape, and the model is asked for a
|
|
62
|
+
* report that cites them.
|
|
63
|
+
*/
|
|
64
|
+
export const RESEARCH = {
|
|
65
|
+
type: 'function',
|
|
66
|
+
function: {
|
|
67
|
+
name: 'research',
|
|
68
|
+
description: 'Research the question in depth on the live web and answer with a cited report.',
|
|
69
|
+
parameters: { type: 'object', properties: {}, required: [] },
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
/** The word a Research turn's model is given. */
|
|
73
|
+
const REPORT = 'Research mode: answer with a detailed report — a short direct answer first, then the evidence under headings — citing each claim with the numbered sources above as [n](url). Say plainly where the sources disagree or fall short.';
|
|
56
74
|
/** The two tools that mark a turn as one that may use the web. */
|
|
57
75
|
export const WEB = [
|
|
58
76
|
{
|
|
@@ -527,7 +545,7 @@ function pointed(text) {
|
|
|
527
545
|
/** What the model is told of a page it was not given. */
|
|
528
546
|
const withheld = (url) => `The person's message names ${label(url)}. It was not opened: opening an address like it can sign in, confirm, unsubscribe or spend a one-time link for them.`;
|
|
529
547
|
/** Runs one search: the results, best first, and the text of the best pages. */
|
|
530
|
-
async function search(call, q, lang, now) {
|
|
548
|
+
async function search(call, q, lang, now, deep = false) {
|
|
531
549
|
let found;
|
|
532
550
|
try {
|
|
533
551
|
found = await call('/v1/websearch', lang ? { q, language: lang } : { q }, SEARCH_MS);
|
|
@@ -536,11 +554,11 @@ async function search(call, q, lang, now) {
|
|
|
536
554
|
return unavailable(e);
|
|
537
555
|
}
|
|
538
556
|
const words = keywords(q);
|
|
539
|
-
const ranked = rank(found?.results ?? [], words).slice(0, LISTED);
|
|
557
|
+
const ranked = rank(found?.results ?? [], words).slice(0, deep ? DEEP_LISTED : LISTED);
|
|
540
558
|
const head = `Web search for "${q}", ${stamp(now)}.`;
|
|
541
559
|
if (!ranked.length)
|
|
542
560
|
return `${head}\nNothing was found.`;
|
|
543
|
-
const pages = await Promise.all(ranked.slice(0, READ).map((r) => call('/v1/crawl', { url: r.url }, SKIM_MS)
|
|
561
|
+
const pages = await Promise.all(ranked.slice(0, deep ? DEEP_READ : READ).map((r) => call('/v1/crawl', { url: r.url }, SKIM_MS)
|
|
544
562
|
.then((d) => (d?.success ? excerpt(d.data?.markdown ?? '', words, PAGE_CHARS) : ''))
|
|
545
563
|
.catch(() => '')));
|
|
546
564
|
const items = ranked.map((r, i) => {
|
|
@@ -563,6 +581,19 @@ async function read(call, url, words) {
|
|
|
563
581
|
return `Could not read ${url}: ${e instanceof Error ? e.message : String(e)}.`;
|
|
564
582
|
}
|
|
565
583
|
}
|
|
584
|
+
/** A Research turn is looked up whatever its shape: its own words, searched. Files and pictures still stay home. */
|
|
585
|
+
function forced(messages, spoken) {
|
|
586
|
+
const last = messages.at(-1);
|
|
587
|
+
if (last?.role !== 'user')
|
|
588
|
+
return null;
|
|
589
|
+
const { text: said, carried } = told(textOf(last.content));
|
|
590
|
+
if (carried || pictured(last.content))
|
|
591
|
+
return null;
|
|
592
|
+
const plain = spell(said.trim().slice(0, GATE)).trim();
|
|
593
|
+
if (!plain)
|
|
594
|
+
return null;
|
|
595
|
+
return { pages: [], held: [], q: plain.slice(0, QUERY), lang: language(plain, spoken), about: keywords(plain) };
|
|
596
|
+
}
|
|
566
597
|
/**
|
|
567
598
|
* The lookup the last turn needs, or null. A short follow-up ("and in
|
|
568
599
|
* Moscow?") is read with the question before it, the way the person meant it.
|
|
@@ -667,7 +698,8 @@ export function researched(base, clock = () => new Date(), spoken = reader) {
|
|
|
667
698
|
if (!asked.stream || !Array.isArray(asked.messages) || !WEB.every((t) => names.has(t.function.name)))
|
|
668
699
|
return base(input, init);
|
|
669
700
|
const { tools: _tools, tool_choice: _choice, ...bare } = asked;
|
|
670
|
-
const
|
|
701
|
+
const deep = names.has(RESEARCH.function.name);
|
|
702
|
+
const look = lookup(asked.messages, spoken()) ?? (deep ? forced(asked.messages, spoken()) : null);
|
|
671
703
|
if (!look)
|
|
672
704
|
return base(input, { ...init, body: JSON.stringify(bare) });
|
|
673
705
|
// The reader's stop, and the stream's own cancel, end every request below.
|
|
@@ -695,10 +727,10 @@ export function researched(base, clock = () => new Date(), spoken = reader) {
|
|
|
695
727
|
};
|
|
696
728
|
const found = (await Promise.all([
|
|
697
729
|
...look.pages.map((u) => read(call, u, look.about)),
|
|
698
|
-
...(look.q ? [search(call, look.q, look.lang, now)] : []),
|
|
730
|
+
...(look.q ? [search(call, look.q, look.lang, now, deep)] : []),
|
|
699
731
|
...look.held.map(async (u) => withheld(u)),
|
|
700
732
|
])).join('\n\n');
|
|
701
|
-
const messages = primed(asked.messages, found, now);
|
|
733
|
+
const messages = primed(asked.messages, found, now, deep ? REPORT : '');
|
|
702
734
|
const first = await send(messages);
|
|
703
735
|
if (!streams(first))
|
|
704
736
|
return first;
|
|
@@ -709,7 +741,7 @@ export function researched(base, clock = () => new Date(), spoken = reader) {
|
|
|
709
741
|
// results: asked once more, it answers. One that failed is not asked again.
|
|
710
742
|
const { said, failed } = await relay(first, out);
|
|
711
743
|
if (!said.trim() && !failed) {
|
|
712
|
-
const again = await send(primed(asked.messages, found, now, SILENT));
|
|
744
|
+
const again = await send(primed(asked.messages, found, now, deep ? `${REPORT}\n\n${SILENT}` : SILENT));
|
|
713
745
|
if (streams(again))
|
|
714
746
|
await relay(again, out);
|
|
715
747
|
else
|
package/dist/recent.d.ts
CHANGED
|
@@ -56,12 +56,12 @@ export declare function runs(sessions: {
|
|
|
56
56
|
updatedAt: string;
|
|
57
57
|
}[]): Recent[];
|
|
58
58
|
/**
|
|
59
|
-
* The list as drawn: the rows the view
|
|
59
|
+
* The list as drawn: the rows the view admits, sorted, and cut into
|
|
60
60
|
* headed groups in the order they read. Pinned rows come first, under Pinned,
|
|
61
61
|
* whatever the grouping; a view grouped by None holds the rest in one group with
|
|
62
62
|
* no heading.
|
|
63
63
|
*/
|
|
64
|
-
export declare function arrange(rows: Recent[], view: View,
|
|
64
|
+
export declare function arrange(rows: Recent[], view: View, now?: Date): {
|
|
65
65
|
title: string;
|
|
66
66
|
rows: Recent[];
|
|
67
67
|
}[];
|
package/dist/recent.js
CHANGED
|
@@ -106,15 +106,13 @@ function day(at, now) {
|
|
|
106
106
|
}
|
|
107
107
|
const newest = (a, b) => (a < b ? 1 : a > b ? -1 : 0);
|
|
108
108
|
/**
|
|
109
|
-
* The list as drawn: the rows the view
|
|
109
|
+
* The list as drawn: the rows the view admits, sorted, and cut into
|
|
110
110
|
* headed groups in the order they read. Pinned rows come first, under Pinned,
|
|
111
111
|
* whatever the grouping; a view grouped by None holds the rest in one group with
|
|
112
112
|
* no heading.
|
|
113
113
|
*/
|
|
114
|
-
export function arrange(rows, view,
|
|
115
|
-
const
|
|
116
|
-
const kept = rows.filter((r) => (!w || r.title.toLowerCase().includes(w)) &&
|
|
117
|
-
(view.status === 'all' || r.status === view.status) &&
|
|
114
|
+
export function arrange(rows, view, now = new Date()) {
|
|
115
|
+
const kept = rows.filter((r) => (view.status === 'all' || r.status === view.status) &&
|
|
118
116
|
(view.since === 'any' || now.getTime() - new Date(r.at).getTime() <= SPAN[view.since]));
|
|
119
117
|
kept.sort((a, b) => view.sort === 'title' ? a.title.localeCompare(b.title) : view.sort === 'created' ? newest(a.began, b.began) : newest(a.at, b.at));
|
|
120
118
|
const headOf = (r) => {
|
package/dist/seek.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Press } from './chord.js';
|
|
2
|
+
/** A row the palette can list. */
|
|
3
|
+
export interface Hit {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
/** Other words it is found by: a project's slug and repository, a skill's description. */
|
|
7
|
+
keywords?: string[];
|
|
8
|
+
/** When it last changed, ISO: the newest lead Recent chats. */
|
|
9
|
+
at?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface Group<T extends Hit = Hit> {
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
items: T[];
|
|
15
|
+
}
|
|
16
|
+
/** The most rows one group shows for a query. */
|
|
17
|
+
export declare const CAP = 5;
|
|
18
|
+
/** The chats an empty field lists. */
|
|
19
|
+
export declare const RECENT = 8;
|
|
20
|
+
/**
|
|
21
|
+
* How well a row answers a query: 0 drops it, more ranks it higher. Every word
|
|
22
|
+
* of the query must be found, in the label or the row's other words; each counts
|
|
23
|
+
* most where the label starts with it, then where a word of the label does, then
|
|
24
|
+
* anywhere in the label, then in the other words alone. The label read whole
|
|
25
|
+
* outranks everything.
|
|
26
|
+
*/
|
|
27
|
+
export declare function fit(row: Hit, query: string): number;
|
|
28
|
+
/** The rows a query keeps, best first, ties in the order they came, at most `cap`. */
|
|
29
|
+
export declare function rank<T extends Hit>(rows: T[], query: string, cap?: number): T[];
|
|
30
|
+
/**
|
|
31
|
+
* What the palette lists. An empty field: the newest chats, under Recent chats.
|
|
32
|
+
* A query: Chats, then each other group in its order, each cut to its best
|
|
33
|
+
* `CAP`, a group with nothing left out.
|
|
34
|
+
*/
|
|
35
|
+
export declare function find<T extends Hit>(query: string, chats: T[], groups: Group<T>[]): Group<T>[];
|
|
36
|
+
/** Whether a key press opens the palette: ⌘K or Ctrl+K, once a press, from anywhere, a field included. */
|
|
37
|
+
export declare const summons: (e: Press) => boolean;
|
package/dist/seek.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// The palette's decisions (Palette.tsx draws them): which rows a query keeps,
|
|
2
|
+
// in what order, under which heading, and which key press opens it. No React,
|
|
3
|
+
// so a spec reads them as they are.
|
|
4
|
+
/** The most rows one group shows for a query. */
|
|
5
|
+
export const CAP = 5;
|
|
6
|
+
/** The chats an empty field lists. */
|
|
7
|
+
export const RECENT = 8;
|
|
8
|
+
/**
|
|
9
|
+
* How well a row answers a query: 0 drops it, more ranks it higher. Every word
|
|
10
|
+
* of the query must be found, in the label or the row's other words; each counts
|
|
11
|
+
* most where the label starts with it, then where a word of the label does, then
|
|
12
|
+
* anywhere in the label, then in the other words alone. The label read whole
|
|
13
|
+
* outranks everything.
|
|
14
|
+
*/
|
|
15
|
+
export function fit(row, query) {
|
|
16
|
+
const terms = query.trim().toLowerCase().split(/\s+/).filter(Boolean);
|
|
17
|
+
if (!terms.length)
|
|
18
|
+
return 1;
|
|
19
|
+
const label = row.label.toLowerCase();
|
|
20
|
+
const words = label.split(/[^\p{L}\p{N}]+/u);
|
|
21
|
+
const more = (row.keywords ?? []).join(' ').toLowerCase();
|
|
22
|
+
let total = label === terms.join(' ') ? 10 : 0;
|
|
23
|
+
for (const t of terms) {
|
|
24
|
+
const s = label.startsWith(t) ? 4 : words.some((w) => w.startsWith(t)) ? 3 : label.includes(t) ? 2 : more.includes(t) ? 1 : 0;
|
|
25
|
+
if (!s)
|
|
26
|
+
return 0;
|
|
27
|
+
total += s;
|
|
28
|
+
}
|
|
29
|
+
return total;
|
|
30
|
+
}
|
|
31
|
+
/** The rows a query keeps, best first, ties in the order they came, at most `cap`. */
|
|
32
|
+
export function rank(rows, query, cap = CAP) {
|
|
33
|
+
return rows
|
|
34
|
+
.map((row, i) => ({ row, i, score: fit(row, query) }))
|
|
35
|
+
.filter((x) => x.score > 0)
|
|
36
|
+
.sort((a, b) => b.score - a.score || a.i - b.i)
|
|
37
|
+
.slice(0, cap)
|
|
38
|
+
.map((x) => x.row);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* What the palette lists. An empty field: the newest chats, under Recent chats.
|
|
42
|
+
* A query: Chats, then each other group in its order, each cut to its best
|
|
43
|
+
* `CAP`, a group with nothing left out.
|
|
44
|
+
*/
|
|
45
|
+
export function find(query, chats, groups) {
|
|
46
|
+
if (!query.trim()) {
|
|
47
|
+
const newest = [...chats].sort((a, b) => (b.at ?? '').localeCompare(a.at ?? '')).slice(0, RECENT);
|
|
48
|
+
return newest.length ? [{ id: 'recent', label: 'Recent chats', items: newest }] : [];
|
|
49
|
+
}
|
|
50
|
+
return [{ id: 'chats', label: 'Chats', items: chats }, ...groups]
|
|
51
|
+
.map((g) => ({ ...g, items: rank(g.items, query) }))
|
|
52
|
+
.filter((g) => g.items.length);
|
|
53
|
+
}
|
|
54
|
+
/** Whether a key press opens the palette: ⌘K or Ctrl+K, once a press, from anywhere, a field included. */
|
|
55
|
+
export const summons = (e) => (e.metaKey || e.ctrlKey) && !e.altKey && !e.shiftKey && !e.repeat && !e.isComposing && (e.key ?? '').toLowerCase() === 'k';
|
package/dist/team.js
CHANGED
|
@@ -6,19 +6,11 @@
|
|
|
6
6
|
// build (scripts/team.mjs), so the rooms keep no copy of a persona: moving the
|
|
7
7
|
// pin is how a persona changes.
|
|
8
8
|
//
|
|
9
|
-
//
|
|
9
|
+
// Every member the package publishes, in the order the crew is drawn in
|
|
10
|
+
// (scripts/team.mjs `ORDER`): this is the default cast.
|
|
10
11
|
import { parse } from '@hanzo/personas/team.js';
|
|
11
12
|
import { FILES } from './team.gen.js';
|
|
12
|
-
export const TEAM = [
|
|
13
|
-
parse('dev', FILES.dev),
|
|
14
|
-
parse('des', FILES.des),
|
|
15
|
-
parse('vi', FILES.vi),
|
|
16
|
-
parse('feynman', FILES.feynman),
|
|
17
|
-
parse('einstein', FILES.einstein),
|
|
18
|
-
parse('nora', FILES.nora),
|
|
19
|
-
parse('maya', FILES.maya),
|
|
20
|
-
parse('leo', FILES.leo),
|
|
21
|
-
];
|
|
13
|
+
export const TEAM = Object.entries(FILES).map(([id, text]) => parse(id, text));
|
|
22
14
|
const BY_ID = new Map(TEAM.map((one) => [one.id, one]));
|
|
23
15
|
/** The core member an agent name refers to, whatever its case, or undefined. */
|
|
24
16
|
export const member = (name) => BY_ID.get(name?.toLowerCase() ?? '');
|
package/dist/thread.js
CHANGED
|
@@ -33,6 +33,8 @@ import { attempt, bind, enlist, reread, snapshot, subscribe, sweep, tick, view }
|
|
|
33
33
|
import { chord, named, said } from './chord.js';
|
|
34
34
|
import { carry } from './stars.js';
|
|
35
35
|
import { ShareDialog } from './Share.js';
|
|
36
|
+
/** Each client's last read of each shelf. */
|
|
37
|
+
const last = new WeakMap();
|
|
36
38
|
/**
|
|
37
39
|
* The member's conversations: pinned first, then most recently spoken in, the
|
|
38
40
|
* archived ones left out — or, with `archived`, those alone — with every change
|
|
@@ -43,7 +45,8 @@ import { ShareDialog } from './Share.js';
|
|
|
43
45
|
export function useThreadList(archived = false, seek = null) {
|
|
44
46
|
const ai = useAi();
|
|
45
47
|
// What was read, when, and from which shelf: the other shelf's rows are never drawn as this one's.
|
|
46
|
-
|
|
48
|
+
// A list mounted later (the palette) draws the client's last read of its shelf at once.
|
|
49
|
+
const [read, setRead] = useState(() => last.get(ai)?.[`${archived}`] ?? { threads: [], at: 0, archived });
|
|
47
50
|
const [loading, setLoading] = useState(true);
|
|
48
51
|
const [error, setError] = useState(null);
|
|
49
52
|
const [asks, ask] = useState(0);
|
|
@@ -59,7 +62,9 @@ export function useThreadList(archived = false, seek = null) {
|
|
|
59
62
|
if (control.signal.aborted)
|
|
60
63
|
return;
|
|
61
64
|
me.current.at = at;
|
|
62
|
-
|
|
65
|
+
const got = { threads, at, archived };
|
|
66
|
+
last.set(ai, { ...last.get(ai), [`${archived}`]: got });
|
|
67
|
+
setRead(got);
|
|
63
68
|
setError(null);
|
|
64
69
|
setLoading(false);
|
|
65
70
|
sweep();
|
package/dist/view.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** The visual viewport, as `window.visualViewport` reports it. */
|
|
2
|
+
export interface Viewport {
|
|
3
|
+
height: number;
|
|
4
|
+
offsetTop: number;
|
|
5
|
+
scale: number;
|
|
6
|
+
}
|
|
7
|
+
/** Where the window stands, px. */
|
|
8
|
+
export interface Fit {
|
|
9
|
+
top: number;
|
|
10
|
+
height: number;
|
|
11
|
+
keyboard: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Where the window stands in a layout viewport `layout` px tall: null while the
|
|
15
|
+
* visual viewport is all of it, or zoomed in.
|
|
16
|
+
*/
|
|
17
|
+
export declare function fit(layout: number, view: Viewport): Fit | null;
|
|
18
|
+
/** The window's top, for a style: the visual viewport's while a keyboard is up. */
|
|
19
|
+
export declare const TOP = "var(--view-top, 0px)";
|
|
20
|
+
/** The window's height, for a style: the visual viewport's while a keyboard is up. */
|
|
21
|
+
export declare const HEIGHT = "var(--view-height, 100dvh)";
|
|
22
|
+
/** The home indicator's inset, which the keyboard covers while it is up. */
|
|
23
|
+
export declare const FOOT = "max(0px, calc(var(--safe-bottom, 0px) - var(--keyboard, 0px)))";
|
|
24
|
+
interface Style {
|
|
25
|
+
setProperty(name: string, value: string): void;
|
|
26
|
+
removeProperty(name: string): string;
|
|
27
|
+
}
|
|
28
|
+
interface Source extends Viewport {
|
|
29
|
+
addEventListener(type: 'resize' | 'scroll', fn: () => void): void;
|
|
30
|
+
removeEventListener(type: 'resize' | 'scroll', fn: () => void): void;
|
|
31
|
+
}
|
|
32
|
+
interface Frame {
|
|
33
|
+
innerHeight: number;
|
|
34
|
+
requestAnimationFrame(fn: () => void): number;
|
|
35
|
+
cancelAnimationFrame(id: number): void;
|
|
36
|
+
}
|
|
37
|
+
/** Writes the window's place on `style` as `view` moves, once a frame; returns the stop. */
|
|
38
|
+
export declare function follow(style: Style, view: Source, frame: Frame): () => void;
|
|
39
|
+
/**
|
|
40
|
+
* The ref for the window: it follows the visual viewport while mounted. A
|
|
41
|
+
* browser without one leaves the window at 100dvh.
|
|
42
|
+
*/
|
|
43
|
+
export declare function useView(): (node: unknown) => void;
|
|
44
|
+
export {};
|
package/dist/view.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
// The window kept inside what the reader can see, so a phone's keyboard never
|
|
3
|
+
// covers the composer.
|
|
4
|
+
//
|
|
5
|
+
// A phone's keyboard does not shrink the page: the layout viewport keeps its
|
|
6
|
+
// height and only the VISUAL viewport (`window.visualViewport`) loses the
|
|
7
|
+
// keyboard's height, and the browser pans it down the layout to show the field.
|
|
8
|
+
// So the app reads the visual viewport and stands the window in it: its top at
|
|
9
|
+
// the visual viewport's top, its height the visual viewport's. The bar stays at
|
|
10
|
+
// the top of what shows, the composer sits on the keyboard, and the transcript
|
|
11
|
+
// scrolls in what is left between them.
|
|
12
|
+
//
|
|
13
|
+
// const view = useView()
|
|
14
|
+
// <YStack ref={view} $max-md={{ position: 'fixed', t: TOP, height: HEIGHT }}>
|
|
15
|
+
//
|
|
16
|
+
// It writes three custom properties on the element, which every descendant
|
|
17
|
+
// reads, and nothing else: no state, so a keyboard opening re-renders nothing.
|
|
18
|
+
//
|
|
19
|
+
// --view-top px from the layout viewport's top to the visual one's
|
|
20
|
+
// --view-height px of the visual viewport
|
|
21
|
+
// --keyboard px the keyboard takes from the view; 0 when it is down
|
|
22
|
+
//
|
|
23
|
+
// While the visual viewport is the layout one, the first two are unset (the
|
|
24
|
+
// window keeps 100dvh) and --keyboard is 0. A pinch-zoom shrinks the visual
|
|
25
|
+
// viewport too, and the window does not chase it. Events are folded into one
|
|
26
|
+
// write per frame.
|
|
27
|
+
import { useCallback, useRef } from 'react';
|
|
28
|
+
/** A zoom past this is the reader's pinch, not a keyboard. */
|
|
29
|
+
const ZOOM = 1.01;
|
|
30
|
+
/**
|
|
31
|
+
* Where the window stands in a layout viewport `layout` px tall: null while the
|
|
32
|
+
* visual viewport is all of it, or zoomed in.
|
|
33
|
+
*/
|
|
34
|
+
export function fit(layout, view) {
|
|
35
|
+
if (view.scale > ZOOM)
|
|
36
|
+
return null;
|
|
37
|
+
const height = Math.round(view.height);
|
|
38
|
+
const keyboard = Math.round(layout) - height;
|
|
39
|
+
if (keyboard < 1)
|
|
40
|
+
return null;
|
|
41
|
+
return { top: Math.max(0, Math.round(view.offsetTop)), height, keyboard };
|
|
42
|
+
}
|
|
43
|
+
/** The window's top, for a style: the visual viewport's while a keyboard is up. */
|
|
44
|
+
export const TOP = 'var(--view-top, 0px)';
|
|
45
|
+
/** The window's height, for a style: the visual viewport's while a keyboard is up. */
|
|
46
|
+
export const HEIGHT = 'var(--view-height, 100dvh)';
|
|
47
|
+
/** The home indicator's inset, which the keyboard covers while it is up. */
|
|
48
|
+
export const FOOT = 'max(0px, calc(var(--safe-bottom, 0px) - var(--keyboard, 0px)))';
|
|
49
|
+
/** Writes the window's place on `style` as `view` moves, once a frame; returns the stop. */
|
|
50
|
+
export function follow(style, view, frame) {
|
|
51
|
+
let queued = 0;
|
|
52
|
+
let last = null;
|
|
53
|
+
const write = () => {
|
|
54
|
+
queued = 0;
|
|
55
|
+
const at = fit(frame.innerHeight, view);
|
|
56
|
+
const key = at ? `${at.top}/${at.height}/${at.keyboard}` : '';
|
|
57
|
+
if (key === last)
|
|
58
|
+
return;
|
|
59
|
+
last = key;
|
|
60
|
+
if (at) {
|
|
61
|
+
style.setProperty('--view-top', `${at.top}px`);
|
|
62
|
+
style.setProperty('--view-height', `${at.height}px`);
|
|
63
|
+
style.setProperty('--keyboard', `${at.keyboard}px`);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
style.removeProperty('--view-top');
|
|
67
|
+
style.removeProperty('--view-height');
|
|
68
|
+
style.setProperty('--keyboard', '0px');
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const queue = () => {
|
|
72
|
+
if (!queued)
|
|
73
|
+
queued = frame.requestAnimationFrame(write);
|
|
74
|
+
};
|
|
75
|
+
view.addEventListener('resize', queue);
|
|
76
|
+
view.addEventListener('scroll', queue);
|
|
77
|
+
write();
|
|
78
|
+
return () => {
|
|
79
|
+
view.removeEventListener('resize', queue);
|
|
80
|
+
view.removeEventListener('scroll', queue);
|
|
81
|
+
if (queued)
|
|
82
|
+
frame.cancelAnimationFrame(queued);
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The ref for the window: it follows the visual viewport while mounted. A
|
|
87
|
+
* browser without one leaves the window at 100dvh.
|
|
88
|
+
*/
|
|
89
|
+
export function useView() {
|
|
90
|
+
const stop = useRef(null);
|
|
91
|
+
return useCallback((node) => {
|
|
92
|
+
stop.current?.();
|
|
93
|
+
stop.current = null;
|
|
94
|
+
// A gui stack hands its host element through a ref typed for native too; on the web it is the element.
|
|
95
|
+
if (node instanceof HTMLElement && window.visualViewport)
|
|
96
|
+
stop.current = follow(node.style, window.visualViewport, window);
|
|
97
|
+
}, []);
|
|
98
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/rooms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.74",
|
|
4
4
|
"description": "The workspace rooms — Home, Chat, Inbox, Contacts, Meet, Cal, Drive, Board, Work, Bots — and the frame that holds them, once, for every Hanzo surface.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -49,14 +49,14 @@
|
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@hanzo/ai": ">=0.6.23",
|
|
51
51
|
"@hanzo/appearance": ">=0.2.6",
|
|
52
|
-
"@hanzo/build": ">=0.2.
|
|
52
|
+
"@hanzo/build": ">=0.2.61",
|
|
53
53
|
"@hanzo/composer": ">=0.2.0",
|
|
54
54
|
"@hanzo/event": ">=0.3.56",
|
|
55
55
|
"@hanzo/gui": "^8.3.5",
|
|
56
|
-
"@hanzo/iam": ">=0.21.
|
|
56
|
+
"@hanzo/iam": ">=0.21.33",
|
|
57
57
|
"@hanzo/logo": ">=1.0.26",
|
|
58
58
|
"@hanzo/studio": ">=0.1.0",
|
|
59
|
-
"@hanzo/ui": ">=8.
|
|
59
|
+
"@hanzo/ui": ">=8.29.0",
|
|
60
60
|
"@hanzo/usage": ">=0.1.18",
|
|
61
61
|
"@hanzo/voice": ">=0.1.16",
|
|
62
62
|
"@hanzogui/shell": "^8.3.20",
|
|
@@ -67,15 +67,15 @@
|
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@hanzo/ai": "0.6.23",
|
|
69
69
|
"@hanzo/appearance": "0.2.8",
|
|
70
|
-
"@hanzo/build": "0.2.
|
|
70
|
+
"@hanzo/build": "0.2.61",
|
|
71
71
|
"@hanzo/composer": "0.2.0",
|
|
72
72
|
"@hanzo/design": "0.5.34",
|
|
73
73
|
"@hanzo/event": "0.3.56",
|
|
74
74
|
"@hanzo/gui": "8.3.5",
|
|
75
|
-
"@hanzo/iam": "0.21.
|
|
75
|
+
"@hanzo/iam": "0.21.33",
|
|
76
76
|
"@hanzo/logo": "1.0.26",
|
|
77
77
|
"@hanzo/studio": "0.1.0",
|
|
78
|
-
"@hanzo/ui": "8.
|
|
78
|
+
"@hanzo/ui": "8.29.0",
|
|
79
79
|
"@hanzo/usage": "0.1.18",
|
|
80
80
|
"@hanzo/voice": "0.1.16",
|
|
81
81
|
"@hanzogui/config": "8.3.5",
|
package/dist/Account.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/** The block at the foot of the sidebar: press it for the menu. */
|
|
2
|
-
export declare function Account({ onToggleCollapse, onProfile, onAppearance, onUsage, onKeys, }?: {
|
|
3
|
-
onToggleCollapse?: () => void;
|
|
4
|
-
/** Opens the profile beside the room. Absent, the row goes to hanzo.id. */
|
|
5
|
-
onProfile?: () => void;
|
|
6
|
-
/** Opens the appearance settings. Absent, the row is not drawn. */
|
|
7
|
-
onAppearance?: () => void;
|
|
8
|
-
/** Opens the plan's usage settings. Absent, the row is not drawn. */
|
|
9
|
-
onUsage?: () => void;
|
|
10
|
-
/** Opens the API keys settings. Absent, the row is not drawn. */
|
|
11
|
-
onKeys?: () => void;
|
|
12
|
-
}): import("react").JSX.Element;
|
package/dist/Account.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
/**
|
|
4
|
-
* WHO YOU ARE, at the foot of the sidebar: the person, and the person's menu.
|
|
5
|
-
*
|
|
6
|
-
* The plan leads it — @hanzo/build's `Meter` (Standing.tsx, loaded with the
|
|
7
|
-
* menu), the block every account menu opens with: the plan by its family and its
|
|
8
|
-
* windows as shares, the free allowance on Free, the balance only with no plan. Then profile, appearance, usage, billing,
|
|
9
|
-
* API keys, security and sign out — the account, and nothing that belongs to the
|
|
10
|
-
* workspace. Which workspace you stand in is the switcher at the TOP of the
|
|
11
|
-
* sidebar, and what it may spend is its Billing: a plan never sits beside a
|
|
12
|
-
* balance here.
|
|
13
|
-
*/
|
|
14
|
-
import { lazy, Suspense, useEffect, useRef, useState } from 'react';
|
|
15
|
-
import { Activity, ChevronsUpDown, CreditCard, KeyRound, LogOut, PanelLeftClose, Palette, Shield, UserRound } from 'lucide-react';
|
|
16
|
-
import { XStack, YStack, Text, Box } from '@hanzo/ui';
|
|
17
|
-
import { sheet } from '@hanzo/ui/glass';
|
|
18
|
-
import { useIam } from '@hanzo/iam/react';
|
|
19
|
-
import { useSignOut } from './lib/signout.js';
|
|
20
|
-
import { useAccount } from './lib/account.js';
|
|
21
|
-
import { Face } from './cast.js';
|
|
22
|
-
import { iam } from './lib/api.js';
|
|
23
|
-
import { link } from './lib/tags.js';
|
|
24
|
-
import { showSettings } from './open.js';
|
|
25
|
-
import { site } from './where.js';
|
|
26
|
-
/** The plan at the menu's head, loaded with the menu. */
|
|
27
|
-
const Standing = lazy(() => import('./Standing.js').then((m) => ({ default: m.Standing })));
|
|
28
|
-
function Row({ icon, onPress, children, }) {
|
|
29
|
-
return (_jsx(Box, { render: "button", onClick: onPress, width: "100%", px: "$3", py: "$2", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsxs(XStack, { items: "center", gap: "$2", children: [icon, _jsx(Text, { fontSize: "$2", color: "$ink", children: children })] }) }));
|
|
30
|
-
}
|
|
31
|
-
/** The block at the foot of the sidebar: press it for the menu. */
|
|
32
|
-
export function Account({ onToggleCollapse, onProfile, onAppearance, onUsage, onKeys, } = {}) {
|
|
33
|
-
const { user } = useIam();
|
|
34
|
-
const logout = useSignOut();
|
|
35
|
-
const { user: account } = useAccount();
|
|
36
|
-
const [open, setOpen] = useState(false);
|
|
37
|
-
const holder = useRef(null);
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
if (!open)
|
|
40
|
-
return;
|
|
41
|
-
const away = (e) => {
|
|
42
|
-
if (holder.current && !holder.current.contains(e.target))
|
|
43
|
-
setOpen(false);
|
|
44
|
-
};
|
|
45
|
-
const esc = (e) => e.key === 'Escape' && setOpen(false);
|
|
46
|
-
document.addEventListener('mousedown', away);
|
|
47
|
-
document.addEventListener('keydown', esc);
|
|
48
|
-
return () => {
|
|
49
|
-
document.removeEventListener('mousedown', away);
|
|
50
|
-
document.removeEventListener('keydown', esc);
|
|
51
|
-
};
|
|
52
|
-
}, [open]);
|
|
53
|
-
const called = account?.name || user?.displayName || user?.name || user?.email || 'You';
|
|
54
|
-
const email = user?.email || '';
|
|
55
|
-
const face = account?.avatar ?? user?.avatar;
|
|
56
|
-
const act = (fn) => () => {
|
|
57
|
-
setOpen(false);
|
|
58
|
-
fn();
|
|
59
|
-
};
|
|
60
|
-
return (_jsxs(YStack
|
|
61
|
-
// @ts-expect-error — ref is forwarded to the DOM node by the primitive.
|
|
62
|
-
, {
|
|
63
|
-
// @ts-expect-error — ref is forwarded to the DOM node by the primitive.
|
|
64
|
-
ref: holder, position: "relative", borderTopWidth: 1, borderColor: "$borderColor", children: [open ? (_jsxs(YStack, { role: "menu", "aria-label": "Account", position: "absolute", b: "calc(100% + 6px)", l: "$2", r: "$2", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", ...sheet(2), py: "$2", gap: "$1", z: "var(--z-modal)", children: [_jsx(Suspense, { fallback: null, children: _jsx(Standing, { onPlans: act(() => window.location.assign(site('/pricing'))), onBilling: act(() => showSettings('billing')) }) }), _jsx(Box, { height: 1, bg: "$borderColor", my: "$1" }), _jsx(Row, { icon: _jsx(UserRound, { size: 15, "aria-hidden": true }), onPress: act(() => (onProfile ? onProfile() : window.location.assign(link(`${iam()}/account`)))), children: "Profile" }), onAppearance ? (_jsx(Row, { icon: _jsx(Palette, { size: 15, "aria-hidden": true }), onPress: act(onAppearance), children: "Appearance" })) : null, onUsage ? (_jsx(Row, { icon: _jsx(Activity, { size: 15, "aria-hidden": true }), onPress: act(onUsage), children: "Usage" })) : null, _jsx(Row, { icon: _jsx(CreditCard, { size: 15, "aria-hidden": true }), onPress: act(() => showSettings('billing')), children: "Billing" }), onKeys ? (_jsx(Row, { icon: _jsx(KeyRound, { size: 15, "aria-hidden": true }), onPress: act(onKeys), children: "API keys" })) : null, _jsx(Row, { icon: _jsx(Shield, { size: 15, "aria-hidden": true }), onPress: act(() => window.location.assign(link(`${iam()}/account/security`))), children: "Security" }), _jsx(Box, { height: 1, bg: "$borderColor", my: "$1" }), _jsx(Row, { icon: _jsx(LogOut, { size: 15, "aria-hidden": true }), onPress: act(() => void logout()), children: "Sign out" })] })) : null, _jsxs(XStack, { items: "center", width: "100%", children: [_jsx(Box, { render: "button", onClick: () => setOpen((v) => !v), "aria-expanded": open, "aria-haspopup": "menu", "aria-label": "Account", flex: 1, minW: 0, px: "$3", py: "$2", hoverStyle: { bg: '$hover' }, children: _jsxs(XStack, { items: "center", gap: "$2", minW: 0, children: [_jsx(Face, { src: face, name: called, size: 22 }), _jsxs(YStack, { flex: 1, minW: 0, items: "flex-start", children: [_jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$color", numberOfLines: 1, children: called }), email && email !== called ? (_jsx(Text, { fontSize: "$1", color: "$soft", numberOfLines: 1, children: email })) : null] }), _jsx(ChevronsUpDown, { size: 14, "aria-hidden": true })] }) }), onToggleCollapse ? (_jsx(Box, { render: "button", onClick: onToggleCollapse, "aria-label": "Collapse sidebar", p: "$2", mr: "$1.5", rounded: "$2", hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsx(PanelLeftClose, { size: 16, "aria-hidden": true }) })) : null] })] }));
|
|
65
|
-
}
|
package/dist/Find.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { type OrgCommandItem } from "@hanzogui/shell";
|
|
2
|
-
export declare function Find({ doors, onClose, navigate, }: {
|
|
3
|
-
/** Where the shell can go without asking anyone: its rooms and its panes. */
|
|
4
|
-
doors: OrgCommandItem[];
|
|
5
|
-
onClose: () => void;
|
|
6
|
-
navigate?: (route: string) => void;
|
|
7
|
-
}): import("react").JSX.Element;
|
package/dist/Find.js
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
/**
|
|
4
|
-
* ⌘K, in the workspace.
|
|
5
|
-
*
|
|
6
|
-
* The estate's palette — `OrgCommandPalette`, the same frame the marketing
|
|
7
|
-
* pages open — so the chord means one thing wherever a reader presses it. Here
|
|
8
|
-
* it used to focus the column's filter box, which left the shortcut that finds
|
|
9
|
-
* anything on every other page of this site narrowing one list in the app: a
|
|
10
|
-
* conversation in the room next door was unreachable from a control offering to
|
|
11
|
-
* search the workspace.
|
|
12
|
-
*
|
|
13
|
-
* MOUNTED ONLY WHILE OPEN. Its rows are read from the platform, and a palette
|
|
14
|
-
* that stayed mounted would fetch every conversation and every project on each
|
|
15
|
-
* load of a room that draws neither.
|
|
16
|
-
*/
|
|
17
|
-
import { dev } from "./lib/host.js";
|
|
18
|
-
import { useEffect, useState } from "react";
|
|
19
|
-
import { OrgCommandPalette } from "@hanzogui/shell";
|
|
20
|
-
import { useThreads } from "@hanzo/ai/react";
|
|
21
|
-
import { useAi } from "./lib/ai.js";
|
|
22
|
-
import { useOpen } from "./open.js";
|
|
23
|
-
export function Find({ doors, onClose, navigate, }) {
|
|
24
|
-
const { threads } = useThreads();
|
|
25
|
-
const { client } = useAi();
|
|
26
|
-
const { open } = useOpen();
|
|
27
|
-
const [projects, setProjects] = useState([]);
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
if (!client)
|
|
30
|
-
return;
|
|
31
|
-
let live = true;
|
|
32
|
-
client.http
|
|
33
|
-
.json({ path: "/v1/projects" })
|
|
34
|
-
.then((found) => live && setProjects(found))
|
|
35
|
-
.catch(() => {
|
|
36
|
-
// A palette that cannot read the projects still finds the rooms.
|
|
37
|
-
});
|
|
38
|
-
return () => {
|
|
39
|
-
live = false;
|
|
40
|
-
};
|
|
41
|
-
}, [client]);
|
|
42
|
-
const rows = [
|
|
43
|
-
...doors,
|
|
44
|
-
// A conversation and a project are OPENED, not navigated to: the address is
|
|
45
|
-
// the room, and which one is open belongs to the store — so a href would
|
|
46
|
-
// land the reader in /chat with whatever they had before still on screen.
|
|
47
|
-
...threads.map((t) => ({
|
|
48
|
-
id: `thread-${t.id}`,
|
|
49
|
-
title: t.title || "Untitled",
|
|
50
|
-
category: "Conversations",
|
|
51
|
-
action: () => {
|
|
52
|
-
open(t.id);
|
|
53
|
-
navigate?.("/chat");
|
|
54
|
-
},
|
|
55
|
-
})),
|
|
56
|
-
...projects.map((p) => ({
|
|
57
|
-
id: `project-${p.id}`,
|
|
58
|
-
title: p.name,
|
|
59
|
-
category: "Projects",
|
|
60
|
-
action: () => {
|
|
61
|
-
navigate?.(dev(p.name));
|
|
62
|
-
},
|
|
63
|
-
})),
|
|
64
|
-
];
|
|
65
|
-
return (_jsx(OrgCommandPalette, { open: true, currentAppId: "chat", commands: rows, onOpenChange: (next) => {
|
|
66
|
-
if (!next)
|
|
67
|
-
onClose();
|
|
68
|
-
}, onNavigate: (href, external) => {
|
|
69
|
-
if (external)
|
|
70
|
-
window.open(href, "_blank", "noopener");
|
|
71
|
-
else
|
|
72
|
-
navigate?.(href);
|
|
73
|
-
onClose();
|
|
74
|
-
},
|
|
75
|
-
// THIS ROOM IS THE AI. A question nobody indexed opens a conversation
|
|
76
|
-
// here, rather than a second copy of the product the reader is in.
|
|
77
|
-
onAsk: (question) => {
|
|
78
|
-
open(null);
|
|
79
|
-
navigate?.(`/chat?q=${encodeURIComponent(question)}`);
|
|
80
|
-
onClose();
|
|
81
|
-
} }));
|
|
82
|
-
}
|