@acornly/ui 0.1.0 → 0.3.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.
Files changed (64) hide show
  1. package/README.md +122 -4
  2. package/dist/brand.d.ts +50 -0
  3. package/dist/brand.d.ts.map +1 -0
  4. package/dist/brand.js +84 -0
  5. package/dist/brand.js.map +1 -0
  6. package/dist/card.d.ts +24 -1
  7. package/dist/card.d.ts.map +1 -1
  8. package/dist/card.js +49 -9
  9. package/dist/card.js.map +1 -1
  10. package/dist/chat-model.d.ts +165 -0
  11. package/dist/chat-model.d.ts.map +1 -0
  12. package/dist/chat-model.js +576 -0
  13. package/dist/chat-model.js.map +1 -0
  14. package/dist/chat-panel.d.ts +36 -0
  15. package/dist/chat-panel.d.ts.map +1 -0
  16. package/dist/chat-panel.js +760 -0
  17. package/dist/chat-panel.js.map +1 -0
  18. package/dist/chat-picker.d.ts +56 -0
  19. package/dist/chat-picker.d.ts.map +1 -0
  20. package/dist/chat-picker.js +284 -0
  21. package/dist/chat-picker.js.map +1 -0
  22. package/dist/chat-protocol.d.ts +151 -0
  23. package/dist/chat-protocol.d.ts.map +1 -0
  24. package/dist/chat-protocol.js +113 -0
  25. package/dist/chat-protocol.js.map +1 -0
  26. package/dist/chat-speech.d.ts +61 -0
  27. package/dist/chat-speech.d.ts.map +1 -0
  28. package/dist/chat-speech.js +119 -0
  29. package/dist/chat-speech.js.map +1 -0
  30. package/dist/chat.d.ts +148 -0
  31. package/dist/chat.d.ts.map +1 -0
  32. package/dist/chat.js +119 -0
  33. package/dist/chat.js.map +1 -0
  34. package/dist/compose.d.ts +1 -1
  35. package/dist/compose.d.ts.map +1 -1
  36. package/dist/compose.js.map +1 -1
  37. package/dist/composer.d.ts +26 -1
  38. package/dist/composer.d.ts.map +1 -1
  39. package/dist/composer.js +73 -5
  40. package/dist/composer.js.map +1 -1
  41. package/dist/index.d.ts +1 -1
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js.map +1 -1
  44. package/dist/media.d.ts.map +1 -1
  45. package/dist/media.js +0 -1
  46. package/dist/media.js.map +1 -1
  47. package/dist/painter.d.ts +20 -2
  48. package/dist/painter.d.ts.map +1 -1
  49. package/dist/painter.js +41 -6
  50. package/dist/painter.js.map +1 -1
  51. package/dist/panel.js +5 -5
  52. package/dist/parts.d.ts +2 -0
  53. package/dist/parts.d.ts.map +1 -1
  54. package/dist/parts.js +2 -0
  55. package/dist/parts.js.map +1 -1
  56. package/dist/pins.d.ts.map +1 -1
  57. package/dist/pins.js +14 -9
  58. package/dist/pins.js.map +1 -1
  59. package/dist/theme.d.ts +1 -1
  60. package/dist/theme.d.ts.map +1 -1
  61. package/dist/theme.js +14 -34
  62. package/dist/theme.js.map +1 -1
  63. package/dist/toolbar.js +3 -3
  64. package/package.json +11 -3
@@ -0,0 +1,113 @@
1
+ /**
2
+ * The app context protocol, version 0.1: what an app tells the in-app chat about where the person
3
+ * is (docs/design/app-context-protocol.md). Experimental: it ships with the chat in
4
+ * `@acornly/ui/chat` and moves to `@acornly/core` once the chat's checks pass.
5
+ */
6
+ export const PROTOCOL_VERSION = "0.1";
7
+ /** Limits on one snapshot and one turn (design doc, sections 3 and 10). */
8
+ export const CHAT_LIMITS = {
9
+ items: 500,
10
+ around: 50,
11
+ /** Characters of text parts sent with one turn. */
12
+ textChars: 64_000,
13
+ images: 4,
14
+ imageBytes: 2_000_000,
15
+ loadsPerTurn: 10,
16
+ roundsPerTurn: 3,
17
+ turns: 20,
18
+ /** Input tokens a conversation may reach, estimated at four characters a token. */
19
+ conversationTokens: 100_000,
20
+ replyTokens: 1024,
21
+ };
22
+ /**
23
+ * Checks a snapshot against the protocol and its limits: items beyond the limit, repeated ids,
24
+ * parents listed after their children, focus ids not in the items, and links beyond the limit
25
+ * are dropped, each named in `dropped`. A snapshot with no usable item throws.
26
+ */
27
+ export function checkSnapshot(input) {
28
+ if (input?.protocol !== PROTOCOL_VERSION) {
29
+ throw new Error(`Unsupported app context protocol "${String(input?.protocol)}"`);
30
+ }
31
+ const dropped = [];
32
+ const items = [];
33
+ const seen = new Set();
34
+ for (const item of input.items ?? []) {
35
+ if (typeof item?.id !== "string" || item.id === "" || typeof item.label !== "string") {
36
+ dropped.push("an item without an id or a label");
37
+ }
38
+ else if (seen.has(item.id)) {
39
+ dropped.push(`a second item "${item.id}"`);
40
+ }
41
+ else if (item.parent !== undefined && !seen.has(item.parent)) {
42
+ dropped.push(`"${item.id}", listed before its parent "${item.parent}"`);
43
+ }
44
+ else if (items.length >= CHAT_LIMITS.items) {
45
+ dropped.push(`"${item.id}" and the items after it (over ${CHAT_LIMITS.items})`);
46
+ break;
47
+ }
48
+ else {
49
+ items.push(item);
50
+ seen.add(item.id);
51
+ }
52
+ }
53
+ if (items.length === 0)
54
+ throw new Error("A context snapshot needs at least one item");
55
+ let focus = (input.focus ?? []).filter((id) => {
56
+ if (seen.has(id))
57
+ return true;
58
+ dropped.push(`the focus "${id}", which is not an item`);
59
+ return false;
60
+ });
61
+ if (focus.length === 0)
62
+ focus = [items[0].id];
63
+ const around = (input.around ?? []).filter((link) => typeof link?.id === "string");
64
+ if (around.length > CHAT_LIMITS.around) {
65
+ dropped.push(`${around.length - CHAT_LIMITS.around} links beyond the first ${CHAT_LIMITS.around}`);
66
+ }
67
+ return {
68
+ snapshot: { ...input, items, focus, around: around.slice(0, CHAT_LIMITS.around) },
69
+ dropped,
70
+ };
71
+ }
72
+ /** The items in tree order, each with its depth, for the text tree and the model's outline. */
73
+ export function treeRows(snapshot) {
74
+ const depth = new Map();
75
+ return snapshot.items.map((item) => {
76
+ const d = item.parent === undefined ? 0 : (depth.get(item.parent) ?? -1) + 1;
77
+ depth.set(item.id, d);
78
+ return { item, depth: d };
79
+ });
80
+ }
81
+ /** The item and its ancestors, nearest first. */
82
+ export function lineage(snapshot, id) {
83
+ const byId = new Map(snapshot.items.map((item) => [item.id, item]));
84
+ const out = [];
85
+ for (let item = byId.get(id); item !== undefined; item = byId.get(item.parent ?? "")) {
86
+ if (out.includes(item))
87
+ break;
88
+ out.push(item);
89
+ }
90
+ return out;
91
+ }
92
+ /** The chip's words for a focus: one label, or the first and last joined ("John 3:16 – 3:18"). */
93
+ export function focusLabel(snapshot, focus) {
94
+ const labels = focus
95
+ .map((id) => snapshot.items.find((item) => item.id === id)?.label)
96
+ .filter((label) => label !== undefined);
97
+ if (labels.length <= 1)
98
+ return labels[0] ?? "";
99
+ return `${labels[0]} – ${labels[labels.length - 1]}`;
100
+ }
101
+ /** Parts that may go to the model. */
102
+ export function sendableParts(parts) {
103
+ return (parts ?? []).filter((part) => part.use !== "label-only");
104
+ }
105
+ /** Whether an item has a part kept from the model, so the model is told it is withheld. */
106
+ export function hasWithheld(parts) {
107
+ return (parts ?? []).some((part) => part.use === "label-only");
108
+ }
109
+ /** The hand-backs that apply to an item, by its kind. */
110
+ export function handBacksFor(snapshot, item) {
111
+ return (snapshot.handBacks ?? []).filter((hb) => hb.kinds === undefined || (item?.kind !== undefined && hb.kinds.includes(item.kind)));
112
+ }
113
+ //# sourceMappingURL=chat-protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-protocol.js","sourceRoot":"","sources":["../src/chat-protocol.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAEtC,2EAA2E;AAC3E,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,KAAK,EAAE,GAAG;IACV,MAAM,EAAE,EAAE;IACV,mDAAmD;IACnD,SAAS,EAAE,MAAM;IACjB,MAAM,EAAE,CAAC;IACT,UAAU,EAAE,SAAS;IACrB,YAAY,EAAE,EAAE;IAChB,aAAa,EAAE,CAAC;IAChB,KAAK,EAAE,EAAE;IACT,mFAAmF;IACnF,kBAAkB,EAAE,OAAO;IAC3B,WAAW,EAAE,IAAI;CACT,CAAC;AAsFX;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,KAAsB;IAClD,IAAI,KAAK,EAAE,QAAQ,KAAK,gBAAgB,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CAAC,qCAAqC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;IACnF,CAAC;IACD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,KAAK,GAAkB,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;QACrC,IAAI,OAAO,IAAI,EAAE,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YACrF,OAAO,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;QACnD,CAAC;aAAM,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YAC7B,OAAO,CAAC,IAAI,CAAC,kBAAkB,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;QAC7C,CAAC;aAAM,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/D,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,EAAE,gCAAgC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;QAC1E,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,IAAI,WAAW,CAAC,KAAK,EAAE,CAAC;YAC7C,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,EAAE,kCAAkC,WAAW,CAAC,KAAK,GAAG,CAAC,CAAC;YAChF,MAAM;QACR,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IACtF,IAAI,KAAK,GAAG,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE;QAC5C,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,OAAO,IAAI,CAAC;QAC9B,OAAO,CAAC,IAAI,CAAC,cAAc,EAAE,yBAAyB,CAAC,CAAC;QACxD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;IACH,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,KAAK,GAAG,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,EAAE,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,EAAE,EAAE,KAAK,QAAQ,CAAC,CAAC;IACnF,IAAI,MAAM,CAAC,MAAM,GAAG,WAAW,CAAC,MAAM,EAAE,CAAC;QACvC,OAAO,CAAC,IAAI,CACV,GAAG,MAAM,CAAC,MAAM,GAAG,WAAW,CAAC,MAAM,2BAA2B,WAAW,CAAC,MAAM,EAAE,CACrF,CAAC;IACJ,CAAC;IACD,OAAO;QACL,QAAQ,EAAE,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE;QACjF,OAAO;KACR,CAAC;AACJ,CAAC;AAQD,+FAA+F;AAC/F,MAAM,UAAU,QAAQ,CAAC,QAAyB;IAChD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IACxC,OAAO,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACjC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAC7E,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QACtB,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,OAAO,CAAC,QAAyB,EAAE,EAAU;IAC3D,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACpE,MAAM,GAAG,GAAkB,EAAE,CAAC;IAC9B,KAAK,IAAI,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,IAAI,KAAK,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;QACrF,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,MAAM;QAC9B,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,UAAU,CAAC,QAAyB,EAAE,KAAwB;IAC5E,MAAM,MAAM,GAAG,KAAK;SACjB,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC;SACjE,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;IAC3D,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/C,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC;AACvD,CAAC;AAED,sCAAsC;AACtC,MAAM,UAAU,aAAa,CAAC,KAAyC;IACrE,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,KAAK,YAAY,CAAC,CAAC;AACnE,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,WAAW,CAAC,KAAyC;IACnE,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,KAAK,YAAY,CAAC,CAAC;AACjE,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,YAAY,CAAC,QAAyB,EAAE,IAA6B;IACnF,OAAO,CAAC,QAAQ,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,MAAM,CACtC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,IAAI,EAAE,IAAI,KAAK,SAAS,IAAI,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAC7F,CAAC;AACJ,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Speech to text for the chat's composer (#734): the browser's own recognizer, the Web Speech API,
3
+ * so the words land in the text box and the person edits them before sending. Acornly never gets
4
+ * the audio; the browser may send it to its maker's speech service (Chrome, Edge and Safari do,
5
+ * Safari not for every language). A lazy chunk, fetched the first time the person taps Speak; see
6
+ * docs/design/app-context-protocol.md, 9.
7
+ */
8
+ /** What the panel uses of a `SpeechRecognition`. */
9
+ export interface Recognition {
10
+ lang: string;
11
+ continuous: boolean;
12
+ interimResults: boolean;
13
+ onresult: ((event: {
14
+ results: ArrayLike<ArrayLike<{
15
+ transcript: string;
16
+ }>>;
17
+ }) => void) | null;
18
+ onerror: ((event: {
19
+ error: string;
20
+ }) => void) | null;
21
+ onend: (() => void) | null;
22
+ start(): void;
23
+ stop(): void;
24
+ abort(): void;
25
+ }
26
+ export type Recognizer = new () => Recognition;
27
+ export interface Dictation {
28
+ /** Stops listening; the last words heard still arrive through `onText`, then `onEnd`. */
29
+ stop(): void;
30
+ /** Stops listening at once and drops what was not heard yet; `onEnd` still follows. */
31
+ abort(): void;
32
+ }
33
+ /** The messages for speaking, kept in this chunk so apps that never speak don't load them. */
34
+ export interface SpeechLabels {
35
+ listening: string;
36
+ micBlocked: string;
37
+ noMic: string;
38
+ speechOff: string;
39
+ noSpeech: string;
40
+ speechFailed: string;
41
+ }
42
+ export declare const DEFAULT_SPEECH_LABELS: SpeechLabels;
43
+ /** The message for a recognizer's error code. */
44
+ export declare function speechError(error: string, labels: SpeechLabels): string;
45
+ /**
46
+ * Joins what was heard. A phrase that repeats the one before and goes on (Android's Chrome sends
47
+ * "why", "why does", "why does the river" as separate results) replaces it.
48
+ */
49
+ export declare function joinHeard(phrases: readonly string[]): string;
50
+ /**
51
+ * Listens until the person stops (or the browser does, after a pause on some phones). `onText`
52
+ * gets the whole transcript so far each time it changes, the words still being heard included.
53
+ * `onEnd` comes once, with the recognizer's error code (`"not-allowed"`, `"no-speech"`, ...)
54
+ * unless it ended cleanly or was aborted.
55
+ */
56
+ export declare function dictate(Recognizer: Recognizer, options: {
57
+ lang?: string | undefined;
58
+ onText: (text: string) => void;
59
+ onEnd: (error?: string) => void;
60
+ }): Dictation;
61
+ //# sourceMappingURL=chat-speech.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-speech.d.ts","sourceRoot":"","sources":["../src/chat-speech.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,oDAAoD;AACpD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,CAAC,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,SAAS,CAAC,SAAS,CAAC;YAAE,UAAU,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC,CAAA;KAAE,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IAC9F,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACrD,KAAK,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3B,KAAK,IAAI,IAAI,CAAC;IACd,IAAI,IAAI,IAAI,CAAC;IACb,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,MAAM,UAAU,GAAG,UAAU,WAAW,CAAC;AAE/C,MAAM,WAAW,SAAS;IACxB,yFAAyF;IACzF,IAAI,IAAI,IAAI,CAAC;IACb,uFAAuF;IACvF,KAAK,IAAI,IAAI,CAAC;CACf;AAED,8FAA8F;AAC9F,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,qBAAqB,EAAE,YAOnC,CAAC;AAEF,iDAAiD;AACjD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,MAAM,CAMvE;AAQD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAc5D;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CACrB,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE;IACP,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC,GACA,SAAS,CAqDX"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Speech to text for the chat's composer (#734): the browser's own recognizer, the Web Speech API,
3
+ * so the words land in the text box and the person edits them before sending. Acornly never gets
4
+ * the audio; the browser may send it to its maker's speech service (Chrome, Edge and Safari do,
5
+ * Safari not for every language). A lazy chunk, fetched the first time the person taps Speak; see
6
+ * docs/design/app-context-protocol.md, 9.
7
+ */
8
+ export const DEFAULT_SPEECH_LABELS = {
9
+ listening: "Listening. Your words land in the box for you to check before sending.",
10
+ micBlocked: "The microphone is blocked. Allow it for this site to speak, or type instead.",
11
+ noMic: "No microphone was found. You can type instead.",
12
+ speechOff: "Speech recognition is off in this browser (on Safari: Siri and Dictation).",
13
+ noSpeech: "Nothing was heard. Tap Speak to try again.",
14
+ speechFailed: "Speaking didn't work this time. You can type instead.",
15
+ };
16
+ /** The message for a recognizer's error code. */
17
+ export function speechError(error, labels) {
18
+ if (error === "not-allowed")
19
+ return labels.micBlocked;
20
+ if (error === "service-not-allowed")
21
+ return labels.speechOff;
22
+ if (error === "audio-capture")
23
+ return labels.noMic;
24
+ if (error === "no-speech")
25
+ return labels.noSpeech;
26
+ return labels.speechFailed;
27
+ }
28
+ /**
29
+ * How long after Stop, and after each word heard since, to wait for the recognizer's own end,
30
+ * which some Safari versions skip; then it is aborted, so the microphone is let go.
31
+ */
32
+ const STOP_WAIT_MS = 5000;
33
+ /**
34
+ * Joins what was heard. A phrase that repeats the one before and goes on (Android's Chrome sends
35
+ * "why", "why does", "why does the river" as separate results) replaces it.
36
+ */
37
+ export function joinHeard(phrases) {
38
+ const kept = [];
39
+ for (const raw of phrases) {
40
+ const phrase = raw.replace(/\s+/g, " ").trim();
41
+ if (!phrase)
42
+ continue;
43
+ const last = kept[kept.length - 1];
44
+ const lower = phrase.toLowerCase();
45
+ const before = last?.toLowerCase();
46
+ // Whole words only: "the" then "there is" are two phrases.
47
+ if (before !== undefined && (lower === before || lower.startsWith(`${before} `)))
48
+ kept[kept.length - 1] = phrase;
49
+ else
50
+ kept.push(phrase);
51
+ }
52
+ return kept.join(" ");
53
+ }
54
+ /**
55
+ * Listens until the person stops (or the browser does, after a pause on some phones). `onText`
56
+ * gets the whole transcript so far each time it changes, the words still being heard included.
57
+ * `onEnd` comes once, with the recognizer's error code (`"not-allowed"`, `"no-speech"`, ...)
58
+ * unless it ended cleanly or was aborted.
59
+ */
60
+ export function dictate(Recognizer, options) {
61
+ let ended = false;
62
+ let quiet = false;
63
+ let error;
64
+ let wait;
65
+ const end = () => {
66
+ if (ended)
67
+ return;
68
+ ended = true;
69
+ clearTimeout(wait);
70
+ options.onEnd(error === "aborted" ? undefined : error);
71
+ };
72
+ const recognition = new Recognizer();
73
+ if (options.lang)
74
+ recognition.lang = options.lang;
75
+ recognition.continuous = true;
76
+ recognition.interimResults = true;
77
+ const giveUp = () => {
78
+ recognition.abort();
79
+ end();
80
+ };
81
+ /** Waits again from now, once stopping: final words can still be on their way. */
82
+ const waitForEnd = () => {
83
+ clearTimeout(wait);
84
+ wait = setTimeout(giveUp, STOP_WAIT_MS);
85
+ };
86
+ let stopped = false;
87
+ recognition.onresult = (event) => {
88
+ if (stopped)
89
+ waitForEnd();
90
+ if (quiet)
91
+ return;
92
+ options.onText(joinHeard(Array.from(event.results, (result) => result[0]?.transcript ?? "")));
93
+ };
94
+ recognition.onerror = (event) => {
95
+ error = event.error;
96
+ };
97
+ recognition.onend = end;
98
+ try {
99
+ recognition.start();
100
+ }
101
+ catch {
102
+ // Started twice, or refused outright: end after the caller has its handle.
103
+ error = "start-failed";
104
+ queueMicrotask(end);
105
+ }
106
+ return {
107
+ stop() {
108
+ stopped = true;
109
+ recognition.stop();
110
+ waitForEnd();
111
+ },
112
+ abort() {
113
+ stopped = quiet = true;
114
+ recognition.abort();
115
+ waitForEnd();
116
+ },
117
+ };
118
+ }
119
+ //# sourceMappingURL=chat-speech.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-speech.js","sourceRoot":"","sources":["../src/chat-speech.ts"],"names":[],"mappings":"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"}
package/dist/chat.d.ts ADDED
@@ -0,0 +1,148 @@
1
+ /**
2
+ * The in-app chat, experimental (0.x): `import { createChat } from "@acornly/ui/chat"`. What an app
3
+ * loads up front is only this: it keeps the latest context snapshot, and the panel itself is a lazy
4
+ * chunk fetched the first time the chat opens. The protocol is docs/design/app-context-protocol.md;
5
+ * math, and the Bible and history trial behind flights (#730), adopt it until its checks pass (#695).
6
+ */
7
+ import type { Target } from "@acornly/core";
8
+ import { type ContextItem, type ContextSnapshot, type Rect } from "./chat-protocol.js";
9
+ import type { SpeechLabels } from "./chat-speech.js";
10
+ import { type AcornlyRoot } from "./root.js";
11
+ export * from "./chat-protocol.js";
12
+ /** A takeaway the person chose to keep, for the app to save as an acorn. */
13
+ export interface KeptTakeaway {
14
+ text: string;
15
+ /** The item it is about. */
16
+ item: string;
17
+ /** Where the acorn anchors: the item by id, then by quote and source. */
18
+ target: Target;
19
+ /** The item's concept keys, to store as the acorn's tags. */
20
+ about: string[];
21
+ /** The item's text on screen (its `anchor.quote`), for `{ context, quote }` in the SDK. */
22
+ quote?: string;
23
+ }
24
+ /** A comment the person wrote on an item, for the app to save as a note: the same shape. */
25
+ export type ContextComment = KeptTakeaway;
26
+ /** What the panel's form does: ask the chat, or save a comment on the item. */
27
+ export type ChatMode = "ask" | "comment";
28
+ /** The speaking messages' defaults load with speaking itself (`chat-speech.ts`). */
29
+ export interface ChatLabels extends Partial<SpeechLabels> {
30
+ title: string;
31
+ close: string;
32
+ about: string;
33
+ change: string;
34
+ rules: string;
35
+ dropped: string;
36
+ tree: string;
37
+ inFocus: string;
38
+ onScreen: string;
39
+ pickHint: string;
40
+ pickList: string;
41
+ cancel: string;
42
+ nowOn: (label: string) => string;
43
+ askInstead: string;
44
+ nowAsking: (label: string) => string;
45
+ placeholder: string;
46
+ send: string;
47
+ speak: string;
48
+ stopSpeaking: string;
49
+ notStored: string;
50
+ read: (labels: string[]) => string;
51
+ refused: string;
52
+ keep: string;
53
+ drafting: string;
54
+ /** Read aloud while the reply has not started, or while the model reads an item. */
55
+ thinking: string;
56
+ takeaway: string;
57
+ save: string;
58
+ kept: string;
59
+ keepFailed: string;
60
+ failed: string;
61
+ capReached: string;
62
+ /** A failed ask, by the model proxy's reason (#740); the first three link to the hub. */
63
+ noGrant: string;
64
+ noKey: string;
65
+ budgetExhausted: string;
66
+ inactive: string;
67
+ rateLimited: string;
68
+ openAppsYouUse: string;
69
+ openYourAi: string;
70
+ newTab: string;
71
+ noTransport: string;
72
+ noGuard: string;
73
+ badContext: string;
74
+ full: string;
75
+ newChat: string;
76
+ modes: string;
77
+ modeAsk: string;
78
+ modeComment: string;
79
+ commentPlaceholder: string;
80
+ commentSave: string;
81
+ commentNote: string;
82
+ commented: (label: string) => string;
83
+ commentFailed: string;
84
+ noAnchor: string;
85
+ }
86
+ export declare const DEFAULT_CHAT_LABELS: ChatLabels;
87
+ export interface ChatOptions {
88
+ /** The model the app's grant allows for chat. */
89
+ model: string;
90
+ /** Calls straight from the browser to the model proxy Worker, with the person's app token. */
91
+ proxy?: {
92
+ url: string;
93
+ appId: string;
94
+ token: () => string | Promise<string>;
95
+ };
96
+ /** The app's own endpoint, for snapshots whose rules say `guard: "relay"`. */
97
+ relay?: {
98
+ endpoint: string;
99
+ headers?: () => Record<string, string> | Promise<Record<string, string>>;
100
+ };
101
+ /** An item's content, for `read_context`; `null` for anything the person may not see. */
102
+ load?: (id: string) => Promise<ContextItem | null>;
103
+ /** An item's rectangle on screen, for what is not an element; `null` when off screen. */
104
+ rect?: (id: string) => Rect | null;
105
+ /** True when a sentence breaks the app's rules (with `guard: "callback"`). */
106
+ guard?: (sentence: string, replySoFar: string) => boolean | Promise<boolean>;
107
+ /** The person tapped one of the app's hand-backs, about an item. */
108
+ onHandBack?: (handBack: string, itemId: string) => void;
109
+ /** Saves a kept takeaway as an acorn. Without it, "Keep" is not offered. */
110
+ onKeep?: (kept: KeptTakeaway) => Promise<void>;
111
+ /**
112
+ * Saves a comment on the item in focus as a note. With it the panel offers "Comment" beside
113
+ * "Ask" (or alone, with no `proxy` or `relay`); a comment calls no model.
114
+ */
115
+ onComment?: (comment: ContextComment) => Promise<void>;
116
+ /**
117
+ * Speak in place of typing, through the browser's own speech recognition (shown only where the
118
+ * browser has it). The words land in the text box to edit before sending. `false` hides it;
119
+ * `lang` is the language to listen for (default: the page's `lang`, else the browser's).
120
+ */
121
+ speech?: false | {
122
+ lang?: string;
123
+ };
124
+ /** The person started (`true`) or stopped speaking to the chat, so the app can pause its audio. */
125
+ onListening?: (listening: boolean) => void;
126
+ /** Draws LaTeX in replies; without it, math shows as written. */
127
+ renderMath?: (tex: string, display: boolean) => Node;
128
+ onClose?: () => void;
129
+ labels?: Partial<ChatLabels>;
130
+ /**
131
+ * The hub's origin, which a failed ask links to for a missing grant or key. Default:
132
+ * `https://acornly.ai`. A path on it is dropped: the hub's pages are at its root.
133
+ */
134
+ hubUrl?: string;
135
+ }
136
+ export interface Chat {
137
+ /** The app's context now. Cheap: the latest is kept, and read only while the chat is open. */
138
+ setContext(snapshot: ContextSnapshot): void;
139
+ /**
140
+ * Opens the chat on the current focus, or on `focus` (the app's "Ask about this"), in `mode`
141
+ * (default: as the person left it, first "ask"; "comment" for an app's own Comment button).
142
+ */
143
+ open(focus?: readonly string[], mode?: ChatMode): Promise<void>;
144
+ close(): void;
145
+ readonly isOpen: boolean;
146
+ }
147
+ export declare function createChat(root: AcornlyRoot, options: ChatOptions): Chat;
148
+ //# sourceMappingURL=chat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../src/chat.ts"],"names":[],"mappings":"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"}
package/dist/chat.js ADDED
@@ -0,0 +1,119 @@
1
+ import { checkSnapshot, } from "./chat-protocol.js";
2
+ import { lazyPart } from "./lazy.js";
3
+ import { layerOf } from "./root.js";
4
+ export * from "./chat-protocol.js";
5
+ export const DEFAULT_CHAT_LABELS = {
6
+ title: "Ask",
7
+ close: "Close the chat",
8
+ about: "About:",
9
+ change: "Change",
10
+ rules: "What this chat won't do here",
11
+ dropped: "Left out, over the limits:",
12
+ tree: "Everything you can ask about",
13
+ inFocus: "in focus",
14
+ onScreen: "on screen",
15
+ pickHint: "Click what you want to ask about.",
16
+ pickList: "Pick from a list",
17
+ cancel: "Cancel",
18
+ nowOn: (label) => `Now on ${label}. `,
19
+ askInstead: "Ask about that instead?",
20
+ nowAsking: (label) => `Now asking about ${label}`,
21
+ placeholder: "Ask about this…",
22
+ send: "Send",
23
+ speak: "Speak",
24
+ stopSpeaking: "Stop",
25
+ notStored: "Chats aren't saved. Only what you keep is.",
26
+ read: (labels) => `Read: ${labels.join(", ")}`,
27
+ refused: "I can't help with that here.",
28
+ keep: "Keep",
29
+ drafting: "Drafting a takeaway…",
30
+ thinking: "Thinking…",
31
+ takeaway: "Your takeaway",
32
+ save: "Save as an acorn",
33
+ kept: "Kept in your acorns.",
34
+ keepFailed: "It couldn't be saved. Try again.",
35
+ failed: "The chat couldn't answer. Try again.",
36
+ capReached: "Chat has used its share for this month; the app's own help still works.",
37
+ noGrant: "Chat needs your leave to use your AI in this app. Turn it on under Apps you use.",
38
+ noKey: "Chat needs a key for its model's provider on your Acornly account. Add one under Your AI.",
39
+ budgetExhausted: "This app has used its AI budget for the month. You can raise it under Apps you use.",
40
+ inactive: "Chat is turned off for this app or your account.",
41
+ rateLimited: "Too many asks just now. Wait a minute and try again.",
42
+ openAppsYouUse: "Open Apps you use",
43
+ openYourAi: "Open Your AI",
44
+ newTab: "(opens in a new tab)",
45
+ noTransport: "The chat isn't set up for this part of the app.",
46
+ noGuard: "The chat can't answer here: the app's check isn't set up.",
47
+ badContext: "The chat can't read this screen. It waits for the next.",
48
+ full: "This chat is long enough.",
49
+ newChat: "Start a new one here",
50
+ modes: "Ask or comment",
51
+ modeAsk: "Ask",
52
+ modeComment: "Comment",
53
+ commentPlaceholder: "Comment on this…",
54
+ commentSave: "Save comment",
55
+ commentNote: "Comments are saved as your notes on what they're about.",
56
+ commented: (label) => `Saved as a note on ${label}.`,
57
+ commentFailed: "The comment couldn't be saved. Try again.",
58
+ noAnchor: "This can't take a comment here. Pick something else with Change.",
59
+ };
60
+ const panelPart = lazyPart(() => import("./chat-panel.js"));
61
+ /** How long a burst of snapshots (a pan, a scrub) settles before an open panel reads the last. */
62
+ const SETTLE_MS = 120;
63
+ export function createChat(root, options) {
64
+ const labels = { ...DEFAULT_CHAT_LABELS, ...options.labels };
65
+ let latest = null;
66
+ let panel = null;
67
+ let timer;
68
+ const push = () => {
69
+ // Called early by `open`, so the waiting push must not land after it and reset its focus.
70
+ clearTimeout(timer);
71
+ timer = undefined;
72
+ if (!panel || !latest)
73
+ return;
74
+ let checked = null;
75
+ try {
76
+ checked = checkSnapshot(latest);
77
+ }
78
+ catch (error) {
79
+ // A bad snapshot stops sending until a good one comes; the app hears why in the console.
80
+ console.error(error);
81
+ }
82
+ panel.setContext(checked?.snapshot ?? null, checked?.dropped ?? []);
83
+ };
84
+ const chat = {
85
+ setContext(snapshot) {
86
+ latest = snapshot;
87
+ if (panel && timer === undefined)
88
+ timer = setTimeout(push, SETTLE_MS);
89
+ },
90
+ async open(focus, mode) {
91
+ if (!latest)
92
+ throw new Error("Call setContext before opening the chat");
93
+ if (panel) {
94
+ push();
95
+ panel.ask(focus, mode);
96
+ return;
97
+ }
98
+ const [{ openChatPanel }, layer] = await Promise.all([panelPart.load(), layerOf(root)]);
99
+ if (panel)
100
+ return chat.open(focus, mode);
101
+ const checked = checkSnapshot(focus ? { ...latest, focus: [...focus] } : latest);
102
+ panel = openChatPanel(layer, {
103
+ ...options,
104
+ onClose: () => {
105
+ panel = null;
106
+ options.onClose?.();
107
+ },
108
+ }, labels, checked, mode);
109
+ },
110
+ close() {
111
+ panel?.close();
112
+ },
113
+ get isOpen() {
114
+ return panel !== null;
115
+ },
116
+ };
117
+ return chat;
118
+ }
119
+ //# sourceMappingURL=chat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat.js","sourceRoot":"","sources":["../src/chat.ts"],"names":[],"mappings":"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"}
package/dist/compose.d.ts CHANGED
@@ -3,6 +3,6 @@
3
3
  * the user selects something, so neither the toolbar nor the composer is on the read path.
4
4
  */
5
5
  export { DEFAULT_TOOLBAR_LABELS, TOOLBAR_SHORTCUTS, openSelectionToolbar, type SelectionToolbar, type ToolbarAction, type ToolbarChoice, type ToolbarLabels, type ToolbarOptions, type ToolbarTarget, } from "./toolbar.js";
6
- export { COMPOSER_SHORTCUTS, DEFAULT_COMPOSER_LABELS, DEFAULT_REACTIONS, openComposer, type Composer, type ComposerChoice, type ComposerChoiceGroup, type ComposerDraft, type ComposerLabels, type ComposerMode, type ComposerOption, type ComposerOptions, } from "./composer.js";
6
+ export { COMPOSER_SHORTCUTS, DEFAULT_COMPOSER_LABELS, DEFAULT_REACTIONS, openComposer, type Composer, type ComposerChoice, type ComposerChoiceGroup, type ComposerChoiceItems, type ComposerDraft, type ComposerLabels, type ComposerMode, type ComposerOption, type ComposerOptions, } from "./composer.js";
7
7
  export type { FloatAnchor } from "./float.js";
8
8
  //# sourceMappingURL=compose.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EACjB,YAAY,EACZ,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,eAAe,GACrB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EACjB,YAAY,EACZ,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,eAAe,GACrB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,GAOrB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EACjB,YAAY,GASb,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,GAOrB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EACjB,YAAY,GAUb,MAAM,eAAe,CAAC"}