@rimori/client 1.3.0 → 1.4.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/.prettierignore +35 -0
- package/dist/cli/scripts/release/release-file-upload.js +1 -1
- package/dist/cli/types/DatabaseTypes.d.ts +2 -2
- package/dist/components/ai/Avatar.js +4 -2
- package/dist/components/ai/EmbeddedAssistent/TTS/Player.js +1 -1
- package/dist/core/controller/AIController.js +9 -7
- package/dist/core/controller/ExerciseController.d.ts +52 -0
- package/dist/core/controller/ExerciseController.js +73 -0
- package/dist/core/core.d.ts +1 -0
- package/dist/plugin/Logger.d.ts +5 -0
- package/dist/plugin/Logger.js +56 -6
- package/dist/plugin/RimoriClient.d.ts +28 -2
- package/dist/plugin/RimoriClient.js +30 -1
- package/eslint.config.js +53 -0
- package/package.json +9 -2
- package/prettier.config.js +8 -0
- package/src/cli/scripts/release/release-file-upload.ts +1 -1
- package/src/cli/types/DatabaseTypes.ts +17 -10
- package/src/components/ai/Avatar.tsx +3 -2
- package/src/components/ai/EmbeddedAssistent/TTS/Player.ts +176 -176
- package/src/core/controller/AIController.ts +36 -34
- package/src/core/controller/ExerciseController.ts +105 -0
- package/src/core/core.ts +1 -0
- package/src/plugin/Logger.ts +59 -8
- package/src/plugin/RimoriClient.ts +40 -6
- package/dist/components/LoggerExample.d.ts +0 -6
- package/dist/components/LoggerExample.js +0 -79
- package/dist/core/controller/AudioController.d.ts +0 -0
- package/dist/core/controller/AudioController.js +0 -1
- package/dist/hooks/UseLogger.d.ts +0 -30
- package/dist/hooks/UseLogger.js +0 -122
- package/dist/plugin/LoggerExample.d.ts +0 -16
- package/dist/plugin/LoggerExample.js +0 -140
- package/dist/utils/audioFormats.d.ts +0 -26
- package/dist/utils/audioFormats.js +0 -67
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
|
|
2
1
|
// Database table structure definitions
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Supported database column data types for table schema definitions.
|
|
6
5
|
*/
|
|
7
|
-
type DbColumnType =
|
|
6
|
+
type DbColumnType =
|
|
7
|
+
| 'decimal'
|
|
8
|
+
| 'integer'
|
|
9
|
+
| 'text'
|
|
10
|
+
| 'boolean'
|
|
11
|
+
| 'json'
|
|
12
|
+
| 'timestamp'
|
|
13
|
+
| 'uuid';
|
|
8
14
|
|
|
9
15
|
/**
|
|
10
16
|
* Foreign key relationship configuration with cascade delete support.
|
|
@@ -45,15 +51,15 @@ export interface DbColumnDefinition {
|
|
|
45
51
|
// primary_key?: boolean;
|
|
46
52
|
/** Restrictions for the column. If the column is restricted, the permission is further restricted. E.g. if the column is restricted to user, then the user can only read the column if they have the right permission.
|
|
47
53
|
* Example: Denying users to update the column, but allowing the moderator to update the column.
|
|
48
|
-
|
|
54
|
+
*/
|
|
49
55
|
restrict?: {
|
|
50
56
|
/** Restrictions for the user */
|
|
51
|
-
user: Partial<Omit<DbPermissionDefinition, 'delete'
|
|
57
|
+
user: Partial<Omit<DbPermissionDefinition, 'delete'>>;
|
|
52
58
|
/** Restrictions for the moderator */
|
|
53
|
-
moderator?: Partial<Omit<DbPermissionDefinition, 'delete'
|
|
59
|
+
moderator?: Partial<Omit<DbPermissionDefinition, 'delete'>>;
|
|
54
60
|
/** Restrictions for the maintainer */
|
|
55
61
|
// maintainer?: Partial<DbPermissionDefinition>,
|
|
56
|
-
}
|
|
62
|
+
};
|
|
57
63
|
}
|
|
58
64
|
|
|
59
65
|
/**
|
|
@@ -80,8 +86,8 @@ export interface DbTableDefinition {
|
|
|
80
86
|
description: string;
|
|
81
87
|
/** Permissions for the table */
|
|
82
88
|
permissions: {
|
|
83
|
-
user: DbPermissionDefinition
|
|
84
|
-
moderator?: DbPermissionDefinition
|
|
89
|
+
user: DbPermissionDefinition;
|
|
90
|
+
moderator?: DbPermissionDefinition;
|
|
85
91
|
// maintainer?: DbPermissionDefinition,
|
|
86
92
|
};
|
|
87
93
|
/** Column definitions for the table */
|
|
@@ -98,7 +104,7 @@ export interface DbTableDefinition {
|
|
|
98
104
|
*
|
|
99
105
|
* Defines the permissions for a database table.
|
|
100
106
|
*/
|
|
101
|
-
export type DbPermission =
|
|
107
|
+
export type DbPermission = 'NONE' | 'OWN' | 'ALL';
|
|
102
108
|
|
|
103
109
|
/**
|
|
104
110
|
* Permission definition for a database table.
|
|
@@ -114,4 +120,5 @@ export interface DbPermissionDefinition {
|
|
|
114
120
|
/**
|
|
115
121
|
* Full table definition that includes automatically generated fields.
|
|
116
122
|
*/
|
|
117
|
-
export type FullTable<T extends Record<string, DbColumnDefinition>> = T &
|
|
123
|
+
export type FullTable<T extends Record<string, DbColumnDefinition>> = T &
|
|
124
|
+
BaseTableStructure;
|
|
@@ -44,6 +44,7 @@ export function Avatar({
|
|
|
44
44
|
}, [isLoading]);
|
|
45
45
|
|
|
46
46
|
useEffect(() => {
|
|
47
|
+
if (!voiceId) return; //at the beginning when being mounted the voiceId is undefined
|
|
47
48
|
sender.setOnLoudnessChange((value) => event.emit('self.avatar.triggerLoudness', { loudness: value }));
|
|
48
49
|
sender.setOnEndOfSpeech(() => setAgentReplying(false));
|
|
49
50
|
|
|
@@ -58,13 +59,13 @@ export function Avatar({
|
|
|
58
59
|
} else if (autoStartConversation.userMessage) {
|
|
59
60
|
append([{ role: 'user', content: autoStartConversation.userMessage, id: messages.length.toString() }]);
|
|
60
61
|
}
|
|
61
|
-
}, [autoStartConversation]);
|
|
62
|
+
}, [autoStartConversation, voiceId]);
|
|
62
63
|
|
|
63
64
|
useEffect(() => {
|
|
64
65
|
if (lastMessage?.role === 'assistant') {
|
|
65
66
|
sender.handleNewText(lastMessage.content, isLoading);
|
|
66
67
|
if (lastMessage.toolCalls) {
|
|
67
|
-
console.log("unlocking mic",lastMessage)
|
|
68
|
+
// console.log("unlocking mic", lastMessage)
|
|
68
69
|
setAgentReplying(false);
|
|
69
70
|
setIsProcessingMessage(false);
|
|
70
71
|
}
|
|
@@ -1,198 +1,198 @@
|
|
|
1
1
|
export class ChunkedAudioPlayer {
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
3
|
+
private audioContext!: AudioContext;
|
|
4
|
+
private chunkQueue: ArrayBuffer[] = [];
|
|
5
|
+
private isPlaying = false;
|
|
6
|
+
private analyser!: AnalyserNode;
|
|
7
|
+
private dataArray!: Uint8Array<ArrayBuffer>;
|
|
8
|
+
private shouldMonitorLoudness = true;
|
|
9
|
+
private isMonitoring = false;
|
|
10
|
+
private handle = 0;
|
|
11
|
+
private volume = 1.0;
|
|
12
|
+
private loudnessCallback: (value: number) => void = () => { };
|
|
13
|
+
private currentIndex = 0;
|
|
14
|
+
private startedPlaying = false;
|
|
15
|
+
private onEndOfSpeech: () => void = () => { };
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
this.init();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private init(): void {
|
|
22
|
+
this.audioContext = new AudioContext();
|
|
23
|
+
this.analyser = this.audioContext.createAnalyser();
|
|
24
|
+
this.analyser.fftSize = 256; // Set the FFT size (smaller values provide faster updates, larger ones give better resolution)
|
|
25
|
+
const bufferLength = this.analyser.frequencyBinCount;
|
|
26
|
+
this.dataArray = new Uint8Array(bufferLength); // Array to hold frequency data
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public setOnLoudnessChange(callback: (value: number) => void) {
|
|
30
|
+
this.loudnessCallback = callback;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public setVolume(volume: number) {
|
|
34
|
+
this.volume = volume;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public async addChunk(chunk: ArrayBuffer, position: number): Promise<void> {
|
|
38
|
+
console.log('Adding chunk', position, chunk);
|
|
39
|
+
this.chunkQueue[position] = chunk;
|
|
40
|
+
// console.log("received chunk", {
|
|
41
|
+
// chunkQueue: this.chunkQueue.length,
|
|
42
|
+
// isPlaying: this.isPlaying,
|
|
43
|
+
// })
|
|
44
|
+
|
|
45
|
+
if (position === 0 && !this.startedPlaying) {
|
|
46
|
+
this.startedPlaying = true;
|
|
47
|
+
this.playChunks();
|
|
19
48
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
public setOnLoudnessChange(callback: (value: number) => void) {
|
|
30
|
-
this.loudnessCallback = callback;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private playChunks(): void {
|
|
52
|
+
// console.log({ isPlaying: this.isPlaying });
|
|
53
|
+
if (this.isPlaying) return;
|
|
54
|
+
if (!this.chunkQueue[this.currentIndex]) {
|
|
55
|
+
// wait until the correct chunk arrives
|
|
56
|
+
setTimeout(() => this.playChunks(), 10);
|
|
31
57
|
}
|
|
58
|
+
this.isPlaying = true;
|
|
32
59
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// console.log(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (position === 0 && !this.startedPlaying) {
|
|
46
|
-
this.startedPlaying = true;
|
|
60
|
+
this.playChunk(this.chunkQueue[this.currentIndex]).then(() => {
|
|
61
|
+
this.isPlaying = false;
|
|
62
|
+
this.currentIndex++;
|
|
63
|
+
if (this.chunkQueue[this.currentIndex]) {
|
|
64
|
+
this.shouldMonitorLoudness = true;
|
|
65
|
+
this.playChunks();
|
|
66
|
+
} else {
|
|
67
|
+
// console.log('Playback finished', { currentIndex: this.currentIndex, chunkQueue: this.chunkQueue });
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
// console.log('Check again if really playback finished', { currentIndex: this.currentIndex, chunkQueue: this.chunkQueue });
|
|
70
|
+
if (this.chunkQueue.length > this.currentIndex) {
|
|
47
71
|
this.playChunks();
|
|
48
|
-
|
|
72
|
+
} else {
|
|
73
|
+
this.startedPlaying = false;
|
|
74
|
+
this.shouldMonitorLoudness = false;
|
|
75
|
+
}
|
|
76
|
+
}, 1000);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public stopPlayback(): void {
|
|
82
|
+
// console.log('Stopping playback');
|
|
83
|
+
// Implement logic to stop the current playback
|
|
84
|
+
this.isPlaying = false;
|
|
85
|
+
this.chunkQueue = [];
|
|
86
|
+
this.startedPlaying = false;
|
|
87
|
+
this.shouldMonitorLoudness = false;
|
|
88
|
+
cancelAnimationFrame(this.handle);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private playChunk(chunk: ArrayBuffer): Promise<void> {
|
|
92
|
+
// console.log({queue: this.chunkQueue})
|
|
93
|
+
if (!chunk) {
|
|
94
|
+
return Promise.resolve();
|
|
49
95
|
}
|
|
50
96
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
97
|
+
// console.log('Playing chunk', chunk);
|
|
98
|
+
return new Promise((resolve) => {
|
|
99
|
+
const source = this.audioContext.createBufferSource();
|
|
100
|
+
this.audioContext.decodeAudioData(chunk.slice(0)).then((audioBuffer) => {
|
|
101
|
+
source.buffer = audioBuffer;
|
|
102
|
+
|
|
103
|
+
// Create a GainNode for volume control
|
|
104
|
+
const gainNode = this.audioContext.createGain();
|
|
105
|
+
gainNode.gain.value = this.volume;
|
|
106
|
+
|
|
107
|
+
// Connect the source to the GainNode, then to the analyser node, then to the destination (speakers)
|
|
108
|
+
source.connect(gainNode);
|
|
109
|
+
gainNode.connect(this.analyser);
|
|
110
|
+
this.analyser.connect(this.audioContext.destination);
|
|
111
|
+
|
|
112
|
+
source.start(0);
|
|
113
|
+
// console.log('Playing chunk', this.currentIndex);
|
|
114
|
+
gainNode.gain.value = this.volume;
|
|
115
|
+
source.onended = () => {
|
|
116
|
+
// console.log('Chunk ended');
|
|
117
|
+
resolve();
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Start monitoring loudness only once
|
|
121
|
+
if (!this.isMonitoring) {
|
|
122
|
+
this.isMonitoring = true;
|
|
123
|
+
this.shouldMonitorLoudness = true;
|
|
124
|
+
this.monitorLoudness();
|
|
57
125
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
} else {
|
|
67
|
-
// console.log('Playback finished', { currentIndex: this.currentIndex, chunkQueue: this.chunkQueue });
|
|
68
|
-
setTimeout(() => {
|
|
69
|
-
// console.log('Check again if really playback finished', { currentIndex: this.currentIndex, chunkQueue: this.chunkQueue });
|
|
70
|
-
if (this.chunkQueue.length > this.currentIndex) {
|
|
71
|
-
this.playChunks();
|
|
72
|
-
} else {
|
|
73
|
-
this.startedPlaying = false;
|
|
74
|
-
this.shouldMonitorLoudness = false;
|
|
75
|
-
}
|
|
76
|
-
}, 1000);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async playAgain(): Promise<void> {
|
|
131
|
+
console.log('Playing again');
|
|
132
|
+
if (this.chunkQueue.length > 0 && !this.isPlaying) {
|
|
133
|
+
this.playChunks();
|
|
79
134
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private monitorLoudness(): void {
|
|
138
|
+
// Stop monitoring when the flag is false
|
|
139
|
+
if (!this.shouldMonitorLoudness) {
|
|
140
|
+
// console.log('Loudness monitoring stopped.');
|
|
141
|
+
cancelAnimationFrame(this.handle);
|
|
142
|
+
this.loudnessCallback(0);
|
|
143
|
+
this.onEndOfSpeech();
|
|
144
|
+
return;
|
|
89
145
|
}
|
|
90
146
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (!chunk) {
|
|
94
|
-
return Promise.resolve();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// console.log('Playing chunk', chunk);
|
|
98
|
-
return new Promise((resolve) => {
|
|
99
|
-
const source = this.audioContext.createBufferSource();
|
|
100
|
-
this.audioContext.decodeAudioData(chunk.slice(0)).then((audioBuffer) => {
|
|
101
|
-
source.buffer = audioBuffer;
|
|
102
|
-
|
|
103
|
-
// Create a GainNode for volume control
|
|
104
|
-
const gainNode = this.audioContext.createGain();
|
|
105
|
-
gainNode.gain.value = this.volume;
|
|
106
|
-
|
|
107
|
-
// Connect the source to the GainNode, then to the analyser node, then to the destination (speakers)
|
|
108
|
-
source.connect(gainNode);
|
|
109
|
-
gainNode.connect(this.analyser);
|
|
110
|
-
this.analyser.connect(this.audioContext.destination);
|
|
111
|
-
|
|
112
|
-
source.start(0);
|
|
113
|
-
// console.log('Playing chunk', this.currentIndex);
|
|
114
|
-
gainNode.gain.value = this.volume;
|
|
115
|
-
source.onended = () => {
|
|
116
|
-
// console.log('Chunk ended');
|
|
117
|
-
resolve();
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
// Start monitoring loudness only once
|
|
121
|
-
if (!this.isMonitoring) {
|
|
122
|
-
this.isMonitoring = true;
|
|
123
|
-
this.shouldMonitorLoudness = true;
|
|
124
|
-
this.monitorLoudness();
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
}
|
|
147
|
+
// Get the time domain data from the analyser (this is a snapshot of the waveform)
|
|
148
|
+
this.analyser.getByteTimeDomainData(this.dataArray);
|
|
129
149
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
150
|
+
// Calculate the RMS (root mean square) of the waveform values to get the perceived loudness
|
|
151
|
+
let sum = 0;
|
|
152
|
+
for (let i = 0; i < this.dataArray.length; i++) {
|
|
153
|
+
const value = this.dataArray[i] / 128.0 - 1.0; // Normalize between -1 and 1
|
|
154
|
+
sum += value * value;
|
|
135
155
|
}
|
|
136
156
|
|
|
137
|
-
|
|
138
|
-
// Stop monitoring when the flag is false
|
|
139
|
-
if (!this.shouldMonitorLoudness) {
|
|
140
|
-
// console.log('Loudness monitoring stopped.');
|
|
141
|
-
cancelAnimationFrame(this.handle);
|
|
142
|
-
this.loudnessCallback(0);
|
|
143
|
-
this.onEndOfSpeech();
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Get the time domain data from the analyser (this is a snapshot of the waveform)
|
|
148
|
-
this.analyser.getByteTimeDomainData(this.dataArray);
|
|
149
|
-
|
|
150
|
-
// Calculate the RMS (root mean square) of the waveform values to get the perceived loudness
|
|
151
|
-
let sum = 0;
|
|
152
|
-
for (let i = 0; i < this.dataArray.length; i++) {
|
|
153
|
-
const value = this.dataArray[i] / 128.0 - 1.0; // Normalize between -1 and 1
|
|
154
|
-
sum += value * value;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const rms = Math.sqrt(sum / this.dataArray.length);
|
|
158
|
-
|
|
159
|
-
// Handle the case where RMS is 0 to avoid log10(0)
|
|
160
|
-
if (rms === 0) {
|
|
161
|
-
// console.log('Current loudness: Silent');
|
|
162
|
-
} else {
|
|
163
|
-
let loudnessInDb = 20 * Math.log10(rms); // Convert to dB
|
|
164
|
-
// console.log('Current loudness:' + loudnessInDb);
|
|
165
|
-
const minDb = -57;
|
|
166
|
-
const maxDb = -15;
|
|
157
|
+
const rms = Math.sqrt(sum / this.dataArray.length);
|
|
167
158
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
159
|
+
// Handle the case where RMS is 0 to avoid log10(0)
|
|
160
|
+
if (rms === 0) {
|
|
161
|
+
// console.log('Current loudness: Silent');
|
|
162
|
+
} else {
|
|
163
|
+
let loudnessInDb = 20 * Math.log10(rms); // Convert to dB
|
|
164
|
+
// console.log('Current loudness:' + loudnessInDb);
|
|
165
|
+
const minDb = -57;
|
|
166
|
+
const maxDb = -15;
|
|
174
167
|
|
|
175
|
-
|
|
176
|
-
|
|
168
|
+
if (loudnessInDb < minDb) {
|
|
169
|
+
loudnessInDb = minDb;
|
|
170
|
+
}
|
|
171
|
+
if (loudnessInDb > maxDb) {
|
|
172
|
+
loudnessInDb = maxDb;
|
|
173
|
+
}
|
|
177
174
|
|
|
178
|
-
|
|
179
|
-
|
|
175
|
+
const loudnessScale = ((loudnessInDb - minDb) / (maxDb - minDb)) * 100;
|
|
176
|
+
// console.log("root:corrent loudness", loudnessScale);
|
|
180
177
|
|
|
181
|
-
|
|
182
|
-
this.handle = requestAnimationFrame(() => this.monitorLoudness());
|
|
183
|
-
}
|
|
184
|
-
public reset() {
|
|
185
|
-
// console.log('Resetting player');
|
|
186
|
-
this.stopPlayback();
|
|
187
|
-
this.currentIndex = 0;
|
|
188
|
-
this.shouldMonitorLoudness = true;
|
|
189
|
-
//reset to the beginning when the class gets initialized
|
|
190
|
-
this.isMonitoring = false;
|
|
191
|
-
this.isPlaying = false;
|
|
192
|
-
this.init();
|
|
178
|
+
this.loudnessCallback(loudnessScale);
|
|
193
179
|
}
|
|
194
180
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
181
|
+
// Call this method again at regular intervals if you want continuous loudness monitoring
|
|
182
|
+
this.handle = requestAnimationFrame(() => this.monitorLoudness());
|
|
183
|
+
}
|
|
184
|
+
public reset() {
|
|
185
|
+
// console.log('Resetting player');
|
|
186
|
+
this.stopPlayback();
|
|
187
|
+
this.currentIndex = 0;
|
|
188
|
+
this.shouldMonitorLoudness = true;
|
|
189
|
+
//reset to the beginning when the class gets initialized
|
|
190
|
+
this.isMonitoring = false;
|
|
191
|
+
this.isPlaying = false;
|
|
192
|
+
this.init();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
public setOnEndOfSpeech(callback: () => void) {
|
|
196
|
+
this.onEndOfSpeech = callback;
|
|
197
|
+
}
|
|
198
198
|
}
|
|
@@ -71,10 +71,10 @@ export async function streamChatGPT(backendUrl: string, messages: Message[], too
|
|
|
71
71
|
if (value) {
|
|
72
72
|
const chunk = decoder.decode(value, { stream: true });
|
|
73
73
|
buffer += chunk;
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
// Split by lines, but handle incomplete lines
|
|
76
76
|
const lines = buffer.split('\n');
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
// Keep the last line in buffer if it's incomplete
|
|
79
79
|
if (lines.length > 1) {
|
|
80
80
|
buffer = lines.pop() || "";
|
|
@@ -82,11 +82,11 @@ export async function streamChatGPT(backendUrl: string, messages: Message[], too
|
|
|
82
82
|
|
|
83
83
|
for (const line of lines) {
|
|
84
84
|
if (line.trim() === '') continue;
|
|
85
|
-
|
|
85
|
+
|
|
86
86
|
// Handle the new streaming format
|
|
87
87
|
if (line.startsWith('data: ')) {
|
|
88
88
|
const dataStr = line.substring(6); // Remove 'data: ' prefix
|
|
89
|
-
|
|
89
|
+
|
|
90
90
|
// Handle [DONE] marker
|
|
91
91
|
if (dataStr === '[DONE]') {
|
|
92
92
|
done = true;
|
|
@@ -95,39 +95,39 @@ export async function streamChatGPT(backendUrl: string, messages: Message[], too
|
|
|
95
95
|
|
|
96
96
|
try {
|
|
97
97
|
const data = JSON.parse(dataStr);
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
// Log the first message to understand the format
|
|
100
100
|
if (!content && !isToolCallMode) {
|
|
101
|
-
console.log('First stream message received:', data);
|
|
101
|
+
// console.log('First stream message received:', data);
|
|
102
102
|
}
|
|
103
|
-
|
|
103
|
+
|
|
104
104
|
switch (data.type) {
|
|
105
105
|
case 'start':
|
|
106
106
|
// Stream started, no action needed
|
|
107
|
-
console.log('Stream started');
|
|
107
|
+
// console.log('Stream started');
|
|
108
108
|
break;
|
|
109
|
-
|
|
109
|
+
|
|
110
110
|
case 'start-step':
|
|
111
111
|
// Step started, no action needed
|
|
112
|
-
console.log('Step started');
|
|
112
|
+
// console.log('Step started');
|
|
113
113
|
break;
|
|
114
|
-
|
|
114
|
+
|
|
115
115
|
case 'reasoning-start':
|
|
116
116
|
// Reasoning started, no action needed
|
|
117
117
|
console.log('Reasoning started:', data.id);
|
|
118
118
|
break;
|
|
119
|
-
|
|
119
|
+
|
|
120
120
|
case 'reasoning-end':
|
|
121
121
|
// Reasoning ended, no action needed
|
|
122
122
|
console.log('Reasoning ended:', data.id);
|
|
123
123
|
break;
|
|
124
|
-
|
|
124
|
+
|
|
125
125
|
case 'text-start':
|
|
126
126
|
// Text generation started, store the ID
|
|
127
127
|
currentTextId = data.id;
|
|
128
128
|
console.log('Text generation started:', data.id);
|
|
129
129
|
break;
|
|
130
|
-
|
|
130
|
+
|
|
131
131
|
case 'text-delta':
|
|
132
132
|
// Text delta received, append to content
|
|
133
133
|
if (data.delta) {
|
|
@@ -135,73 +135,75 @@ export async function streamChatGPT(backendUrl: string, messages: Message[], too
|
|
|
135
135
|
onResponse(messageId, content, false);
|
|
136
136
|
}
|
|
137
137
|
break;
|
|
138
|
-
|
|
138
|
+
|
|
139
139
|
case 'text-end':
|
|
140
140
|
// Text generation ended
|
|
141
141
|
console.log('Text generation ended:', data.id);
|
|
142
142
|
break;
|
|
143
|
-
|
|
143
|
+
|
|
144
144
|
case 'finish-step':
|
|
145
145
|
// Step finished, no action needed
|
|
146
|
-
console.log('Step finished');
|
|
146
|
+
// console.log('Step finished');
|
|
147
147
|
break;
|
|
148
|
-
|
|
148
|
+
|
|
149
149
|
case 'finish':
|
|
150
150
|
// Stream finished
|
|
151
|
-
console.log('Stream finished');
|
|
151
|
+
// console.log('Stream finished');
|
|
152
152
|
done = true;
|
|
153
153
|
break;
|
|
154
|
-
|
|
154
|
+
|
|
155
155
|
// Additional message types that might be present in the AI library
|
|
156
156
|
case 'tool-call':
|
|
157
|
+
case 'tool-input-available': //for now input calls should be handled the same way as tool calls
|
|
157
158
|
// Tool call initiated
|
|
158
159
|
console.log('Tool call initiated:', data);
|
|
159
160
|
isToolCallMode = true;
|
|
160
|
-
if (data.toolCallId && data.toolName && data.args) {
|
|
161
|
+
if (data.toolCallId && data.toolName && (data.args || data.input)) {
|
|
161
162
|
toolInvocations.push({
|
|
162
163
|
toolCallId: data.toolCallId,
|
|
163
164
|
toolName: data.toolName,
|
|
164
|
-
args: data.args
|
|
165
|
+
args: data.args || data.input
|
|
165
166
|
});
|
|
166
167
|
}
|
|
167
168
|
break;
|
|
168
|
-
|
|
169
|
+
|
|
170
|
+
case 'tool-input-delta': //for now input calls should be handled the same way as tool calls
|
|
169
171
|
case 'tool-call-delta':
|
|
170
172
|
// Tool call delta (for streaming tool calls)
|
|
171
173
|
console.log('Tool call delta:', data);
|
|
172
174
|
break;
|
|
173
|
-
|
|
175
|
+
|
|
174
176
|
case 'tool-call-end':
|
|
175
177
|
// Tool call completed
|
|
176
178
|
console.log('Tool call completed:', data);
|
|
177
179
|
break;
|
|
178
|
-
|
|
180
|
+
|
|
179
181
|
case 'tool-result':
|
|
180
182
|
// Tool execution result
|
|
181
183
|
console.log('Tool result:', data);
|
|
182
184
|
break;
|
|
183
|
-
|
|
185
|
+
|
|
184
186
|
case 'error':
|
|
185
187
|
// Error occurred
|
|
186
188
|
console.error('Stream error:', data);
|
|
187
189
|
break;
|
|
188
|
-
|
|
190
|
+
|
|
189
191
|
case 'usage':
|
|
190
192
|
// Usage information
|
|
191
193
|
console.log('Usage info:', data);
|
|
192
194
|
break;
|
|
193
|
-
|
|
195
|
+
|
|
194
196
|
case 'model':
|
|
195
197
|
// Model information
|
|
196
198
|
console.log('Model info:', data);
|
|
197
199
|
break;
|
|
198
|
-
|
|
200
|
+
|
|
199
201
|
case 'stop':
|
|
200
202
|
// Stop signal
|
|
201
203
|
console.log('Stop signal received');
|
|
202
204
|
done = true;
|
|
203
205
|
break;
|
|
204
|
-
|
|
206
|
+
|
|
205
207
|
default:
|
|
206
208
|
// Unknown type, log for debugging
|
|
207
209
|
console.log('Unknown stream type:', data.type, data);
|
|
@@ -225,14 +227,14 @@ export async function streamChatGPT(backendUrl: string, messages: Message[], too
|
|
|
225
227
|
id: messageId,
|
|
226
228
|
role: "assistant",
|
|
227
229
|
content: content,
|
|
228
|
-
toolCalls: toolInvocations.length > 0 ? toolInvocations: undefined,
|
|
230
|
+
toolCalls: toolInvocations.length > 0 ? toolInvocations : undefined,
|
|
229
231
|
});
|
|
230
232
|
}
|
|
231
233
|
|
|
232
234
|
// Handle tool call scenario if tools were provided
|
|
233
235
|
if (tools.length > 0 && toolInvocations.length > 0) {
|
|
234
236
|
console.log('Tool calls detected, executing tools...');
|
|
235
|
-
|
|
237
|
+
|
|
236
238
|
const toolResults: Message[] = [];
|
|
237
239
|
for (const toolInvocation of toolInvocations) {
|
|
238
240
|
const tool = tools.find(t => t.name === toolInvocation.toolName);
|
|
@@ -254,7 +256,7 @@ export async function streamChatGPT(backendUrl: string, messages: Message[], too
|
|
|
254
256
|
}
|
|
255
257
|
}
|
|
256
258
|
}
|
|
257
|
-
|
|
259
|
+
|
|
258
260
|
if (toolResults.length > 0) {
|
|
259
261
|
currentMessages.push(...toolResults);
|
|
260
262
|
// Continue the loop to handle the next response
|
|
@@ -273,7 +275,7 @@ export async function streamChatGPT(backendUrl: string, messages: Message[], too
|
|
|
273
275
|
|
|
274
276
|
onResponse(messageId, content, true, toolInvocations);
|
|
275
277
|
return;
|
|
276
|
-
|
|
278
|
+
|
|
277
279
|
} catch (error) {
|
|
278
280
|
console.error('Error in streamChatGPT:', error);
|
|
279
281
|
onResponse(messageId, `Error: ${error instanceof Error ? error.message : String(error)}`, true, []);
|