@fungi.computer/parakeet 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/builtins.js +1 -1
- package/dist/commands/run.d.ts +3 -2
- package/dist/commands/run.js +5 -2
- package/dist/commands/slash.d.ts +2 -0
- package/dist/commands/slash.js +10 -2
- package/dist/commands/use-slash-commands.js +2 -2
- package/dist/components/AgentHead.d.ts +7 -12
- package/dist/components/AgentHead.js +10 -43
- package/dist/components/ShiitakeAvatar.d.ts +7 -5
- package/dist/components/ShiitakeAvatar.js +6 -4
- package/dist/components/avatar-presentation.d.ts +7 -14
- package/dist/components/avatar-presentation.js +6 -13
- package/dist/conversation.d.ts +3 -2
- package/dist/conversation.js +3 -1
- package/dist/core/tool-view.d.ts +1 -1
- package/dist/core/tool-view.js +1 -1
- package/dist/model-commands.js +1 -1
- package/dist/provider-choices.js +10 -7
- package/dist/styles.css +85 -112
- package/docs/api/index.md +11 -15
- package/package.json +9 -9
- package/src/styles.css +4 -0
- package/dist/components/still-frame-cache.d.ts +0 -16
- package/dist/components/still-frame-cache.js +0 -63
|
@@ -49,7 +49,7 @@ export function parakeetBuiltinCommands(runtime, options) {
|
|
|
49
49
|
category: "Advanced",
|
|
50
50
|
projections: {
|
|
51
51
|
help: { order: -2 },
|
|
52
|
-
slash: { aliases: ["keys"], order: -2 },
|
|
52
|
+
slash: { aliases: ["keys"], order: -2, consumesDraft: true },
|
|
53
53
|
},
|
|
54
54
|
handler: async (input) => {
|
|
55
55
|
const keys = keyBindings(runtime.snapshot());
|
package/dist/commands/run.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export declare function isCancelledOutcome(outcome: WhistleExecutionOutcome): bo
|
|
|
6
6
|
/**
|
|
7
7
|
* Run one composer draft: a prompt goes to `send`, a `/command` runs through
|
|
8
8
|
* Whistle. A grammar that asks for a request id keeps the same id across
|
|
9
|
-
* retries of the same draft until the command is handled.
|
|
9
|
+
* retries of the same draft until the command is handled. A slash projection
|
|
10
|
+
* can request draft consumption before dispatch, including while a dialog is open.
|
|
10
11
|
*/
|
|
11
|
-
export declare function runSlashDraft(draft: string, commands: SlashRuntime, send: () => void, origin: WhistleExecutionOrigin): Promise<WhistleExecutionOutcome | undefined>;
|
|
12
|
+
export declare function runSlashDraft(draft: string, commands: SlashRuntime, send: () => void, origin: WhistleExecutionOrigin, consumeDraft?: () => void): Promise<WhistleExecutionOutcome | undefined>;
|
package/dist/commands/run.js
CHANGED
|
@@ -12,9 +12,10 @@ export function isCancelledOutcome(outcome) {
|
|
|
12
12
|
/**
|
|
13
13
|
* Run one composer draft: a prompt goes to `send`, a `/command` runs through
|
|
14
14
|
* Whistle. A grammar that asks for a request id keeps the same id across
|
|
15
|
-
* retries of the same draft until the command is handled.
|
|
15
|
+
* retries of the same draft until the command is handled. A slash projection
|
|
16
|
+
* can request draft consumption before dispatch, including while a dialog is open.
|
|
16
17
|
*/
|
|
17
|
-
export async function runSlashDraft(draft, commands, send, origin) {
|
|
18
|
+
export async function runSlashDraft(draft, commands, send, origin, consumeDraft) {
|
|
18
19
|
const parsed = parseSlashDraft(slashRegistry(commands.snapshot()), draft);
|
|
19
20
|
if (parsed.kind === "prompt") {
|
|
20
21
|
send();
|
|
@@ -26,6 +27,8 @@ export async function runSlashDraft(draft, commands, send, origin) {
|
|
|
26
27
|
error: { code: "invalid_arguments", message: parsed.message },
|
|
27
28
|
};
|
|
28
29
|
const { command, requestIdField } = parsed;
|
|
30
|
+
if (command.consumesDraft && command.availability.status === "available")
|
|
31
|
+
consumeDraft?.();
|
|
29
32
|
if (parsed.arguments === undefined)
|
|
30
33
|
return commands.execute(command.commandId, { origin });
|
|
31
34
|
if (requestIdField === undefined)
|
package/dist/commands/slash.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export type SlashCommand = Readonly<{
|
|
|
5
5
|
alias: string;
|
|
6
6
|
/** Canonical Whistle command identity. */
|
|
7
7
|
commandId: string;
|
|
8
|
+
/** Consume this draft on dispatch, including for a dialog or refusal. */
|
|
9
|
+
consumesDraft?: boolean | undefined;
|
|
8
10
|
/** Human-facing title. */
|
|
9
11
|
title: string;
|
|
10
12
|
/** Optional longer explanation. */
|
package/dist/commands/slash.js
CHANGED
|
@@ -38,6 +38,7 @@ export function slashRegistry(snapshot) {
|
|
|
38
38
|
category: row.category,
|
|
39
39
|
order: row.order,
|
|
40
40
|
grammar: row.arguments,
|
|
41
|
+
consumesDraft: row.consumesDraft,
|
|
41
42
|
keys: keys.get(row.commandId) ?? [],
|
|
42
43
|
availability: row.availability,
|
|
43
44
|
}));
|
|
@@ -135,6 +136,7 @@ function rank(entries, text, query, order) {
|
|
|
135
136
|
text(left.entry).localeCompare(text(right.entry)))
|
|
136
137
|
.map(({ entry, match }) => ({ entry, indices: match.indices }));
|
|
137
138
|
}
|
|
139
|
+
const commonAliases = ["help", "login", "model"];
|
|
138
140
|
/** Rank completions for one draft; renderers draw exactly these rows. */
|
|
139
141
|
export function completeSlash(registry, draft, options = {}) {
|
|
140
142
|
if (!draft.startsWith("/") || draft.includes("\n"))
|
|
@@ -144,11 +146,17 @@ export function completeSlash(registry, draft, options = {}) {
|
|
|
144
146
|
const query = draft.slice(1);
|
|
145
147
|
const items = rank(registry.filter((command) => command.availability.status === "available" &&
|
|
146
148
|
(query.length === 0
|
|
147
|
-
? command.category === (options.idleCategory ?? "Conversation")
|
|
149
|
+
? command.category === (options.idleCategory ?? "Conversation") ||
|
|
150
|
+
commonAliases.includes(command.alias)
|
|
148
151
|
: command.category !== "Advanced" ||
|
|
149
152
|
command.alias
|
|
150
153
|
.toLocaleLowerCase()
|
|
151
|
-
.startsWith(query.toLocaleLowerCase()))), (command) => command.alias, query, (command) =>
|
|
154
|
+
.startsWith(query.toLocaleLowerCase()))), (command) => command.alias, query, (command) => {
|
|
155
|
+
const common = commonAliases.indexOf(command.alias);
|
|
156
|
+
return query.length === 0 && common >= 0
|
|
157
|
+
? common - commonAliases.length
|
|
158
|
+
: command.order;
|
|
159
|
+
}).map(({ entry, indices }) => ({
|
|
152
160
|
command: entry,
|
|
153
161
|
label: `/${entry.alias}`,
|
|
154
162
|
matches: indices.map((index) => index + 1),
|
|
@@ -123,9 +123,9 @@ export function useSlashCommands(options) {
|
|
|
123
123
|
store.report(undefined);
|
|
124
124
|
setDismissed(submitted);
|
|
125
125
|
void current
|
|
126
|
-
.submitCommand((captured) => runSlashDraft(captured, commands, () => {
|
|
126
|
+
.submitCommand((captured, consumeDraft) => runSlashDraft(captured, commands, () => {
|
|
127
127
|
current.send(delivery);
|
|
128
|
-
}, origin))
|
|
128
|
+
}, origin, consumeDraft))
|
|
129
129
|
.then((outcome) => {
|
|
130
130
|
const message = failureMessage(outcome);
|
|
131
131
|
if (message !== undefined &&
|
|
@@ -2,26 +2,21 @@ import type { Avatar } from "@fungi.computer/sprite/avatar";
|
|
|
2
2
|
/** What the head is doing: at rest, working something out, or talking. */
|
|
3
3
|
export type AgentHeadActivity = "idle" | "thinking" | "speaking";
|
|
4
4
|
/**
|
|
5
|
-
* An Agent's head at avatar size
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* live canvas, so a long rail is not a wall of animation. A render style
|
|
11
|
-
* other than "plain" is a CSS filter over the live canvas, not part of the
|
|
12
|
-
* captured pixels, so a styled head always stays live.
|
|
5
|
+
* An Agent's head at avatar size, drawn from the Agent's own avatar (a
|
|
6
|
+
* property of the Agent record, never of a conversation). The letter shows
|
|
7
|
+
* until a frame is ready. The live
|
|
8
|
+
* canvas stays mounted and is paused unless `thinking` or `speaking`, so an
|
|
9
|
+
* idle head shows its real look without animating.
|
|
13
10
|
*/
|
|
14
11
|
export declare const AgentHead: import("react").NamedExoticComponent<{
|
|
15
12
|
/** Motion presented by the head; defaults to idle. */
|
|
16
13
|
activity?: AgentHeadActivity;
|
|
17
|
-
/** The Agent's
|
|
18
|
-
avatar
|
|
14
|
+
/** The Agent's avatar, from its Agent record. */
|
|
15
|
+
avatar: Avatar;
|
|
19
16
|
/** Host classes applied to the head container. */
|
|
20
17
|
className?: string;
|
|
21
18
|
/** The name whose first letter stands in until a frame is ready. */
|
|
22
19
|
name: string;
|
|
23
|
-
/** Stable identity that picks the fallback cap colour. */
|
|
24
|
-
seed: string;
|
|
25
20
|
/** Width and height in pixels. */
|
|
26
21
|
size?: number;
|
|
27
22
|
}>;
|
|
@@ -1,53 +1,20 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Monogram } from "@fungi.computer/caps/components/monogram";
|
|
3
3
|
import { Sprite } from "@fungi.computer/sprite/react";
|
|
4
|
-
import { memo,
|
|
4
|
+
import { memo, useMemo, useState } from "react";
|
|
5
5
|
import { avatarPresentation } from "./avatar-presentation.js";
|
|
6
6
|
import { shiitakeSprite } from "./sprite-service.js";
|
|
7
|
-
import { captureStillFrame, getStillFrame, setStillFrame, stillFrameKey, } from "./still-frame-cache.js";
|
|
8
7
|
/**
|
|
9
|
-
* An Agent's head at avatar size
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* live canvas, so a long rail is not a wall of animation. A render style
|
|
15
|
-
* other than "plain" is a CSS filter over the live canvas, not part of the
|
|
16
|
-
* captured pixels, so a styled head always stays live.
|
|
8
|
+
* An Agent's head at avatar size, drawn from the Agent's own avatar (a
|
|
9
|
+
* property of the Agent record, never of a conversation). The letter shows
|
|
10
|
+
* until a frame is ready. The live
|
|
11
|
+
* canvas stays mounted and is paused unless `thinking` or `speaking`, so an
|
|
12
|
+
* idle head shows its real look without animating.
|
|
17
13
|
*/
|
|
18
|
-
export const AgentHead = memo(function AgentHead({ activity = "idle", avatar, className, name,
|
|
14
|
+
export const AgentHead = memo(function AgentHead({ activity = "idle", avatar, className, name, size = 24, }) {
|
|
19
15
|
const [ready, setReady] = useState(false);
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
const dpr = window.devicePixelRatio;
|
|
23
|
-
const cacheKey = useMemo(() => stillFrameKey(presentation, size, dpr), [presentation, size, dpr]);
|
|
24
|
-
const [frame, setFrame] = useState(() => getStillFrame(cacheKey));
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
setFrame(getStillFrame(cacheKey));
|
|
27
|
-
}, [cacheKey]);
|
|
28
|
-
// A render style is a CSS filter on the live stage (render-style.ts), not
|
|
29
|
-
// part of the captured canvas pixels, so a styled head must stay live;
|
|
30
|
-
// only a "plain" idle head can be replaced by its cached still frame.
|
|
31
|
-
const live = activity !== "idle" ||
|
|
32
|
-
frame === undefined ||
|
|
33
|
-
presentation.renderStyle !== "plain";
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
// A cached idle head has no player; its next player must become ready anew.
|
|
36
|
-
if (!live)
|
|
37
|
-
setReady(false);
|
|
38
|
-
}, [live]);
|
|
39
|
-
return (_jsxs("span", { "aria-hidden": "true", className: `relative inline-block shrink-0 ${className ?? ""}`, "data-agent-head": frame !== undefined || ready ? "ready" : "loading", style: { height: size, width: size }, children: [frame === undefined && !ready ? (_jsx(Monogram, { className: "absolute inset-0", name: name, seed: seed })) : null, frame !== undefined &&
|
|
40
|
-
presentation.renderStyle === "plain" &&
|
|
41
|
-
(!live || !ready) ? (_jsx("img", { alt: "", className: "absolute inset-0 h-full w-full", src: frame })) : null, live ? (_jsx(Sprite, { activity: activity, appearance: presentation.appearance, className: "absolute inset-0", expression: presentation.expression, onState: (state) => {
|
|
16
|
+
const presentation = useMemo(() => avatarPresentation(avatar), [avatar]);
|
|
17
|
+
return (_jsxs("span", { "aria-hidden": "true", className: `relative inline-block shrink-0 ${className ?? ""}`, "data-agent-head": ready ? "ready" : "loading", style: { height: size, width: size }, children: [ready ? null : (_jsx(Monogram, { className: "absolute inset-0", name: name, seed: name })), _jsx(Sprite, { activity: activity, appearance: presentation.appearance, className: "absolute inset-0", expression: presentation.expression, onState: (state) => {
|
|
42
18
|
setReady(state.status === "ready");
|
|
43
|
-
|
|
44
|
-
activity === "idle" &&
|
|
45
|
-
presentation.renderStyle === "plain") {
|
|
46
|
-
const captured = captureStillFrame(host.current);
|
|
47
|
-
if (captured !== undefined) {
|
|
48
|
-
setStillFrame(cacheKey, captured);
|
|
49
|
-
setFrame(captured);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}, paused: activity === "idle", ref: host, renderStyle: presentation.renderStyle, service: shiitakeSprite, src: presentation.src })) : null] }));
|
|
19
|
+
}, paused: activity === "idle", renderStyle: presentation.renderStyle, service: shiitakeSprite, src: presentation.src })] }));
|
|
53
20
|
});
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import type { Avatar } from "@fungi.computer/sprite/avatar";
|
|
2
2
|
import { type AvatarPresentation, type SpriteService } from "@fungi.computer/sprite";
|
|
3
|
-
/** The current activity and expression supplied to a
|
|
3
|
+
/** The current activity and expression supplied to a live avatar. */
|
|
4
4
|
export type AvatarState = Readonly<Pick<AvatarPresentation, "activity" | "expression">>;
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* A sprite head drawn in the live activity and expression it is given. With
|
|
7
|
+
* an `avatar` it draws that look (a member's own, in their settings); with
|
|
8
|
+
* none it is Shiitake, the product mascot, in the character's own defaults.
|
|
9
|
+
* Conversations have no avatar: an Agent's head is `AgentHead`, drawn from
|
|
10
|
+
* the Agent record.
|
|
9
11
|
*/
|
|
10
12
|
export declare const ShiitakeAvatar: import("react").NamedExoticComponent<{
|
|
11
|
-
/** The
|
|
13
|
+
/** The look to draw; omitted or `null` draws the mascot. */
|
|
12
14
|
avatar?: Avatar | null;
|
|
13
15
|
/** Replaces the default 3rem size, for a host that sizes the head itself. */
|
|
14
16
|
className?: string;
|
|
@@ -5,12 +5,14 @@ import { memo, useMemo } from "react";
|
|
|
5
5
|
import { avatarPresentation } from "./avatar-presentation.js";
|
|
6
6
|
import { shiitakeSprite } from "./sprite-service.js";
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* A sprite head drawn in the live activity and expression it is given. With
|
|
9
|
+
* an `avatar` it draws that look (a member's own, in their settings); with
|
|
10
|
+
* none it is Shiitake, the product mascot, in the character's own defaults.
|
|
11
|
+
* Conversations have no avatar: an Agent's head is `AgentHead`, drawn from
|
|
12
|
+
* the Agent record.
|
|
11
13
|
*/
|
|
12
14
|
export const ShiitakeAvatar = memo(function ShiitakeAvatar({ avatar, className = "h-12 w-12 shrink-0", service = shiitakeSprite, state, }) {
|
|
13
|
-
const presentation = useMemo(() => (avatar ? avatarPresentation(avatar
|
|
15
|
+
const presentation = useMemo(() => (avatar ? avatarPresentation(avatar) : undefined), [avatar]);
|
|
14
16
|
return (_jsx(Sprite, { "aria-hidden": "true", className: className, service: service, src: presentation?.src ?? { local: "mushroom" }, ...(presentation
|
|
15
17
|
? {
|
|
16
18
|
appearance: presentation.appearance,
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Avatar } from "@fungi.computer/sprite/avatar";
|
|
2
2
|
import type { AvatarPresentation as SpriteAvatarPresentation } from "@fungi.computer/sprite";
|
|
3
3
|
/**
|
|
4
|
-
* Sprite props for one avatar, independent of the live `activity` a
|
|
5
|
-
* drives moment to moment.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* service's own `AvatarPresentation`, these are never optional: the mapper
|
|
9
|
-
* always resolves a complete avatar, stored or legacy.
|
|
4
|
+
* Sprite props for one Agent's avatar, independent of the live `activity` a
|
|
5
|
+
* host drives moment to moment. Unlike the Sprite service's own
|
|
6
|
+
* `AvatarPresentation`, these are never optional: every Agent record carries
|
|
7
|
+
* a complete avatar.
|
|
10
8
|
*/
|
|
11
9
|
export type AvatarSpriteProps = {
|
|
12
10
|
readonly src: SpriteAvatarPresentation["src"];
|
|
@@ -14,10 +12,5 @@ export type AvatarSpriteProps = {
|
|
|
14
12
|
readonly expression: NonNullable<SpriteAvatarPresentation["expression"]>;
|
|
15
13
|
readonly renderStyle: NonNullable<SpriteAvatarPresentation["renderStyle"]>;
|
|
16
14
|
};
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
* Sprite props. The fallback is `legacyAvatar(seed)`: the mushroom, in the
|
|
20
|
-
* same seeded cap colour Agents have always worn, so an existing Agent
|
|
21
|
-
* renders byte-identically to before avatars existed.
|
|
22
|
-
*/
|
|
23
|
-
export declare function avatarPresentation(avatar: Avatar | null | undefined, seed: string): AvatarSpriteProps;
|
|
15
|
+
/** Map an Agent's avatar to Sprite props. */
|
|
16
|
+
export declare function avatarPresentation(avatar: Avatar): AvatarSpriteProps;
|
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* Map a stored avatar (or `null`/`undefined`, meaning none is saved yet) to
|
|
4
|
-
* Sprite props. The fallback is `legacyAvatar(seed)`: the mushroom, in the
|
|
5
|
-
* same seeded cap colour Agents have always worn, so an existing Agent
|
|
6
|
-
* renders byte-identically to before avatars existed.
|
|
7
|
-
*/
|
|
8
|
-
export function avatarPresentation(avatar, seed) {
|
|
9
|
-
const resolved = avatar ?? legacyAvatar(seed);
|
|
1
|
+
/** Map an Agent's avatar to Sprite props. */
|
|
2
|
+
export function avatarPresentation(avatar) {
|
|
10
3
|
return {
|
|
11
|
-
src: { local:
|
|
12
|
-
appearance:
|
|
13
|
-
expression:
|
|
14
|
-
renderStyle:
|
|
4
|
+
src: { local: avatar.character },
|
|
5
|
+
appearance: avatar.appearance,
|
|
6
|
+
expression: avatar.expression,
|
|
7
|
+
renderStyle: avatar.renderStyle,
|
|
15
8
|
};
|
|
16
9
|
}
|
package/dist/conversation.d.ts
CHANGED
|
@@ -98,9 +98,10 @@ export type ComposerController = Readonly<{
|
|
|
98
98
|
* Run a host command, such as Whistle slash dispatch, for the current draft.
|
|
99
99
|
* Parakeet clears the draft only when the command handled it without
|
|
100
100
|
* declining (a refused replacement keeps the text) and the draft was
|
|
101
|
-
* not edited while the command ran.
|
|
101
|
+
* not edited while the command ran. The callback can consume that exact
|
|
102
|
+
* draft on dispatch when the command definition requests it.
|
|
102
103
|
*/
|
|
103
|
-
submitCommand: (run: (draft: string) => Promise<WhistleExecutionOutcome | undefined>) => Promise<WhistleExecutionOutcome | undefined>;
|
|
104
|
+
submitCommand: (run: (draft: string, consumeDraft: () => void) => Promise<WhistleExecutionOutcome | undefined>) => Promise<WhistleExecutionOutcome | undefined>;
|
|
104
105
|
/** Replace the local input. */
|
|
105
106
|
setDraft: (value: string) => void;
|
|
106
107
|
}>;
|
package/dist/conversation.js
CHANGED
|
@@ -423,7 +423,9 @@ function createConversationAtoms(session, queryClient, authorOf) {
|
|
|
423
423
|
draft: get(draftAtom),
|
|
424
424
|
revision: get(draftRevisionAtom),
|
|
425
425
|
};
|
|
426
|
-
const outcome = await run(submitted.draft)
|
|
426
|
+
const outcome = await run(submitted.draft, () => {
|
|
427
|
+
set(clearSubmittedDraftAtom, submitted);
|
|
428
|
+
});
|
|
427
429
|
if (outcome?.status === "handled" && !isCancelledOutcome(outcome))
|
|
428
430
|
set(clearSubmittedDraftAtom, submitted);
|
|
429
431
|
return outcome;
|
package/dist/core/tool-view.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export declare const toolKindSigils: {
|
|
|
19
19
|
/** Artwork for file reads. */
|
|
20
20
|
readonly read: "read";
|
|
21
21
|
/** Artwork for search results. */
|
|
22
|
-
readonly search: "
|
|
22
|
+
readonly search: "search";
|
|
23
23
|
};
|
|
24
24
|
/** One changed or context line of a diff hunk. */
|
|
25
25
|
export type ToolDiffLine = Readonly<{
|
package/dist/core/tool-view.js
CHANGED
|
@@ -25,7 +25,7 @@ export const toolKindSigils = {
|
|
|
25
25
|
/** Artwork for file reads. */
|
|
26
26
|
read: "read",
|
|
27
27
|
/** Artwork for search results. */
|
|
28
|
-
search: "
|
|
28
|
+
search: "search",
|
|
29
29
|
};
|
|
30
30
|
/** Items shown before "more" for excerpts, matches, and output. */
|
|
31
31
|
export const toolPreviewLineLimit = 12;
|
package/dist/model-commands.js
CHANGED
|
@@ -198,7 +198,7 @@ export function sessionModelCommandDefinitions(session, options) {
|
|
|
198
198
|
projections: {
|
|
199
199
|
palette: { order: 1 },
|
|
200
200
|
help: {},
|
|
201
|
-
slash: { aliases: ["thinking"] },
|
|
201
|
+
slash: { aliases: ["thinking"], consumesDraft: true },
|
|
202
202
|
},
|
|
203
203
|
availability: options.availability ?? (() => ({ status: "available" })),
|
|
204
204
|
handler: async (input) => {
|
package/dist/provider-choices.js
CHANGED
|
@@ -18,13 +18,16 @@ export function providerChoices(providers, access, popularProviderIds, screen) {
|
|
|
18
18
|
.map((provider) => {
|
|
19
19
|
const methods = provider.methods.map((method) => method.kind === "api_key" ? "API key" : "Subscription");
|
|
20
20
|
const credential = signedIn.get(provider.id);
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
// Fungi's sign-in is the Fungi account; its Team pays, not a subscription.
|
|
22
|
+
const description = provider.id === "fungi"
|
|
23
|
+
? "Fungi account"
|
|
24
|
+
: methods.length > 0
|
|
25
|
+
? methods.join(" or ")
|
|
26
|
+
: credential?.type === "oauth"
|
|
27
|
+
? "Subscription"
|
|
28
|
+
: credential?.type === "api_key"
|
|
29
|
+
? "API key"
|
|
30
|
+
: undefined;
|
|
28
31
|
const choice = {
|
|
29
32
|
value: provider.id,
|
|
30
33
|
label: provider.name,
|
package/dist/styles.css
CHANGED
|
@@ -5743,9 +5743,6 @@
|
|
|
5743
5743
|
.mb-1 {
|
|
5744
5744
|
margin-bottom: calc(var(--spacing) * 1);
|
|
5745
5745
|
}
|
|
5746
|
-
.mb-xs {
|
|
5747
|
-
margin-bottom: var(--spacing-xs);
|
|
5748
|
-
}
|
|
5749
5746
|
.ml-2 {
|
|
5750
5747
|
margin-left: calc(var(--spacing) * 2);
|
|
5751
5748
|
}
|
|
@@ -6081,9 +6078,6 @@
|
|
|
6081
6078
|
.h-0 {
|
|
6082
6079
|
height: calc(var(--spacing) * 0);
|
|
6083
6080
|
}
|
|
6084
|
-
.h-4 {
|
|
6085
|
-
height: calc(var(--spacing) * 4);
|
|
6086
|
-
}
|
|
6087
6081
|
.h-8 {
|
|
6088
6082
|
height: calc(var(--spacing) * 8);
|
|
6089
6083
|
}
|
|
@@ -6152,12 +6146,6 @@
|
|
|
6152
6146
|
.w-12 {
|
|
6153
6147
|
width: calc(var(--spacing) * 12);
|
|
6154
6148
|
}
|
|
6155
|
-
.w-20 {
|
|
6156
|
-
width: calc(var(--spacing) * 20);
|
|
6157
|
-
}
|
|
6158
|
-
.w-\[calc\(100\%-1rem\)\] {
|
|
6159
|
-
width: calc(100% - 1rem);
|
|
6160
|
-
}
|
|
6161
6149
|
.w-\[min\(32rem\,calc\(100vw-2rem\)\)\] {
|
|
6162
6150
|
width: min(32rem, calc(100vw - 2rem));
|
|
6163
6151
|
}
|
|
@@ -6203,12 +6191,12 @@
|
|
|
6203
6191
|
.flex-shrink-0 {
|
|
6204
6192
|
flex-shrink: 0;
|
|
6205
6193
|
}
|
|
6194
|
+
.shrink {
|
|
6195
|
+
flex-shrink: 1;
|
|
6196
|
+
}
|
|
6206
6197
|
.shrink-0 {
|
|
6207
6198
|
flex-shrink: 0;
|
|
6208
6199
|
}
|
|
6209
|
-
.grow {
|
|
6210
|
-
flex-grow: 1;
|
|
6211
|
-
}
|
|
6212
6200
|
.border-collapse {
|
|
6213
6201
|
border-collapse: collapse;
|
|
6214
6202
|
}
|
|
@@ -6360,9 +6348,6 @@
|
|
|
6360
6348
|
.self-stretch {
|
|
6361
6349
|
align-self: stretch;
|
|
6362
6350
|
}
|
|
6363
|
-
.justify-self-end {
|
|
6364
|
-
justify-self: flex-end;
|
|
6365
|
-
}
|
|
6366
6351
|
.truncate {
|
|
6367
6352
|
overflow: hidden;
|
|
6368
6353
|
text-overflow: ellipsis;
|
|
@@ -6578,9 +6563,6 @@
|
|
|
6578
6563
|
.p-lg {
|
|
6579
6564
|
padding: var(--spacing-lg);
|
|
6580
6565
|
}
|
|
6581
|
-
.p-lg\! {
|
|
6582
|
-
padding: var(--spacing-lg) !important;
|
|
6583
|
-
}
|
|
6584
6566
|
.p-md {
|
|
6585
6567
|
padding: var(--spacing-md);
|
|
6586
6568
|
}
|
|
@@ -6590,9 +6572,6 @@
|
|
|
6590
6572
|
.p-xs {
|
|
6591
6573
|
padding: var(--spacing-xs);
|
|
6592
6574
|
}
|
|
6593
|
-
.p-xs\! {
|
|
6594
|
-
padding: var(--spacing-xs) !important;
|
|
6595
|
-
}
|
|
6596
6575
|
.caps-stat-compact {
|
|
6597
6576
|
& > .stat {
|
|
6598
6577
|
padding-block: var(--spacing-xs);
|
|
@@ -6608,9 +6587,6 @@
|
|
|
6608
6587
|
.px-3 {
|
|
6609
6588
|
padding-inline: calc(var(--spacing) * 3);
|
|
6610
6589
|
}
|
|
6611
|
-
.px-lg {
|
|
6612
|
-
padding-inline: var(--spacing-lg);
|
|
6613
|
-
}
|
|
6614
6590
|
.px-md {
|
|
6615
6591
|
padding-inline: var(--spacing-md);
|
|
6616
6592
|
}
|
|
@@ -6644,15 +6620,6 @@
|
|
|
6644
6620
|
.py-xs {
|
|
6645
6621
|
padding-block: var(--spacing-xs);
|
|
6646
6622
|
}
|
|
6647
|
-
.ps-md {
|
|
6648
|
-
padding-inline-start: var(--spacing-md);
|
|
6649
|
-
}
|
|
6650
|
-
.ps-xs {
|
|
6651
|
-
padding-inline-start: var(--spacing-xs);
|
|
6652
|
-
}
|
|
6653
|
-
.pe-sm {
|
|
6654
|
-
padding-inline-end: var(--spacing-sm);
|
|
6655
|
-
}
|
|
6656
6623
|
.pt-1\.5 {
|
|
6657
6624
|
padding-top: calc(var(--spacing) * 1.5);
|
|
6658
6625
|
}
|
|
@@ -7056,9 +7023,6 @@
|
|
|
7056
7023
|
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
|
7057
7024
|
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
|
7058
7025
|
}
|
|
7059
|
-
.transition-none {
|
|
7060
|
-
transition-property: none;
|
|
7061
|
-
}
|
|
7062
7026
|
.ease-in {
|
|
7063
7027
|
--tw-ease: var(--ease-in);
|
|
7064
7028
|
transition-timing-function: var(--ease-in);
|
|
@@ -7324,20 +7288,6 @@
|
|
|
7324
7288
|
}
|
|
7325
7289
|
}
|
|
7326
7290
|
}
|
|
7327
|
-
.hover\:gap-md {
|
|
7328
|
-
&:hover {
|
|
7329
|
-
@media (hover: hover) {
|
|
7330
|
-
gap: var(--spacing-md);
|
|
7331
|
-
}
|
|
7332
|
-
}
|
|
7333
|
-
}
|
|
7334
|
-
.hover\:gap-sm {
|
|
7335
|
-
&:hover {
|
|
7336
|
-
@media (hover: hover) {
|
|
7337
|
-
gap: var(--spacing-sm);
|
|
7338
|
-
}
|
|
7339
|
-
}
|
|
7340
|
-
}
|
|
7341
7291
|
.hover\:bg-base-200 {
|
|
7342
7292
|
&:hover {
|
|
7343
7293
|
@media (hover: hover) {
|
|
@@ -7373,11 +7323,6 @@
|
|
|
7373
7323
|
}
|
|
7374
7324
|
}
|
|
7375
7325
|
}
|
|
7376
|
-
.focus\:gap-xs {
|
|
7377
|
-
&:focus {
|
|
7378
|
-
gap: var(--spacing-xs);
|
|
7379
|
-
}
|
|
7380
|
-
}
|
|
7381
7326
|
.focus\:\!outline-none {
|
|
7382
7327
|
&:focus {
|
|
7383
7328
|
--tw-outline-style: none !important;
|
|
@@ -7538,16 +7483,6 @@
|
|
|
7538
7483
|
}
|
|
7539
7484
|
}
|
|
7540
7485
|
}
|
|
7541
|
-
.data-\[open\]\:bg-base-200 {
|
|
7542
|
-
&[data-open] {
|
|
7543
|
-
background-color: var(--color-base-200);
|
|
7544
|
-
}
|
|
7545
|
-
}
|
|
7546
|
-
.data-\[open\]\:bg-base-300 {
|
|
7547
|
-
&[data-open] {
|
|
7548
|
-
background-color: var(--color-base-300);
|
|
7549
|
-
}
|
|
7550
|
-
}
|
|
7551
7486
|
.motion-reduce\:transition-none {
|
|
7552
7487
|
@media (prefers-reduced-motion: reduce) {
|
|
7553
7488
|
transition-property: none;
|
|
@@ -7568,16 +7503,6 @@
|
|
|
7568
7503
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
7569
7504
|
}
|
|
7570
7505
|
}
|
|
7571
|
-
.md\:px-lg {
|
|
7572
|
-
@media (width >= 48rem) {
|
|
7573
|
-
padding-inline: var(--spacing-lg);
|
|
7574
|
-
}
|
|
7575
|
-
}
|
|
7576
|
-
.md\:px-xs {
|
|
7577
|
-
@media (width >= 48rem) {
|
|
7578
|
-
padding-inline: var(--spacing-xs);
|
|
7579
|
-
}
|
|
7580
|
-
}
|
|
7581
7506
|
.md\:btn-xs {
|
|
7582
7507
|
@media (width >= 48rem) {
|
|
7583
7508
|
@layer daisyui.l1.l2 {
|
|
@@ -9262,37 +9187,6 @@
|
|
|
9262
9187
|
font-family: var(--font-mono);
|
|
9263
9188
|
line-height: 1;
|
|
9264
9189
|
}
|
|
9265
|
-
.caps-led {
|
|
9266
|
-
display: inline-flex;
|
|
9267
|
-
align-items: center;
|
|
9268
|
-
gap: var(--spacing-xs);
|
|
9269
|
-
color: var(--marginalia-ink);
|
|
9270
|
-
font-size: 0.75rem;
|
|
9271
|
-
}
|
|
9272
|
-
.caps-led-bead {
|
|
9273
|
-
width: 0.625rem;
|
|
9274
|
-
height: 0.625rem;
|
|
9275
|
-
flex: none;
|
|
9276
|
-
border: var(--card-rule-width) solid var(--card-rule);
|
|
9277
|
-
border-radius: var(--radius-selector);
|
|
9278
|
-
background: var(--caps-led-color);
|
|
9279
|
-
box-shadow: 0 0 0 var(--card-rule-width) var(--caps-led-color);
|
|
9280
|
-
@supports (color: color-mix(in lab, red, red)) {
|
|
9281
|
-
box-shadow: 0 0 0 var(--card-rule-width) color-mix(in srgb, var(--caps-led-color) 36%, transparent);
|
|
9282
|
-
}
|
|
9283
|
-
}
|
|
9284
|
-
.caps-led[data-tone="spore"] {
|
|
9285
|
-
--caps-led-color: var(--growth-spore);
|
|
9286
|
-
}
|
|
9287
|
-
.caps-led[data-tone="mycelium"] {
|
|
9288
|
-
--caps-led-color: var(--growth-mycelium);
|
|
9289
|
-
}
|
|
9290
|
-
.caps-led[data-tone="fruiting"] {
|
|
9291
|
-
--caps-led-color: var(--growth-fruiting);
|
|
9292
|
-
}
|
|
9293
|
-
.caps-led[data-active="false"] {
|
|
9294
|
-
--caps-led-color: var(--marginalia-ink);
|
|
9295
|
-
}
|
|
9296
9190
|
.caps-cartridge-label {
|
|
9297
9191
|
display: grid;
|
|
9298
9192
|
justify-items: center;
|
|
@@ -14185,6 +14079,88 @@
|
|
|
14185
14079
|
min-height: 44px;
|
|
14186
14080
|
}
|
|
14187
14081
|
}
|
|
14082
|
+
.caps-window-header:has(> .caps-window-crumbs) {
|
|
14083
|
+
min-height: 40px;
|
|
14084
|
+
}
|
|
14085
|
+
.caps-window-crumbs {
|
|
14086
|
+
display: flex;
|
|
14087
|
+
align-items: center;
|
|
14088
|
+
min-width: 0;
|
|
14089
|
+
flex-shrink: 1;
|
|
14090
|
+
font-family: var(--caps-label-font, inherit);
|
|
14091
|
+
font-size: var(--text-sm);
|
|
14092
|
+
font-weight: 500;
|
|
14093
|
+
}
|
|
14094
|
+
.caps-window-crumb-item[data-caps-crumb-position="current"] {
|
|
14095
|
+
font-weight: 600;
|
|
14096
|
+
}
|
|
14097
|
+
.caps-window-crumb-item {
|
|
14098
|
+
display: flex;
|
|
14099
|
+
align-items: center;
|
|
14100
|
+
min-width: 0;
|
|
14101
|
+
}
|
|
14102
|
+
.caps-window-crumb-separator {
|
|
14103
|
+
padding-inline: var(--spacing-2xs);
|
|
14104
|
+
color: currentColor;
|
|
14105
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
14106
|
+
color: color-mix(in srgb, currentColor 50%, transparent);
|
|
14107
|
+
}
|
|
14108
|
+
}
|
|
14109
|
+
.caps-window-crumb {
|
|
14110
|
+
min-width: 0;
|
|
14111
|
+
font-size: inherit;
|
|
14112
|
+
font-weight: inherit;
|
|
14113
|
+
gap: var(--spacing-2xs);
|
|
14114
|
+
}
|
|
14115
|
+
.caps-window-crumb-label {
|
|
14116
|
+
overflow: hidden;
|
|
14117
|
+
text-overflow: ellipsis;
|
|
14118
|
+
white-space: nowrap;
|
|
14119
|
+
}
|
|
14120
|
+
.caps-window-crumb-item[data-caps-crumb-position="current"] .caps-window-crumb {
|
|
14121
|
+
padding-inline: var(--spacing-xs);
|
|
14122
|
+
}
|
|
14123
|
+
.caps-window-crumbs:has(+ .caps-window-title) {
|
|
14124
|
+
max-width: 70%;
|
|
14125
|
+
}
|
|
14126
|
+
.caps-window-statuses {
|
|
14127
|
+
display: flex;
|
|
14128
|
+
align-items: center;
|
|
14129
|
+
gap: var(--spacing-sm);
|
|
14130
|
+
flex-shrink: 0;
|
|
14131
|
+
padding-inline-end: var(--spacing-sm);
|
|
14132
|
+
font-size: var(--text-sm);
|
|
14133
|
+
color: currentColor;
|
|
14134
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
14135
|
+
color: color-mix(in srgb, currentColor 65%, transparent);
|
|
14136
|
+
}
|
|
14137
|
+
}
|
|
14138
|
+
.caps-window-commands {
|
|
14139
|
+
display: flex;
|
|
14140
|
+
align-items: center;
|
|
14141
|
+
gap: var(--spacing-2xs);
|
|
14142
|
+
flex-shrink: 0;
|
|
14143
|
+
}
|
|
14144
|
+
@media (max-width: 640px) {
|
|
14145
|
+
.caps-window-statuses {
|
|
14146
|
+
gap: var(--spacing-2xs);
|
|
14147
|
+
padding-inline-end: var(--spacing-2xs);
|
|
14148
|
+
}
|
|
14149
|
+
.caps-window-status > span {
|
|
14150
|
+
position: absolute;
|
|
14151
|
+
width: 1px;
|
|
14152
|
+
height: 1px;
|
|
14153
|
+
overflow: hidden;
|
|
14154
|
+
clip-path: inset(50%);
|
|
14155
|
+
white-space: nowrap;
|
|
14156
|
+
}
|
|
14157
|
+
.caps-window-crumb-item[data-caps-crumb-position="ancestor"] {
|
|
14158
|
+
display: none;
|
|
14159
|
+
}
|
|
14160
|
+
.caps-window-crumb-item[data-caps-crumb-position="parent"] .caps-window-crumb-separator {
|
|
14161
|
+
display: none;
|
|
14162
|
+
}
|
|
14163
|
+
}
|
|
14188
14164
|
.caps-window-body {
|
|
14189
14165
|
flex: 1;
|
|
14190
14166
|
min-height: 0;
|
|
@@ -15222,9 +15198,6 @@
|
|
|
15222
15198
|
font-size: var(--text-xs);
|
|
15223
15199
|
white-space: nowrap;
|
|
15224
15200
|
}
|
|
15225
|
-
.caps-status-compact .caps-status-bead {
|
|
15226
|
-
transform: scale(0.6);
|
|
15227
|
-
}
|
|
15228
15201
|
.caps-action-row-trailing {
|
|
15229
15202
|
display: flex;
|
|
15230
15203
|
width: 6rem;
|
package/docs/api/index.md
CHANGED
|
@@ -42,7 +42,7 @@ type AvatarState = Readonly<
|
|
|
42
42
|
>;
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
The current activity and expression supplied to a
|
|
45
|
+
The current activity and expression supplied to a live avatar.
|
|
46
46
|
|
|
47
47
|
---
|
|
48
48
|
|
|
@@ -802,7 +802,7 @@ Stable row grammar rendered by Parakeet's timeline.
|
|
|
802
802
|
```ts
|
|
803
803
|
const AgentHead: NamedExoticComponent<{
|
|
804
804
|
activity?: AgentHeadActivity;
|
|
805
|
-
avatar
|
|
805
|
+
avatar: {
|
|
806
806
|
v: 1;
|
|
807
807
|
character: "mushroom" | "cat" | "burger";
|
|
808
808
|
appearance: z.ZodRecord<
|
|
@@ -819,22 +819,17 @@ const AgentHead: NamedExoticComponent<{
|
|
|
819
819
|
| "blueprint"
|
|
820
820
|
| "crt"
|
|
821
821
|
| "watercolor";
|
|
822
|
-
}
|
|
822
|
+
};
|
|
823
823
|
className?: string;
|
|
824
824
|
name: string;
|
|
825
|
-
seed: string;
|
|
826
825
|
size?: number;
|
|
827
826
|
}>;
|
|
828
827
|
```
|
|
829
828
|
|
|
830
|
-
An Agent's head at avatar size
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
`
|
|
834
|
-
has a cached still frame for its look never mounts a live canvas, so a long rail
|
|
835
|
-
is not a wall of animation. A render style other than "plain" is a CSS filter
|
|
836
|
-
over the live canvas, not part of the captured pixels, so a styled head always
|
|
837
|
-
stays live.
|
|
829
|
+
An Agent's head at avatar size, drawn from the Agent's own avatar (a property of
|
|
830
|
+
the Agent record, never of a conversation). The letter shows until a frame is
|
|
831
|
+
ready. The live canvas stays mounted and is paused unless `thinking` or
|
|
832
|
+
`speaking`, so an idle head shows its real look without animating.
|
|
838
833
|
|
|
839
834
|
---
|
|
840
835
|
|
|
@@ -866,9 +861,10 @@ const ShiitakeAvatar: NamedExoticComponent<{
|
|
|
866
861
|
}>;
|
|
867
862
|
```
|
|
868
863
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
864
|
+
A sprite head drawn in the live activity and expression it is given. With an
|
|
865
|
+
`avatar` it draws that look (a member's own, in their settings); with none it is
|
|
866
|
+
Shiitake, the product mascot, in the character's own defaults. Conversations
|
|
867
|
+
have no avatar: an Agent's head is `AgentHead`, drawn from the Agent record.
|
|
872
868
|
|
|
873
869
|
---
|
|
874
870
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fungi.computer/parakeet",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"author": "Levi <levi@technomancy.dev>",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Standalone React composer for a typed Shiitake Session",
|
|
@@ -44,13 +44,13 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@fungi.computer/cairn": "0.0.1",
|
|
47
|
-
"@fungi.computer/caps": "0.0
|
|
48
|
-
"@fungi.computer/knapsack": "0.0
|
|
49
|
-
"@fungi.computer/shiitake": "0.
|
|
50
|
-
"@fungi.computer/sigils": "0.0
|
|
51
|
-
"@fungi.computer/sprite": "0.0
|
|
52
|
-
"@fungi.computer/sprite-zdog": "0.0
|
|
53
|
-
"@fungi.computer/whistle": "0.0.
|
|
47
|
+
"@fungi.computer/caps": "0.1.0",
|
|
48
|
+
"@fungi.computer/knapsack": "0.1.0",
|
|
49
|
+
"@fungi.computer/shiitake": "0.2.0",
|
|
50
|
+
"@fungi.computer/sigils": "0.1.0",
|
|
51
|
+
"@fungi.computer/sprite": "0.1.0",
|
|
52
|
+
"@fungi.computer/sprite-zdog": "0.1.0",
|
|
53
|
+
"@fungi.computer/whistle": "0.0.2",
|
|
54
54
|
"@tanstack/markdown": "0.0.12",
|
|
55
55
|
"@tanstack/react-virtual": "3.14.10",
|
|
56
56
|
"diff": "8.0.4",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"lucide-react": "0.460.0",
|
|
60
60
|
"strip-ansi": "7.2.0",
|
|
61
61
|
"zod": "4.4.3",
|
|
62
|
-
"@fungi.computer/gills": "0.0
|
|
62
|
+
"@fungi.computer/gills": "0.1.0"
|
|
63
63
|
},
|
|
64
64
|
"dependenciesMeta": {
|
|
65
65
|
"@fungi.computer/caps": {
|
package/src/styles.css
CHANGED
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
@import "@fungi.computer/caps/tailwind.css";
|
|
7
7
|
|
|
8
|
+
/* In the workspace Tailwind scans the component source; published, this
|
|
9
|
+
* entry ships without it, and the compiled components in dist carry the same
|
|
10
|
+
* class names. */
|
|
8
11
|
@source "./";
|
|
12
|
+
@source "../dist/**/*.js";
|
|
9
13
|
|
|
10
14
|
@plugin "@tailwindcss/typography";
|
|
11
15
|
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { AvatarSpriteProps } from "./avatar-presentation.js";
|
|
2
|
-
/** The still-frame cache key for one presentation at one pixel size. */
|
|
3
|
-
export declare function stillFrameKey(presentation: AvatarSpriteProps, size: number, devicePixelRatio: number): string;
|
|
4
|
-
/** The cached still frame for `key`, refreshing its recency, or `undefined`. */
|
|
5
|
-
export declare function getStillFrame(key: string): string | undefined;
|
|
6
|
-
/** Cache `value` (a data URL) for `key`, evicting the oldest entry past 128. */
|
|
7
|
-
export declare function setStillFrame(key: string, value: string): void;
|
|
8
|
-
/**
|
|
9
|
-
* Capture the idle frame of a just-rendered Sprite host as a still image, or
|
|
10
|
-
* `undefined` when the host has no live canvas yet (not ready) or the
|
|
11
|
-
* canvas export is denied because the canvas is tainted. Other export failures
|
|
12
|
-
* propagate with their cause.
|
|
13
|
-
*/
|
|
14
|
-
export declare function captureStillFrame(host: HTMLElement | null): string | undefined;
|
|
15
|
-
/** Test-only: clears every cached still frame so tests do not leak between runs. */
|
|
16
|
-
export declare function clearStillFrameCache(): void;
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
const MAX_ENTRIES = 128;
|
|
2
|
-
/**
|
|
3
|
-
* Idle heads shown over and over (an Agent rail) do not need their own live
|
|
4
|
-
* canvas: once one instance of a descriptor has rendered, every other idle
|
|
5
|
-
* instance can show the same still frame. Keyed by the resolved presentation
|
|
6
|
-
* plus the pixel size it was drawn at, so a resize or a different size
|
|
7
|
-
* elsewhere never serves the wrong frame. An LRU of about 128 entries; the
|
|
8
|
-
* oldest is evicted once that is exceeded.
|
|
9
|
-
*/
|
|
10
|
-
const cache = new Map();
|
|
11
|
-
/** The still-frame cache key for one presentation at one pixel size. */
|
|
12
|
-
export function stillFrameKey(presentation, size, devicePixelRatio) {
|
|
13
|
-
return JSON.stringify([
|
|
14
|
-
presentation.src,
|
|
15
|
-
presentation.appearance,
|
|
16
|
-
presentation.expression,
|
|
17
|
-
presentation.renderStyle,
|
|
18
|
-
size,
|
|
19
|
-
devicePixelRatio,
|
|
20
|
-
]);
|
|
21
|
-
}
|
|
22
|
-
/** The cached still frame for `key`, refreshing its recency, or `undefined`. */
|
|
23
|
-
export function getStillFrame(key) {
|
|
24
|
-
const value = cache.get(key);
|
|
25
|
-
if (value === undefined)
|
|
26
|
-
return undefined;
|
|
27
|
-
cache.delete(key);
|
|
28
|
-
cache.set(key, value);
|
|
29
|
-
return value;
|
|
30
|
-
}
|
|
31
|
-
/** Cache `value` (a data URL) for `key`, evicting the oldest entry past 128. */
|
|
32
|
-
export function setStillFrame(key, value) {
|
|
33
|
-
cache.delete(key);
|
|
34
|
-
cache.set(key, value);
|
|
35
|
-
if (cache.size <= MAX_ENTRIES)
|
|
36
|
-
return;
|
|
37
|
-
const oldestKey = cache.keys().next().value;
|
|
38
|
-
if (oldestKey !== undefined)
|
|
39
|
-
cache.delete(oldestKey);
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Capture the idle frame of a just-rendered Sprite host as a still image, or
|
|
43
|
-
* `undefined` when the host has no live canvas yet (not ready) or the
|
|
44
|
-
* canvas export is denied because the canvas is tainted. Other export failures
|
|
45
|
-
* propagate with their cause.
|
|
46
|
-
*/
|
|
47
|
-
export function captureStillFrame(host) {
|
|
48
|
-
const canvas = host?.querySelector("canvas");
|
|
49
|
-
if (!canvas)
|
|
50
|
-
return undefined;
|
|
51
|
-
try {
|
|
52
|
-
return canvas.toDataURL("image/png");
|
|
53
|
-
}
|
|
54
|
-
catch (cause) {
|
|
55
|
-
if (cause instanceof DOMException && cause.name === "SecurityError")
|
|
56
|
-
return undefined;
|
|
57
|
-
throw new Error("Could not capture an avatar still frame", { cause });
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
/** Test-only: clears every cached still frame so tests do not leak between runs. */
|
|
61
|
-
export function clearStillFrameCache() {
|
|
62
|
-
cache.clear();
|
|
63
|
-
}
|