mini-chat-bot-widget 0.2.0 → 0.8.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.
@@ -0,0 +1,363 @@
1
+ export class BhashiniFrontend {
2
+ constructor() {
3
+ this.apiKey = '23588533c1-d990-4a7d-b052-d970c886147e';
4
+ this.userId = '749bd0b0c65e4d17a372be0ad92af981';
5
+ this.callbackUrl = 'https://meity-auth.ulcacontrib.org/ulca/apis/v0/model/getModelsPipeline';
6
+ this.inferenceUrl = null;
7
+ this.inferenceHeaders = {};
8
+ this.asrServiceId = {
9
+ 'hi': 'ai4bharat/conformer-hi-gpu--t4',
10
+ 'en': 'ai4bharat/whisper-medium-en--gpu--t4'
11
+ };
12
+ this.ttsServiceId = {
13
+ 'hi': 'ai4bharat/indic-tts-coqui-indo_aryan-gpu--t4',
14
+ 'en': 'ai4bharat/indic-tts-coqui-misc-gpu--t4'
15
+ };
16
+ this.translationServiceId = 'ai4bharat/indictrans-v2-all-gpu--t4';
17
+ }
18
+
19
+ async initialize() {
20
+ try {
21
+ const response = await fetch(this.callbackUrl, {
22
+ method: 'POST',
23
+ headers: {
24
+ 'Content-Type': 'application/json',
25
+ 'userID': this.userId,
26
+ 'ulcaApiKey': this.apiKey
27
+ },
28
+ body: JSON.stringify({
29
+ pipelineTasks: [{ taskType: 'asr' }],
30
+ pipelineRequestConfig: {
31
+ pipelineId: '64392f96daac500b55c543cd'
32
+ }
33
+ })
34
+ });
35
+
36
+ const data = await response.json();
37
+
38
+ if (data.pipelineInferenceAPIEndPoint) {
39
+ this.inferenceUrl = data.pipelineInferenceAPIEndPoint.callbackUrl;
40
+ const keyName = data.pipelineInferenceAPIEndPoint.inferenceApiKey.name;
41
+ const keyValue = data.pipelineInferenceAPIEndPoint.inferenceApiKey.value;
42
+ this.inferenceHeaders = {
43
+ 'Content-Type': 'application/json',
44
+ [keyName]: keyValue
45
+ };
46
+ } else {
47
+ // Fallback to direct endpoint
48
+ this.inferenceUrl = 'https://dhruva-api.bhashini.gov.in/services/inference/pipeline';
49
+ this.inferenceHeaders = {
50
+ 'Content-Type': 'application/json',
51
+ 'userID': this.userId,
52
+ 'ulcaApiKey': this.apiKey
53
+ };
54
+ }
55
+
56
+ return true;
57
+ } catch (error) {
58
+ console.error('Failed to initialize Bhashini:', error);
59
+ // Use fallback endpoint
60
+ this.inferenceUrl = 'https://dhruva-api.bhashini.gov.in/services/inference/pipeline';
61
+ this.inferenceHeaders = {
62
+ 'Content-Type': 'application/json',
63
+ 'userID': this.userId,
64
+ 'ulcaApiKey': this.apiKey
65
+ };
66
+ return false;
67
+ }
68
+ }
69
+
70
+ async convertToWav(audioBlob) {
71
+ return new Promise((resolve, reject) => {
72
+ const audioContext = new (window.AudioContext || window.webkitAudioContext)();
73
+ const reader = new FileReader();
74
+
75
+ reader.onload = async (e) => {
76
+ try {
77
+ const arrayBuffer = e.target.result;
78
+ const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
79
+
80
+ // Convert to mono 16kHz
81
+ const offlineContext = new OfflineAudioContext(1, audioBuffer.duration * 16000, 16000);
82
+ const source = offlineContext.createBufferSource();
83
+ source.buffer = audioBuffer;
84
+ source.connect(offlineContext.destination);
85
+ source.start();
86
+
87
+ const renderedBuffer = await offlineContext.startRendering();
88
+
89
+ // Convert to WAV
90
+ const wav = this.audioBufferToWav(renderedBuffer);
91
+ const wavBlob = new Blob([wav], { type: 'audio/wav' });
92
+
93
+ // Convert to base64
94
+ const wavReader = new FileReader();
95
+ wavReader.onloadend = () => {
96
+ const base64 = wavReader.result.split(',')[1];
97
+ resolve(base64);
98
+ };
99
+ wavReader.readAsDataURL(wavBlob);
100
+ } catch (error) {
101
+ reject(error);
102
+ }
103
+ };
104
+
105
+ reader.onerror = reject;
106
+ reader.readAsArrayBuffer(audioBlob);
107
+ });
108
+ }
109
+
110
+ audioBufferToWav(buffer) {
111
+ const length = buffer.length * buffer.numberOfChannels * 2 + 44;
112
+ const arrayBuffer = new ArrayBuffer(length);
113
+ const view = new DataView(arrayBuffer);
114
+ const channels = [];
115
+ let offset = 0;
116
+ let pos = 0;
117
+
118
+ // Write WAV header
119
+ const setUint16 = (data) => {
120
+ view.setUint16(pos, data, true);
121
+ pos += 2;
122
+ };
123
+ const setUint32 = (data) => {
124
+ view.setUint32(pos, data, true);
125
+ pos += 4;
126
+ };
127
+
128
+ // "RIFF" chunk descriptor
129
+ setUint32(0x46464952); // "RIFF"
130
+ setUint32(length - 8); // file length - 8
131
+ setUint32(0x45564157); // "WAVE"
132
+
133
+ // "fmt " sub-chunk
134
+ setUint32(0x20746d66); // "fmt "
135
+ setUint32(16); // SubChunk1Size = 16
136
+ setUint16(1); // AudioFormat = 1 (PCM)
137
+ setUint16(buffer.numberOfChannels);
138
+ setUint32(buffer.sampleRate);
139
+ setUint32(buffer.sampleRate * 2 * buffer.numberOfChannels); // byte rate
140
+ setUint16(buffer.numberOfChannels * 2); // block align
141
+ setUint16(16); // bits per sample
142
+
143
+ // "data" sub-chunk
144
+ setUint32(0x61746164); // "data"
145
+ setUint32(length - pos - 4); // SubChunk2Size
146
+
147
+ // Write interleaved data
148
+ for (let i = 0; i < buffer.numberOfChannels; i++) {
149
+ channels.push(buffer.getChannelData(i));
150
+ }
151
+
152
+ while (pos < length) {
153
+ for (let i = 0; i < buffer.numberOfChannels; i++) {
154
+ let sample = Math.max(-1, Math.min(1, channels[i][offset]));
155
+ sample = sample < 0 ? sample * 0x8000 : sample * 0x7FFF;
156
+ view.setInt16(pos, sample, true);
157
+ pos += 2;
158
+ }
159
+ offset++;
160
+ }
161
+
162
+ return arrayBuffer;
163
+ }
164
+
165
+ async speechToText(
166
+ base64Audio,
167
+ sourceLanguage = 'auto',
168
+ targetLanguage = 'hi'
169
+ ) {
170
+ if (!this.inferenceUrl) {
171
+ await this.initialize();
172
+ }
173
+
174
+ const pipelineTasks = [
175
+ {
176
+ taskType: 'asr',
177
+ config: {
178
+ language: { sourceLanguage },
179
+ serviceId:
180
+ this.asrServiceId[sourceLanguage] ||
181
+ this.asrServiceId['hi'],
182
+ audioFormat: 'wav',
183
+ samplingRate: 16000
184
+ }
185
+ }
186
+ ];
187
+
188
+ // Only translate if languages differ
189
+ if (sourceLanguage !== targetLanguage) {
190
+ pipelineTasks.push({
191
+ taskType: 'translation',
192
+ config: {
193
+ sourceLanguage:
194
+ sourceLanguage === 'auto' ? undefined : sourceLanguage,
195
+ targetLanguage
196
+ }
197
+ });
198
+ }
199
+
200
+ const response = await fetch(this.inferenceUrl, {
201
+ method: 'POST',
202
+ headers: this.inferenceHeaders,
203
+ body: JSON.stringify({
204
+ pipelineTasks,
205
+ inputData: {
206
+ audio: [{ audioContent: base64Audio }]
207
+ }
208
+ })
209
+ });
210
+
211
+ if (!response.ok) {
212
+ throw new Error(`API error: ${response.status} ${response.statusText}`);
213
+ }
214
+
215
+ const data = await response.json();
216
+
217
+ // If translated, return translation
218
+ if (pipelineTasks.length > 1) {
219
+ return data.pipelineResponse[1].output[0].target;
220
+ }
221
+
222
+ // Otherwise return raw transcription
223
+ return data.pipelineResponse[0].output[0].source;
224
+ }
225
+
226
+
227
+ async textToSpeech(text, language = 'en', gender = 'female') {
228
+ if (!this.inferenceUrl) {
229
+ await this.initialize();
230
+ }
231
+
232
+ // ✅ Clean the text first
233
+ const cleanedText = text.replace(/!/g, ".");
234
+
235
+ const response = await fetch(this.inferenceUrl, {
236
+ method: 'POST',
237
+ headers: this.inferenceHeaders,
238
+ body: JSON.stringify({
239
+ pipelineTasks: [
240
+ {
241
+ taskType: "tts",
242
+ config: {
243
+ language: { sourceLanguage: language },
244
+ serviceId: this.ttsServiceId[language] || this.ttsServiceId["en"],
245
+ samplingRate: 8000,
246
+ gender: gender,
247
+ },
248
+ },
249
+ ],
250
+ inputData: {
251
+ input: [{ source: cleanedText }],
252
+ },
253
+ }),
254
+ });
255
+
256
+ if (!response.ok) {
257
+ throw new Error(`TTS API error: ${response.status} ${response.statusText}`);
258
+ }
259
+
260
+ const data = await response.json();
261
+ return data.pipelineResponse[0].audio[0].audioContent;
262
+ }
263
+
264
+
265
+ async translateText(text, sourceLanguage, targetLanguage) {
266
+ if (!this.inferenceUrl) {
267
+ await this.initialize();
268
+ }
269
+
270
+ const response = await fetch(this.inferenceUrl, {
271
+ method: 'POST',
272
+ headers: this.inferenceHeaders,
273
+ body: JSON.stringify({
274
+ pipelineTasks: [{
275
+ taskType: 'translation',
276
+ config: {
277
+ language: {
278
+ sourceLanguage: sourceLanguage,
279
+ targetLanguage: targetLanguage
280
+ },
281
+ serviceId: this.translationServiceId
282
+ }
283
+ }],
284
+ inputData: {
285
+ input: [{ source: text }]
286
+ }
287
+ })
288
+ });
289
+
290
+ if (!response.ok) {
291
+ throw new Error(`Translation API error: ${response.status} ${response.statusText}`);
292
+ }
293
+
294
+ const data = await response.json();
295
+ return data.pipelineResponse[0].output[0].target;
296
+ }
297
+
298
+ async speechToEnglish(base64Audio, sourceLanguage = 'hi') {
299
+ // Step 1: Transcribe audio to source language text
300
+ const transcribedText = await this.speechToText(base64Audio, sourceLanguage);
301
+
302
+ // Step 2: If already English, return as is
303
+ if (sourceLanguage === 'en') {
304
+ return { transcription: transcribedText, translation: transcribedText };
305
+ }
306
+
307
+ // Step 3: Translate to English
308
+ const translatedText = await this.translateText(transcribedText, sourceLanguage, 'en');
309
+
310
+ return {
311
+ transcription: transcribedText,
312
+ translation: translatedText
313
+ };
314
+ }
315
+
316
+ async speechToHindi(base64Audio, sourceLanguage = 'en') {
317
+ // Step 1: Transcribe audio to source language text
318
+ const transcribedText = await this.speechToText(base64Audio, sourceLanguage);
319
+
320
+ // Step 2: If already Hindi, return as is
321
+ if (sourceLanguage === 'hi') {
322
+ return { transcription: transcribedText, translation: transcribedText };
323
+ }
324
+
325
+ // Step 3: Translate to Hindi
326
+ const translatedText = await this.translateText(transcribedText, sourceLanguage, 'hi');
327
+
328
+ return {
329
+ transcription: transcribedText,
330
+ translation: translatedText
331
+ };
332
+ }
333
+
334
+
335
+ async transcribeSpeechToEnglish(base64Audio, sourceLanguage = "hi") {
336
+ // Step 1: Transcribe audio to source language text
337
+ const transcribedText = await this.speechToText(
338
+ base64Audio,
339
+ sourceLanguage,
340
+ );
341
+
342
+ // Step 2: If already English, return as is
343
+ if (sourceLanguage === "en") {
344
+ return {
345
+ transcription: transcribedText,
346
+ translation: transcribedText,
347
+ };
348
+ }
349
+
350
+ // Step 3: Translate to English
351
+ const translatedText = await this.translateText(
352
+ transcribedText,
353
+ sourceLanguage,
354
+ "en",
355
+ );
356
+
357
+ return {
358
+ transcription: transcribedText,
359
+ translation: translatedText,
360
+ };
361
+ }
362
+
363
+ }