@pinecall/room 0.1.0 → 0.1.1
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/README.md +39 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/karaoke.d.ts +29 -0
- package/dist/karaoke.d.ts.map +1 -0
- package/dist/karaoke.js +64 -0
- package/dist/karaoke.js.map +1 -0
- package/dist/react/index.d.ts +7 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +27 -1
- package/dist/react/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ that change it. The package has three entry points, and each one is for a differ
|
|
|
11
11
|
|
|
12
12
|
| import | where it runs | what it gives you |
|
|
13
13
|
|---|---|---|
|
|
14
|
-
| `@pinecall/room` | the page | `room()`, the store of one conversation,
|
|
14
|
+
| `@pinecall/room` | the page | `room()`, the store of one conversation, `rowsOf`, `knownBy`, `brief` to draw it, and the karaoke of what is being said |
|
|
15
15
|
| `@pinecall/room/react` | the page, in React | `useRoom()` and `useStore()` |
|
|
16
16
|
| `@pinecall/room/server` | your server | `mint()`, `dial()` and `GatewayRefused`: the only code that touches the key |
|
|
17
17
|
|
|
@@ -246,6 +246,44 @@ as one short line each, `status` (`running`, `done`, `failed`) and `error` — a
|
|
|
246
246
|
`[name, value]` pairs with the empty ones left out. `brief(value, max)` is a value as one line of
|
|
247
247
|
JSON, cut at `max` characters. A field that is an object is opened one level — `person.name`, `person.email` — so a page shows a name rather than a JSON blob.
|
|
248
248
|
|
|
249
|
+
## What is being said right now
|
|
250
|
+
|
|
251
|
+
Both sides of a spoken call can be drawn as they happen, karaoke-style.
|
|
252
|
+
|
|
253
|
+
**The visitor** — `state.log.live.user` is what the recogniser has heard so far of the turn in
|
|
254
|
+
progress, rewritten as they speak ("Hi", "Hi ther", "Hi there. I would…"), and `null` once the
|
|
255
|
+
turn closes.
|
|
256
|
+
|
|
257
|
+
**The agent** — a voice call's log carries its reply one word at a time, each with the second it
|
|
258
|
+
is spoken at. The words usually reach the page before the voice says them, so a page can draw the
|
|
259
|
+
whole sentence and light each word as it sounds:
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
import { useKaraoke } from "@pinecall/room/react";
|
|
263
|
+
|
|
264
|
+
const saying = useKaraoke(room.entries); // null when nobody is mid-reply
|
|
265
|
+
{saying && <p><b>{saying.lit}</b><span className="dim">{saying.coming}</span></p>}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Without React, the same with a clock of your own: note the time the reply's first word arrived,
|
|
269
|
+
and on each frame split it with `litAt`.
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
import { litAt, sayingOf } from "@pinecall/room";
|
|
273
|
+
|
|
274
|
+
let began: { speech: string; at: number } | null = null;
|
|
275
|
+
function frame() {
|
|
276
|
+
const saying = sayingOf(call.state.entries);
|
|
277
|
+
if (saying && began?.speech !== saying.speech) began = { speech: saying.speech, at: performance.now() };
|
|
278
|
+
if (saying && began) draw(litAt(saying, performance.now() - began.at)); // { lit, coming }
|
|
279
|
+
requestAnimationFrame(frame);
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
A written call's reply comes as model tokens with no timing: every token is lit as it arrives,
|
|
284
|
+
which reads as the agent typing. Once the turn closes, the reply is a turn in `rowsOf` like any
|
|
285
|
+
other, and `useKaraoke` returns `null`.
|
|
286
|
+
|
|
249
287
|
## Sound and the browser's permission
|
|
250
288
|
|
|
251
289
|
In `talk`, the microphone is turned on once the room is joined, and the agent's voice plays through
|
package/dist/index.d.ts
CHANGED
|
@@ -6,4 +6,5 @@ export type { Connection } from "./log/follow.js";
|
|
|
6
6
|
export type { Speaking } from "./seat/join.js";
|
|
7
7
|
export type { LivekitModule } from "./seat/livekit.js";
|
|
8
8
|
export { brief, knownBy, rowsOf, type Row } from "./rows.js";
|
|
9
|
+
export { joined, litAt, sayingOf, soundedBy, type Lit, type Saying, type Word } from "./karaoke.js";
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mGAAmG;AAEnG,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACxF,YAAY,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,GAAG,EAAE,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mGAAmG;AAEnG,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACxF,YAAY,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,GAAG,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,GAAG,EAAE,KAAK,MAAM,EAAE,KAAK,IAAI,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** @pinecall/room: the caller's side of a Pinecall call in a browser — one store, no framework. */
|
|
2
2
|
export { room } from "./room.js";
|
|
3
3
|
export { brief, knownBy, rowsOf } from "./rows.js";
|
|
4
|
+
export { joined, litAt, sayingOf, soundedBy } from "./karaoke.js";
|
|
4
5
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mGAAmG;AAEnG,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAMjC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAY,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mGAAmG;AAEnG,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAMjC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAY,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAoC,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** The agent's reply while it is being said: its words as the log delivers them, and which have sounded. */
|
|
2
|
+
import { type Entry } from "@pinecall/protocol";
|
|
3
|
+
/** One delta of the reply: a word (voice) or a token (text), and when it sounds, if it does. */
|
|
4
|
+
export interface Word {
|
|
5
|
+
text: string;
|
|
6
|
+
start: number | null;
|
|
7
|
+
}
|
|
8
|
+
/** The reply in progress: its speech id and every delta so far. */
|
|
9
|
+
export interface Saying {
|
|
10
|
+
speech: string;
|
|
11
|
+
words: Word[];
|
|
12
|
+
}
|
|
13
|
+
/** A reply split where the voice is: what has sounded, and what is still to come. */
|
|
14
|
+
export interface Lit {
|
|
15
|
+
lit: string;
|
|
16
|
+
coming: string;
|
|
17
|
+
}
|
|
18
|
+
/** The reply being said now, read back from the newest entry to the last closed turn; null when nobody is mid-reply. */
|
|
19
|
+
export declare function sayingOf(entries: readonly Entry[]): Saying | null;
|
|
20
|
+
/**
|
|
21
|
+
* How many words have sounded `elapsed` ms after the reply's first word reached the page. A word
|
|
22
|
+
* with no timing is lit at once; the count never skips a word, so a late one holds back the rest.
|
|
23
|
+
*/
|
|
24
|
+
export declare function soundedBy(words: readonly Word[], elapsed: number): number;
|
|
25
|
+
/** The deltas as one line, the way the wire's reducer joins them: a timed word is its own word. */
|
|
26
|
+
export declare function joined(words: readonly Word[]): string;
|
|
27
|
+
/** The reply split at `elapsed` ms: the words that have sounded, and the rest. */
|
|
28
|
+
export declare function litAt(saying: Saying, elapsed: number): Lit;
|
|
29
|
+
//# sourceMappingURL=karaoke.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"karaoke.d.ts","sourceRoot":"","sources":["../src/karaoke.ts"],"names":[],"mappings":"AAAA,4GAA4G;AAQ5G,OAAO,EAAyB,KAAK,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAEvE,gGAAgG;AAChG,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,mEAAmE;AACnE,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AAED,qFAAqF;AACrF,MAAM,WAAW,GAAG;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wHAAwH;AACxH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,SAAS,KAAK,EAAE,GAAG,MAAM,GAAG,IAAI,CAgBjE;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAOzE;AAED,mGAAmG;AACnG,wBAAgB,MAAM,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,GAAG,MAAM,CAQrD;AAED,kFAAkF;AAClF,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,GAAG,CAG1D"}
|
package/dist/karaoke.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** The agent's reply while it is being said: its words as the log delivers them, and which have sounded. */
|
|
2
|
+
//
|
|
3
|
+
// A voice call delivers one agent.transcript per word, each with `start`: the second, from the
|
|
4
|
+
// top of the reply, at which the voice says it (the TTS measures it; the runtime passes it on).
|
|
5
|
+
// A word is lit when the clock reaches it, so the words on screen are the words in the ear. A
|
|
6
|
+
// written call's deltas are model tokens with no timing: they are lit the moment they arrive.
|
|
7
|
+
// The visitor's side needs nothing here: `state.log.live.user` is what the ear has heard so far.
|
|
8
|
+
import { AgentTranscriptSchema } from "@pinecall/protocol";
|
|
9
|
+
/** The reply being said now, read back from the newest entry to the last closed turn; null when nobody is mid-reply. */
|
|
10
|
+
export function sayingOf(entries) {
|
|
11
|
+
const words = [];
|
|
12
|
+
let speech = null;
|
|
13
|
+
for (let at = entries.length - 1; at >= 0; at -= 1) {
|
|
14
|
+
const entry = entries[at];
|
|
15
|
+
if (entry === undefined || entry.type === "turn.agent")
|
|
16
|
+
break;
|
|
17
|
+
if (entry.type !== "agent.transcript")
|
|
18
|
+
continue;
|
|
19
|
+
const delta = AgentTranscriptSchema.safeParse(entry.data);
|
|
20
|
+
if (!delta.success)
|
|
21
|
+
continue;
|
|
22
|
+
if (delta.data.final)
|
|
23
|
+
break;
|
|
24
|
+
// Only the newest reply: a delta of an earlier speech is a reply that already closed.
|
|
25
|
+
if (speech === null)
|
|
26
|
+
speech = delta.data.speech_id;
|
|
27
|
+
else if (delta.data.speech_id !== speech)
|
|
28
|
+
break;
|
|
29
|
+
words.unshift({ text: delta.data.text, start: delta.data.start ?? null });
|
|
30
|
+
}
|
|
31
|
+
return speech === null ? null : { speech, words };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* How many words have sounded `elapsed` ms after the reply's first word reached the page. A word
|
|
35
|
+
* with no timing is lit at once; the count never skips a word, so a late one holds back the rest.
|
|
36
|
+
*/
|
|
37
|
+
export function soundedBy(words, elapsed) {
|
|
38
|
+
let lit = 0;
|
|
39
|
+
for (const word of words) {
|
|
40
|
+
if (word.start !== null && word.start * 1000 > elapsed)
|
|
41
|
+
break;
|
|
42
|
+
lit += 1;
|
|
43
|
+
}
|
|
44
|
+
return lit;
|
|
45
|
+
}
|
|
46
|
+
/** The deltas as one line, the way the wire's reducer joins them: a timed word is its own word. */
|
|
47
|
+
export function joined(words) {
|
|
48
|
+
let line = "";
|
|
49
|
+
for (const word of words) {
|
|
50
|
+
if (line === "")
|
|
51
|
+
line = word.text;
|
|
52
|
+
else if (word.start !== null && !/\s$/.test(line) && !/^\s/.test(word.text))
|
|
53
|
+
line = `${line} ${word.text}`;
|
|
54
|
+
else
|
|
55
|
+
line = `${line}${word.text}`;
|
|
56
|
+
}
|
|
57
|
+
return line;
|
|
58
|
+
}
|
|
59
|
+
/** The reply split at `elapsed` ms: the words that have sounded, and the rest. */
|
|
60
|
+
export function litAt(saying, elapsed) {
|
|
61
|
+
const lit = joined(saying.words.slice(0, soundedBy(saying.words, elapsed)));
|
|
62
|
+
return { lit, coming: joined(saying.words).slice(lit.length) };
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=karaoke.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"karaoke.js","sourceRoot":"","sources":["../src/karaoke.ts"],"names":[],"mappings":"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"}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** @pinecall/room/react: a store read the way React reads one, and one room per mounted component. */
|
|
2
|
-
import { type RoomOptions, type RoomState, type RoomStore, type Store } from "../index.js";
|
|
2
|
+
import { type Lit, type RoomOptions, type RoomState, type RoomStore, type Store } from "../index.js";
|
|
3
3
|
/** The store's state, re-rendering when it changes. Any store of this package. */
|
|
4
4
|
export declare function useStore<T>(store: Store<T>): T;
|
|
5
5
|
/** What `useRoom` hands a component: the state, and the verbs to change it. */
|
|
@@ -10,4 +10,10 @@ export type RoomHandle = RoomState & Pick<RoomStore, "start" | "send" | "callMe"
|
|
|
10
10
|
* is the one that runs.
|
|
11
11
|
*/
|
|
12
12
|
export declare function useRoom(options: RoomOptions): RoomHandle;
|
|
13
|
+
/**
|
|
14
|
+
* The agent's reply as the voice says it: `lit` has sounded, `coming` has not, or null when nobody
|
|
15
|
+
* is mid-reply. The clock starts when the reply's first word reaches the page and ticks one frame
|
|
16
|
+
* at a time only while a word is still due. Pass `state.entries` of a live room.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useKaraoke(entries: RoomState["entries"]): Lit | null;
|
|
13
19
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,sGAAsG;AAItG,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,sGAAsG;AAItG,OAAO,EAAyB,KAAK,GAAG,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAE5H,kFAAkF;AAClF,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAI9C;AAED,+EAA+E;AAC/E,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,WAAW,CAAC,CAAC;AAE1G;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,CAuBxD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,SAAS,CAAC,GAAG,GAAG,GAAG,IAAI,CAqBpE"}
|
package/dist/react/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @pinecall/room/react: a store read the way React reads one, and one room per mounted component. */
|
|
2
2
|
import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
3
|
-
import { room } from "../index.js";
|
|
3
|
+
import { litAt, room, sayingOf } from "../index.js";
|
|
4
4
|
/** The store's state, re-rendering when it changes. Any store of this package. */
|
|
5
5
|
export function useStore(store) {
|
|
6
6
|
const subscribe = useCallback((changed) => store.subscribe(changed), [store]);
|
|
@@ -36,6 +36,32 @@ export function useRoom(options) {
|
|
|
36
36
|
const { start, send, callMe, leave, playSound } = store;
|
|
37
37
|
return { ...state, start, send, callMe, leave, playSound };
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* The agent's reply as the voice says it: `lit` has sounded, `coming` has not, or null when nobody
|
|
41
|
+
* is mid-reply. The clock starts when the reply's first word reaches the page and ticks one frame
|
|
42
|
+
* at a time only while a word is still due. Pass `state.entries` of a live room.
|
|
43
|
+
*/
|
|
44
|
+
export function useKaraoke(entries) {
|
|
45
|
+
const saying = sayingOf(entries);
|
|
46
|
+
const began = useRef(null);
|
|
47
|
+
const [now, setNow] = useState(() => performance.now());
|
|
48
|
+
if (saying === null)
|
|
49
|
+
began.current = null;
|
|
50
|
+
else if (began.current?.speech !== saying.speech)
|
|
51
|
+
began.current = { speech: saying.speech, at: performance.now() };
|
|
52
|
+
const split = saying === null || saying.words.length === 0 ? null : litAt(saying, now - (began.current?.at ?? now));
|
|
53
|
+
const waiting = split !== null && split.coming !== "";
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (!waiting)
|
|
56
|
+
return;
|
|
57
|
+
let frame = requestAnimationFrame(function tick() {
|
|
58
|
+
setNow(performance.now());
|
|
59
|
+
frame = requestAnimationFrame(tick);
|
|
60
|
+
});
|
|
61
|
+
return () => cancelAnimationFrame(frame);
|
|
62
|
+
}, [waiting]);
|
|
63
|
+
return split;
|
|
64
|
+
}
|
|
39
65
|
/** Options whose every field is the one the component rendered last. */
|
|
40
66
|
function readingLatest(latest) {
|
|
41
67
|
return {
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,sGAAsG;AAEtG,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAEvF,OAAO,EAAE,IAAI,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pinecall/room",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "The caller's side of a Pinecall call in a browser: one store, the seat and the log joined, no framework",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Pinecall <hello@pinecall.io>",
|