@ourlu/assistant-sdk 0.2.3 → 0.2.5
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/iife/audio.v1.028c93fe.js +160 -0
- package/dist/iife/audio.v1.051ececf.js +183 -0
- package/dist/iife/audio.v1.200db1a6.js +208 -0
- package/dist/iife/audio.v1.20858b08.js +191 -0
- package/dist/iife/audio.v1.2690e445.js +179 -0
- package/dist/iife/audio.v1.2742ff12.js +188 -0
- package/dist/iife/audio.v1.70af81b3.js +191 -0
- package/dist/iife/audio.v1.95146620.js +1 -1
- package/dist/iife/audio.v1.b330baaf.js +166 -0
- package/dist/iife/audio.v1.bb9c2d88.js +181 -0
- package/dist/iife/audio.v1.ea7571b2.js +182 -0
- package/dist/iife/audio.v1.fc0aa8af.js +191 -0
- package/dist/iife/audio.v1.js +107 -95
- package/dist/iife/engine.v1.2b5bb43b.js +735 -0
- package/dist/iife/engine.v1.3b09dc20.js +3 -3
- package/dist/iife/engine.v1.56074e5a.js +769 -0
- package/dist/iife/engine.v1.61c10e6c.js +770 -0
- package/dist/iife/engine.v1.773fc15d.js +2 -2
- package/dist/iife/engine.v1.80d2230f.js +770 -0
- package/dist/iife/engine.v1.940ba9ea.js +764 -0
- package/dist/iife/engine.v1.99a33ee2.js +767 -0
- package/dist/iife/engine.v1.9ca6b7ec.js +756 -0
- package/dist/iife/engine.v1.a1f7dea2.js +764 -0
- package/dist/iife/engine.v1.c0c00bd0.js +721 -0
- package/dist/iife/engine.v1.c127656e.js +820 -0
- package/dist/iife/engine.v1.c54c9a1a.js +770 -0
- package/dist/iife/engine.v1.d1052e81.js +770 -0
- package/dist/iife/engine.v1.f6d23a0f.js +770 -0
- package/dist/iife/engine.v1.js +135 -36
- package/dist/iife/loader.v1.js +5 -1
- package/dist/iife/signalement.v1.d321dfde.js +518 -0
- package/dist/iife/signalement.v1.js +518 -0
- package/dist/iife/ui.v1.00abf020.js +895 -0
- package/dist/iife/ui.v1.5d2d4504.js +942 -0
- package/dist/iife/ui.v1.6afac75f.js +944 -0
- package/dist/iife/ui.v1.6becaa84.js +895 -0
- package/dist/iife/ui.v1.6c9e4995.js +895 -0
- package/dist/iife/ui.v1.7fb4db0b.js +935 -0
- package/dist/iife/ui.v1.88bf5494.js +898 -0
- package/dist/iife/ui.v1.923a4e6d.js +937 -0
- package/dist/iife/ui.v1.9bfe2815.js +942 -0
- package/dist/iife/ui.v1.a8cfe724.js +900 -0
- package/dist/iife/ui.v1.c58e1d58.js +959 -0
- package/dist/iife/ui.v1.cdfe9a45.js +919 -0
- package/dist/iife/ui.v1.e007c7c4.js +926 -0
- package/dist/iife/ui.v1.e24ba2bd.js +903 -0
- package/dist/iife/ui.v1.f1d8e998.js +903 -0
- package/dist/iife/ui.v1.fc52b520.js +895 -0
- package/dist/iife/ui.v1.js +154 -147
- package/dist/iife/widget-manifest.json +4 -3
- package/package.json +2 -1
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
"use strict";
|
|
3
|
+
var runtime = window.__OurluWidgetRuntimeV1 || (window.__OurluWidgetRuntimeV1 = {});
|
|
4
|
+
if (!runtime.utils) {
|
|
5
|
+
throw new Error("Widget runtime utils module must be loaded first.");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
var resolveRecorderMimeType = runtime.utils.resolveRecorderMimeType;
|
|
9
|
+
var encodeArrayBufferToBase64 = runtime.utils.encodeArrayBufferToBase64;
|
|
10
|
+
|
|
11
|
+
function WidgetAudioManager(state, api, ui) {
|
|
12
|
+
this.state = state;
|
|
13
|
+
this.api = api;
|
|
14
|
+
this.ui = ui;
|
|
15
|
+
this.stream = null;
|
|
16
|
+
this.recorder = null;
|
|
17
|
+
this.chunks = [];
|
|
18
|
+
this._recording = false;
|
|
19
|
+
this._processing = false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
WidgetAudioManager.prototype.isSupported = function() {
|
|
23
|
+
return !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia && typeof MediaRecorder !== "undefined");
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
WidgetAudioManager.prototype.toggle = async function() {
|
|
27
|
+
if (this._processing) return;
|
|
28
|
+
if (this._recording) return this.stop();
|
|
29
|
+
return this.start();
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
WidgetAudioManager.prototype.start = async function() {
|
|
33
|
+
if (!this.isSupported() || this.state.sending || this._recording || this._processing) return;
|
|
34
|
+
this.ui.showError("");
|
|
35
|
+
this.ui.setInput("");
|
|
36
|
+
this.ui.setMicListening(true);
|
|
37
|
+
try {
|
|
38
|
+
this.stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
39
|
+
this.chunks = [];
|
|
40
|
+
var mimeType = resolveRecorderMimeType();
|
|
41
|
+
this.recorder = new MediaRecorder(this.stream, mimeType ? { mimeType: mimeType } : {});
|
|
42
|
+
var self = this;
|
|
43
|
+
this.recorder.ondataavailable = function(event) {
|
|
44
|
+
if (event.data && event.data.size > 0) self.chunks.push(event.data);
|
|
45
|
+
};
|
|
46
|
+
this.recorder.start(250);
|
|
47
|
+
this._recording = true;
|
|
48
|
+
this.state.listening = true;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
this.cleanupMedia();
|
|
51
|
+
this._recording = false;
|
|
52
|
+
this.state.listening = false;
|
|
53
|
+
this.ui.setMicListening(false);
|
|
54
|
+
this.ui.showError("Erreur démarrage micro : " + (error.message || error));
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
WidgetAudioManager.prototype.stop = async function() {
|
|
59
|
+
if (!this._recording) return;
|
|
60
|
+
this._recording = false;
|
|
61
|
+
this._processing = true;
|
|
62
|
+
this.state.listening = false;
|
|
63
|
+
this.ui.setMicListening(false);
|
|
64
|
+
this.ui.setComposerDisabled(true);
|
|
65
|
+
try {
|
|
66
|
+
var blob = await this.collectRecordedBlob();
|
|
67
|
+
this.cleanupMedia();
|
|
68
|
+
if (!blob || blob.size === 0) {
|
|
69
|
+
this.ui.showError("Aucun audio enregistré.");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
this.ui.setInputPlaceholder("Transcription en cours…");
|
|
73
|
+
var base64 = encodeArrayBufferToBase64(await blob.arrayBuffer());
|
|
74
|
+
var sessionId = await this.api.ensureSession();
|
|
75
|
+
var audioSession = await this.api.startAudioSession(sessionId);
|
|
76
|
+
var audioSessionId = audioSession.audio_session_id || "";
|
|
77
|
+
await this.api.sendAudioChunk(sessionId, audioSessionId, base64);
|
|
78
|
+
var transcription = this.openTranscriptionListener(sessionId, audioSessionId);
|
|
79
|
+
await transcription.connected;
|
|
80
|
+
await this.api.closeAudioSession(sessionId, audioSessionId);
|
|
81
|
+
var transcribedText = await transcription.result;
|
|
82
|
+
this.ui.setInputPlaceholder("");
|
|
83
|
+
if (transcribedText) {
|
|
84
|
+
this.ui.setInput(transcribedText);
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
this.cleanupMedia();
|
|
88
|
+
this.ui.showError("Erreur transcription audio : " + (error.message || error));
|
|
89
|
+
} finally {
|
|
90
|
+
this._processing = false;
|
|
91
|
+
this.ui.setInputPlaceholder("");
|
|
92
|
+
this.ui.setComposerDisabled(false);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
WidgetAudioManager.prototype.openTranscriptionListener = function(sessionId, audioSessionId) {
|
|
97
|
+
var resolved = false;
|
|
98
|
+
var stopStream = null;
|
|
99
|
+
var resolveResult, rejectResult, resolveConnected;
|
|
100
|
+
var resultPromise = new Promise(function(res, rej) { resolveResult = res; rejectResult = rej; });
|
|
101
|
+
var connectedPromise = new Promise(function(res) { resolveConnected = res; });
|
|
102
|
+
var timeout = setTimeout(function() {
|
|
103
|
+
if (!resolved) {
|
|
104
|
+
resolved = true;
|
|
105
|
+
if (stopStream) stopStream();
|
|
106
|
+
rejectResult(new Error("Transcription audio : délai dépassé."));
|
|
107
|
+
}
|
|
108
|
+
}, 30000);
|
|
109
|
+
function belongsToCurrentSession(payload) {
|
|
110
|
+
if (!audioSessionId) return true;
|
|
111
|
+
var payloadAudioId = payload && payload.audio_session_id ? payload.audio_session_id : "";
|
|
112
|
+
return !payloadAudioId || payloadAudioId === audioSessionId;
|
|
113
|
+
}
|
|
114
|
+
this.api.streamAudioDraft(sessionId, {
|
|
115
|
+
onDelta: function() {},
|
|
116
|
+
onComplete: function(payload) {
|
|
117
|
+
if (resolved || !belongsToCurrentSession(payload)) return;
|
|
118
|
+
resolved = true;
|
|
119
|
+
clearTimeout(timeout);
|
|
120
|
+
if (stopStream) stopStream();
|
|
121
|
+
resolveResult(payload && payload.text ? String(payload.text).trim() : "");
|
|
122
|
+
},
|
|
123
|
+
onError: function(payload) {
|
|
124
|
+
if (resolved || !belongsToCurrentSession(payload)) return;
|
|
125
|
+
resolved = true;
|
|
126
|
+
clearTimeout(timeout);
|
|
127
|
+
if (stopStream) stopStream();
|
|
128
|
+
rejectResult(new Error((payload && payload.message) || "Erreur transcription audio"));
|
|
129
|
+
},
|
|
130
|
+
onTransportError: function(message) {
|
|
131
|
+
if (resolved) return;
|
|
132
|
+
var normalized = String(message || "").toLowerCase();
|
|
133
|
+
var isAbort = normalized.indexOf("aborted") !== -1 || normalized.indexOf("aborterror") !== -1;
|
|
134
|
+
if (isAbort) return;
|
|
135
|
+
resolved = true;
|
|
136
|
+
clearTimeout(timeout);
|
|
137
|
+
if (stopStream) stopStream();
|
|
138
|
+
rejectResult(new Error(message || "Erreur stream audio"));
|
|
139
|
+
}
|
|
140
|
+
}).then(function(stop) {
|
|
141
|
+
stopStream = stop;
|
|
142
|
+
resolveConnected();
|
|
143
|
+
}).catch(function(err) {
|
|
144
|
+
resolveConnected();
|
|
145
|
+
if (!resolved) {
|
|
146
|
+
resolved = true;
|
|
147
|
+
clearTimeout(timeout);
|
|
148
|
+
rejectResult(err);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
return { connected: connectedPromise, result: resultPromise };
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
WidgetAudioManager.prototype.collectRecordedBlob = function() {
|
|
155
|
+
var self = this;
|
|
156
|
+
if (!this.recorder) return Promise.resolve(new Blob());
|
|
157
|
+
return new Promise(function(resolve) {
|
|
158
|
+
var recorder = self.recorder;
|
|
159
|
+
function done() {
|
|
160
|
+
var blob = new Blob(self.chunks, { type: recorder.mimeType || "audio/webm" });
|
|
161
|
+
resolve(blob);
|
|
162
|
+
}
|
|
163
|
+
if (recorder.state === "inactive") return done();
|
|
164
|
+
recorder.addEventListener("stop", done, { once: true });
|
|
165
|
+
recorder.stop();
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
WidgetAudioManager.prototype.cleanupMedia = function() {
|
|
170
|
+
if (this.recorder) {
|
|
171
|
+
this.recorder.ondataavailable = null;
|
|
172
|
+
this.recorder = null;
|
|
173
|
+
}
|
|
174
|
+
if (this.stream) {
|
|
175
|
+
this.stream.getTracks().forEach(function(track) { track.stop(); });
|
|
176
|
+
this.stream = null;
|
|
177
|
+
}
|
|
178
|
+
this.chunks = [];
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
runtime.WidgetAudioManager = WidgetAudioManager;
|
|
182
|
+
})();
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
"use strict";
|
|
3
|
+
var runtime = window.__OurluWidgetRuntimeV1 || (window.__OurluWidgetRuntimeV1 = {});
|
|
4
|
+
if (!runtime.utils) {
|
|
5
|
+
throw new Error("Widget runtime utils module must be loaded first.");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
var resolveRecorderMimeType = runtime.utils.resolveRecorderMimeType;
|
|
9
|
+
var encodeArrayBufferToBase64 = runtime.utils.encodeArrayBufferToBase64;
|
|
10
|
+
|
|
11
|
+
function WidgetAudioManager(state, api, ui) {
|
|
12
|
+
this.state = state;
|
|
13
|
+
this.api = api;
|
|
14
|
+
this.ui = ui;
|
|
15
|
+
this.stream = null;
|
|
16
|
+
this.recorder = null;
|
|
17
|
+
this.chunks = [];
|
|
18
|
+
this._recording = false;
|
|
19
|
+
this._processing = false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
WidgetAudioManager.prototype.isSupported = function() {
|
|
23
|
+
return !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia && typeof MediaRecorder !== "undefined");
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
WidgetAudioManager.prototype.toggle = async function() {
|
|
27
|
+
if (this._processing) return;
|
|
28
|
+
if (this._recording) return this.stop();
|
|
29
|
+
return this.start();
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
WidgetAudioManager.prototype.start = async function() {
|
|
33
|
+
if (!this.isSupported() || this.state.sending || this._recording || this._processing) return;
|
|
34
|
+
this.ui.showError("");
|
|
35
|
+
this.ui.setInput("");
|
|
36
|
+
this.ui.setMicListening(true);
|
|
37
|
+
try {
|
|
38
|
+
this.stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
39
|
+
this.chunks = [];
|
|
40
|
+
var mimeType = resolveRecorderMimeType();
|
|
41
|
+
this.recorder = new MediaRecorder(this.stream, mimeType ? { mimeType: mimeType } : {});
|
|
42
|
+
var self = this;
|
|
43
|
+
this.recorder.ondataavailable = function(event) {
|
|
44
|
+
if (event.data && event.data.size > 0) self.chunks.push(event.data);
|
|
45
|
+
};
|
|
46
|
+
this.recorder.start(250);
|
|
47
|
+
this._recording = true;
|
|
48
|
+
this.state.listening = true;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
this.cleanupMedia();
|
|
51
|
+
this._recording = false;
|
|
52
|
+
this.state.listening = false;
|
|
53
|
+
this.ui.setMicListening(false);
|
|
54
|
+
this.ui.showError("Erreur démarrage micro : " + (error.message || error));
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
WidgetAudioManager.prototype.stop = async function() {
|
|
59
|
+
if (!this._recording) return;
|
|
60
|
+
this._recording = false;
|
|
61
|
+
this._processing = true;
|
|
62
|
+
this.state.listening = false;
|
|
63
|
+
this.ui.setMicListening(false);
|
|
64
|
+
this.ui.setComposerDisabled(true);
|
|
65
|
+
this.ui.setInputPlaceholder("Transcription en cours…");
|
|
66
|
+
try {
|
|
67
|
+
var blobAndSession = await Promise.all([
|
|
68
|
+
this.collectRecordedBlob(),
|
|
69
|
+
this.api.ensureSession()
|
|
70
|
+
]);
|
|
71
|
+
var blob = blobAndSession[0];
|
|
72
|
+
var sessionId = blobAndSession[1];
|
|
73
|
+
this.cleanupMedia();
|
|
74
|
+
if (!blob || blob.size === 0) {
|
|
75
|
+
this.ui.showError("Aucun audio enregistré.");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
var base64 = encodeArrayBufferToBase64(await blob.arrayBuffer());
|
|
79
|
+
var transcription = this.openTranscriptionListener(sessionId);
|
|
80
|
+
await transcription.connected;
|
|
81
|
+
var audioSession = await this.api.startAudioSession(sessionId);
|
|
82
|
+
var audioSessionId = audioSession.audio_session_id || "";
|
|
83
|
+
transcription.setAudioSessionId(audioSessionId);
|
|
84
|
+
await this.api.sendAudioChunk(sessionId, audioSessionId, base64);
|
|
85
|
+
await this.api.closeAudioSession(sessionId, audioSessionId);
|
|
86
|
+
var transcribedText = await transcription.result;
|
|
87
|
+
if (transcribedText) {
|
|
88
|
+
this.ui.setInput(transcribedText);
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
this.cleanupMedia();
|
|
92
|
+
this.ui.showError("Erreur transcription audio : " + (error.message || error));
|
|
93
|
+
} finally {
|
|
94
|
+
this._processing = false;
|
|
95
|
+
this.ui.setInputPlaceholder("");
|
|
96
|
+
this.ui.setComposerDisabled(false);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
WidgetAudioManager.prototype.openTranscriptionListener = function(sessionId) {
|
|
101
|
+
var resolved = false;
|
|
102
|
+
var stopStream = null;
|
|
103
|
+
var expectedAudioSessionId = "";
|
|
104
|
+
var resolveResult, rejectResult, resolveConnected;
|
|
105
|
+
var resultPromise = new Promise(function(res, rej) { resolveResult = res; rejectResult = rej; });
|
|
106
|
+
var connectedPromise = new Promise(function(res) { resolveConnected = res; });
|
|
107
|
+
var timeout = setTimeout(function() {
|
|
108
|
+
if (!resolved) {
|
|
109
|
+
resolved = true;
|
|
110
|
+
if (stopStream) stopStream();
|
|
111
|
+
rejectResult(new Error("Transcription audio : délai dépassé."));
|
|
112
|
+
}
|
|
113
|
+
}, 60000);
|
|
114
|
+
function belongsToCurrentSession(payload) {
|
|
115
|
+
if (!expectedAudioSessionId) return true;
|
|
116
|
+
var payloadAudioId = payload && payload.audio_session_id ? payload.audio_session_id : "";
|
|
117
|
+
return !payloadAudioId || payloadAudioId === expectedAudioSessionId;
|
|
118
|
+
}
|
|
119
|
+
this.api.streamAudioDraft(sessionId, {
|
|
120
|
+
onDelta: function() {},
|
|
121
|
+
onComplete: function(payload) {
|
|
122
|
+
if (resolved || !belongsToCurrentSession(payload)) return;
|
|
123
|
+
resolved = true;
|
|
124
|
+
clearTimeout(timeout);
|
|
125
|
+
if (stopStream) stopStream();
|
|
126
|
+
resolveResult(payload && payload.text ? String(payload.text).trim() : "");
|
|
127
|
+
},
|
|
128
|
+
onError: function(payload) {
|
|
129
|
+
if (resolved || !belongsToCurrentSession(payload)) return;
|
|
130
|
+
resolved = true;
|
|
131
|
+
clearTimeout(timeout);
|
|
132
|
+
if (stopStream) stopStream();
|
|
133
|
+
rejectResult(new Error((payload && payload.message) || "Erreur transcription audio"));
|
|
134
|
+
},
|
|
135
|
+
onTransportError: function(message) {
|
|
136
|
+
if (resolved) return;
|
|
137
|
+
var normalized = String(message || "").toLowerCase();
|
|
138
|
+
var isAbort = normalized.indexOf("aborted") !== -1 || normalized.indexOf("aborterror") !== -1;
|
|
139
|
+
if (isAbort) return;
|
|
140
|
+
resolved = true;
|
|
141
|
+
clearTimeout(timeout);
|
|
142
|
+
if (stopStream) stopStream();
|
|
143
|
+
rejectResult(new Error(message || "Erreur stream audio"));
|
|
144
|
+
}
|
|
145
|
+
}).then(function(stop) {
|
|
146
|
+
stopStream = stop;
|
|
147
|
+
resolveConnected();
|
|
148
|
+
}).catch(function(err) {
|
|
149
|
+
resolveConnected();
|
|
150
|
+
if (!resolved) {
|
|
151
|
+
resolved = true;
|
|
152
|
+
clearTimeout(timeout);
|
|
153
|
+
rejectResult(err);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
return {
|
|
157
|
+
connected: connectedPromise,
|
|
158
|
+
result: resultPromise,
|
|
159
|
+
setAudioSessionId: function(id) { expectedAudioSessionId = id; }
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
WidgetAudioManager.prototype.collectRecordedBlob = function() {
|
|
164
|
+
var self = this;
|
|
165
|
+
if (!this.recorder) return Promise.resolve(new Blob());
|
|
166
|
+
return new Promise(function(resolve) {
|
|
167
|
+
var recorder = self.recorder;
|
|
168
|
+
function done() {
|
|
169
|
+
var blob = new Blob(self.chunks, { type: recorder.mimeType || "audio/webm" });
|
|
170
|
+
resolve(blob);
|
|
171
|
+
}
|
|
172
|
+
if (recorder.state === "inactive") return done();
|
|
173
|
+
recorder.addEventListener("stop", done, { once: true });
|
|
174
|
+
recorder.stop();
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
WidgetAudioManager.prototype.cleanupMedia = function() {
|
|
179
|
+
if (this.recorder) {
|
|
180
|
+
this.recorder.ondataavailable = null;
|
|
181
|
+
this.recorder = null;
|
|
182
|
+
}
|
|
183
|
+
if (this.stream) {
|
|
184
|
+
this.stream.getTracks().forEach(function(track) { track.stop(); });
|
|
185
|
+
this.stream = null;
|
|
186
|
+
}
|
|
187
|
+
this.chunks = [];
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
runtime.WidgetAudioManager = WidgetAudioManager;
|
|
191
|
+
})();
|
package/dist/iife/audio.v1.js
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
throw new Error("Widget runtime utils module must be loaded first.");
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
var mergeTranscript = runtime.utils.mergeTranscript;
|
|
9
8
|
var resolveRecorderMimeType = runtime.utils.resolveRecorderMimeType;
|
|
10
9
|
var encodeArrayBufferToBase64 = runtime.utils.encodeArrayBufferToBase64;
|
|
11
10
|
|
|
@@ -16,11 +15,8 @@
|
|
|
16
15
|
this.stream = null;
|
|
17
16
|
this.recorder = null;
|
|
18
17
|
this.chunks = [];
|
|
19
|
-
this.
|
|
20
|
-
this.
|
|
21
|
-
this.cleanupTimer = null;
|
|
22
|
-
this._starting = false;
|
|
23
|
-
this._stopping = false;
|
|
18
|
+
this._recording = false;
|
|
19
|
+
this._processing = false;
|
|
24
20
|
}
|
|
25
21
|
|
|
26
22
|
WidgetAudioManager.prototype.isSupported = function() {
|
|
@@ -28,34 +24,17 @@
|
|
|
28
24
|
};
|
|
29
25
|
|
|
30
26
|
WidgetAudioManager.prototype.toggle = async function() {
|
|
31
|
-
if (this.
|
|
32
|
-
if (this.
|
|
27
|
+
if (this._processing) return;
|
|
28
|
+
if (this._recording) return this.stop();
|
|
33
29
|
return this.start();
|
|
34
30
|
};
|
|
35
31
|
|
|
36
32
|
WidgetAudioManager.prototype.start = async function() {
|
|
37
|
-
if (!this.isSupported() || this.state.sending || this.
|
|
38
|
-
this.
|
|
33
|
+
if (!this.isSupported() || this.state.sending || this._recording || this._processing) return;
|
|
34
|
+
this.ui.showError("");
|
|
35
|
+
this.ui.setInput("");
|
|
39
36
|
this.ui.setMicListening(true);
|
|
40
37
|
try {
|
|
41
|
-
this.ui.showError("");
|
|
42
|
-
this.clearDraftCleanup();
|
|
43
|
-
if (this.activeAudioSessionId && this.state.sessionId) {
|
|
44
|
-
try {
|
|
45
|
-
await this.api.closeAudioSession(this.state.sessionId, this.activeAudioSessionId);
|
|
46
|
-
} catch (_) { /* best-effort */ }
|
|
47
|
-
this.activeAudioSessionId = "";
|
|
48
|
-
}
|
|
49
|
-
var sessionId = await this.api.ensureSession();
|
|
50
|
-
if (this.stopDraftStream) this.stopDraftStream();
|
|
51
|
-
this.stopDraftStream = await this.api.streamAudioDraft(sessionId, {
|
|
52
|
-
onDelta: this.handleDraftDelta.bind(this),
|
|
53
|
-
onComplete: this.handleDraftComplete.bind(this),
|
|
54
|
-
onError: this.handleDraftError.bind(this),
|
|
55
|
-
onTransportError: this.handleDraftTransportError.bind(this)
|
|
56
|
-
});
|
|
57
|
-
var audioSession = await this.api.startAudioSession(sessionId);
|
|
58
|
-
this.activeAudioSessionId = audioSession.audio_session_id || "";
|
|
59
38
|
this.stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
60
39
|
this.chunks = [];
|
|
61
40
|
var mimeType = resolveRecorderMimeType();
|
|
@@ -65,52 +44,129 @@
|
|
|
65
44
|
if (event.data && event.data.size > 0) self.chunks.push(event.data);
|
|
66
45
|
};
|
|
67
46
|
this.recorder.start(250);
|
|
47
|
+
this._recording = true;
|
|
68
48
|
this.state.listening = true;
|
|
69
49
|
} catch (error) {
|
|
70
50
|
this.cleanupMedia();
|
|
71
|
-
this.
|
|
51
|
+
this._recording = false;
|
|
72
52
|
this.state.listening = false;
|
|
73
53
|
this.ui.setMicListening(false);
|
|
74
|
-
this.ui.showError("Erreur démarrage
|
|
75
|
-
} finally {
|
|
76
|
-
this._starting = false;
|
|
54
|
+
this.ui.showError("Erreur démarrage micro : " + (error.message || error));
|
|
77
55
|
}
|
|
78
56
|
};
|
|
79
57
|
|
|
80
58
|
WidgetAudioManager.prototype.stop = async function() {
|
|
81
|
-
if (!this.
|
|
82
|
-
this.
|
|
59
|
+
if (!this._recording) return;
|
|
60
|
+
this._recording = false;
|
|
61
|
+
this._processing = true;
|
|
83
62
|
this.state.listening = false;
|
|
84
63
|
this.ui.setMicListening(false);
|
|
85
|
-
|
|
86
|
-
|
|
64
|
+
this.ui.setComposerDisabled(true);
|
|
65
|
+
this.ui.setInputPlaceholder("Transcription en cours…");
|
|
87
66
|
try {
|
|
88
|
-
var
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
67
|
+
var blobAndSession = await Promise.all([
|
|
68
|
+
this.collectRecordedBlob(),
|
|
69
|
+
this.api.ensureSession()
|
|
70
|
+
]);
|
|
71
|
+
var blob = blobAndSession[0];
|
|
72
|
+
var sessionId = blobAndSession[1];
|
|
73
|
+
this.cleanupMedia();
|
|
74
|
+
if (!blob || blob.size === 0) {
|
|
75
|
+
this.ui.showError("Aucun audio enregistré.");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
var base64 = encodeArrayBufferToBase64(await blob.arrayBuffer());
|
|
79
|
+
var transcription = this.openTranscriptionListener(sessionId);
|
|
80
|
+
await transcription.connected;
|
|
81
|
+
var audioSession = await this.api.startAudioSession(sessionId);
|
|
82
|
+
var audioSessionId = audioSession.audio_session_id || "";
|
|
83
|
+
transcription.setAudioSessionId(audioSessionId);
|
|
84
|
+
await this.api.sendAudioChunk(sessionId, audioSessionId, base64);
|
|
85
|
+
await this.api.closeAudioSession(sessionId, audioSessionId);
|
|
86
|
+
var transcribedText = await transcription.result;
|
|
87
|
+
if (transcribedText) {
|
|
88
|
+
this.ui.setInput(transcribedText);
|
|
92
89
|
}
|
|
93
|
-
if (sessionId && audioSessionId) await this.api.closeAudioSession(sessionId, audioSessionId);
|
|
94
90
|
} catch (error) {
|
|
95
|
-
this.
|
|
91
|
+
this.cleanupMedia();
|
|
92
|
+
this.ui.showError("Erreur transcription audio : " + (error.message || error));
|
|
96
93
|
} finally {
|
|
97
|
-
this.
|
|
98
|
-
this.
|
|
99
|
-
this.
|
|
94
|
+
this._processing = false;
|
|
95
|
+
this.ui.setInputPlaceholder("");
|
|
96
|
+
this.ui.setComposerDisabled(false);
|
|
100
97
|
}
|
|
101
98
|
};
|
|
102
99
|
|
|
103
|
-
WidgetAudioManager.prototype.
|
|
104
|
-
var
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
100
|
+
WidgetAudioManager.prototype.openTranscriptionListener = function(sessionId) {
|
|
101
|
+
var resolved = false;
|
|
102
|
+
var stopStream = null;
|
|
103
|
+
var expectedAudioSessionId = "";
|
|
104
|
+
var resolveResult, rejectResult, resolveConnected;
|
|
105
|
+
var resultPromise = new Promise(function(res, rej) { resolveResult = res; rejectResult = rej; });
|
|
106
|
+
var connectedPromise = new Promise(function(res) { resolveConnected = res; });
|
|
107
|
+
var timeout = setTimeout(function() {
|
|
108
|
+
if (!resolved) {
|
|
109
|
+
resolved = true;
|
|
110
|
+
if (stopStream) stopStream();
|
|
111
|
+
rejectResult(new Error("Transcription audio : délai dépassé."));
|
|
112
|
+
}
|
|
113
|
+
}, 60000);
|
|
114
|
+
function belongsToCurrentSession(payload) {
|
|
115
|
+
if (!expectedAudioSessionId) return false;
|
|
116
|
+
var payloadAudioId = payload && payload.audio_session_id ? payload.audio_session_id : "";
|
|
117
|
+
return !payloadAudioId || payloadAudioId === expectedAudioSessionId;
|
|
108
118
|
}
|
|
119
|
+
this.api.streamAudioDraft(sessionId, {
|
|
120
|
+
onDelta: function() {},
|
|
121
|
+
onComplete: function(payload) {
|
|
122
|
+
if (resolved || !belongsToCurrentSession(payload)) return;
|
|
123
|
+
resolved = true;
|
|
124
|
+
clearTimeout(timeout);
|
|
125
|
+
if (stopStream) stopStream();
|
|
126
|
+
resolveResult(payload && payload.text ? String(payload.text).trim() : "");
|
|
127
|
+
},
|
|
128
|
+
onError: function(payload) {
|
|
129
|
+
if (resolved || !belongsToCurrentSession(payload)) return;
|
|
130
|
+
resolved = true;
|
|
131
|
+
clearTimeout(timeout);
|
|
132
|
+
if (stopStream) stopStream();
|
|
133
|
+
rejectResult(new Error((payload && payload.message) || "Erreur transcription audio"));
|
|
134
|
+
},
|
|
135
|
+
onTransportError: function(message) {
|
|
136
|
+
if (resolved) return;
|
|
137
|
+
var normalized = String(message || "").toLowerCase();
|
|
138
|
+
var isAbort = normalized.indexOf("aborted") !== -1 || normalized.indexOf("aborterror") !== -1;
|
|
139
|
+
if (isAbort) return;
|
|
140
|
+
resolved = true;
|
|
141
|
+
clearTimeout(timeout);
|
|
142
|
+
if (stopStream) stopStream();
|
|
143
|
+
rejectResult(new Error(message || "Erreur stream audio"));
|
|
144
|
+
}
|
|
145
|
+
}).then(function(stop) {
|
|
146
|
+
stopStream = stop;
|
|
147
|
+
resolveConnected();
|
|
148
|
+
}).catch(function(err) {
|
|
149
|
+
resolveConnected();
|
|
150
|
+
if (!resolved) {
|
|
151
|
+
resolved = true;
|
|
152
|
+
clearTimeout(timeout);
|
|
153
|
+
rejectResult(err);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
return {
|
|
157
|
+
connected: connectedPromise,
|
|
158
|
+
result: resultPromise,
|
|
159
|
+
setAudioSessionId: function(id) { expectedAudioSessionId = id; }
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
WidgetAudioManager.prototype.collectRecordedBlob = function() {
|
|
164
|
+
var self = this;
|
|
165
|
+
if (!this.recorder) return Promise.resolve(new Blob());
|
|
109
166
|
return new Promise(function(resolve) {
|
|
110
167
|
var recorder = self.recorder;
|
|
111
168
|
function done() {
|
|
112
169
|
var blob = new Blob(self.chunks, { type: recorder.mimeType || "audio/webm" });
|
|
113
|
-
self.cleanupMedia();
|
|
114
170
|
resolve(blob);
|
|
115
171
|
}
|
|
116
172
|
if (recorder.state === "inactive") return done();
|
|
@@ -131,49 +187,5 @@
|
|
|
131
187
|
this.chunks = [];
|
|
132
188
|
};
|
|
133
189
|
|
|
134
|
-
WidgetAudioManager.prototype.handleDraftDelta = function(payload) {
|
|
135
|
-
if (!payload || !payload.delta) return;
|
|
136
|
-
this.ui.setInput(mergeTranscript(this.ui.inputValue(), payload.delta));
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
WidgetAudioManager.prototype.handleDraftComplete = function(payload) {
|
|
140
|
-
if (payload && payload.text) this.ui.setInput(String(payload.text).trim());
|
|
141
|
-
this.scheduleDraftCleanup();
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
WidgetAudioManager.prototype.handleDraftError = function(payload) {
|
|
145
|
-
this.ui.showError((payload && payload.message) || "Erreur transcription audio");
|
|
146
|
-
this.scheduleDraftCleanup();
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
WidgetAudioManager.prototype.isExpectedStreamAbortMessage = function(message) {
|
|
150
|
-
var normalized = String(message || "").toLowerCase();
|
|
151
|
-
return normalized.indexOf("aborted") !== -1 ||
|
|
152
|
-
normalized.indexOf("aborterror") !== -1 ||
|
|
153
|
-
normalized.indexOf("body stream buffer was aborted") !== -1 ||
|
|
154
|
-
normalized.indexOf("the operation was aborted") !== -1;
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
WidgetAudioManager.prototype.handleDraftTransportError = function(message) {
|
|
158
|
-
if (this.isExpectedStreamAbortMessage(message)) return;
|
|
159
|
-
this.ui.showError(message || "Erreur stream audio");
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
WidgetAudioManager.prototype.clearDraftCleanup = function() {
|
|
163
|
-
if (!this.cleanupTimer) return;
|
|
164
|
-
clearTimeout(this.cleanupTimer);
|
|
165
|
-
this.cleanupTimer = null;
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
WidgetAudioManager.prototype.scheduleDraftCleanup = function() {
|
|
169
|
-
var self = this;
|
|
170
|
-
this.clearDraftCleanup();
|
|
171
|
-
this.cleanupTimer = setTimeout(function() {
|
|
172
|
-
if (self.stopDraftStream) self.stopDraftStream();
|
|
173
|
-
self.stopDraftStream = null;
|
|
174
|
-
self.cleanupTimer = null;
|
|
175
|
-
}, 3000);
|
|
176
|
-
};
|
|
177
|
-
|
|
178
190
|
runtime.WidgetAudioManager = WidgetAudioManager;
|
|
179
191
|
})();
|