@artooi/ag-ui-web-component 0.27.0 → 0.29.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/CHANGELOG.md +663 -1
- package/README.md +557 -11
- package/dist/ag-ui-web-component.bundle.js +294 -36
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +69 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +262 -1
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/agui_client.d.ts +46 -1
- package/dist/core/agui_client.d.ts.map +1 -1
- package/dist/core/conversation_store.d.ts +43 -1
- package/dist/core/conversation_store.d.ts.map +1 -1
- package/dist/core/create_http_agent.d.ts +13 -0
- package/dist/core/create_http_agent.d.ts.map +1 -1
- package/dist/core/remote_conversation_store.d.ts +23 -1
- package/dist/core/remote_conversation_store.d.ts.map +1 -1
- package/dist/core/utils.d.ts +28 -0
- package/dist/core/utils.d.ts.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2104 -194
- package/dist/index.js.map +4 -4
- package/dist/tools/is_destructive.d.ts +8 -2
- package/dist/tools/is_destructive.d.ts.map +1 -1
- package/dist/tools/parse_tool_catalog.d.ts +11 -4
- package/dist/tools/parse_tool_catalog.d.ts.map +1 -1
- package/dist/ui/approval_card.d.ts +18 -0
- package/dist/ui/approval_card.d.ts.map +1 -1
- package/dist/ui/checkpoint_menu.d.ts +10 -0
- package/dist/ui/checkpoint_menu.d.ts.map +1 -1
- package/dist/ui/confirmation_card.d.ts +16 -0
- package/dist/ui/confirmation_card.d.ts.map +1 -1
- package/dist/ui/message_actions.d.ts +46 -0
- package/dist/ui/message_actions.d.ts.map +1 -0
- package/dist/ui/page_quote_offer.d.ts +33 -0
- package/dist/ui/page_quote_offer.d.ts.map +1 -0
- package/dist/ui/quote_selection.d.ts +66 -0
- package/dist/ui/quote_selection.d.ts.map +1 -0
- package/dist/ui/relative_time.d.ts +10 -0
- package/dist/ui/relative_time.d.ts.map +1 -1
- package/dist/ui/render_markdown.d.ts +23 -5
- package/dist/ui/render_markdown.d.ts.map +1 -1
- package/dist/ui/resize_handle.d.ts +5 -1
- package/dist/ui/resize_handle.d.ts.map +1 -1
- package/dist/ui/stick_to_bottom.d.ts +55 -0
- package/dist/ui/stick_to_bottom.d.ts.map +1 -0
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/suggestion_chips.d.ts +29 -0
- package/dist/ui/suggestion_chips.d.ts.map +1 -0
- package/dist/ui/thread_drawer.d.ts +10 -0
- package/dist/ui/thread_drawer.d.ts.map +1 -1
- package/dist/ui/tool_call_card.d.ts +8 -0
- package/dist/ui/tool_call_card.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +53 -7
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/dist/ui/voice_input.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +75 -0
- package/src/core/ag_ui_chat.ts +1357 -113
- package/src/core/agui_client.ts +81 -1
- package/src/core/conversation_store.ts +128 -42
- package/src/core/create_http_agent.ts +24 -2
- package/src/core/remote_conversation_store.ts +35 -2
- package/src/core/utils.ts +58 -0
- package/src/index.ts +39 -0
- package/src/tools/is_destructive.ts +8 -2
- package/src/tools/parse_tool_catalog.ts +18 -6
- package/src/ui/approval_card.ts +90 -2
- package/src/ui/checkpoint_menu.ts +22 -5
- package/src/ui/confirmation_card.ts +29 -1
- package/src/ui/message_actions.ts +158 -0
- package/src/ui/page_quote_offer.ts +215 -0
- package/src/ui/quote_selection.ts +345 -0
- package/src/ui/relative_time.ts +11 -0
- package/src/ui/render_markdown.ts +111 -21
- package/src/ui/resize_handle.ts +32 -2
- package/src/ui/stick_to_bottom.ts +126 -0
- package/src/ui/styles.ts +227 -0
- package/src/ui/suggestion_chips.ts +73 -0
- package/src/ui/thread_drawer.ts +22 -2
- package/src/ui/tool_call_card.ts +9 -0
- package/src/ui/ui_strings.ts +79 -8
- package/src/ui/voice_input.ts +43 -0
- package/src/version.ts +1 -1
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Whether a tool's JSON-Schema `parameters` marks it destructive.
|
|
3
3
|
*
|
|
4
|
-
* Reads the `x-destructive` extension
|
|
5
|
-
* `
|
|
4
|
+
* Reads the `x-destructive` extension off a schema the **host** declared —
|
|
5
|
+
* a tool passed to `registerTool`, or one of the built-ins. A server-side
|
|
6
|
+
* tool's schema never reaches the browser: tool definitions travel
|
|
7
|
+
* client-to-server on `RunAgentInput.tools`, and the only channel coming the
|
|
8
|
+
* other way is the tool catalog (`data-tools-url`), which carries labels, not
|
|
9
|
+
* schemas. So a server tool marked destructive there is not gated here, and
|
|
10
|
+
* must be gated server-side instead — the confirmation this flag drives is a
|
|
11
|
+
* property of tools the browser itself executes.
|
|
6
12
|
*/
|
|
7
13
|
export declare function isDestructive(parameters: Record<string, unknown>): boolean;
|
|
8
14
|
//# sourceMappingURL=is_destructive.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"is_destructive.d.ts","sourceRoot":"","sources":["../../src/tools/is_destructive.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"is_destructive.d.ts","sourceRoot":"","sources":["../../src/tools/is_destructive.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAE1E"}
|
|
@@ -7,13 +7,20 @@ export interface ToolCatalogEntry {
|
|
|
7
7
|
readonly name: string;
|
|
8
8
|
/** A friendly card label for the tool. */
|
|
9
9
|
readonly summary: string;
|
|
10
|
-
/** Optional longer blurb (e.g. for a
|
|
10
|
+
/** Optional longer blurb (e.g. for a tooltip). */
|
|
11
11
|
readonly description?: string;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
|
-
* Parse a fetched tool catalog into a `name →
|
|
14
|
+
* Parse a fetched tool catalog into a `name → entry` map, skipping any entry
|
|
15
15
|
* that isn't a `{ name: string, summary: string }` object. Tolerant by design:
|
|
16
|
-
* a malformed payload yields an empty map rather than throwing
|
|
16
|
+
* a malformed payload yields an empty map rather than throwing, and an
|
|
17
|
+
* optional field of the wrong type costs that field rather than the entry.
|
|
18
|
+
*
|
|
19
|
+
* Whole entries rather than bare summaries, even though the element itself
|
|
20
|
+
* only labels cards with `summary`: the map is what a caller gets, so
|
|
21
|
+
* narrowing it here would put `description` on the wire with nowhere to
|
|
22
|
+
* arrive, and no consumer could recover it without changing this signature
|
|
23
|
+
* first.
|
|
17
24
|
*/
|
|
18
|
-
export declare function parseToolCatalog(data: unknown): Record<string,
|
|
25
|
+
export declare function parseToolCatalog(data: unknown): Record<string, ToolCatalogEntry>;
|
|
19
26
|
//# sourceMappingURL=parse_tool_catalog.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse_tool_catalog.d.ts","sourceRoot":"","sources":["../../src/tools/parse_tool_catalog.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,
|
|
1
|
+
{"version":3,"file":"parse_tool_catalog.d.ts","sourceRoot":"","sources":["../../src/tools/parse_tool_catalog.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kDAAkD;IAClD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAsBhF"}
|
|
@@ -9,6 +9,11 @@ export interface ApprovalRequest {
|
|
|
9
9
|
message?: string;
|
|
10
10
|
/** Tool name, surfaced as a `data-tool-name` attribute for styling/tests. */
|
|
11
11
|
toolName?: string;
|
|
12
|
+
/**
|
|
13
|
+
* The call's arguments, shown for editing when {@link ApprovalOptions.onEdit}
|
|
14
|
+
* is set. Omitted when the interrupt names no call whose arguments are known.
|
|
15
|
+
*/
|
|
16
|
+
args?: Record<string, unknown>;
|
|
12
17
|
}
|
|
13
18
|
/** Options for {@link requestApproval}. */
|
|
14
19
|
export interface ApprovalOptions {
|
|
@@ -19,6 +24,19 @@ export interface ApprovalOptions {
|
|
|
19
24
|
signal?: AbortSignal;
|
|
20
25
|
/** Localized strings; defaults to the English {@link DEFAULT_UI_STRINGS}. */
|
|
21
26
|
strings?: UiStrings;
|
|
27
|
+
/**
|
|
28
|
+
* Offer the call's arguments for editing, and receive what the user approved.
|
|
29
|
+
*
|
|
30
|
+
* Called **only** on approval, and only when the text parses and differs from
|
|
31
|
+
* what was proposed -- an untouched call resolves as a plain approval, so a
|
|
32
|
+
* server sees `editedArgs` exactly when something was actually edited.
|
|
33
|
+
*
|
|
34
|
+
* Absent means no editor, which is deliberate: AG-UI gates this on the
|
|
35
|
+
* agent's own `approveWithEdits` capability, and a card that let a user
|
|
36
|
+
* rewrite arguments a server will discard is worse than one that does not
|
|
37
|
+
* offer to.
|
|
38
|
+
*/
|
|
39
|
+
onEdit?: (args: Record<string, unknown>) => void;
|
|
22
40
|
}
|
|
23
41
|
/**
|
|
24
42
|
* A fully custom renderer for a server-side-tool approval, set via
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval_card.d.ts","sourceRoot":"","sources":["../../src/ui/approval_card.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAErE,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"approval_card.d.ts","sourceRoot":"","sources":["../../src/ui/approval_card.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAErE,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAYD,2CAA2C;AAC3C,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CAClD;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAC7B,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,KAC7B,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,IAAI,GAAG,UAAU,EACvB,OAAO,EAAE,eAAe,EACxB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,OAAO,CAAC,CA4DlB"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { RunRow } from "../core/run_index.js";
|
|
2
|
+
import { type RelativeTimeFormatter } from "./relative_time.js";
|
|
2
3
|
import { type UiStrings } from "./ui_strings.js";
|
|
3
4
|
/** How the host continues a picked run. */
|
|
4
5
|
export type CheckpointVerb = "resume" | "fork";
|
|
@@ -23,6 +24,15 @@ export declare class CheckpointMenu {
|
|
|
23
24
|
/** Replace the rows. The host passes only `continuable` runs. */
|
|
24
25
|
setRuns(runs: readonly RunRow[]): void;
|
|
25
26
|
/** Re-localize a panel built before the host's strings resolved. */
|
|
27
|
+
/**
|
|
28
|
+
* Replace the timestamp formatter, or restore the built-in with `null`.
|
|
29
|
+
*
|
|
30
|
+
* The built-in is deliberately locale-neutral -- there is no `Intl` anywhere
|
|
31
|
+
* in this component, so it never disagrees with a host's own formatting by
|
|
32
|
+
* guessing a locale. That is a defensible default and a poor requirement, so
|
|
33
|
+
* this is the way out.
|
|
34
|
+
*/
|
|
35
|
+
setRelativeTimeFormatter(format: RelativeTimeFormatter | null): void;
|
|
26
36
|
setStrings(strings: UiStrings): void;
|
|
27
37
|
open(): void;
|
|
28
38
|
close(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkpoint_menu.d.ts","sourceRoot":"","sources":["../../src/ui/checkpoint_menu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"checkpoint_menu.d.ts","sourceRoot":"","sources":["../../src/ui/checkpoint_menu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,KAAK,qBAAqB,EAAgB,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAErE,2CAA2C;AAC3C,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,MAAM,CAAC;AAqB/C;;;;;;;;;;;;GAYG;AACH,qBAAa,cAAc;;IACzB,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IAWjC,YACE,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,KAAK,IAAI,EACrD,OAAO,GAAE,SAA8B,EA+BxC;IAED,iEAAiE;IACjE,OAAO,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAGrC;IAED,oEAAoE;IACpE;;;;;;;OAOG;IACH,wBAAwB,CAAC,MAAM,EAAE,qBAAqB,GAAG,IAAI,GAAG,IAAI,CAEnE;IASD,UAAU,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI,CAKnC;IAED,IAAI,IAAI,IAAI,CAYX;IAED,KAAK,IAAI,IAAI,CAOZ;IAqCD,IAAI,KAAK,IAAI,OAAO,CAEnB;CAoHF"}
|
|
@@ -19,6 +19,16 @@ export interface ConfirmationOptions {
|
|
|
19
19
|
signal?: AbortSignal;
|
|
20
20
|
/** Localized strings; defaults to the English {@link DEFAULT_UI_STRINGS}. */
|
|
21
21
|
strings?: UiStrings;
|
|
22
|
+
/**
|
|
23
|
+
* Offer a third button — "always allow, this session" — and call this when
|
|
24
|
+
* the user picks it. The card still resolves `true`: the extra decision is
|
|
25
|
+
* *in addition to* approving this call, not instead of it.
|
|
26
|
+
*
|
|
27
|
+
* Absent means no button, which is deliberate: presence of the handler is
|
|
28
|
+
* what enables it, so the affordance can never be rendered with nothing
|
|
29
|
+
* listening. A caller that cannot honour the waiver simply does not pass one.
|
|
30
|
+
*/
|
|
31
|
+
onAlwaysAllow?: () => void;
|
|
22
32
|
}
|
|
23
33
|
/**
|
|
24
34
|
* Append an inline confirmation card to `host` (the chat message list) and
|
|
@@ -30,6 +40,12 @@ export interface ConfirmationOptions {
|
|
|
30
40
|
* Answering it removes it: the record of the decision belongs to the tool card
|
|
31
41
|
* this gates, which settles to `done` or `declined` and carries it. A spent
|
|
32
42
|
* form left in place reads as still outstanding.
|
|
43
|
+
*
|
|
44
|
+
* With `onAlwaysAllow` set the card offers a third button. A prompt that is
|
|
45
|
+
* approved nearly every time is not a decision, it is a speed bump — and the
|
|
46
|
+
* reflex it trains is what makes the rare refusal easy to miss. Letting the
|
|
47
|
+
* user say "not this one again, this session" is the affordance that keeps the
|
|
48
|
+
* remaining prompts meaningful.
|
|
33
49
|
*/
|
|
34
50
|
export declare function requestConfirmation(host: Node & ParentNode, request: ConfirmationRequest, options?: ConfirmationOptions): Promise<boolean>;
|
|
35
51
|
//# sourceMappingURL=confirmation_card.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"confirmation_card.d.ts","sourceRoot":"","sources":["../../src/ui/confirmation_card.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAErE,kDAAkD;AAClD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAYD,+CAA+C;AAC/C,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"confirmation_card.d.ts","sourceRoot":"","sources":["../../src/ui/confirmation_card.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAErE,kDAAkD;AAClD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAYD,+CAA+C;AAC/C,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,IAAI,GAAG,UAAU,EACvB,OAAO,EAAE,mBAAmB,EAC5B,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,OAAO,CAAC,CAqElB"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { UiStrings } from "./ui_strings.js";
|
|
2
|
+
/** What an action bar can do, beyond copying. */
|
|
3
|
+
export interface MessageActionsOptions {
|
|
4
|
+
/** Localized strings. */
|
|
5
|
+
strings: UiStrings;
|
|
6
|
+
/**
|
|
7
|
+
* The text Copy puts on the clipboard. A function rather than a string
|
|
8
|
+
* because a bubble's content is rewritten while it streams, and the bar is
|
|
9
|
+
* attached to the element rather than to a snapshot of it.
|
|
10
|
+
*/
|
|
11
|
+
text: () => string;
|
|
12
|
+
/**
|
|
13
|
+
* Report a rating for this message. Absent means no feedback buttons.
|
|
14
|
+
*
|
|
15
|
+
* The component stores nothing: a rating is the host's to keep, and a
|
|
16
|
+
* write-only table nobody reads is not worth a schema.
|
|
17
|
+
*/
|
|
18
|
+
onFeedback?: (rating: "up" | "down") => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Give one finished message bubble its row of actions.
|
|
22
|
+
*
|
|
23
|
+
* **Finished** is load-bearing. A streaming bubble reassigns its `innerHTML` on
|
|
24
|
+
* every delta, so anything attached mid-stream is discarded and rebuilt for
|
|
25
|
+
* each one -- the same constraint `attachCopyButtons` records, one level up.
|
|
26
|
+
*
|
|
27
|
+
* Retry is deliberately **not** here: it belongs to the last turn only, so the
|
|
28
|
+
* element owns it and moves it as the transcript grows. Everything on this bar
|
|
29
|
+
* is safe to offer on any message, however old.
|
|
30
|
+
*
|
|
31
|
+
* Idempotent -- a bubble already given a bar is skipped, so a re-render or a
|
|
32
|
+
* second call cannot stack rows.
|
|
33
|
+
*/
|
|
34
|
+
export declare function attachMessageActions(bubble: HTMLElement, options: MessageActionsOptions): void;
|
|
35
|
+
/**
|
|
36
|
+
* The empty action row on `bubble`, created if it has none yet.
|
|
37
|
+
*
|
|
38
|
+
* Shared so a bubble that wants *only* Retry -- a failed run, which has nothing
|
|
39
|
+
* worth copying and nothing to rate -- gets the same row, the same part name
|
|
40
|
+
* and the same accessible grouping as every other message, rather than a
|
|
41
|
+
* second thing that looks like one.
|
|
42
|
+
*/
|
|
43
|
+
export declare function messageActionBar(bubble: HTMLElement, strings: UiStrings): HTMLElement;
|
|
44
|
+
/** Build one action button, labelled for screen readers rather than by glyph. */
|
|
45
|
+
export declare function messageActionButton(modifier: string, label: string, glyph: string): HTMLButtonElement;
|
|
46
|
+
//# sourceMappingURL=message_actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message_actions.d.ts","sourceRoot":"","sources":["../../src/ui/message_actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAKjD,iDAAiD;AACjD,MAAM,WAAW,qBAAqB;IACpC,yBAAyB;IACzB,OAAO,EAAE,SAAS,CAAC;IACnB;;;;OAIG;IACH,IAAI,EAAE,MAAM,MAAM,CAAC;IACnB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,GAAG,MAAM,KAAK,IAAI,CAAC;CAC9C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAY9F;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,GAAG,WAAW,CAmBrF;AAQD,iFAAiF;AACjF,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,iBAAiB,CAYnB"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** A live page-side offer. */
|
|
2
|
+
export interface PageQuoteOffer {
|
|
3
|
+
/** The button itself, for a host that wants to style or inspect it. */
|
|
4
|
+
readonly element: HTMLButtonElement;
|
|
5
|
+
/** Stop offering: every listener removed, the button and its styles gone. */
|
|
6
|
+
detach(): void;
|
|
7
|
+
}
|
|
8
|
+
/** What {@link attachQuoteOffer} needs to know. */
|
|
9
|
+
export interface PageQuoteOfferOptions {
|
|
10
|
+
/** Where a selection is worth offering to quote. */
|
|
11
|
+
within: HTMLElement;
|
|
12
|
+
/** What the offer says. */
|
|
13
|
+
label: string;
|
|
14
|
+
/**
|
|
15
|
+
* A subtree to stay out of -- the chat widget itself.
|
|
16
|
+
*
|
|
17
|
+
* Its transcript runs this same gesture on the inside, so without this a
|
|
18
|
+
* selection there would be offered twice and quoted twice.
|
|
19
|
+
*/
|
|
20
|
+
exclude: Node;
|
|
21
|
+
/** Take the offer. */
|
|
22
|
+
onQuote: (text: string) => void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Offer to quote what the user selects inside `within`.
|
|
26
|
+
*
|
|
27
|
+
* Nothing is quoted until the offer is taken -- which is the whole point. An
|
|
28
|
+
* automatic version of this is easy to write and horrible to use: every drag
|
|
29
|
+
* made to read, to copy, or to fix a typo silently appends to whatever the user
|
|
30
|
+
* was in the middle of typing.
|
|
31
|
+
*/
|
|
32
|
+
export declare function attachQuoteOffer(options: PageQuoteOfferOptions): PageQuoteOffer;
|
|
33
|
+
//# sourceMappingURL=page_quote_offer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page_quote_offer.d.ts","sourceRoot":"","sources":["../../src/ui/page_quote_offer.ts"],"names":[],"mappings":"AAuDA,8BAA8B;AAC9B,MAAM,WAAW,cAAc;IAC7B,uEAAuE;IACvE,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,6EAA6E;IAC7E,MAAM,IAAI,IAAI,CAAC;CAChB;AAED,mDAAmD;AACnD,MAAM,WAAW,qBAAqB;IACpC,oDAAoD;IACpD,MAAM,EAAE,WAAW,CAAC;IACpB,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,OAAO,EAAE,IAAI,CAAC;IACd,sBAAsB;IACtB,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,qBAAqB,GAAG,cAAc,CA4F/E"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a text selection out of the shadow tree, and shaping it for the
|
|
3
|
+
* composer.
|
|
4
|
+
*
|
|
5
|
+
* Selection is the one DOM API shadow encapsulation genuinely broke, and it
|
|
6
|
+
* broke it in two different directions. WebKit rescopes a selection made inside
|
|
7
|
+
* a shadow tree to the **host** element, so `document.getSelection()` reports
|
|
8
|
+
* the whole widget and none of the words; Chromium exposes the shadow nodes
|
|
9
|
+
* directly, so the same call reports exactly the words. `getComposedRanges` is
|
|
10
|
+
* the settled answer to both -- it hands back real endpoints for each shadow
|
|
11
|
+
* root it is *given permission to see* -- but it is recent enough that the
|
|
12
|
+
* direct read has to stay behind it.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* The longest quotation put into the composer, in characters.
|
|
16
|
+
*
|
|
17
|
+
* A cap rather than no cap because the point of a quotation is to say *which*
|
|
18
|
+
* part of an answer the next question is about. Select-all-then-quote is a
|
|
19
|
+
* gesture the transcript already answers -- it is the whole conversation, which
|
|
20
|
+
* the model has -- and pasting it back costs the user tokens to say nothing.
|
|
21
|
+
*/
|
|
22
|
+
export declare const MAX_QUOTE_CHARS = 500;
|
|
23
|
+
/** A selection worth offering to quote. */
|
|
24
|
+
export interface QuotableSelection {
|
|
25
|
+
/** The selected text, trimmed. Never empty. */
|
|
26
|
+
readonly text: string;
|
|
27
|
+
/**
|
|
28
|
+
* One **line** of the selection, for placing the affordance beside it.
|
|
29
|
+
*
|
|
30
|
+
* Deliberately not the selection's bounding box. See {@link lineToHangFrom}:
|
|
31
|
+
* the union of a selection spanning several elements has a centre with no
|
|
32
|
+
* selected text anywhere near it.
|
|
33
|
+
*/
|
|
34
|
+
readonly rect: DOMRect;
|
|
35
|
+
}
|
|
36
|
+
/** Where a gesture ended, in viewport coordinates. */
|
|
37
|
+
export interface Point {
|
|
38
|
+
readonly x: number;
|
|
39
|
+
readonly y: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The current selection, when it lies wholly inside `container`.
|
|
43
|
+
*
|
|
44
|
+
* `roots` are the shadow roots the read is allowed to look inside, in the shape
|
|
45
|
+
* `getComposedRanges` itself takes. Pass the one holding `container` for a
|
|
46
|
+
* selection made in a shadow tree, or nothing at all for one made in the page.
|
|
47
|
+
*
|
|
48
|
+
* `near` is where the gesture ended, when a pointer made it -- it decides which
|
|
49
|
+
* line of a multi-line selection the offer is hung from.
|
|
50
|
+
*
|
|
51
|
+
* `null` for no selection, a collapsed one, whitespace only, or one that
|
|
52
|
+
* starts or ends outside `container` -- a drag that ran off the transcript and
|
|
53
|
+
* into the page is not a quotation from the transcript.
|
|
54
|
+
*/
|
|
55
|
+
export declare function quotableSelection(container: HTMLElement, roots?: readonly ShadowRoot[], near?: Point): QuotableSelection | null;
|
|
56
|
+
/**
|
|
57
|
+
* Wrap `text` as a markdown blockquote, ready to be followed by a question.
|
|
58
|
+
*
|
|
59
|
+
* Markdown rather than a bespoke fence because the transcript renders markdown
|
|
60
|
+
* and the server reads markdown: a quotation that survives both ends without a
|
|
61
|
+
* convention to agree on first. The trailing blank line is what leaves the
|
|
62
|
+
* caret on a fresh paragraph, which is the whole point of quoting into a
|
|
63
|
+
* composer rather than sending straight away.
|
|
64
|
+
*/
|
|
65
|
+
export declare function asQuote(text: string): string;
|
|
66
|
+
//# sourceMappingURL=quote_selection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quote_selection.d.ts","sourceRoot":"","sources":["../../src/ui/quote_selection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,MAAM,CAAC;AAEnC,2CAA2C;AAC3C,MAAM,WAAW,iBAAiB;IAChC,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED,sDAAsD;AACtD,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;CACpB;AAOD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,WAAW,EACtB,KAAK,GAAE,SAAS,UAAU,EAAO,EACjC,IAAI,CAAC,EAAE,KAAK,GACX,iBAAiB,GAAG,IAAI,CAqB1B;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAa5C"}
|
|
@@ -10,4 +10,14 @@ import { type UiStrings } from "./ui_strings.js";
|
|
|
10
10
|
* filled in here; the bucketing stays integer-rounded and locale-neutral.
|
|
11
11
|
*/
|
|
12
12
|
export declare function relativeTime(timestamp: number, now?: number, strings?: UiStrings): string;
|
|
13
|
+
/**
|
|
14
|
+
* A host's replacement for {@link relativeTime}.
|
|
15
|
+
*
|
|
16
|
+
* Takes an epoch-milliseconds timestamp and returns the text a row shows.
|
|
17
|
+
* `Intl.RelativeTimeFormat` is the obvious implementation and is deliberately
|
|
18
|
+
* *not* the default: a component that guessed a locale would disagree with the
|
|
19
|
+
* host page's own formatting, and being wrong in a second language is worse
|
|
20
|
+
* than being neutral in one.
|
|
21
|
+
*/
|
|
22
|
+
export type RelativeTimeFormatter = (timestamp: number) => string;
|
|
13
23
|
//# sourceMappingURL=relative_time.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relative_time.d.ts","sourceRoot":"","sources":["../../src/ui/relative_time.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAErE;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,GAAG,GAAE,MAAmB,EACxB,OAAO,GAAE,SAA8B,GACtC,MAAM,CAqBR"}
|
|
1
|
+
{"version":3,"file":"relative_time.d.ts","sourceRoot":"","sources":["../../src/ui/relative_time.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAErE;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,GAAG,GAAE,MAAmB,EACxB,OAAO,GAAE,SAA8B,GACtC,MAAM,CAqBR;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC"}
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
import { type Config } from "dompurify";
|
|
2
|
+
/**
|
|
3
|
+
* The sanitiser's declared allowlist — and, because {@link harden} runs inside
|
|
4
|
+
* the sanitiser rather than after it, its *effective* one too.
|
|
5
|
+
*
|
|
6
|
+
* `ALLOW_DATA_ATTR` and `ALLOW_ARIA_ATTR` default to `true`, which would admit
|
|
7
|
+
* every `data-*` and `aria-*` attribute on top of the five named above. Both
|
|
8
|
+
* matter here: the cards drive their resolved / expanded / status appearance off
|
|
9
|
+
* `[data-resolved]`, `[data-status]` and `[data-expanded]`, and an `aria-label`
|
|
10
|
+
* on model output makes a screen reader announce something other than what is on
|
|
11
|
+
* screen. Turning them off is what makes the declared list the real one.
|
|
12
|
+
*
|
|
13
|
+
* Exported so a test can assert the returned markup is a fixed point of it.
|
|
14
|
+
*/
|
|
15
|
+
export declare const SANITIZE_CONFIG: Config;
|
|
16
|
+
/** {@link SANITIZE_CONFIG} plus the images a host opts into. */
|
|
17
|
+
export declare const SANITIZE_CONFIG_WITH_IMAGES: Config;
|
|
1
18
|
/** Options for {@link renderMarkdown}. */
|
|
2
19
|
export interface RenderMarkdownOptions {
|
|
3
20
|
/**
|
|
@@ -11,12 +28,13 @@ export interface RenderMarkdownOptions {
|
|
|
11
28
|
* Render markdown (and any embedded raw HTML) to a sanitised HTML string.
|
|
12
29
|
*
|
|
13
30
|
* Markdown syntax and literal HTML share one path: `marked` emits HTML, then
|
|
14
|
-
* DOMPurify strips everything outside {@link
|
|
15
|
-
*
|
|
16
|
-
*
|
|
31
|
+
* DOMPurify strips everything outside {@link SANITIZE_CONFIG} — scripts, event
|
|
32
|
+
* handlers, `javascript:` URLs, `data-*`/`aria-*`, and every class but a code
|
|
33
|
+
* fence's `language-*` hint. Links come out with `target="_blank"` and
|
|
34
|
+
* `rel="noopener noreferrer"`.
|
|
17
35
|
*
|
|
18
|
-
* The
|
|
19
|
-
*
|
|
36
|
+
* The returned string is the sanitiser's own output, trimmed. Nothing edits it
|
|
37
|
+
* afterwards, so what a caller inserts is what DOMPurify approved.
|
|
20
38
|
*/
|
|
21
39
|
export declare function renderMarkdown(text: string, options?: RenderMarkdownOptions): string;
|
|
22
40
|
//# sourceMappingURL=render_markdown.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render_markdown.d.ts","sourceRoot":"","sources":["../../src/ui/render_markdown.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"render_markdown.d.ts","sourceRoot":"","sources":["../../src/ui/render_markdown.ts"],"names":[],"mappings":"AAMA,OAAkB,EAAE,KAAK,MAAM,EAA8B,MAAM,WAAW,CAAC;AA+D/E;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,EAAE,MAK7B,CAAC;AAEF,gEAAgE;AAChE,eAAO,MAAM,2BAA2B,EAAE,MAIzC,CAAC;AAiEF,0CAA0C;AAC1C,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAMpF"}
|
|
@@ -41,7 +41,11 @@ export interface ResizeOptions {
|
|
|
41
41
|
readonly rect: () => PanelRect;
|
|
42
42
|
/** Apply a size (the host writes the custom properties). */
|
|
43
43
|
readonly apply: (size: ResizeSize) => void;
|
|
44
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Called once per completed resize, for persistence: on `pointerup` for a
|
|
46
|
+
* drag, and when the key comes up (or focus leaves the handle) for a key
|
|
47
|
+
* press. Never per pointer move, and never per key repeat.
|
|
48
|
+
*/
|
|
45
49
|
readonly commit: (size: ResizeSize) => void;
|
|
46
50
|
/** Accessible label. */
|
|
47
51
|
readonly label: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resize_handle.d.ts","sourceRoot":"","sources":["../../src/ui/resize_handle.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,8CAA8C;IAC9C,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC7B,4CAA4C;IAC5C,QAAQ,CAAC,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;CAC9B;AAED;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAEnD,gEAAgE;AAChE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,kEAAkE;AAClE,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,yDAAyD;AACzD,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,UAAU,CAAC;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,YAAY,CAAC;IACpC,wCAAwC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,SAAS,CAAC;IAC/B,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;IAC3C
|
|
1
|
+
{"version":3,"file":"resize_handle.d.ts","sourceRoot":"","sources":["../../src/ui/resize_handle.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,8CAA8C;IAC9C,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC7B,4CAA4C;IAC5C,QAAQ,CAAC,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;CAC9B;AAED;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAEnD,gEAAgE;AAChE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,kEAAkE;AAClE,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,yDAAyD;AACzD,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,UAAU,CAAC;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,YAAY,CAAC;IACpC,wCAAwC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,SAAS,CAAC;IAC/B,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;IAC5C,wBAAwB;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAMD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,cAAc,CAiHzE"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** A transcript that follows new content, unless the reader has other ideas. */
|
|
2
|
+
export interface StickToBottom {
|
|
3
|
+
/**
|
|
4
|
+
* New content arrived. Scrolls to the bottom only while following, so
|
|
5
|
+
* reading older messages during a run is no longer undone on the next token.
|
|
6
|
+
*/
|
|
7
|
+
readonly follow: () => void;
|
|
8
|
+
/** Go to the bottom and resume following, whatever the reader was doing. */
|
|
9
|
+
readonly jump: () => void;
|
|
10
|
+
/** Whether the transcript is currently following new content. */
|
|
11
|
+
readonly following: () => boolean;
|
|
12
|
+
readonly dispose: () => void;
|
|
13
|
+
}
|
|
14
|
+
export interface StickToBottomOptions {
|
|
15
|
+
/** The scrolling element -- the message list. */
|
|
16
|
+
readonly viewport: HTMLElement;
|
|
17
|
+
/**
|
|
18
|
+
* Called whenever the answer to "should a jump-to-latest affordance show?"
|
|
19
|
+
* changes. True means the reader has scrolled away *and* has since missed
|
|
20
|
+
* something; scrolling up through a settled transcript is not a reason to
|
|
21
|
+
* nag.
|
|
22
|
+
*/
|
|
23
|
+
readonly onMissedContent: (missed: boolean) => void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Follow the foot of a scrolling transcript, and stop when the reader scrolls
|
|
27
|
+
* away.
|
|
28
|
+
*
|
|
29
|
+
* Before this, eleven separate sites assigned `scrollTop = scrollHeight`
|
|
30
|
+
* unconditionally and nothing anywhere listened for a `scroll` event -- so
|
|
31
|
+
* nothing knew the reader had scrolled up, and scrolling back through a run was
|
|
32
|
+
* undone by the next token. Stick-to-bottom with a jump-to-latest affordance is
|
|
33
|
+
* a named primitive elsewhere for exactly this reason: shadcn ships it as
|
|
34
|
+
* `MessageScroller`, AI Elements as `ConversationScrollButton`.
|
|
35
|
+
*
|
|
36
|
+
* **Telling a reader's scroll from our own is the whole problem**, and the
|
|
37
|
+
* answer here is that it does not have to be told. A programmatic scroll only
|
|
38
|
+
* ever happens while already following, and it lands at the bottom, so the
|
|
39
|
+
* `scroll` event it provokes recomputes "at the bottom" as true and changes
|
|
40
|
+
* nothing. A reader's scroll is the only kind that can move the answer.
|
|
41
|
+
*
|
|
42
|
+
* A `ResizeObserver` covers the case scroll events cannot see: the *viewport*
|
|
43
|
+
* changing size. Resizing the panel, or the keyboard opening on a phone, moves
|
|
44
|
+
* the foot without anything scrolling and without any content arriving, so a
|
|
45
|
+
* pinned transcript would silently come unpinned.
|
|
46
|
+
*
|
|
47
|
+
* ⚠ It does **not** cover content that grows after insertion -- an image
|
|
48
|
+
* decoding, a chart laying out. A `ResizeObserver` on a scroll container does
|
|
49
|
+
* not fire when its `scrollHeight` changes, so catching that means observing
|
|
50
|
+
* every child, and the payoff is one late nudge in a case the reader can fix by
|
|
51
|
+
* scrolling. Insertion itself is covered: every site that adds to the
|
|
52
|
+
* transcript calls {@link StickToBottom.follow}.
|
|
53
|
+
*/
|
|
54
|
+
export declare function createStickToBottom({ viewport, onMissedContent, }: StickToBottomOptions): StickToBottom;
|
|
55
|
+
//# sourceMappingURL=stick_to_bottom.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stick_to_bottom.d.ts","sourceRoot":"","sources":["../../src/ui/stick_to_bottom.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC;IAC5B,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC;IAC1B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,EAAE,MAAM,OAAO,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,oBAAoB;IACnC,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CACrD;AAWD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,QAAQ,EACR,eAAe,GAChB,EAAE,oBAAoB,GAAG,aAAa,CA0DtC"}
|