@datalayer/agent-runtimes 1.5.43 → 1.5.45
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-embed/chunks/1f30a-DxwAt2Ba.js +1 -0
- package/dist-embed/chunks/1f310-AqUM9a8q.js +1 -0
- package/dist-embed/chunks/1f324-8MHLOhTj.js +1 -0
- package/dist-embed/chunks/1f326-YSBr3aXI.js +1 -0
- package/dist-embed/chunks/1f327-Ie-nQBNO.js +1 -0
- package/dist-embed/chunks/1f33e-Cfl2WM63.js +1 -0
- package/dist-embed/chunks/1f372-3XLDlXkE.js +1 -0
- package/dist-embed/chunks/1f373-BPUJt2bp.js +1 -0
- package/dist-embed/chunks/1f39b-CSNtbAXr.js +1 -0
- package/dist-embed/chunks/1f3ab-BUCNQHz1.js +1 -0
- package/dist-embed/chunks/1f41b-Dre1z0gP.js +1 -0
- package/dist-embed/chunks/1f441-D8yah4C4.js +1 -0
- package/dist-embed/chunks/1f465-CPle6Hjq.js +1 -0
- package/dist-embed/chunks/1f4b9-aacFyckt.js +1 -0
- package/dist-embed/chunks/1f4be-DPAcAU4R.js +1 -0
- package/dist-embed/chunks/1f4c1-Ch6aJ0cj.js +1 -0
- package/dist-embed/chunks/1f4c2-Dx7BKmLO.js +1 -0
- package/dist-embed/chunks/1f4c4-CNvjvC5k.js +1 -0
- package/dist-embed/chunks/1f4d3-GTxRgjv7.js +1 -0
- package/dist-embed/chunks/1f4d6-COOZY-Q_.js +1 -0
- package/dist-embed/chunks/1f4dc-BhoW4cjD.js +1 -0
- package/dist-embed/chunks/1f4e1-DezU8ow-.js +1 -0
- package/dist-embed/chunks/1f4e2-DsStZsI-.js +1 -0
- package/dist-embed/chunks/1f4e7-BBi7c_zx.js +1 -0
- package/dist-embed/chunks/1f500-D9xRo-IT.js +1 -0
- package/dist-embed/chunks/1f501-CzKeDPcw.js +1 -0
- package/dist-embed/chunks/1f504-BYIHMoDP.js +1 -0
- package/dist-embed/chunks/1f52d-2QxCLKhU.js +1 -0
- package/dist-embed/chunks/1f552-4d0Y0kn7.js +1 -0
- package/dist-embed/chunks/1f58b-Cp8F_HBg.js +1 -0
- package/dist-embed/chunks/1f5b2-D6BUULuh.js +1 -0
- package/dist-embed/chunks/1f5c3-DjkgJLaz.js +1 -0
- package/dist-embed/chunks/1f5d1-C4Kpe4HL.js +1 -0
- package/dist-embed/chunks/1f5dc-DyLO7aB9.js +1 -0
- package/dist-embed/chunks/1f604-CEekcaYE.js +1 -0
- package/dist-embed/chunks/1f917-CyyS9fgf.js +1 -0
- package/dist-embed/chunks/1f9d1-200d-2696-D9eSB-0q.js +1 -0
- package/dist-embed/chunks/1fa79-DHhIZbWB.js +1 -0
- package/dist-embed/chunks/1fa9d-DUI9e77m.js +1 -0
- package/dist-embed/chunks/1fa9f-10ALsMaD.js +1 -0
- package/dist-embed/chunks/23f3-ASjqFsbT.js +1 -0
- package/dist-embed/chunks/2611-BnnmLm-K.js +1 -0
- package/dist-embed/chunks/2702-CfLyp35r.js +1 -0
- package/dist-embed/chunks/270f-CLwo30mk.js +1 -0
- package/dist-embed/chunks/2728-Qg6be-6p.js +1 -0
- package/dist-embed/chunks/2b1c-Uc1aRwAf.js +1 -0
- package/dist-embed/chunks/{ChatView-kCmBbnDa.js → ChatView-Dy-IgGFL.js} +1 -1
- package/dist-embed/datalayer-app-main.js +111 -106
- package/lib/apps/plugins/chat/ChatView.js +2 -2
- package/lib/chat/ChatFloating.js +1 -1
- package/lib/chat/ChatInline.js +43 -22
- package/lib/chat/base/ChatBase.js +66 -10
- package/lib/chat/messages/ChatMessageList.d.ts +7 -1
- package/lib/chat/messages/ChatMessageList.js +8 -2
- package/lib/components/agents/AgentSummary.d.ts +2 -24
- package/lib/components/agents/AgentSummary.js +3 -19
- package/lib/hooks/useChat.js +110 -144
- package/lib/portable/activity.d.ts +78 -0
- package/lib/portable/activity.js +129 -0
- package/lib/portable/fixtures.d.ts +7 -0
- package/lib/portable/fixtures.js +50 -0
- package/lib/portable/index.d.ts +4 -1
- package/lib/portable/index.js +7 -1
- package/lib/portable/lifecycle.d.ts +77 -0
- package/lib/portable/lifecycle.js +135 -0
- package/lib/portable/runtimes.d.ts +68 -0
- package/lib/portable/runtimes.js +69 -0
- package/lib/protocols/VercelAIAdapter.js +11 -0
- package/lib/specs/voices.js +62 -0
- package/lib/types/chat.d.ts +10 -0
- package/lib/voice/ComposerVoice.js +12 -3
- package/lib/voice/VoiceMenu.d.ts +2 -1
- package/lib/voice/VoiceMenu.js +2 -2
- package/lib/voice/captions.d.ts +31 -0
- package/lib/voice/captions.js +156 -0
- package/lib/voice/chatVoice.d.ts +5 -2
- package/lib/voice/chatVoice.js +10 -4
- package/lib/voice/choices.d.ts +2 -0
- package/lib/voice/choices.js +6 -0
- package/lib/voice/handsFree.d.ts +12 -0
- package/lib/voice/handsFree.js +18 -0
- package/lib/voice/index.d.ts +1 -0
- package/lib/voice/index.js +1 -0
- package/lib/voice/sayAloud.d.ts +25 -0
- package/lib/voice/sayAloud.js +34 -0
- package/lib/voice/serverHearing.d.ts +63 -2
- package/lib/voice/serverHearing.js +311 -7
- package/lib/voice/settings.d.ts +12 -0
- package/lib/voice/settings.js +9 -0
- package/package.json +1 -1
- package/scripts/voice/measure.py +77 -17
package/lib/portable/index.js
CHANGED
|
@@ -27,7 +27,7 @@ export { conversationEventsOf, sanitizeAssistantContent, snapshotOfMessages, } f
|
|
|
27
27
|
export { PRESENCE_LINES, presenceOfConversation, presenceState, presenceToolOf, } from './presence';
|
|
28
28
|
// One contract for every renderer of a conversation's components.
|
|
29
29
|
export { ANSWER_BELOW, ANSWER_COMPLETE, composerOf, droppedWords, ENDED_WORDS, messageLabel, OPENING_WORDS, presenceLine, showsQuestion, stepLine, TOOL_STATUS_WORDS, toolLabel, } from './views';
|
|
30
|
-
export { conversationFixture } from './fixtures';
|
|
30
|
+
export { activityFixture, conversationFixture } from './fixtures';
|
|
31
31
|
// The protocol displays a run's tool results carry (MOBILE M-037).
|
|
32
32
|
export { DISPLAY_META_KEY, displayLine, displayOfToolResult, PAP_CONFIRM_TYPE, PAP_HANDOFF_TOOL, PAP_OUTCOME_TYPE, PAP_REPLIES_TYPE, PAP_SIGN_IN_TYPE, protocolDisplayOf, replyAuthor,
|
|
33
33
|
// A coding agent's diff and terminal (SHIELD §4.3, SH-16).
|
|
@@ -42,5 +42,11 @@ export { SIGN_IN_POLL_MS, SIGN_IN_STATUS_PATH, signInStandsOf, signInStatusClien
|
|
|
42
42
|
export { emptyFields, fieldProblems, MAX_VALUE_LENGTH, PROTECTED_INPUT_WORDS, protectedInputClient, protectedRequestOf, ProtectedRequestGone, sentWords, submitBody, submitWords, ticketComplete, ticketOfPage, } from './protectedInput';
|
|
43
43
|
// How a streamed turn went, measured without its words (MOBILE M-036).
|
|
44
44
|
export { failureOf, otlpMetricsOf, RunMeter, } from './runMeasures';
|
|
45
|
+
// What an application is doing, and its record's entries (LOOP R-15, MOBILE M-015b).
|
|
46
|
+
export { ACTIVITY_WORDS, activityOf, entryOf, entrySaid, mergeActivityPages, runEndOf, scheduledSays, sessionSentence, wakesOn, } from './activity';
|
|
47
|
+
// A deployment's pause, resume and reset, and its recorded events (LOOP R-17, MOBILE M-041b).
|
|
48
|
+
export { deploymentEventLine, deploymentEventOf, notDeletedOf, resetBody, resetCountsOf, resetLines, stateChangeBody, stoppedWorkLine, stoppedWorkOf, } from './lifecycle';
|
|
49
|
+
// A person's agent runtimes, and an agent's summary (MOBILE M-014b).
|
|
50
|
+
export { agentRuntimesOf, agentSummaryOfRuntime, CREATING_STATUSES, isAgentRuntime, isCreatingAgent, runtimeOf, } from './runtimes';
|
|
45
51
|
// The runtime's routes a native client drives.
|
|
46
52
|
export { agentRunClient, agentRuntimesUrlOf, appSessionsClient, RuntimeRefusal, } from './sessions';
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A deployment's lifecycle as its owner drives it (LOOP R-17, D-04): pause
|
|
3
|
+
* and resume one exact version, reset what it holds, and the events recorded
|
|
4
|
+
* with who did each. The requests' bodies, the answers read and the words
|
|
5
|
+
* said — the same in the Studio and Datalayer Mobile (MOBILE M-041b). Pure:
|
|
6
|
+
* each client sends them with its own authentication.
|
|
7
|
+
*
|
|
8
|
+
* @module portable/lifecycle
|
|
9
|
+
*/
|
|
10
|
+
/** Whether a deployment answers now. */
|
|
11
|
+
export type DeploymentState = 'live' | 'paused';
|
|
12
|
+
/** Pause or resume, for one exact version: `PATCH /apps/deployments/{uid}`'s body. */
|
|
13
|
+
export declare const stateChangeBody: (version: number, state: DeploymentState) => {
|
|
14
|
+
version: number;
|
|
15
|
+
state: DeploymentState;
|
|
16
|
+
};
|
|
17
|
+
/** What pausing stopped, and what it could not, with why. */
|
|
18
|
+
export type StoppedWork = {
|
|
19
|
+
stopped: {
|
|
20
|
+
what: string;
|
|
21
|
+
runtime: string;
|
|
22
|
+
}[];
|
|
23
|
+
notStopped: {
|
|
24
|
+
what: string;
|
|
25
|
+
runtime: string;
|
|
26
|
+
why: string;
|
|
27
|
+
}[];
|
|
28
|
+
};
|
|
29
|
+
export declare function stoppedWorkOf(raw: Record<string, unknown>): StoppedWork;
|
|
30
|
+
/** What pausing did, in a sentence. */
|
|
31
|
+
export declare function stoppedWorkLine(work: StoppedWork): string;
|
|
32
|
+
/** What a Reset deletes, counted for its confirmation. */
|
|
33
|
+
export type ResetCounts = {
|
|
34
|
+
conversations: number;
|
|
35
|
+
entries: number;
|
|
36
|
+
schedules: number;
|
|
37
|
+
/**
|
|
38
|
+
* Why its memories stay: they are its application's, not the
|
|
39
|
+
* deployment's (LOOP R-18) — forgotten under *What it remembers*.
|
|
40
|
+
*/
|
|
41
|
+
memoriesNote: string;
|
|
42
|
+
};
|
|
43
|
+
export declare function resetCountsOf(raw: Record<string, unknown>): ResetCounts;
|
|
44
|
+
/** The Reset confirmed against the counts the person was shown: `POST …/reset`'s body. */
|
|
45
|
+
export declare const resetBody: (counts: Pick<ResetCounts, "conversations" | "schedules">) => {
|
|
46
|
+
conversations: number;
|
|
47
|
+
schedules: number;
|
|
48
|
+
};
|
|
49
|
+
/** What a Reset could not delete, with why: `POST …/reset`'s answer. */
|
|
50
|
+
export declare function notDeletedOf(body: Record<string, unknown>): {
|
|
51
|
+
what: string;
|
|
52
|
+
why: string;
|
|
53
|
+
}[];
|
|
54
|
+
/**
|
|
55
|
+
* What goes, as the confirmation lists it — "12 conversations, 2
|
|
56
|
+
* schedules" — and what does not, said apart.
|
|
57
|
+
*/
|
|
58
|
+
export declare function resetLines(counts: ResetCounts): {
|
|
59
|
+
goes: string;
|
|
60
|
+
stays: string[];
|
|
61
|
+
};
|
|
62
|
+
/** What happened to a deployment, recorded with who did it (D-04, R-17). */
|
|
63
|
+
export type DeploymentEventKind = 'promoted' | 'rolled_back' | 'paused' | 'resumed' | 'reset';
|
|
64
|
+
/** Who promoted, rolled back, paused, resumed or reset a deployment, and when. */
|
|
65
|
+
export type DeploymentEvent = {
|
|
66
|
+
uid: string;
|
|
67
|
+
kind: DeploymentEventKind;
|
|
68
|
+
actor: string;
|
|
69
|
+
at: string;
|
|
70
|
+
details: Record<string, unknown>;
|
|
71
|
+
};
|
|
72
|
+
export declare function deploymentEventOf(raw: Record<string, unknown>): DeploymentEvent;
|
|
73
|
+
/**
|
|
74
|
+
* One event, in a line: "Paused by eric, 4 Oct 2026, 10:12", "Rolled back
|
|
75
|
+
* from version 3 to version 2 by eric, …".
|
|
76
|
+
*/
|
|
77
|
+
export declare function deploymentEventLine(event: DeploymentEvent, locale?: string): string;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/** Pause or resume, for one exact version: `PATCH /apps/deployments/{uid}`'s body. */
|
|
6
|
+
export const stateChangeBody = (version, state) => {
|
|
7
|
+
if (!Number.isInteger(version) || version < 1) {
|
|
8
|
+
throw new Error(`A deployment's state is changed for one exact version: ${String(version)} is none.`);
|
|
9
|
+
}
|
|
10
|
+
return { version, state };
|
|
11
|
+
};
|
|
12
|
+
const listOf = (raw) => Array.isArray(raw)
|
|
13
|
+
? raw.filter(item => item && typeof item === 'object')
|
|
14
|
+
: [];
|
|
15
|
+
const said = (value) => typeof value === 'string' ? value : value == null ? '' : String(value);
|
|
16
|
+
const count = (value) => {
|
|
17
|
+
const n = Number(value);
|
|
18
|
+
return Number.isFinite(n) && n > 0 ? Math.trunc(n) : 0;
|
|
19
|
+
};
|
|
20
|
+
const plural = (n, word) => `${n} ${word}${n === 1 ? '' : 's'}`;
|
|
21
|
+
export function stoppedWorkOf(raw) {
|
|
22
|
+
return {
|
|
23
|
+
stopped: listOf(raw.stopped).map(item => ({
|
|
24
|
+
what: said(item.what),
|
|
25
|
+
runtime: said(item.runtime),
|
|
26
|
+
})),
|
|
27
|
+
notStopped: listOf(raw.not_stopped).map(item => ({
|
|
28
|
+
what: said(item.what),
|
|
29
|
+
runtime: said(item.runtime),
|
|
30
|
+
why: said(item.why),
|
|
31
|
+
})),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** What pausing did, in a sentence. */
|
|
35
|
+
export function stoppedWorkLine(work) {
|
|
36
|
+
const stopped = work.stopped.length;
|
|
37
|
+
const first = stopped === 0
|
|
38
|
+
? 'Paused: nothing was running.'
|
|
39
|
+
: `Paused: ${plural(stopped, 'session')} in progress stopped.`;
|
|
40
|
+
if (!work.notStopped.length) {
|
|
41
|
+
return first;
|
|
42
|
+
}
|
|
43
|
+
const missed = work.notStopped
|
|
44
|
+
.map(item => `${item.what}${item.runtime ? ` (${item.runtime})` : ''}: ${item.why}`)
|
|
45
|
+
.join(' ');
|
|
46
|
+
return `${first} Not stopped — ${missed}`;
|
|
47
|
+
}
|
|
48
|
+
export function resetCountsOf(raw) {
|
|
49
|
+
return {
|
|
50
|
+
conversations: count(raw.conversations),
|
|
51
|
+
entries: count(raw.entries),
|
|
52
|
+
schedules: count(raw.schedules),
|
|
53
|
+
memoriesNote: typeof raw.memories_note === 'string' ? raw.memories_note : '',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/** The Reset confirmed against the counts the person was shown: `POST …/reset`'s body. */
|
|
57
|
+
export const resetBody = (counts) => ({
|
|
58
|
+
conversations: counts.conversations,
|
|
59
|
+
schedules: counts.schedules,
|
|
60
|
+
});
|
|
61
|
+
/** What a Reset could not delete, with why: `POST …/reset`'s answer. */
|
|
62
|
+
export function notDeletedOf(body) {
|
|
63
|
+
return listOf(body.not_deleted).map(item => ({
|
|
64
|
+
what: said(item.what),
|
|
65
|
+
why: said(item.why),
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* What goes, as the confirmation lists it — "12 conversations, 2
|
|
70
|
+
* schedules" — and what does not, said apart.
|
|
71
|
+
*/
|
|
72
|
+
export function resetLines(counts) {
|
|
73
|
+
return {
|
|
74
|
+
goes: [
|
|
75
|
+
plural(counts.conversations, 'conversation'),
|
|
76
|
+
plural(counts.schedules, 'schedule'),
|
|
77
|
+
].join(', '),
|
|
78
|
+
stays: [
|
|
79
|
+
'The deployment, its address and the version it runs stay.',
|
|
80
|
+
...(counts.memoriesNote ? [counts.memoriesNote] : []),
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const EVENT_KINDS = [
|
|
85
|
+
'promoted',
|
|
86
|
+
'rolled_back',
|
|
87
|
+
'paused',
|
|
88
|
+
'resumed',
|
|
89
|
+
'reset',
|
|
90
|
+
];
|
|
91
|
+
export function deploymentEventOf(raw) {
|
|
92
|
+
if (!EVENT_KINDS.includes(raw.kind)) {
|
|
93
|
+
throw new Error(`A deployment's event of no known kind: ${String(raw.kind)}.`);
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
uid: said(raw.uid),
|
|
97
|
+
kind: raw.kind,
|
|
98
|
+
actor: said(raw.actor_handle || raw.actor_uid || ''),
|
|
99
|
+
at: typeof raw.at === 'string' ? raw.at : '',
|
|
100
|
+
details: raw.details && typeof raw.details === 'object'
|
|
101
|
+
? raw.details
|
|
102
|
+
: {},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* One event, in a line: "Paused by eric, 4 Oct 2026, 10:12", "Rolled back
|
|
107
|
+
* from version 3 to version 2 by eric, …".
|
|
108
|
+
*/
|
|
109
|
+
export function deploymentEventLine(event, locale) {
|
|
110
|
+
const from = count(event.details.from);
|
|
111
|
+
const to = count(event.details.to);
|
|
112
|
+
const moved = from && to ? ` from version ${from} to version ${to}` : '';
|
|
113
|
+
const verb = {
|
|
114
|
+
promoted: `Promoted${moved}`,
|
|
115
|
+
rolled_back: `Rolled back${moved}`,
|
|
116
|
+
paused: 'Paused',
|
|
117
|
+
resumed: 'Resumed',
|
|
118
|
+
reset: 'Reset',
|
|
119
|
+
}[event.kind];
|
|
120
|
+
const when = event.at
|
|
121
|
+
? new Date(event.at).toLocaleString(locale, {
|
|
122
|
+
dateStyle: 'medium',
|
|
123
|
+
timeStyle: 'short',
|
|
124
|
+
})
|
|
125
|
+
: '';
|
|
126
|
+
const deleted = event.details.deleted;
|
|
127
|
+
const what = event.kind === 'reset' && deleted
|
|
128
|
+
? ` — ${plural(count(deleted.conversations), 'conversation')} and ${plural(count(deleted.schedules), 'schedule')} deleted`
|
|
129
|
+
: '';
|
|
130
|
+
// Paused by an owner of its owner's organization (I-09): said as such.
|
|
131
|
+
const forOrganization = event.kind === 'paused' && event.details.by === 'organization_owner'
|
|
132
|
+
? ', an owner of your organization'
|
|
133
|
+
: '';
|
|
134
|
+
return `${verb}${event.actor ? ` by ${event.actor}${forOrganization}` : ''}${when ? `, ${when}` : ''}${what}`;
|
|
135
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A person's agent runtimes as the runtimes service lists them
|
|
3
|
+
* (`GET /api/runtimes/v1/runtimes`), and an agent's summary and status as
|
|
4
|
+
* every client says them — the web's `AgentSummary` badge and Datalayer
|
|
5
|
+
* Mobile's Agents (MOBILE M-014b). A raw runtime carries no application
|
|
6
|
+
* capabilities: no Rules, Protocols or Activity are read from it.
|
|
7
|
+
*
|
|
8
|
+
* @module portable/runtimes
|
|
9
|
+
*/
|
|
10
|
+
/** One agent runtime, as a client lists it. */
|
|
11
|
+
export type AgentRuntime = {
|
|
12
|
+
uid: string;
|
|
13
|
+
runtimeName: string;
|
|
14
|
+
givenName: string;
|
|
15
|
+
environment: string;
|
|
16
|
+
/** The agentspec it runs, when the runtime says. */
|
|
17
|
+
agentSpecId: string;
|
|
18
|
+
type: string;
|
|
19
|
+
state: string;
|
|
20
|
+
startedAt: string;
|
|
21
|
+
expiredAt: string;
|
|
22
|
+
/** Its Jupyter address, from which its agents' is read (`agentRuntimesUrlOf`). */
|
|
23
|
+
ingress: string;
|
|
24
|
+
};
|
|
25
|
+
/** A runtime of the runtimes service's answer. */
|
|
26
|
+
export declare function runtimeOf(raw: Record<string, unknown>): AgentRuntime;
|
|
27
|
+
/** The runtimes service's list, agent runtimes only: notebooks and terminals belong to the web workspace. */
|
|
28
|
+
export declare function agentRuntimesOf(body: Record<string, unknown>): AgentRuntime[];
|
|
29
|
+
/** An agent runtime, not a notebook's or a terminal's. */
|
|
30
|
+
export declare const isAgentRuntime: (runtime: AgentRuntime) => boolean;
|
|
31
|
+
/** What a summary of an agent says of it. */
|
|
32
|
+
export interface AgentSummaryData {
|
|
33
|
+
agentName: string;
|
|
34
|
+
location: string;
|
|
35
|
+
specId?: string;
|
|
36
|
+
/**
|
|
37
|
+
* The framework turning this agent's loop, as its spec declares it —
|
|
38
|
+
* `pydantic-ai` server-side, `vercel-ai` in the page.
|
|
39
|
+
*/
|
|
40
|
+
harness?: string;
|
|
41
|
+
/**
|
|
42
|
+
* The runtime variant the host chose: `<location>-<harness>`.
|
|
43
|
+
*
|
|
44
|
+
* Shown next to the harness rather than instead of it, because they answer
|
|
45
|
+
* different questions and can disagree: the harness is what the spec asked
|
|
46
|
+
* for, the variant is what it actually got.
|
|
47
|
+
*/
|
|
48
|
+
variant?: string;
|
|
49
|
+
status?: string;
|
|
50
|
+
baseUrl?: string;
|
|
51
|
+
sandboxBaseUrl?: string;
|
|
52
|
+
agentId?: string;
|
|
53
|
+
isReady?: boolean;
|
|
54
|
+
error?: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Statuses that represent an agent actively being created/started. The spinner
|
|
58
|
+
* only shows for these; app-navigation states ("selected", "switching") and
|
|
59
|
+
* settled states ("ready", "running", ...) never spin.
|
|
60
|
+
*/
|
|
61
|
+
export declare const CREATING_STATUSES: ReadonlySet<string>;
|
|
62
|
+
/**
|
|
63
|
+
* An agent genuinely being created: it is not ready, has no id yet, and its
|
|
64
|
+
* status is one of creation or startup.
|
|
65
|
+
*/
|
|
66
|
+
export declare function isCreatingAgent(summary: AgentSummaryData): boolean;
|
|
67
|
+
/** The summary of an agent the runtimes service lists: named as given, at Datalayer. */
|
|
68
|
+
export declare function agentSummaryOfRuntime(runtime: AgentRuntime): AgentSummaryData;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
6
|
+
const text = (value) => typeof value === 'string' ? value : '';
|
|
7
|
+
/** A runtime of the runtimes service's answer. */
|
|
8
|
+
export function runtimeOf(raw) {
|
|
9
|
+
const environment = isRecord(raw.environment) ? raw.environment : {};
|
|
10
|
+
return {
|
|
11
|
+
uid: text(raw.uid),
|
|
12
|
+
runtimeName: text(raw.runtime_name) || text(raw.pod_name),
|
|
13
|
+
givenName: text(raw.given_name),
|
|
14
|
+
environment: text(environment.title) ||
|
|
15
|
+
text(environment.name) ||
|
|
16
|
+
text(raw.environment_title) ||
|
|
17
|
+
text(raw.environment_name),
|
|
18
|
+
agentSpecId: text(raw.agent_spec_id) || text(raw.attached_agent_spec_id_s),
|
|
19
|
+
type: text(raw.type),
|
|
20
|
+
state: text(raw.state) || text(raw.status) || 'running',
|
|
21
|
+
startedAt: text(raw.started_at),
|
|
22
|
+
expiredAt: text(raw.expired_at),
|
|
23
|
+
ingress: text(raw.ingress),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** The runtimes service's list, agent runtimes only: notebooks and terminals belong to the web workspace. */
|
|
27
|
+
export function agentRuntimesOf(body) {
|
|
28
|
+
const listed = Array.isArray(body.runtimes) ? body.runtimes : [];
|
|
29
|
+
return listed
|
|
30
|
+
.filter(isRecord)
|
|
31
|
+
.map(runtimeOf)
|
|
32
|
+
.filter(runtime => runtime.runtimeName && isAgentRuntime(runtime));
|
|
33
|
+
}
|
|
34
|
+
/** An agent runtime, not a notebook's or a terminal's. */
|
|
35
|
+
export const isAgentRuntime = (runtime) => runtime.type === 'agent' ||
|
|
36
|
+
Boolean(runtime.agentSpecId) ||
|
|
37
|
+
/agent/i.test(runtime.environment);
|
|
38
|
+
/**
|
|
39
|
+
* Statuses that represent an agent actively being created/started. The spinner
|
|
40
|
+
* only shows for these; app-navigation states ("selected", "switching") and
|
|
41
|
+
* settled states ("ready", "running", ...) never spin.
|
|
42
|
+
*/
|
|
43
|
+
export const CREATING_STATUSES = new Set([
|
|
44
|
+
'creating',
|
|
45
|
+
'starting',
|
|
46
|
+
'launching',
|
|
47
|
+
'connecting',
|
|
48
|
+
'pending',
|
|
49
|
+
'resuming',
|
|
50
|
+
]);
|
|
51
|
+
/**
|
|
52
|
+
* An agent genuinely being created: it is not ready, has no id yet, and its
|
|
53
|
+
* status is one of creation or startup.
|
|
54
|
+
*/
|
|
55
|
+
export function isCreatingAgent(summary) {
|
|
56
|
+
return (!summary.isReady &&
|
|
57
|
+
!summary.agentId &&
|
|
58
|
+
CREATING_STATUSES.has((summary.status || '').toLowerCase()));
|
|
59
|
+
}
|
|
60
|
+
/** The summary of an agent the runtimes service lists: named as given, at Datalayer. */
|
|
61
|
+
export function agentSummaryOfRuntime(runtime) {
|
|
62
|
+
return {
|
|
63
|
+
agentName: runtime.givenName || runtime.runtimeName,
|
|
64
|
+
location: 'datalayer',
|
|
65
|
+
specId: runtime.agentSpecId || undefined,
|
|
66
|
+
status: runtime.state,
|
|
67
|
+
isReady: !CREATING_STATUSES.has(runtime.state.toLowerCase()),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -627,6 +627,17 @@ export class VercelAIAdapter extends BaseProtocolAdapter {
|
|
|
627
627
|
});
|
|
628
628
|
}
|
|
629
629
|
}
|
|
630
|
+
else if (typeof event.type === 'string' &&
|
|
631
|
+
event.type.startsWith('data-')) {
|
|
632
|
+
// What the server says besides the conversation's text, as
|
|
633
|
+
// AG-UI's custom events say it: `data-voice.say`, a line the
|
|
634
|
+
// agent says aloud, and `data-voice.settings` (VOICE.md VO-36).
|
|
635
|
+
this.emit({
|
|
636
|
+
type: 'activity',
|
|
637
|
+
activity: { type: event.type, data: event.data },
|
|
638
|
+
timestamp: new Date(),
|
|
639
|
+
});
|
|
640
|
+
}
|
|
630
641
|
}
|
|
631
642
|
catch (parseError) {
|
|
632
643
|
console.error('[VercelAIAdapter] Failed to parse SSE data:', parseError);
|
package/lib/specs/voices.js
CHANGED
|
@@ -100,6 +100,68 @@ export const SPEECH_MODEL_CATALOGUE = {
|
|
|
100
100
|
},
|
|
101
101
|
],
|
|
102
102
|
},
|
|
103
|
+
'moonshine-base-en-server': {
|
|
104
|
+
id: 'moonshine-base-en-server',
|
|
105
|
+
version: '0.0.1',
|
|
106
|
+
name: 'Moonshine Base (English, server)',
|
|
107
|
+
description: "Speech to text on Datalayer's servers, in English: Useful Sensors' Moonshine Base (61M parameters), the same 8-bit ONNX export the browser runs, run by onnxruntime on the CPU with a greedy decoder. Its encoder is as long as the speech, with no 30-second window: measured beside Whisper small for short spoken turns (decision 18).",
|
|
108
|
+
provider: 'usefulsensors',
|
|
109
|
+
task: 'stt',
|
|
110
|
+
engine: 'onnxruntime',
|
|
111
|
+
dtype: 'q8',
|
|
112
|
+
languages: ['en'],
|
|
113
|
+
where: ['server'],
|
|
114
|
+
streaming: false,
|
|
115
|
+
licence: {
|
|
116
|
+
weights: 'MIT',
|
|
117
|
+
code: 'MIT',
|
|
118
|
+
},
|
|
119
|
+
attribution: '',
|
|
120
|
+
upstream: 'https://huggingface.co/UsefulSensors/moonshine-base',
|
|
121
|
+
revision: 'b1e9b6aae3c3c7298f10c3798393fdf38e8fbbad',
|
|
122
|
+
files: [
|
|
123
|
+
{
|
|
124
|
+
path: 'config.json',
|
|
125
|
+
sha256: 'fab7241d1e9fc6c2370c4c6dfb5da79bb54d67ed9ab6b507ac51d29d2abe01d1',
|
|
126
|
+
size: 922,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
path: 'generation_config.json',
|
|
130
|
+
sha256: 'f9b3f711b57be7def2e50a8942f64f36ee0a55fad5b84ff93a687b6c5bcc1d44',
|
|
131
|
+
size: 147,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
path: 'preprocessor_config.json',
|
|
135
|
+
sha256: 'fa43a7017ef85cd1d0fba0d9aae77c8adb16990ae6f11115631f41ec5d8aa679',
|
|
136
|
+
size: 128,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
path: 'special_tokens_map.json',
|
|
140
|
+
sha256: 'ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356',
|
|
141
|
+
size: 3,
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
path: 'tokenizer.json',
|
|
145
|
+
sha256: '7b913404bdd039af4756783218af4440bc07fb7d6d8258d677e34f95b3ec416f',
|
|
146
|
+
size: 3761754,
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
path: 'tokenizer_config.json',
|
|
150
|
+
sha256: 'edaee394565d428ea98a663ae7209cdcfeefc5585c42d7a570ff7c986df2cd15',
|
|
151
|
+
size: 135735,
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
path: 'onnx/encoder_model_quantized.onnx',
|
|
155
|
+
sha256: '1dd9ab0a7f987113d30affcba5a068d11c8f90fa0223caa3e491ade431ad9751',
|
|
156
|
+
size: 20513063,
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
path: 'onnx/decoder_model_merged_quantized.onnx',
|
|
160
|
+
sha256: 'cc9f3cd6698a369c6008b41aa60aa3fb3322e7f03c9bdf19d8e6b7200afca4f3',
|
|
161
|
+
size: 42498870,
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
},
|
|
103
165
|
'moonshine-base-en': {
|
|
104
166
|
id: 'moonshine-base-en',
|
|
105
167
|
version: '0.0.1',
|
package/lib/types/chat.d.ts
CHANGED
|
@@ -689,6 +689,11 @@ export interface ChatBaseProps {
|
|
|
689
689
|
* Absent while its answers are not heard.
|
|
690
690
|
*/
|
|
691
691
|
stopSpeaking?: () => void;
|
|
692
|
+
/**
|
|
693
|
+
* Marks the answer the person spoke over *interrupted* (VOICE.md
|
|
694
|
+
* VO-13), for a host whose composer holds the microphone.
|
|
695
|
+
*/
|
|
696
|
+
markInterrupted: () => void;
|
|
692
697
|
} | null) => void;
|
|
693
698
|
/**
|
|
694
699
|
* Reports whether the agent's voice is speaking, for a host that draws the
|
|
@@ -1013,6 +1018,11 @@ export interface ChatBaseProps {
|
|
|
1013
1018
|
voice?: ChatVoice;
|
|
1014
1019
|
/** The agent is speaking: the composer offers *Stop speaking* (`Esc`). */
|
|
1015
1020
|
voiceSpeaking?: boolean;
|
|
1021
|
+
/**
|
|
1022
|
+
* The sentence the host's speaker is saying, marked in the conversation
|
|
1023
|
+
* (VOICE.md VO-65); a chat that speaks for itself marks its own.
|
|
1024
|
+
*/
|
|
1025
|
+
voiceSentence?: string;
|
|
1016
1026
|
/** Stops the agent's voice: the composer's *Stop speaking*, and the microphone opening (VO-13). */
|
|
1017
1027
|
onStopSpeaking?: () => void;
|
|
1018
1028
|
/**
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useMemo, useRef } from 'react';
|
|
3
3
|
import { DeviceHearing } from './hearing';
|
|
4
4
|
import { HandsFree } from './HandsFree';
|
|
5
|
-
import { ServerHearing, hearingPlace } from './serverHearing';
|
|
5
|
+
import { AutoHearing, ServerHearing, hearingPlace } from './serverHearing';
|
|
6
6
|
import { VoiceInput } from './VoiceInput';
|
|
7
7
|
import { VoiceMenu } from './VoiceMenu';
|
|
8
8
|
/** Whether something rides on a run as a spoken message's metadata (VO-27). */
|
|
@@ -24,13 +24,20 @@ export function ComposerVoice({ voice, input, setInput, onSpoken, send, disabled
|
|
|
24
24
|
return undefined;
|
|
25
25
|
}
|
|
26
26
|
const device = new DeviceHearing(voice.modelsUrl, voice.engines);
|
|
27
|
-
|
|
27
|
+
const server = voice.speechUrl
|
|
28
28
|
? new ServerHearing(device, {
|
|
29
29
|
speechUrl: voice.speechUrl,
|
|
30
30
|
token: voice.token,
|
|
31
31
|
detect: voice.detect,
|
|
32
32
|
})
|
|
33
|
-
:
|
|
33
|
+
: undefined;
|
|
34
|
+
// `where: auto` (§5): the device when it hears the language and its model
|
|
35
|
+
// loads, the server otherwise — and back to the device when the server
|
|
36
|
+
// refuses (a visitor's day spent).
|
|
37
|
+
if (server && !voice.detect && (voice.where ?? 'auto') === 'auto') {
|
|
38
|
+
return new AutoHearing(device, server, voice.language);
|
|
39
|
+
}
|
|
40
|
+
return place === 'server' && server ? server : device;
|
|
34
41
|
}, [
|
|
35
42
|
voice.input,
|
|
36
43
|
voice.modelsUrl,
|
|
@@ -39,6 +46,8 @@ export function ComposerVoice({ voice, input, setInput, onSpoken, send, disabled
|
|
|
39
46
|
voice.speechUrl,
|
|
40
47
|
voice.token,
|
|
41
48
|
voice.detect,
|
|
49
|
+
voice.where,
|
|
50
|
+
voice.language,
|
|
42
51
|
]);
|
|
43
52
|
// What the box held when the microphone opened: the words heard go after it.
|
|
44
53
|
const base = useRef('');
|
package/lib/voice/VoiceMenu.d.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* The composer's voice menu (VOICE.md VO-14, VO-21, VO-25, VO-30): beside
|
|
3
3
|
* the microphone, what a person chooses for themselves — the language they
|
|
4
4
|
* speak and hear, when answers are heard, the voice, its speed and loudness,
|
|
5
|
-
*
|
|
5
|
+
* how long they pause before they are done (VO-69), *Send what I say*, the
|
|
6
|
+
* microphone, and *Keep speech on this device*. Each
|
|
6
7
|
* choice is kept in the person's settings and heard at once by every chat
|
|
7
8
|
* on the page; *Use the application's* forgets them all.
|
|
8
9
|
*
|
package/lib/voice/VoiceMenu.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { ActionList, ActionMenu, IconButton } from '@primer/react';
|
|
4
4
|
import { GearIcon } from '@primer/octicons-react';
|
|
5
|
-
import { OUTPUT_CHOICES, RATE_CHOICES, VOLUME_CHOICES, catalogueVoiceSpeaks, voiceLanguages, voicesSpeaking, } from './choices';
|
|
5
|
+
import { OUTPUT_CHOICES, PAUSE_CHOICES, RATE_CHOICES, VOLUME_CHOICES, catalogueVoiceSpeaks, voiceLanguages, voicesSpeaking, } from './choices';
|
|
6
6
|
import { withVoiceSetting } from './settings';
|
|
7
7
|
import { useVoiceSettings } from './useVoiceSettings';
|
|
8
8
|
/** The microphones the browser lists; named only once one was allowed. */
|
|
@@ -28,6 +28,6 @@ export function VoiceMenu({ voice, answers, disabled = false, }) {
|
|
|
28
28
|
void microphones().then(setDevices);
|
|
29
29
|
}
|
|
30
30
|
}, children: [_jsx(ActionMenu.Anchor, { children: _jsx(IconButton, { icon: GearIcon, size: "small", variant: "invisible", "aria-label": "Voice settings", "data-voice-menu": "", disabled: disabled }) }), _jsx(ActionMenu.Overlay, { width: "medium", children: _jsxs(ActionList, { children: [_jsxs(ActionList.Group, { selectionVariant: "single", children: [_jsx(ActionList.GroupHeading, { children: "Language" }), voiceLanguages().map(language => (_jsx(ActionList.Item, { selected: language.value === voice.language, onSelect: () => choose('language', language.value), "data-voice-language": language.value, children: language.label }, language.value)))] }), answers && !onDevice ? (_jsxs(_Fragment, { children: [_jsx(ActionList.Divider, {}), _jsxs(ActionList.Group, { selectionVariant: "single", children: [_jsx(ActionList.GroupHeading, { children: "Answers heard" }), OUTPUT_CHOICES.map(output => (_jsx(ActionList.Item, { selected: output.value === voice.output, onSelect: () => choose('output', output.value), "data-voice-output": output.value, children: output.label }, output.value)))] }), voices.length > 0 ? (_jsxs(ActionList.Group, { selectionVariant: "single", children: [_jsx(ActionList.GroupHeading, { children: "Voice" }), voices.map(spoken => (_jsx(ActionList.Item, { selected: spoken.id === voice.voice, onSelect: () => choose('voice', spoken.id), "data-voice-voice": spoken.id, children: spoken.name }, spoken.id)))] })) : null, _jsxs(ActionList.Group, { selectionVariant: "single", children: [_jsx(ActionList.GroupHeading, { children: "Speed" }), RATE_CHOICES.map(rate => (_jsx(ActionList.Item, { selected: rate.value === (voice.rate ?? 1), onSelect: () => choose('rate', rate.value), "data-voice-rate": rate.value, children: rate.label }, rate.value)))] }), _jsxs(ActionList.Group, { selectionVariant: "single", children: [_jsx(ActionList.GroupHeading, { children: "Loudness" }), VOLUME_CHOICES.map(volume => (_jsx(ActionList.Item, { selected: volume.value === (voice.volume ?? 1), onSelect: () => choose('volume', volume.value), "data-voice-volume": volume.value, children: volume.label }, volume.value)))] })] })) : null, named.length > 1 ? (_jsxs(_Fragment, { children: [_jsx(ActionList.Divider, {}), _jsxs(ActionList.Group, { selectionVariant: "single", children: [_jsx(ActionList.GroupHeading, { children: "Microphone" }), named.map(device => (_jsx(ActionList.Item, { selected: device.deviceId ===
|
|
31
|
-
(voice.microphoneId ?? named[0].deviceId), onSelect: () => choose('microphoneId', device.deviceId), "data-voice-microphone": device.deviceId, children: device.label }, device.deviceId)))] })] })) : null, _jsx(ActionList.Divider, {}), _jsxs(ActionList.Group, { selectionVariant: "multiple", children: [_jsxs(ActionList.Item, { selected: Boolean(voice.sendWhatISay), onSelect: () => choose('sendWhatISay', !voice.sendWhatISay), "data-voice-send-what-i-say": "", children: ["Send what I say", _jsx(ActionList.Description, { variant: "block", children: "Sent as soon as it is heard, not left in the box." })] }), voice.speechUrl && !onDevice ? (_jsxs(ActionList.Item, { selected: detect, onSelect: () => choose('detectLanguage', !detect), "data-voice-detect": "", children: ["Detect the language I speak", _jsx(ActionList.Description, { variant: "block", children: "Heard on Datalayer\u2019s servers by Whisper, which finds the language; nothing is kept there." })] })) : null, _jsxs(ActionList.Item, { selected: onDevice, onSelect: () => choose('onDeviceOnly', !onDevice), "data-voice-on-device": "", children: ["Keep speech on this device", _jsx(ActionList.Description, { variant: "block", children: "Nothing is sent to Datalayer\u2019s speech service: you are heard in the browser, answers are not spoken." })] })] }), Object.keys(settings).length > 0 ? (_jsxs(_Fragment, { children: [_jsx(ActionList.Divider, {}), _jsx(ActionList.Item, { onSelect: () => keep({}), "data-voice-settings-reset": "", children: "Use the application\u2019s" })] })) : null] }) })] }));
|
|
31
|
+
(voice.microphoneId ?? named[0].deviceId), onSelect: () => choose('microphoneId', device.deviceId), "data-voice-microphone": device.deviceId, children: device.label }, device.deviceId)))] })] })) : null, _jsx(ActionList.Divider, {}), _jsxs(ActionList.Group, { selectionVariant: "single", children: [_jsx(ActionList.GroupHeading, { children: "Pauses before I am done" }), PAUSE_CHOICES.map(pause => (_jsx(ActionList.Item, { selected: pause.value === (settings.pause ?? 1), onSelect: () => choose('pause', pause.value === 1 ? undefined : pause.value), "data-voice-pause": pause.value, children: pause.label }, pause.value)))] }), _jsx(ActionList.Divider, {}), _jsxs(ActionList.Group, { selectionVariant: "multiple", children: [_jsxs(ActionList.Item, { selected: Boolean(voice.sendWhatISay), onSelect: () => choose('sendWhatISay', !voice.sendWhatISay), "data-voice-send-what-i-say": "", children: ["Send what I say", _jsx(ActionList.Description, { variant: "block", children: "Sent as soon as it is heard, not left in the box." })] }), voice.speechUrl && !onDevice ? (_jsxs(ActionList.Item, { selected: detect, onSelect: () => choose('detectLanguage', !detect), "data-voice-detect": "", children: ["Detect the language I speak", _jsx(ActionList.Description, { variant: "block", children: "Heard on Datalayer\u2019s servers by Whisper, which finds the language; nothing is kept there." })] })) : null, _jsxs(ActionList.Item, { selected: onDevice, onSelect: () => choose('onDeviceOnly', !onDevice), "data-voice-on-device": "", children: ["Keep speech on this device", _jsx(ActionList.Description, { variant: "block", children: "Nothing is sent to Datalayer\u2019s speech service: you are heard in the browser, answers are not spoken." })] })] }), Object.keys(settings).length > 0 ? (_jsxs(_Fragment, { children: [_jsx(ActionList.Divider, {}), _jsx(ActionList.Item, { onSelect: () => keep({}), "data-voice-settings-reset": "", children: "Use the application\u2019s" })] })) : null] }) })] }));
|
|
32
32
|
}
|
|
33
33
|
export default VoiceMenu;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Captions always (VOICE.md VO-65): the sentence being said is marked in the
|
|
3
|
+
* open conversation, not by colour alone. The answer that holds it is marked
|
|
4
|
+
* `aria-current` and `data-voice-saying` with a bar beside it; the sentence
|
|
5
|
+
* itself is underlined in place through the CSS Custom Highlight API, which
|
|
6
|
+
* draws over the text without touching the DOM React owns. A browser without
|
|
7
|
+
* that API keeps the answer's mark.
|
|
8
|
+
*
|
|
9
|
+
* @module voice/captions
|
|
10
|
+
*/
|
|
11
|
+
import { type RefObject } from 'react';
|
|
12
|
+
/** The name the sentence's highlight is registered under. */
|
|
13
|
+
export declare const SAID_HIGHLIGHT = "voice-said";
|
|
14
|
+
/**
|
|
15
|
+
* The id of the answer a sentence is being said from: the newest answer of
|
|
16
|
+
* the agent whose words hold it, or none.
|
|
17
|
+
*/
|
|
18
|
+
export declare function saidMessageId(items: readonly unknown[], sentence: string | undefined): string | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Where a sentence is in what an element shows, as a `Range` over its text
|
|
21
|
+
* — across the bold, the links and the line breaks it is drawn with — or
|
|
22
|
+
* none when the element does not show it as said (a code block said as *a
|
|
23
|
+
* code block*).
|
|
24
|
+
*/
|
|
25
|
+
export declare function sentenceRange(root: Node, sentence: string): Range | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Underlines `sentence` in the answer marked `data-voice-saying` under
|
|
28
|
+
* `root`, for as long as it is said; the mark goes when it ends. Drawn
|
|
29
|
+
* again whenever `version` changes (the answer still being written).
|
|
30
|
+
*/
|
|
31
|
+
export declare function useSaidSentence(root: RefObject<HTMLElement | null>, sentence: string | undefined, version: unknown): void;
|