@hoanghong/speaking-angular 0.2.0 → 0.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/CHANGELOG.md CHANGED
@@ -1,12 +1,28 @@
1
- # Changelog
2
-
3
- ## 0.2.0
4
-
5
- - Add standalone FselSpeakingComponent and provideSpeakingGateway.
6
- - Include microphone, upload, five languages, cancellation and result/error outputs.
7
- - Document installation, worklet assets, application bootstrap, gateway contract and troubleshooting with full code.
8
- - Preserve existing low-level 0.1 APIs.
9
-
10
- ## 0.1.0
11
-
12
- - Initial Angular service and core SDK integration.
1
+ ## 0.4.0
2
+
3
+ - Add US English pronunciation assessment client: 120-second audio, reference passage, optional IPA tokens and uncalibrated phoneme diagnostics.
4
+ - Share existing gateway/public application sessions; no service credentials in client apps.
5
+ - Local test release; requires server pronunciation endpoint and model provisioning.
6
+
7
+ # Changelog
8
+
9
+ ## 0.3.0
10
+
11
+ - Per-application keys with server-managed quota; appKey setup without a service URL.
12
+
13
+ - Hosted public service configuration with automatic short-lived sessions and quota errors.
14
+ - Select realtime/upload using serviceType; no URL or token setup for hosted use.
15
+ - Completed callback includes text and audio: recorded PCM as WAV, or original uploaded file.
16
+ - Bounded five-minute recording buffer and preserved final residual audio.
17
+
18
+
19
+ ## 0.2.0
20
+
21
+ - Add standalone FselSpeakingComponent and provideSpeakingGateway.
22
+ - Include microphone, upload, five languages, cancellation and result/error outputs.
23
+ - Document installation, worklet assets, application bootstrap, gateway contract and troubleshooting with full code.
24
+ - Preserve existing low-level 0.1 APIs.
25
+
26
+ ## 0.1.0
27
+
28
+ - Initial Angular service and core SDK integration.
package/README.md CHANGED
@@ -1,175 +1,158 @@
1
1
  # @hoanghong/speaking-angular
2
2
 
3
- Component Angular dùng ngay để nhận dạng lời nói **online**, gồm microphone realtime, upload audio, chọn 5 ngôn ngữ và hiển thị kết quả. MIT. Angular **18.x**, RxJS 7.8, trình duyệt có AudioWorklet. Model chạy trên server, không tải model xuống máy người dùng.
3
+ Component nhận dạng lời nói online dùng ngay. Chỉ cài package, chọn `serviceType`, ngôn ngữ và nhận callback **text + file audio**. Không khai báo URL, không tạo gateway, không nhập token, không copy worklet. Model chạy tại dịch vụ FSEL, không tải xuống máy người dùng. MIT. Angular 18.x, RxJS 7.8.
4
4
 
5
- ## 1. Chuẩn bị kết nối một lần
6
-
7
- Ứng dụng cần gateway của backend, ví dụ `https://your-app.example/api/speaking`. Gateway xác thực người dùng bằng phiên đăng nhập của ứng dụng và chuyển tiếp HTTP/WebSocket tới FSEL ASR. Service token chỉ nằm trên backend. Người dùng cuối **không nhập token**.
8
-
9
- Đây là SDK kết nối dịch vụ, không kèm dịch vụ ASR miễn phí. Địa chỉ gateway vẫn nhìn thấy trong Network của trình duyệt; không thể giấu địa chỉ mạng trong thư viện frontend. Không nhúng service token hay thông tin S3 vào Angular.
10
-
11
- Backend cần chuyển tiếp các đường dẫn bên dưới, bỏ prefix `/api/speaking` trước khi gửi tới ASR:
12
-
13
- | Đường dẫn phía gateway | Chức năng |
14
- | --- | --- |
15
- | `POST /api/speaking/v1/realtime-tickets` | Tạo ticket ngắn hạn; body `{"language":"vi","audio_profile":"short_answer"}`; trả JSON có `ticket` |
16
- | `WS /api/speaking/v1/realtime` | WebSocket realtime, hỗ trợ Upgrade và kết nối dài |
17
- | `POST /api/speaking/v1/transcription-jobs` | Upload multipart audio |
18
- | `GET /api/speaking/v1/transcription-jobs/{id}` | Trạng thái job |
19
- | `DELETE /api/speaking/v1/transcription-jobs/{id}` | Hủy job |
20
-
21
- Đây phải là relay tương thích giao thức FSEL ASR, không phải API speech tùy ý. Bảo vệ các route bằng đăng nhập, kiểm tra quyền sở hữu job, quota/rate limit; không mở proxy công khai có service token. Trình duyệt nên dùng gateway cùng origin để phiên cookie hoạt động. HTTPS cho production và WSS cho WebSocket. Server cần có weights cho ngôn ngữ được chọn; cài npm không tự cài model server.
22
-
23
- ## 2. Cài thư viện
24
-
25
- Trong project Angular 18:
5
+ ## 1. Cài đặt
26
6
 
27
7
  ```bash
28
- npm install @hoanghong/speaking-angular@^0.2.0
8
+ npm install ./vendor/hoanghong-speaking-core-0.4.0.tgz ./vendor/hoanghong-speaking-angular-0.4.0.tgz
29
9
  ```
30
10
 
31
- `@hoanghong/speaking-core` được cài tự động. Với project mới: `npx @angular/cli@18 new speaking-demo --standalone --routing=false --style=css`, sau đó `cd speaking-demo` và chạy lệnh cài trên.
32
-
33
- ## 3. Copy file microphone bằng angular.json
11
+ Core được cài tự động. Với ứng dụng mới: `npx @angular/cli@18 new speaking-demo --standalone --routing=false --style=css`, rồi `cd speaking-demo` và chạy lệnh trên.
34
12
 
35
- Trong `projects.<tên-project>.architect.build.options.assets`, giữ asset hiện có và thêm object sau:
13
+ Bản 0.4.0 là bộ cài test local: giải nén ZIP và copy hai tarball vào `vendor/` trước.
14
+ Chưa publish 0.4.0 lên registry. Các tính năng nhận dạng cũ vẫn dùng được.
36
15
 
37
- ```json
38
- {
39
- "glob": "pcm-worklet.js",
40
- "input": "node_modules/@hoanghong/speaking-core/assets",
41
- "output": "assets/speaking"
42
- }
43
- ```
16
+ ## Đánh giá phát âm tiếng Anh Mỹ (0.4.0)
44
17
 
45
- Sau khi build, URL `assets/speaking/pcm-worklet.js` phải trả JavaScript, không phải trang HTML. Nếu app deploy dưới thư mục con, giữ `base href` đúng; có thể chỉ định `workletUrl` ở bước tiếp theo.
46
-
47
- ## 4. Cấu hình app.config.ts
48
-
49
- `src/app/app.config.ts`:
18
+ Thêm `SpeakingService` vào `providers` của component dùng để test, import từ
19
+ `@hoanghong/speaking-angular`, rồi gọi trong class component:
50
20
 
51
21
  ```ts
52
- import { ApplicationConfig } from '@angular/core';
53
- import { provideSpeakingGateway } from '@hoanghong/speaking-angular';
54
-
55
- export const appConfig: ApplicationConfig = {
56
- providers: [
57
- provideSpeakingGateway({ baseUrl: '/api/speaking' }),
58
- ],
59
- };
22
+ constructor(private readonly speaking: SpeakingService) {}
23
+
24
+ async assessPronunciation(file: File): Promise<void> {
25
+ const result = await this.speaking.pronunciation.assessAudio(file, {
26
+ referenceText: 'cat',
27
+ expectedPhonemes: ['k', 'æ', 't'],
28
+ filename: file.name,
29
+ });
30
+ console.log(result.phoneme_similarity, result.comparisons, result.warnings);
31
+ }
60
32
  ```
61
33
 
62
- Giữ các provider router/HTTP hiện có nếu app đã có. `baseUrl` là prefix gateway, **không** thêm `/v1/realtime`. Mặc định là `/api/speaking`. Provider dùng trong browser; nếu app SSR, chỉ render màn Speaking phía browser.
34
+ Gọi bằng file vừa chọn hoặc `result.audio` trong callback `completed` của component hiện có.
35
+ Có thể dùng câu/đoạn đọc: thay referenceText bằng đúng nội dung cần đọc và bỏ
36
+ expectedPhonemes nếu server đã cài eSpeak; không dùng chuỗi âm của `cat` cho đoạn khác.
37
+ Hỗ trợ audio tối đa 120 giây/25 MiB, reference 4096 ký tự, client chờ tối đa 11 phút.
38
+ Thêm `signal: controller.signal` để hủy request. Hàm reject khi lỗi; UI cần catch và hiển thị.
39
+ `calibrated=false`: similarity chỉ là đối chiếu âm vị, chưa phải điểm phát âm chính thức.
40
+ Kết quả có timestamp, âm thiếu/thừa/thay thế, formant/pitch/intensity/PSD và warnings.
41
+
42
+ Dùng `provideSpeakingApp` hiện có nếu có appKey. SDK tự cấp phiên riêng cho mỗi lần đánh giá,
43
+ không nhập service token. Endpoint public `/sdk/v1/pronunciation-assessments` và model phải
44
+ được triển khai trước; 404/503 nghĩa là server chưa sẵn sàng. Bộ cài không triển khai server.
45
+ Test server riêng qua `provideSpeakingGateway({baseUrl: 'https://YOUR_TEST_GATEWAY'})`;
46
+ gateway phải chuyển tiếp endpoint và giữ service credential ở backend, không đưa vào app.
63
47
 
64
- ## 5. Dùng component có sẵn
48
+ ## 2. Thêm component — ví dụ đầy đủ
65
49
 
66
- Thay nội dung `src/app/app.component.ts` bằng ví dụ đầy đủ này:
50
+ Thay `src/app/app.component.ts` bằng:
67
51
 
68
52
  ```ts
69
- import { Component } from '@angular/core';
70
- import {
71
- FselSpeakingComponent, Job, RealtimeFinal,
72
- } from '@hoanghong/speaking-angular';
53
+ import { Component, OnDestroy } from '@angular/core';
54
+ import { FselSpeakingComponent, SpeakingResult } from '@hoanghong/speaking-angular';
73
55
 
74
56
  @Component({
75
- selector: 'app-root',
76
- standalone: true,
57
+ selector: 'app-root', standalone: true,
77
58
  imports: [FselSpeakingComponent],
78
59
  template: `
79
- <main style="max-width:800px;margin:24px auto;padding:16px">
60
+ <main style="max-width:800px;margin:auto;padding:20px">
80
61
  <h1>Luyện nói</h1>
81
- <fsel-speaking
82
- initialLanguage="vi"
83
- [uploadEnabled]="true"
84
- (completed)="onCompleted($event)"
85
- (failed)="onFailed($event)"
86
- />
87
- <p aria-live="polite">{{ message }}</p>
62
+ <fsel-speaking serviceType="realtime" initialLanguage="vi"
63
+ (completed)="onCompleted($event)" (failed)="onFailed($event)" />
64
+ <p>{{ text }}</p>
65
+ @if (audioUrl) {
66
+ <audio controls [src]="audioUrl"></audio>
67
+ <p><a [href]="audioUrl" [download]="filename">Tải file bài nói</a></p>
68
+ }
69
+ <p role="alert">{{ error }}</p>
88
70
  </main>
89
71
  `,
90
72
  })
91
- export class AppComponent {
92
- message = '';
93
- onCompleted(value: Job | RealtimeFinal): void {
94
- this.message = value.result?.text ?? 'Không có lời nói.';
95
- }
96
- onFailed(error: Error): void {
97
- this.message = error.message;
73
+ export class AppComponent implements OnDestroy {
74
+ text = ''; error = ''; filename = ''; audioUrl = '';
75
+ onCompleted(result: SpeakingResult): void {
76
+ if (this.audioUrl) URL.revokeObjectURL(this.audioUrl);
77
+ this.text = result.text;
78
+ this.filename = result.audio.name;
79
+ this.audioUrl = URL.createObjectURL(result.audio);
80
+ // result.audio là File: có thể đưa vào FormData để lưu ở hệ thống của bạn.
98
81
  }
82
+ onFailed(error: Error): void { this.error = error.message; }
83
+ ngOnDestroy(): void { if (this.audioUrl) URL.revokeObjectURL(this.audioUrl); }
99
84
  }
100
85
  ```
101
86
 
102
- `src/main.ts`:
87
+ Giữ `src/main.ts` chuẩn Angular CLI; nếu cần tạo lại:
103
88
 
104
89
  ```ts
105
90
  import { bootstrapApplication } from '@angular/platform-browser';
106
91
  import { AppComponent } from './app/app.component';
107
- import { appConfig } from './app/app.config';
108
-
109
- bootstrapApplication(AppComponent, appConfig).catch(console.error);
92
+ bootstrapApplication(AppComponent).catch(console.error);
110
93
  ```
111
94
 
112
- ## 6. Chạy và thử
95
+ Không cần `provideSpeakingGateway` hoặc chỉnh assets trong `angular.json` khi dùng dịch vụ public. Nếu nâng cấp từ 0.2, bỏ provider gateway cũ để sử dụng dịch vụ public mặc định.
113
96
 
114
- 1. Khởi động backend/gateway đã có ASR và model.
115
- 2. Chạy `npx ng serve`, mở URL được CLI in ra.
116
- 3. Đăng nhập ứng dụng nếu gateway yêu cầu phiên đăng nhập.
117
- 4. Chọn ngôn ngữ → **Bắt đầu nói** → cho phép microphone → chờ “Đang nghe…”.
118
- 5. Nói một câu, bấm **Dừng và lấy kết quả**. Văn bản tạm xuất hiện trong lúc nói; kết quả cuối phát qua `completed`.
119
- 6. Hoặc chọn file audio để tự upload, chờ job hoàn tất. Bấm **Hủy** để dừng phiên/job đang chạy.
97
+ ## Key riêng của ứng dụng (cấu hình một lần)
120
98
 
121
- `ng serve` không tự tạo gateway. Khi frontend và gateway khác port, cần reverse proxy của môi trường phát triển hỗ trợ HTTP **và WebSocket**, hoặc phục vụ frontend từ cùng origin gateway. Không đưa service token vào `environment.ts`.
99
+ Nếu được FSEL cấp key riêng, thêm vào `src/app/app.config.ts`:
122
100
 
123
- ### Chạy local trực tiếp với ASR đã có (chỉ dành cho developer)
101
+ ```ts
102
+ import { ApplicationConfig } from '@angular/core';
103
+ import { provideSpeakingApp } from '@hoanghong/speaking-angular';
104
+ export const appConfig: ApplicationConfig = {
105
+ providers: [provideSpeakingApp({ appKey: 'KEY_DUOC_FSEL_CAP' })],
106
+ };
107
+ ```
124
108
 
125
- Nếu chưa có gateway ứng dụng, dùng Angular dev proxy trên máy của bạn. Tạo file `proxy.speaking.cjs` ở gốc project:
109
+ Khi có appConfig, `main.ts` cần truyền nó cho bootstrap:
126
110
 
127
- ```js
128
- const token = process.env.ASR_SERVICE_TOKEN;
129
- if (!token) throw new Error('Set ASR_SERVICE_TOKEN on the development server');
130
- module.exports = {
131
- '/v1/**': {
132
- target: process.env.ASR_TARGET || 'http://127.0.0.1:8000',
133
- secure: true,
134
- changeOrigin: true,
135
- ws: true,
136
- headers: { Authorization: `Bearer ${token}` },
137
- },
138
- };
111
+ ```ts
112
+ import { bootstrapApplication } from '@angular/platform-browser';
113
+ import { AppComponent } from './app/app.component';
114
+ import { appConfig } from './app/app.config';
115
+ bootstrapApplication(AppComponent, appConfig).catch(console.error);
139
116
  ```
140
117
 
141
- Đổi provider thành `provideSpeakingGateway({ baseUrl: '/' })`. Đặt `ASR_SERVICE_TOKEN` trong môi trường terminal chạy Node (token do quản trị ASR cấp; không ghi vào source). PowerShell:
118
+ Component và callback text/audio không đổi; không cần URL. Đây là key định danh có thể quan sát trong frontend, không phải khóa quản trị. Hạn mức của key được FSEL quản lý phía server, thay đổi không cần build lại ứng dụng. Key sai/bị khóa trả INVALID_APP_KEY; không tự chuyển sang dùng ẩn danh. Bỏ cấu hình key thì dùng hạn mức public cơ bản.
142
119
 
143
- ```powershell
144
- $env:ASR_SERVICE_TOKEN = Read-Host 'ASR token (trusted local development only)'
145
- npx ng serve --host 127.0.0.1 --proxy-config proxy.speaking.cjs
120
+ ## 3. Chạy
121
+
122
+ ```bash
123
+ npx ng serve
146
124
  ```
147
125
 
148
- Mở `http://127.0.0.1:4200`. Node dev server thêm header; browser không nhận service token. Chỉ dùng trên loopback, không public `ng serve` hoặc proxy này. Khi production, chuyển về gateway có đăng nhập/quyền người dùng ở bước 1; dev proxy không thay thế gateway production. Dừng và chạy lại dev server khi đổi môi trường/proxy.
126
+ Mở localhost do CLI in ra → chọn ngôn ngữ → Bắt đầu nói → cho phép microphone → chờ “Đang nghe…” → nói → Dừng và lấy kết quả. Callback nhận text và WAV, ví dụ trên cho nghe lại/tải file. Production phải dùng HTTPS. Không ghi âm tự động.
149
127
 
150
- ## API component
128
+ ## 4. Chọn dịch vụ
151
129
 
152
- | Thuộc tính | Mặc định | Ý nghĩa |
153
- | --- | --- | --- |
154
- | `initialLanguage` | `vi` | `en`, `ja`, `ko`, `zh`, `vi` |
155
- | `uploadEnabled` | `true` | Hiện/ẩn upload file |
156
- | `completed` | event | `RealtimeFinal` hoặc `Job`, lấy text tại `value.result?.text` |
157
- | `failed` | event | `Error` để ứng dụng hiển thị/thống kê |
130
+ - `serviceType="realtime"`: microphone, text tạm khi nói, kết quả cuối kèm WAV PCM16 mono 16 kHz.
131
+ - `serviceType="upload"`: chọn file audio, trả transcript và chính File đầu vào, giữ tên/định dạng.
132
+ - Không đặt `serviceType`: hiện cả hai cách. `uploadEnabled=false` ẩn upload.
133
+ - `initialLanguage`: `en` (Anh), `ja` (Nhật), `ko` (Hàn), `zh` (Trung), `vi` (Việt, mặc định).
158
134
 
159
- UI có tiếng Việt; model hỗ trợ Anh, Nhật, Hàn, Trung, Việt tùy weights server. Mỗi component có phiên riêng, tự giải phóng microphone khi bị hủy. Realtime tối đa 5 phút theo giao thức server; upload UI chặn trên 25 MiB, polling mỗi 1,5 giây và dừng chờ sau 10 phút. Server có thể đặt giới hạn thấp hơn. Rời màn hình ngừng polling; job upload đã được server nhận có thể vẫn chạy. Không có tự reconnect/replay audio sau khi mất mạng.
135
+ Ví dụ upload: thay `serviceType="realtime"` bằng `serviceType="upload"`. Callback không đổi.
160
136
 
161
- Nếu muốn tự thiết kế UI, các API `SpeakingService`, `provideSpeaking`, `BatchSpeakingClient`, `RealtimeSpeakingClient` vẫn được export. Với `provideSpeakingGateway`, tự cung cấp `SpeakingService` tại component của bạn nếu inject trực tiếp; component có sẵn đã làm việc này. Bản 0.2 giữ API cấu hình thủ công 0.1.
137
+ ## 5. Kết quả trả về
162
138
 
163
- ## Lỗi thường gặp
139
+ `completed` nhận `SpeakingResult`:
164
140
 
165
- | Lỗi | Cách kiểm tra |
141
+ | Trường | Kiểu / ý nghĩa |
166
142
  | --- | --- |
167
- | 401/403 | Phiên đăng nhập và quyền gateway; service token nằm trên backend |
168
- | 404 worklet / lỗi MIME | Kiểm tra asset bước 3, base href và workletUrl |
169
- | 404 ticket/job | Sai prefix gateway hoặc chưa rewrite `/api/speaking` |
170
- | 503 / model unavailable | Server chưa có model hoặc không đủ tài nguyên CPU/GPU |
171
- | Microphone bị từ chối | Bật quyền microphone của site; dùng HTTPS hoặc localhost |
172
- | WS không kết nối | Proxy phải bật Upgrade; HTTPS phải dùng WSS; kiểm tra timeout |
173
- | CORS | Ưu tiên cùng origin; nếu khác origin, backend phải cho phép origin và auth phù hợp |
174
-
175
- Không ghi âm tự động. Chỉ bắt đầu sau thao tác người dùng và quyền microphone. Không chứa weights, khóa S3 hoặc service token.
143
+ | `text` | String nhận dạng cuối; rỗng khi không có lời nói |
144
+ | `audio` | File, dùng createObjectURL/FormData/arrayBuffer; realtime có tên speaking.wav |
145
+ | `language` | en/ja/ko/zh/vi |
146
+ | `serviceType` | realtime/upload |
147
+ | `status` | succeeded/no_speech; các metadata gốc vẫn được giữ |
148
+ | `result` | Transcript chi tiết từ server nếu có |
149
+
150
+ File realtime chứa âm thanh người dùng vừa nói, đã chuẩn hóa cho nhận dạng; không phải giọng AI đọc text. File không tự được lưu vĩnh viễn lên S3 hoặc cấp URL tải công khai. Ứng dụng sở hữu File sau callback, tự lưu nếu cần. Revoke object URL khi thay file/rời trang như ví dụ để tránh giữ RAM. Hủy/lỗi không phát callback thành công.
151
+
152
+ ## 6. Giới hạn và lỗi
153
+
154
+ Dịch vụ public tự cấp phiên ngắn hạn. Mặc định server giới hạn 5 lượt/IP/ngày và 100 lượt/ngày cho mỗi instance, tính theo UTC; hạn mức có thể được FSEL điều chỉnh. Một phiên cấp mới tính một lượt kể cả khi nhận dạng thất bại. Người dùng cùng mạng có thể chung IP. Realtime tối đa 5 phút; buffer PCM tối đa khoảng 9,6 MB trước khi tạo WAV. Upload UI tối đa 25 MiB, server có thể giới hạn thấp hơn; chờ job tối đa 10 phút. Không tự reconnect/replay audio sau mất mạng. Rời màn ngừng thu/polling; job đã upload có thể còn chạy trên server.
155
+
156
+ `failed` nhận Error; lỗi dịch vụ có `code`/`status` qua SpeakingError. QUOTA_EXCEEDED/429: hết lượt, chờ ngày mới. 503: dịch vụ/model chưa sẵn sàng. 404: server chưa triển khai phiên bản dịch vụ public. Microphone denied: bật quyền site và dùng HTTPS/localhost. Mạng/CSP: cho phép kết nối, worklet tới dịch vụ FSEL theo chính sách ứng dụng. Server phải chạy bản hỗ trợ public SDK trước khi dùng 0.3.
157
+
158
+ API thấp của 0.2 vẫn có sẵn cho tích hợp tùy chỉnh; không cần dùng trong luồng trên. Microphone thiết bị và khả năng truy cập mạng cần kiểm tra trong ứng dụng triển khai thực tế.
@@ -5,6 +5,7 @@ import * as i0 from "@angular/core";
5
5
  export class FselSpeakingComponent {
6
6
  initialLanguage = 'vi';
7
7
  uploadEnabled = true;
8
+ serviceType;
8
9
  completed = new EventEmitter();
9
10
  failed = new EventEmitter();
10
11
  speaking = inject(SpeakingService);
@@ -74,7 +75,7 @@ export class FselSpeakingComponent {
74
75
  return;
75
76
  this.text = result.result?.text ?? '';
76
77
  this.status = result.status === 'no_speech' ? 'Không nhận thấy lời nói.' : 'Đã hoàn tất.';
77
- this.completed.emit(result);
78
+ this.completed.emit({ ...result, language: this.language, text: this.text, audio: result.audio, serviceType: 'realtime' });
78
79
  }
79
80
  catch (error) {
80
81
  if (generation === this.generation)
@@ -143,7 +144,8 @@ export class FselSpeakingComponent {
143
144
  throw new Error(job.error.message);
144
145
  this.text = job.result?.text ?? '';
145
146
  this.status = job.status === 'no_speech' ? 'Không nhận thấy lời nói.' : job.status === 'cancelled' ? 'Đã hủy.' : 'Đã hoàn tất.';
146
- this.completed.emit(job);
147
+ if (job.status !== 'cancelled')
148
+ this.completed.emit({ ...job, language: this.language, text: this.text, audio: file, serviceType: 'upload' });
147
149
  }
148
150
  catch (error) {
149
151
  if (generation === this.generation)
@@ -157,17 +159,19 @@ export class FselSpeakingComponent {
157
159
  }
158
160
  }
159
161
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FselSpeakingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
160
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FselSpeakingComponent, isStandalone: true, selector: "fsel-speaking", inputs: { initialLanguage: "initialLanguage", uploadEnabled: "uploadEnabled" }, outputs: { completed: "completed", failed: "failed" }, providers: [SpeakingService], ngImport: i0, template: `
162
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FselSpeakingComponent, isStandalone: true, selector: "fsel-speaking", inputs: { initialLanguage: "initialLanguage", uploadEnabled: "uploadEnabled", serviceType: "serviceType" }, outputs: { completed: "completed", failed: "failed" }, providers: [SpeakingService], ngImport: i0, template: `
161
163
  <section aria-label="FSEL Speaking">
162
164
  <label>Ngôn ngữ <select [value]="language" [disabled]="busy" (change)="selectLanguage($event)">
163
165
  @for (item of languages; track item.code) { <option [value]="item.code" [selected]="item.code === language">{{ item.name }}</option> }
164
166
  </select></label>
165
167
  <div class="actions">
168
+ @if (serviceType !== 'upload') {
166
169
  <button type="button" (click)="start()" [disabled]="busy">Bắt đầu nói</button>
167
170
  <button type="button" (click)="stop()" [disabled]="!recording">Dừng và lấy kết quả</button>
171
+ }
168
172
  <button type="button" (click)="cancel()" [disabled]="!busy || (uploading && !job)">Hủy</button>
169
173
  </div>
170
- @if (uploadEnabled) { <label>Hoặc chọn file audio (tối đa 25 MB)
174
+ @if (serviceType !== 'realtime' && uploadEnabled) { <label>Chọn file audio (tối đa 25 MB)
171
175
  <input type="file" accept="audio/*,.wav,.mp3,.m4a,.ogg,.flac,.webm" [disabled]="busy" (change)="upload($event)">
172
176
  </label> }
173
177
  <p role="status">{{ status }}</p>
@@ -183,11 +187,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
183
187
  @for (item of languages; track item.code) { <option [value]="item.code" [selected]="item.code === language">{{ item.name }}</option> }
184
188
  </select></label>
185
189
  <div class="actions">
190
+ @if (serviceType !== 'upload') {
186
191
  <button type="button" (click)="start()" [disabled]="busy">Bắt đầu nói</button>
187
192
  <button type="button" (click)="stop()" [disabled]="!recording">Dừng và lấy kết quả</button>
193
+ }
188
194
  <button type="button" (click)="cancel()" [disabled]="!busy || (uploading && !job)">Hủy</button>
189
195
  </div>
190
- @if (uploadEnabled) { <label>Hoặc chọn file audio (tối đa 25 MB)
196
+ @if (serviceType !== 'realtime' && uploadEnabled) { <label>Chọn file audio (tối đa 25 MB)
191
197
  <input type="file" accept="audio/*,.wav,.mp3,.m4a,.ogg,.flac,.webm" [disabled]="busy" (change)="upload($event)">
192
198
  </label> }
193
199
  <p role="status">{{ status }}</p>
@@ -198,9 +204,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
198
204
  type: Input
199
205
  }], uploadEnabled: [{
200
206
  type: Input
207
+ }], serviceType: [{
208
+ type: Input
201
209
  }], completed: [{
202
210
  type: Output
203
211
  }], failed: [{
204
212
  type: Output
205
213
  }] } });
206
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BlYWtpbmcuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NwZWFraW5nLmNvbXBvbmVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsU0FBUyxFQUFFLFVBQVUsRUFBRSxZQUFZLEVBQUUsS0FBSyxFQUFVLE1BQU0sRUFBRSxNQUFNLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDbkcsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFFaEUsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLG9CQUFvQixDQUFDOztBQXVCckQsTUFBTSxPQUFPLHFCQUFxQjtJQUN2QixlQUFlLEdBQWEsSUFBSSxDQUFDO0lBQ2pDLGFBQWEsR0FBRyxJQUFJLENBQUM7SUFDcEIsU0FBUyxHQUFHLElBQUksWUFBWSxFQUF1QixDQUFDO0lBQ3BELE1BQU0sR0FBRyxJQUFJLFlBQVksRUFBUyxDQUFDO0lBQ3BDLFFBQVEsR0FBRyxNQUFNLENBQUMsZUFBZSxDQUFDLENBQUM7SUFDM0IsT0FBTyxHQUFHLE1BQU0sQ0FBQyxVQUFVLENBQUMsQ0FBQztJQUNyQyxTQUFTLEdBQUcsQ0FBQyxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLFdBQVcsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsWUFBWSxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxXQUFXLEVBQUUsRUFBRSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLGFBQWEsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsWUFBWSxFQUFFLENBQUMsQ0FBQztJQUN6TSxRQUFRLEdBQWEsSUFBSSxDQUFDO0lBQUMsSUFBSSxHQUFHLEtBQUssQ0FBQztJQUFDLFNBQVMsR0FBRyxLQUFLLENBQUM7SUFBQyxTQUFT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QmpDLFNBQVM7K0JBQ0UsZUFBZSxjQUFjLElBQUksYUFBYSxDQUFDLGVBQWUsQ0FBQyxZQUMvRDs7Ozs7Ozs7Ozs7Ozs7OztlQWdCRzt3REFJSixlQUFlO3NCQUF2QixLQUFLO2dCQUNHLGFBQWE7c0JBQXJCLEtBQUs7Z0JBQ0ksU0FBUztzQkFBbEIsTUFBTTtnQkFDRyxNQUFNO3NCQUFmLE1BQU0iLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb21wb25lbnQsIERlc3Ryb3lSZWYsIEV2ZW50RW1pdHRlciwgSW5wdXQsIE9uSW5pdCwgT3V0cHV0LCBpbmplY3QgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcclxuaW1wb3J0IHsgdGFrZVVudGlsRGVzdHJveWVkIH0gZnJvbSAnQGFuZ3VsYXIvY29yZS9yeGpzLWludGVyb3AnO1xyXG5pbXBvcnQgeyBKb2IsIExhbmd1YWdlLCBSZWFsdGltZUZpbmFsLCBTcGVha2luZ0V2ZW50IH0gZnJvbSAnQGhvYW5naG9uZy9zcGVha2luZy1jb3JlJztcclxuaW1wb3J0IHsgU3BlYWtpbmdTZXJ2aWNlIH0gZnJvbSAnLi9zcGVha2luZy5zZXJ2aWNlJztcclxuXHJcbkBDb21wb25lbnQoe1xyXG4gIHNlbGVjdG9yOiAnZnNlbC1zcGVha2luZycsIHN0YW5kYWxvbmU6IHRydWUsIHByb3ZpZGVyczogW1NwZWFraW5nU2VydmljZV0sXHJcbiAgdGVtcGxhdGU6IGBcclxuICAgIDxzZWN0aW9uIGFyaWEtbGFiZWw9XCJGU0VMIFNwZWFraW5nXCI+XHJcbiAgICAgIDxsYWJlbD5OZ8O0biBuZ+G7ryA8c2VsZWN0IFt2YWx1ZV09XCJsYW5ndWFnZVwiIFtkaXNhYmxlZF09XCJidXN5XCIgKGNoYW5nZSk9XCJzZWxlY3RMYW5ndWFnZSgkZXZlbnQpXCI+XHJcbiAgICAgICAgQGZvciAoaXRlbSBvZiBsYW5ndWFnZXM7IHRyYWNrIGl0ZW0uY29kZSkgeyA8b3B0aW9uIFt2YWx1ZV09XCJpdGVtLmNvZGVcIiBbc2VsZWN0ZWRdPVwiaXRlbS5jb2RlID09PSBsYW5ndWFnZVwiPnt7IGl0ZW0ubmFtZSB9fTwvb3B0aW9uPiB9XHJcbiAgICAgIDwvc2VsZWN0PjwvbGFiZWw+XHJcbiAgICAgIDxkaXYgY2xhc3M9XCJhY3Rpb25zXCI+XHJcbiAgICAgICAgPGJ1dHRvbiB0eXBlPVwiYnV0dG9uXCIgKGNsaWNrKT1cInN0YXJ0KClcIiBbZGlzYWJsZWRdPVwiYnVzeVwiPkLhuq90IMSR4bqndSBuw7NpPC9idXR0b24+XHJcbiAgICAgICAgPGJ1dHRvbiB0eXBlPVwiYnV0dG9uXCIgKGNsaWNrKT1cInN0b3AoKVwiIFtkaXNhYmxlZF09XCIhcmVjb3JkaW5nXCI+ROG7q25nIHbDoCBs4bqleSBr4bq/dCBxdeG6ozwvYnV0dG9uPlxyXG4gICAgICAgIDxidXR0b24gdHlwZT1cImJ1dHRvblwiIChjbGljayk9XCJjYW5jZWwoKVwiIFtkaXNhYmxlZF09XCIhYnVzeSB8fCAodXBsb2FkaW5nICYmICFqb2IpXCI+SOG7p3k8L2J1dHRvbj5cclxuICAgICAgPC9kaXY+XHJcbiAgICAgIEBpZiAodXBsb2FkRW5hYmxlZCkgeyA8bGFiZWw+SG/hurdjIGNo4buNbiBmaWxlIGF1ZGlvICh04buRaSDEkWEgMjUgTUIpXHJcbiAgICAgICAgPGlucHV0IHR5cGU9XCJmaWxlXCIgYWNjZXB0PVwiYXVkaW8vKiwud2F2LC5tcDMsLm00YSwub2dnLC5mbGFjLC53ZWJtXCIgW2Rpc2FibGVkXT1cImJ1c3lcIiAoY2hhbmdlKT1cInVwbG9hZCgkZXZlbnQpXCI+XHJcbiAgICAgIDwvbGFiZWw+IH1cclxuICAgICAgPHAgcm9sZT1cInN0YXR1c1wiPnt7IHN0YXR1cyB9fTwvcD5cclxuICAgICAgQGlmIChlcnJvcikgeyA8cCByb2xlPVwiYWxlcnRcIiBjbGFzcz1cImVycm9yXCI+e3sgZXJyb3IgfX08L3A+IH1cclxuICAgICAgPGxhYmVsPlbEg24gYuG6o24gbmjhuq1uIGThuqFuZyA8dGV4dGFyZWEgcm93cz1cIjdcIiByZWFkb25seSBbdmFsdWVdPVwidGV4dFwiPjwvdGV4dGFyZWE+PC9sYWJlbD5cclxuICAgIDwvc2VjdGlvbj5gLFxyXG4gIHN0eWxlczogW2A6aG9zdHtkaXNwbGF5OmJsb2NrO2ZvbnQ6aW5oZXJpdDtjb2xvcjppbmhlcml0fXNlY3Rpb257Ym9yZGVyOjFweCBzb2xpZCAjYjljMWJjO3BhZGRpbmc6MjBweDtib3JkZXItcmFkaXVzOjRweH1sYWJlbHtkaXNwbGF5OmdyaWQ7Z2FwOjhweDttYXJnaW4tYm90dG9tOjE2cHh9c2VsZWN0LGlucHV0LHRleHRhcmVhLGJ1dHRvbntmb250OmluaGVyaXQ7Ym94LXNpemluZzpib3JkZXItYm94O21heC13aWR0aDoxMDAlfXNlbGVjdCxpbnB1dCx0ZXh0YXJlYXtwYWRkaW5nOjEwcHg7Ym9yZGVyOjFweCBzb2xpZCAjOTI5Yzk1O2JvcmRlci1yYWRpdXM6M3B4O2JhY2tncm91bmQ6I2ZmZjtjb2xvcjojMjAyNzIzfXRleHRhcmVhe3dpZHRoOjEwMCU7cmVzaXplOnZlcnRpY2FsfS5hY3Rpb25ze2Rpc3BsYXk6ZmxleDtnYXA6OHB4O2ZsZXgtd3JhcDp3cmFwO21hcmdpbi1ib3R0b206MTZweH1idXR0b257bWluLWhlaWdodDo0NHB4O3BhZGRpbmc6OHB4IDE0cHg7YmFja2dyb3VuZDojMzY1NDQ4O2NvbG9yOndoaXRlO2JvcmRlcjoxcHggc29saWQgIzM2NTQ0ODtib3JkZXItcmFkaXVzOjRweDtjdXJzb3I6cG9pbnRlcn1idXR0b246ZGlzYWJsZWR7b3BhY2l0eTouNTtjdXJzb3I6ZGVmYXVsdH1idXR0b246Zm9jdXMtdmlzaWJsZSxpbnB1dDpmb2N1cy12aXNpYmxlLHNlbGVjdDpmb2N1cy12aXNpYmxlLHRleHRhcmVhOmZvY3VzLXZpc2libGV7b3V0bGluZTozcHggc29saWQgI2E2NmExNTtvdXRsaW5lLW9mZnNldDoycHh9LmVycm9ye2NvbG9yOiNhMDIwMjA7b3ZlcmZsb3ctd3JhcDphbnl3aGVyZX1gXSxcclxufSlcclxuZXhwb3J0IGNsYXNzIEZzZWxTcGVha2luZ0NvbXBvbmVudCBpbXBsZW1lbnRzIE9uSW5pdCB7XHJcbiAgQElucHV0KCkgaW5pdGlhbExhbmd1YWdlOiBMYW5ndWFnZSA9ICd2aSc7XHJcbiAgQElucHV0KCkgdXBsb2FkRW5hYmxlZCA9IHRydWU7XHJcbiAgQE91dHB1dCgpIGNvbXBsZXRlZCA9IG5ldyBFdmVudEVtaXR0ZXI8UmVhbHRpbWVGaW5hbCB8IEpvYj4oKTtcclxuICBAT3V0cHV0KCkgZmFpbGVkID0gbmV3IEV2ZW50RW1pdHRlcjxFcnJvcj4oKTtcclxuICByZWFkb25seSBzcGVha2luZyA9IGluamVjdChTcGVha2luZ1NlcnZpY2UpO1xyXG4gIHByaXZhdGUgcmVhZG9ubHkgZGVzdHJveSA9IGluamVjdChEZXN0cm95UmVmKTtcclxuICByZWFkb25seSBsYW5ndWFnZXMgPSBbeyBjb2RlOiAnZW4nLCBuYW1lOiAnVGnhur9uZyBBbmgnIH0sIHsgY29kZTogJ2phJywgbmFtZTogJ1Rp4bq/bmcgTmjhuq10JyB9LCB7IGNvZGU6ICdrbycsIG5hbWU6ICdUaeG6v25nIEjDoG4nIH0sIHsgY29kZTogJ3poJywgbmFtZTogJ1Rp4bq/bmcgVHJ1bmcnIH0sIHsgY29kZTogJ3ZpJywgbmFtZTogJ1Rp4bq/bmcgVmnhu4d0JyB9XTtcclxuICBsYW5ndWFnZTogTGFuZ3VhZ2UgPSAndmknOyBidXN5ID0gZmFsc2U7IHJlY29yZGluZyA9IGZhbHNlOyB1cGxvYWRpbmcgPSBmYWxzZTtcclxuICB0ZXh0ID0gJyc7IGVycm9yID0gJyc7IHN0YXR1cyA9ICdDaOG7jW4gbmfDtG4gbmfhu68sIHNhdSDEkcOzIGLhuqVtIG7Ds2kgaG/hurdjIGNo4buNbiBmaWxlLic7XHJcbiAgam9iPzogSm9iOyBwcml2YXRlIGdlbmVyYXRpb24gPSAwOyBwcml2YXRlIGFib3J0PzogQWJvcnRDb250cm9sbGVyO1xyXG4gIGNvbnN0cnVjdG9yKCkge1xyXG4gICAgdGhpcy5zcGVha2luZy5ldmVudHMkLnBpcGUodGFrZVVudGlsRGVzdHJveWVkKHRoaXMuZGVzdHJveSkpLnN1YnNjcmliZSgoZXZlbnQ6IFNwZWFraW5nRXZlbnQpID0+IHtcclxuICAgICAgaWYgKGV2ZW50LnR5cGUgPT09ICdwYXJ0aWFsJykgdGhpcy50ZXh0ID0gZXZlbnQudGV4dDtcclxuICAgICAgaWYgKGV2ZW50LnR5cGUgPT09ICdzdGF0ZScpIHRoaXMucmVjb3JkaW5nID0gZXZlbnQuc3RhdGUgPT09ICdyZWNvcmRpbmcnO1xyXG4gICAgICBpZiAoZXZlbnQudHlwZSA9PT0gJ2NsaWVudEVycm9yJykgeyB0aGlzLnJlcG9ydChldmVudC5lcnJvcik7IHRoaXMuYnVzeSA9IGZhbHNlOyB0aGlzLnJlY29yZGluZyA9IGZhbHNlOyB9XHJcbiAgICB9KTtcclxuICAgIHRoaXMuZGVzdHJveS5vbkRlc3Ryb3koKCkgPT4geyArK3RoaXMuZ2VuZXJhdGlvbjsgdGhpcy5hYm9ydD8uYWJvcnQoKTsgfSk7XHJcbiAgfVxyXG4gIG5nT25Jbml0KCk6IHZvaWQgeyB0aGlzLmxhbmd1YWdlID0gdGhpcy5pbml0aWFsTGFuZ3VhZ2U7IH1cclxuICBzZWxlY3RMYW5ndWFnZShldmVudDogRXZlbnQpOiB2b2lkIHsgdGhpcy5sYW5ndWFnZSA9IChldmVudC50YXJnZXQgYXMgSFRNTFNlbGVjdEVsZW1lbnQpLnZhbHVlIGFzIExhbmd1YWdlOyB9XHJcbiAgcHJpdmF0ZSByZXBvcnQoZXJyb3I6IHVua25vd24pOiB2b2lkIHtcclxuICAgIGNvbnN0IHZhbHVlID0gZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yIDogbmV3IEVycm9yKCdTcGVha2luZyBmYWlsZWQnKTtcclxuICAgIHRoaXMuZXJyb3IgPSB2YWx1ZS5tZXNzYWdlOyB0aGlzLmZhaWxlZC5lbWl0KHZhbHVlKTtcclxuICB9XHJcbiAgYXN5bmMgc3RhcnQoKTogUHJvbWlzZTx2b2lkPiB7XHJcbiAgICBpZiAodGhpcy5idXN5KSByZXR1cm47XHJcbiAgICBjb25zdCBnZW5lcmF0aW9uID0gKyt0aGlzLmdlbmVyYXRpb247XHJcbiAgICB0aGlzLmJ1c3kgPSB0cnVlOyB0aGlzLnVwbG9hZGluZyA9IGZhbHNlOyB0aGlzLmVycm9yID0gJyc7IHRoaXMudGV4dCA9ICcnOyB0aGlzLnN0YXR1cyA9ICfEkGFuZyBt4bufIG1pY3JvcGhvbmUgdsOgIGNodeG6qW4gYuG7iyBtb2RlbOKApic7XHJcbiAgICB0cnkgeyBhd2FpdCB0aGlzLnNwZWFraW5nLnN0YXJ0KHsgbGFuZ3VhZ2U6IHRoaXMubGFuZ3VhZ2UgfSk7IGlmIChnZW5lcmF0aW9uID09PSB0aGlzLmdlbmVyYXRpb24pIHRoaXMuc3RhdHVzID0gJ8SQYW5nIG5naGXigKYnOyB9XHJcbiAgICBjYXRjaCAoZXJyb3IpIHsgaWYgKGdlbmVyYXRpb24gPT09IHRoaXMuZ2VuZXJhdGlvbikgeyB0aGlzLnJlcG9ydChlcnJvcik7IHRoaXMuYnVzeSA9IGZhbHNlOyB9IH1cclxuICB9XHJcbiAgYXN5bmMgc3RvcCgpOiBQcm9taXNlPHZvaWQ+IHtcclxuICAgIGlmICghdGhpcy5yZWNvcmRpbmcpIHJldHVybjtcclxuICAgIGNvbnN0IGdlbmVyYXRpb24gPSB0aGlzLmdlbmVyYXRpb247XHJcbiAgICB0aGlzLnJlY29yZGluZyA9IGZhbHNlOyB0aGlzLnN0YXR1cyA9ICfEkGFuZyBs4bqleSBr4bq/dCBxdeG6o+KApic7XHJcbiAgICB0cnkge1xyXG4gICAgICBjb25zdCByZXN1bHQgPSBhd2FpdCB0aGlzLnNwZWFraW5nLnN0b3AoKTtcclxuICAgICAgaWYgKGdlbmVyYXRpb24gIT09IHRoaXMuZ2VuZXJhdGlvbikgcmV0dXJuO1xyXG4gICAgICB0aGlzLnRleHQgPSByZXN1bHQucmVzdWx0Py50ZXh0ID8/ICcnOyB0aGlzLnN0YXR1cyA9IHJlc3VsdC5zdGF0dXMgPT09ICdub19zcGVlY2gnID8gJ0tow7RuZyBuaOG6rW4gdGjhuqV5IGzhu51pIG7Ds2kuJyA6ICfEkMOjIGhvw6BuIHThuqV0Lic7IHRoaXMuY29tcGxldGVkLmVtaXQocmVzdWx0KTtcclxuICAgIH0gY2F0Y2ggKGVycm9yKSB7IGlmIChnZW5lcmF0aW9uID09PSB0aGlzLmdlbmVyYXRpb24pIHRoaXMucmVwb3J0KGVycm9yKTsgfVxyXG4gICAgZmluYWxseSB7IGlmIChnZW5lcmF0aW9uID09PSB0aGlzLmdlbmVyYXRpb24pIHRoaXMuYnVzeSA9IGZhbHNlOyB9XHJcbiAgfVxyXG4gIGFzeW5jIGNhbmNlbCgpOiBQcm9taXNlPHZvaWQ+IHtcclxuICAgICsrdGhpcy5nZW5lcmF0aW9uOyB0aGlzLmFib3J0Py5hYm9ydCgpO1xyXG4gICAgdHJ5IHsgaWYgKHRoaXMudXBsb2FkaW5nICYmIHRoaXMuam9iKSBhd2FpdCB0aGlzLnNwZWFraW5nLmJhdGNoLmNhbmNlbEpvYih0aGlzLmpvYi5qb2JfaWQpOyBlbHNlIGF3YWl0IHRoaXMuc3BlYWtpbmcuY2FuY2VsKCk7IHRoaXMuc3RhdHVzID0gJ8SQw6MgaOG7p3kuJzsgfVxyXG4gICAgY2F0Y2ggKGVycm9yKSB7IHRoaXMucmVwb3J0KGVycm9yKTsgfVxyXG4gICAgZmluYWxseSB7IHRoaXMuYnVzeSA9IGZhbHNlOyB0aGlzLnJlY29yZGluZyA9IGZhbHNlOyB0aGlzLnVwbG9hZGluZyA9IGZhbHNlOyB9XHJcbiAgfVxyXG4gIGFzeW5jIHVwbG9hZChldmVudDogRXZlbnQpOiBQcm9taXNlPHZvaWQ+IHtcclxuICAgIGNvbnN0IGlucHV0ID0gZXZlbnQudGFyZ2V0IGFzIEhUTUxJbnB1dEVsZW1lbnQ7IGNvbnN0IGZpbGUgPSBpbnB1dC5maWxlcz8uWzBdOyBpbnB1dC52YWx1ZSA9ICcnO1xyXG4gICAgaWYgKCFmaWxlIHx8IHRoaXMuYnVzeSkgcmV0dXJuO1xyXG4gICAgaWYgKGZpbGUuc2l6ZSA+IDI1ICogMTAyNCAqIDEwMjQpIHsgdGhpcy5yZXBvcnQobmV3IEVycm9yKCdGaWxlIHThu5FpIMSRYSAyNSBNQi4nKSk7IHJldHVybjsgfVxyXG4gICAgY29uc3QgZ2VuZXJhdGlvbiA9ICsrdGhpcy5nZW5lcmF0aW9uOyB0aGlzLmFib3J0ID0gbmV3IEFib3J0Q29udHJvbGxlcigpO1xyXG4gICAgdGhpcy5idXN5ID0gdHJ1ZTsgdGhpcy51cGxvYWRpbmcgPSB0cnVlOyB0aGlzLmpvYiA9IHVuZGVmaW5lZDsgdGhpcy50ZXh0ID0gJyc7IHRoaXMuZXJyb3IgPSAnJzsgdGhpcy5zdGF0dXMgPSAnxJBhbmcgdXBsb2Fk4oCmJztcclxuICAgIHRyeSB7XHJcbiAgICAgIGxldCBqb2IgPSBhd2FpdCB0aGlzLnNwZWFraW5nLmJhdGNoLnN1Ym1pdEF1ZGlvKGZpbGUsIHRoaXMubGFuZ3VhZ2UsIGNyeXB0by5yYW5kb21VVUlEKCksICdzaG9ydF9hbnN3ZXInLCBmaWxlLm5hbWUsIHRoaXMuYWJvcnQuc2lnbmFsKTtcclxuICAgICAgY29uc3QgZGVhZGxpbmUgPSBEYXRlLm5vdygpICsgMTAgKiA2MCAqIDEwMDA7XHJcbiAgICAgIHdoaWxlIChnZW5lcmF0aW9uID09PSB0aGlzLmdlbmVyYXRpb24pIHtcclxuICAgICAgICB0aGlzLmpvYiA9IGpvYjtcclxuICAgICAgICBpZiAoWydzdWNjZWVkZWQnLCAnbm9fc3BlZWNoJywgJ2ZhaWxlZCcsICdjYW5jZWxsZWQnXS5pbmNsdWRlcyhqb2Iuc3RhdHVzKSkgYnJlYWs7XHJcbiAgICAgICAgaWYgKERhdGUubm93KCkgPiBkZWFkbGluZSkgdGhyb3cgbmV3IEVycm9yKCdI4bq/dCB0aOG7nWkgZ2lhbiBjaOG7nTsgam9iIGPDsyB0aOG7gyB24bqrbiDEkWFuZyBjaOG6oXkgdHLDqm4gc2VydmVyLicpO1xyXG4gICAgICAgIHRoaXMuc3RhdHVzID0gJ8SQYW5nIG5o4bqtbiBk4bqhbmcgZmlsZeKApic7XHJcbiAgICAgICAgYXdhaXQgbmV3IFByb21pc2UocmVzb2x2ZSA9PiBzZXRUaW1lb3V0KHJlc29sdmUsIDE1MDApKTtcclxuICAgICAgICBpZiAoZ2VuZXJhdGlvbiAhPT0gdGhpcy5nZW5lcmF0aW9uKSByZXR1cm47XHJcbiAgICAgICAgam9iID0gYXdhaXQgdGhpcy5zcGVha2luZy5iYXRjaC5nZXRSZXN1bHQoam9iLmpvYl9pZCwgdGhpcy5hYm9ydC5zaWduYWwpO1xyXG4gICAgICB9XHJcbiAgICAgIGlmIChnZW5lcmF0aW9uICE9PSB0aGlzLmdlbmVyYXRpb24pIHJldHVybjtcclxuICAgICAgaWYgKGpvYi5lcnJvcikgdGhyb3cgbmV3IEVycm9yKGpvYi5lcnJvci5tZXNzYWdlKTtcclxuICAgICAgdGhpcy50ZXh0ID0gam9iLnJlc3VsdD8udGV4dCA/PyAnJzsgdGhpcy5zdGF0dXMgPSBqb2Iuc3RhdHVzID09PSAnbm9fc3BlZWNoJyA/ICdLaMO0bmcgbmjhuq1uIHRo4bqleSBs4budaSBuw7NpLicgOiBqb2Iuc3RhdHVzID09PSAnY2FuY2VsbGVkJyA/ICfEkMOjIGjhu6d5LicgOiAnxJDDoyBob8OgbiB04bqldC4nOyB0aGlzLmNvbXBsZXRlZC5lbWl0KGpvYik7XHJcbiAgICB9IGNhdGNoIChlcnJvcikgeyBpZiAoZ2VuZXJhdGlvbiA9PT0gdGhpcy5nZW5lcmF0aW9uKSB0aGlzLnJlcG9ydChlcnJvcik7IH1cclxuICAgIGZpbmFsbHkgeyBpZiAoZ2VuZXJhdGlvbiA9PT0gdGhpcy5nZW5lcmF0aW9uKSB7IHRoaXMuYnVzeSA9IGZhbHNlOyB0aGlzLnVwbG9hZGluZyA9IGZhbHNlOyB9IH1cclxuICB9XHJcbn1cclxuIl19
214
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BlYWtpbmcuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NwZWFraW5nLmNvbXBvbmVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsU0FBUyxFQUFFLFVBQVUsRUFBRSxZQUFZLEVBQUUsS0FBSyxFQUFVLE1BQU0sRUFBRSxNQUFNLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDbkcsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFFaEUsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLG9CQUFvQixDQUFDOztBQXlCckQsTUFBTSxPQUFPLHFCQUFxQjtJQUN2QixlQUFlLEdBQWEsSUFBSSxDQUFDO0lBQ2pDLGFBQWEsR0FBRyxJQUFJLENBQUM7SUFDckIsV0FBVyxDQUF1QjtJQUNqQyxTQUFTLEdBQUcsSUFBSSxZQUFZLEVBQWtCLENBQUM7SUFDL0MsTUFBTSxHQUFHLElBQUksWUFBWSxFQUFTLENBQUM7SUFDcEMsUUFBUSxHQUFHLE1BQU0sQ0FBQyxlQUFlLENBQUMsQ0FBQztJQUMzQixPQUFPLEdBQUcsTUFBTSxDQUFDLFVBQVUsQ0FBQyxDQUFDO0lBQ3JDLFNBQVMsR0FBRyxDQUFDLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsV0FBVyxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxZQUFZLEVBQUUsRUFBRSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLFdBQVcsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsYUFBYSxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxZQUFZLEVBQUUsQ0FBQyxDQUFDO0lBQ3pNLFFBQVEsR0FBYSxJQUFJLENBQUM7SUFBQyxJQUFJLEdBQUcsS0FBSyxDQUFDO0lBQUMsU0FBUyxHQUFHLEtB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QUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQUMsQ0FBQztnQkFDbkUsQ0FBQztZQUFDLElBQUksVUFBVSxLQUFLLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQztnQkFBQyxJQUFJLENBQUMsSUFBSSxHQUFHLEtBQUssQ0FBQztnQkFBQyxJQUFJLENBQUMsU0FBUyxHQUFHLEtBQUssQ0FBQztZQUFDLENBQUM7UUFBQyxDQUFDO0lBQ2hHLENBQUM7d0dBM0VVLHFCQUFxQjs0RkFBckIscUJBQXFCLCtOQXRCd0IsQ0FBQyxlQUFlLENBQUMsMEJBQy9EOzs7Ozs7Ozs7Ozs7Ozs7Ozs7ZUFrQkc7OzRGQUdGLHFCQUFxQjtrQkF2QmpDLFNBQVM7K0JBQ0UsZUFBZSxjQUFjLElBQUksYUFBYSxDQUFDLGVBQWUsQ0FBQyxZQUMvRDs7Ozs7Ozs7Ozs7Ozs7Ozs7O2VBa0JHO3dEQUlKLGVBQWU7c0JBQXZCLEtBQUs7Z0JBQ0csYUFBYTtzQkFBckIsS0FBSztnQkFDRyxXQUFXO3NCQUFuQixLQUFLO2dCQUNJLFNBQVM7c0JBQWxCLE1BQU07Z0JBQ0csTUFBTTtzQkFBZixNQUFNIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29tcG9uZW50LCBEZXN0cm95UmVmLCBFdmVudEVtaXR0ZXIsIElucHV0LCBPbkluaXQsIE91dHB1dCwgaW5qZWN0IH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XHJcbmltcG9ydCB7IHRha2VVbnRpbERlc3Ryb3llZCB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUvcnhqcy1pbnRlcm9wJztcclxuaW1wb3J0IHsgSm9iLCBMYW5ndWFnZSwgU3BlYWtpbmdSZXN1bHQsIFNwZWFraW5nU2VydmljZVR5cGUsIFNwZWFraW5nRXZlbnQgfSBmcm9tICdAaG9hbmdob25nL3NwZWFraW5nLWNvcmUnO1xyXG5pbXBvcnQgeyBTcGVha2luZ1NlcnZpY2UgfSBmcm9tICcuL3NwZWFraW5nLnNlcnZpY2UnO1xyXG5cclxuQENvbXBvbmVudCh7XHJcbiAgc2VsZWN0b3I6ICdmc2VsLXNwZWFraW5nJywgc3RhbmRhbG9uZTogdHJ1ZSwgcHJvdmlkZXJzOiBbU3BlYWtpbmdTZXJ2aWNlXSxcclxuICB0ZW1wbGF0ZTogYFxyXG4gICAgPHNlY3Rpb24gYXJpYS1sYWJlbD1cIkZTRUwgU3BlYWtpbmdcIj5cclxuICAgICAgPGxhYmVsPk5nw7RuIG5n4buvIDxzZWxlY3QgW3ZhbHVlXT1cImxhbmd1YWdlXCIgW2Rpc2FibGVkXT1cImJ1c3lcIiAoY2hhbmdlKT1cInNlbGVjdExhbmd1YWdlKCRldmVudClcIj5cclxuICAgICAgICBAZm9yIChpdGVtIG9mIGxhbmd1YWdlczsgdHJhY2sgaXRlbS5jb2RlKSB7IDxvcHRpb24gW3ZhbHVlXT1cIml0ZW0uY29kZVwiIFtzZWxlY3RlZF09XCJpdGVtLmNvZGUgPT09IGxhbmd1YWdlXCI+e3sgaXRlbS5uYW1lIH19PC9vcHRpb24+IH1cclxuICAgICAgPC9zZWxlY3Q+PC9sYWJlbD5cclxuICAgICAgPGRpdiBjbGFzcz1cImFjdGlvbnNcIj5cclxuICAgICAgICBAaWYgKHNlcnZpY2VUeXBlICE9PSAndXBsb2FkJykge1xyXG4gICAgICAgIDxidXR0b24gdHlwZT1cImJ1dHRvblwiIChjbGljayk9XCJzdGFydCgpXCIgW2Rpc2FibGVkXT1cImJ1c3lcIj5C4bqvdCDEkeG6p3UgbsOzaTwvYnV0dG9uPlxyXG4gICAgICAgIDxidXR0b24gdHlwZT1cImJ1dHRvblwiIChjbGljayk9XCJzdG9wKClcIiBbZGlzYWJsZWRdPVwiIXJlY29yZGluZ1wiPkThu6tuZyB2w6AgbOG6pXkga+G6v3QgcXXhuqM8L2J1dHRvbj5cclxuICAgICAgICB9XHJcbiAgICAgICAgPGJ1dHRvbiB0eXBlPVwiYnV0dG9uXCIgKGNsaWNrKT1cImNhbmNlbCgpXCIgW2Rpc2FibGVkXT1cIiFidXN5IHx8ICh1cGxvYWRpbmcgJiYgIWpvYilcIj5I4buneTwvYnV0dG9uPlxyXG4gICAgICA8L2Rpdj5cclxuICAgICAgQGlmIChzZXJ2aWNlVHlwZSAhPT0gJ3JlYWx0aW1lJyAmJiB1cGxvYWRFbmFibGVkKSB7IDxsYWJlbD5DaOG7jW4gZmlsZSBhdWRpbyAodOG7kWkgxJFhIDI1IE1CKVxyXG4gICAgICAgIDxpbnB1dCB0eXBlPVwiZmlsZVwiIGFjY2VwdD1cImF1ZGlvLyosLndhdiwubXAzLC5tNGEsLm9nZywuZmxhYywud2VibVwiIFtkaXNhYmxlZF09XCJidXN5XCIgKGNoYW5nZSk9XCJ1cGxvYWQoJGV2ZW50KVwiPlxyXG4gICAgICA8L2xhYmVsPiB9XHJcbiAgICAgIDxwIHJvbGU9XCJzdGF0dXNcIj57eyBzdGF0dXMgfX08L3A+XHJcbiAgICAgIEBpZiAoZXJyb3IpIHsgPHAgcm9sZT1cImFsZXJ0XCIgY2xhc3M9XCJlcnJvclwiPnt7IGVycm9yIH19PC9wPiB9XHJcbiAgICAgIDxsYWJlbD5WxINuIGLhuqNuIG5o4bqtbiBk4bqhbmcgPHRleHRhcmVhIHJvd3M9XCI3XCIgcmVhZG9ubHkgW3ZhbHVlXT1cInRleHRcIj48L3RleHRhcmVhPjwvbGFiZWw+XHJcbiAgICA8L3NlY3Rpb24+YCxcclxuICBzdHlsZXM6IFtgOmhvc3R7ZGlzcGxheTpibG9jaztmb250OmluaGVyaXQ7Y29sb3I6aW5oZXJpdH1zZWN0aW9ue2JvcmRlcjoxcHggc29saWQgI2I5YzFiYztwYWRkaW5nOjIwcHg7Ym9yZGVyLXJhZGl1czo0cHh9bGFiZWx7ZGlzcGxheTpncmlkO2dhcDo4cHg7bWFyZ2luLWJvdHRvbToxNnB4fXNlbGVjdCxpbnB1dCx0ZXh0YXJlYSxidXR0b257Zm9udDppbmhlcml0O2JveC1zaXppbmc6Ym9yZGVyLWJveDttYXgtd2lkdGg6MTAwJX1zZWxlY3QsaW5wdXQsdGV4dGFyZWF7cGFkZGluZzoxMHB4O2JvcmRlcjoxcHggc29saWQgIzkyOWM5NTtib3JkZXItcmFkaXVzOjNweDtiYWNrZ3JvdW5kOiNmZmY7Y29sb3I6IzIwMjcyM310ZXh0YXJlYXt3aWR0aDoxMDAlO3Jlc2l6ZTp2ZXJ0aWNhbH0uYWN0aW9uc3tkaXNwbGF5OmZsZXg7Z2FwOjhweDtmbGV4LXdyYXA6d3JhcDttYXJnaW4tYm90dG9tOjE2cHh9YnV0dG9ue21pbi1oZWlnaHQ6NDRweDtwYWRkaW5nOjhweCAxNHB4O2JhY2tncm91bmQ6IzM2NTQ0ODtjb2xvcjp3aGl0ZTtib3JkZXI6MXB4IHNvbGlkICMzNjU0NDg7Ym9yZGVyLXJhZGl1czo0cHg7Y3Vyc29yOnBvaW50ZXJ9YnV0dG9uOmRpc2FibGVke29wYWNpdHk6LjU7Y3Vyc29yOmRlZmF1bHR9YnV0dG9uOmZvY3VzLXZpc2libGUsaW5wdXQ6Zm9jdXMtdmlzaWJsZSxzZWxlY3Q6Zm9jdXMtdmlzaWJsZSx0ZXh0YXJlYTpmb2N1cy12aXNpYmxle291dGxpbmU6M3B4IHNvbGlkICNhNjZhMTU7b3V0bGluZS1vZmZzZXQ6MnB4fS5lcnJvcntjb2xvcjojYTAyMDIwO292ZXJmbG93LXdyYXA6YW55d2hlcmV9YF0sXHJcbn0pXHJcbmV4cG9ydCBjbGFzcyBGc2VsU3BlYWtpbmdDb21wb25lbnQgaW1wbGVtZW50cyBPbkluaXQge1xyXG4gIEBJbnB1dCgpIGluaXRpYWxMYW5ndWFnZTogTGFuZ3VhZ2UgPSAndmknO1xyXG4gIEBJbnB1dCgpIHVwbG9hZEVuYWJsZWQgPSB0cnVlO1xyXG4gIEBJbnB1dCgpIHNlcnZpY2VUeXBlPzogU3BlYWtpbmdTZXJ2aWNlVHlwZTtcclxuICBAT3V0cHV0KCkgY29tcGxldGVkID0gbmV3IEV2ZW50RW1pdHRlcjxTcGVha2luZ1Jlc3VsdD4oKTtcclxuICBAT3V0cHV0KCkgZmFpbGVkID0gbmV3IEV2ZW50RW1pdHRlcjxFcnJvcj4oKTtcclxuICByZWFkb25seSBzcGVha2luZyA9IGluamVjdChTcGVha2luZ1NlcnZpY2UpO1xyXG4gIHByaXZhdGUgcmVhZG9ubHkgZGVzdHJveSA9IGluamVjdChEZXN0cm95UmVmKTtcclxuICByZWFkb25seSBsYW5ndWFnZXMgPSBbeyBjb2RlOiAnZW4nLCBuYW1lOiAnVGnhur9uZyBBbmgnIH0sIHsgY29kZTogJ2phJywgbmFtZTogJ1Rp4bq/bmcgTmjhuq10JyB9LCB7IGNvZGU6ICdrbycsIG5hbWU6ICdUaeG6v25nIEjDoG4nIH0sIHsgY29kZTogJ3poJywgbmFtZTogJ1Rp4bq/bmcgVHJ1bmcnIH0sIHsgY29kZTogJ3ZpJywgbmFtZTogJ1Rp4bq/bmcgVmnhu4d0JyB9XTtcclxuICBsYW5ndWFnZTogTGFuZ3VhZ2UgPSAndmknOyBidXN5ID0gZmFsc2U7IHJlY29yZGluZyA9IGZhbHNlOyB1cGxvYWRpbmcgPSBmYWxzZTtcclxuICB0ZXh0ID0gJyc7IGVycm9yID0gJyc7IHN0YXR1cyA9ICdDaOG7jW4gbmfDtG4gbmfhu68sIHNhdSDEkcOzIGLhuqVtIG7Ds2kgaG/hurdjIGNo4buNbiBmaWxlLic7XHJcbiAgam9iPzogSm9iOyBwcml2YXRlIGdlbmVyYXRpb24gPSAwOyBwcml2YXRlIGFib3J0PzogQWJvcnRDb250cm9sbGVyO1xyXG4gIGNvbnN0cnVjdG9yKCkge1xyXG4gICAgdGhpcy5zcGVha2luZy5ldmVudHMkLnBpcGUodGFrZVVudGlsRGVzdHJveWVkKHRoaXMuZGVzdHJveSkpLnN1YnNjcmliZSgoZXZlbnQ6IFNwZWFraW5nRXZlbnQpID0+IHtcclxuICAgICAgaWYgKGV2ZW50LnR5cGUgPT09ICdwYXJ0aWFsJykgdGhpcy50ZXh0ID0gZXZlbnQudGV4dDtcclxuICAgICAgaWYgKGV2ZW50LnR5cGUgPT09ICdzdGF0ZScpIHRoaXMucmVjb3JkaW5nID0gZXZlbnQuc3RhdGUgPT09ICdyZWNvcmRpbmcnO1xyXG4gICAgICBpZiAoZXZlbnQudHlwZSA9PT0gJ2NsaWVudEVycm9yJykgeyB0aGlzLnJlcG9ydChldmVudC5lcnJvcik7IHRoaXMuYnVzeSA9IGZhbHNlOyB0aGlzLnJlY29yZGluZyA9IGZhbHNlOyB9XHJcbiAgICB9KTtcclxuICAgIHRoaXMuZGVzdHJveS5vbkRlc3Ryb3koKCkgPT4geyArK3RoaXMuZ2VuZXJhdGlvbjsgdGhpcy5hYm9ydD8uYWJvcnQoKTsgfSk7XHJcbiAgfVxyXG4gIG5nT25Jbml0KCk6IHZvaWQgeyB0aGlzLmxhbmd1YWdlID0gdGhpcy5pbml0aWFsTGFuZ3VhZ2U7IH1cclxuICBzZWxlY3RMYW5ndWFnZShldmVudDogRXZlbnQpOiB2b2lkIHsgdGhpcy5sYW5ndWFnZSA9IChldmVudC50YXJnZXQgYXMgSFRNTFNlbGVjdEVsZW1lbnQpLnZhbHVlIGFzIExhbmd1YWdlOyB9XHJcbiAgcHJpdmF0ZSByZXBvcnQoZXJyb3I6IHVua25vd24pOiB2b2lkIHtcclxuICAgIGNvbnN0IHZhbHVlID0gZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yIDogbmV3IEVycm9yKCdTcGVha2luZyBmYWlsZWQnKTtcclxuICAgIHRoaXMuZXJyb3IgPSB2YWx1ZS5tZXNzYWdlOyB0aGlzLmZhaWxlZC5lbWl0KHZhbHVlKTtcclxuICB9XHJcbiAgYXN5bmMgc3RhcnQoKTogUHJvbWlzZTx2b2lkPiB7XHJcbiAgICBpZiAodGhpcy5idXN5KSByZXR1cm47XHJcbiAgICBjb25zdCBnZW5lcmF0aW9uID0gKyt0aGlzLmdlbmVyYXRpb247XHJcbiAgICB0aGlzLmJ1c3kgPSB0cnVlOyB0aGlzLnVwbG9hZGluZyA9IGZhbHNlOyB0aGlzLmVycm9yID0gJyc7IHRoaXMudGV4dCA9ICcnOyB0aGlzLnN0YXR1cyA9ICfEkGFuZyBt4bufIG1pY3JvcGhvbmUgdsOgIGNodeG6qW4gYuG7iyBtb2RlbOKApic7XHJcbiAgICB0cnkgeyBhd2FpdCB0aGlzLnNwZWFraW5nLnN0YXJ0KHsgbGFuZ3VhZ2U6IHRoaXMubGFuZ3VhZ2UgfSk7IGlmIChnZW5lcmF0aW9uID09PSB0aGlzLmdlbmVyYXRpb24pIHRoaXMuc3RhdHVzID0gJ8SQYW5nIG5naGXigKYnOyB9XHJcbiAgICBjYXRjaCAoZXJyb3IpIHsgaWYgKGdlbmVyYXRpb24gPT09IHRoaXMuZ2VuZXJhdGlvbikgeyB0aGlzLnJlcG9ydChlcnJvcik7IHRoaXMuYnVzeSA9IGZhbHNlOyB9IH1cclxuICB9XHJcbiAgYXN5bmMgc3RvcCgpOiBQcm9taXNlPHZvaWQ+IHtcclxuICAgIGlmICghdGhpcy5yZWNvcmRpbmcpIHJldHVybjtcclxuICAgIGNvbnN0IGdlbmVyYXRpb24gPSB0aGlzLmdlbmVyYXRpb247XHJcbiAgICB0aGlzLnJlY29yZGluZyA9IGZhbHNlOyB0aGlzLnN0YXR1cyA9ICfEkGFuZyBs4bqleSBr4bq/dCBxdeG6o+KApic7XHJcbiAgICB0cnkge1xyXG4gICAgICBjb25zdCByZXN1bHQgPSBhd2FpdCB0aGlzLnNwZWFraW5nLnN0b3AoKTtcclxuICAgICAgaWYgKGdlbmVyYXRpb24gIT09IHRoaXMuZ2VuZXJhdGlvbikgcmV0dXJuO1xyXG4gICAgICB0aGlzLnRleHQgPSByZXN1bHQucmVzdWx0Py50ZXh0ID8/ICcnOyB0aGlzLnN0YXR1cyA9IHJlc3VsdC5zdGF0dXMgPT09ICdub19zcGVlY2gnID8gJ0tow7RuZyBuaOG6rW4gdGjhuqV5IGzhu51pIG7Ds2kuJyA6ICfEkMOjIGhvw6BuIHThuqV0Lic7XHJcbiAgICAgIHRoaXMuY29tcGxldGVkLmVtaXQoeyAuLi5yZXN1bHQsIGxhbmd1YWdlOiB0aGlzLmxhbmd1YWdlLCB0ZXh0OiB0aGlzLnRleHQsIGF1ZGlvOiByZXN1bHQuYXVkaW8sIHNlcnZpY2VUeXBlOiAncmVhbHRpbWUnIH0pO1xyXG4gICAgfSBjYXRjaCAoZXJyb3IpIHsgaWYgKGdlbmVyYXRpb24gPT09IHRoaXMuZ2VuZXJhdGlvbikgdGhpcy5yZXBvcnQoZXJyb3IpOyB9XHJcbiAgICBmaW5hbGx5IHsgaWYgKGdlbmVyYXRpb24gPT09IHRoaXMuZ2VuZXJhdGlvbikgdGhpcy5idXN5ID0gZmFsc2U7IH1cclxuICB9XHJcbiAgYXN5bmMgY2FuY2VsKCk6IFByb21pc2U8dm9pZD4ge1xyXG4gICAgKyt0aGlzLmdlbmVyYXRpb247IHRoaXMuYWJvcnQ/LmFib3J0KCk7XHJcbiAgICB0cnkgeyBpZiAodGhpcy51cGxvYWRpbmcgJiYgdGhpcy5qb2IpIGF3YWl0IHRoaXMuc3BlYWtpbmcuYmF0Y2guY2FuY2VsSm9iKHRoaXMuam9iLmpvYl9pZCk7IGVsc2UgYXdhaXQgdGhpcy5zcGVha2luZy5jYW5jZWwoKTsgdGhpcy5zdGF0dXMgPSAnxJDDoyBo4buneS4nOyB9XHJcbiAgICBjYXRjaCAoZXJyb3IpIHsgdGhpcy5yZXBvcnQoZXJyb3IpOyB9XHJcbiAgICBmaW5hbGx5IHsgdGhpcy5idXN5ID0gZmFsc2U7IHRoaXMucmVjb3JkaW5nID0gZmFsc2U7IHRoaXMudXBsb2FkaW5nID0gZmFsc2U7IH1cclxuICB9XHJcbiAgYXN5bmMgdXBsb2FkKGV2ZW50OiBFdmVudCk6IFByb21pc2U8dm9pZD4ge1xyXG4gICAgY29uc3QgaW5wdXQgPSBldmVudC50YXJnZXQgYXMgSFRNTElucHV0RWxlbWVudDsgY29uc3QgZmlsZSA9IGlucHV0LmZpbGVzPy5bMF07IGlucHV0LnZhbHVlID0gJyc7XHJcbiAgICBpZiAoIWZpbGUgfHwgdGhpcy5idXN5KSByZXR1cm47XHJcbiAgICBpZiAoZmlsZS5zaXplID4gMjUgKiAxMDI0ICogMTAyNCkgeyB0aGlzLnJlcG9ydChuZXcgRXJyb3IoJ0ZpbGUgdOG7kWkgxJFhIDI1IE1CLicpKTsgcmV0dXJuOyB9XHJcbiAgICBjb25zdCBnZW5lcmF0aW9uID0gKyt0aGlzLmdlbmVyYXRpb247IHRoaXMuYWJvcnQgPSBuZXcgQWJvcnRDb250cm9sbGVyKCk7XHJcbiAgICB0aGlzLmJ1c3kgPSB0cnVlOyB0aGlzLnVwbG9hZGluZyA9IHRydWU7IHRoaXMuam9iID0gdW5kZWZpbmVkOyB0aGlzLnRleHQgPSAnJzsgdGhpcy5lcnJvciA9ICcnOyB0aGlzLnN0YXR1cyA9ICfEkGFuZyB1cGxvYWTigKYnO1xyXG4gICAgdHJ5IHtcclxuICAgICAgbGV0IGpvYiA9IGF3YWl0IHRoaXMuc3BlYWtpbmcuYmF0Y2guc3VibWl0QXVkaW8oZmlsZSwgdGhpcy5sYW5ndWFnZSwgY3J5cHRvLnJhbmRvbVVVSUQoKSwgJ3Nob3J0X2Fuc3dlcicsIGZpbGUubmFtZSwgdGhpcy5hYm9ydC5zaWduYWwpO1xyXG4gICAgICBjb25zdCBkZWFkbGluZSA9IERhdGUubm93KCkgKyAxMCAqIDYwICogMTAwMDtcclxuICAgICAgd2hpbGUgKGdlbmVyYXRpb24gPT09IHRoaXMuZ2VuZXJhdGlvbikge1xyXG4gICAgICAgIHRoaXMuam9iID0gam9iO1xyXG4gICAgICAgIGlmIChbJ3N1Y2NlZWRlZCcsICdub19zcGVlY2gnLCAnZmFpbGVkJywgJ2NhbmNlbGxlZCddLmluY2x1ZGVzKGpvYi5zdGF0dXMpKSBicmVhaztcclxuICAgICAgICBpZiAoRGF0ZS5ub3coKSA+IGRlYWRsaW5lKSB0aHJvdyBuZXcgRXJyb3IoJ0jhur90IHRo4budaSBnaWFuIGNo4budOyBqb2IgY8OzIHRo4buDIHbhuqtuIMSRYW5nIGNo4bqheSB0csOqbiBzZXJ2ZXIuJyk7XHJcbiAgICAgICAgdGhpcy5zdGF0dXMgPSAnxJBhbmcgbmjhuq1uIGThuqFuZyBmaWxl4oCmJztcclxuICAgICAgICBhd2FpdCBuZXcgUHJvbWlzZShyZXNvbHZlID0+IHNldFRpbWVvdXQocmVzb2x2ZSwgMTUwMCkpO1xyXG4gICAgICAgIGlmIChnZW5lcmF0aW9uICE9PSB0aGlzLmdlbmVyYXRpb24pIHJldHVybjtcclxuICAgICAgICBqb2IgPSBhd2FpdCB0aGlzLnNwZWFraW5nLmJhdGNoLmdldFJlc3VsdChqb2Iuam9iX2lkLCB0aGlzLmFib3J0LnNpZ25hbCk7XHJcbiAgICAgIH1cclxuICAgICAgaWYgKGdlbmVyYXRpb24gIT09IHRoaXMuZ2VuZXJhdGlvbikgcmV0dXJuO1xyXG4gICAgICBpZiAoam9iLmVycm9yKSB0aHJvdyBuZXcgRXJyb3Ioam9iLmVycm9yLm1lc3NhZ2UpO1xyXG4gICAgICB0aGlzLnRleHQgPSBqb2IucmVzdWx0Py50ZXh0ID8/ICcnOyB0aGlzLnN0YXR1cyA9IGpvYi5zdGF0dXMgPT09ICdub19zcGVlY2gnID8gJ0tow7RuZyBuaOG6rW4gdGjhuqV5IGzhu51pIG7Ds2kuJyA6IGpvYi5zdGF0dXMgPT09ICdjYW5jZWxsZWQnID8gJ8SQw6MgaOG7p3kuJyA6ICfEkMOjIGhvw6BuIHThuqV0Lic7XHJcbiAgICAgIGlmIChqb2Iuc3RhdHVzICE9PSAnY2FuY2VsbGVkJykgdGhpcy5jb21wbGV0ZWQuZW1pdCh7IC4uLmpvYiwgbGFuZ3VhZ2U6IHRoaXMubGFuZ3VhZ2UsIHRleHQ6IHRoaXMudGV4dCwgYXVkaW86IGZpbGUsIHNlcnZpY2VUeXBlOiAndXBsb2FkJyB9KTtcclxuICAgIH0gY2F0Y2ggKGVycm9yKSB7IGlmIChnZW5lcmF0aW9uID09PSB0aGlzLmdlbmVyYXRpb24pIHRoaXMucmVwb3J0KGVycm9yKTsgfVxyXG4gICAgZmluYWxseSB7IGlmIChnZW5lcmF0aW9uID09PSB0aGlzLmdlbmVyYXRpb24pIHsgdGhpcy5idXN5ID0gZmFsc2U7IHRoaXMudXBsb2FkaW5nID0gZmFsc2U7IH0gfVxyXG4gIH1cclxufVxyXG4iXX0=
@@ -1,12 +1,15 @@
1
1
  import { Inject, Injectable, InjectionToken } from '@angular/core';
2
2
  import { Subject } from 'rxjs';
3
- import { BatchSpeakingClient, RealtimeSpeakingClient, SpeakingMicrophone, createSpeakingGateway, } from '@hoanghong/speaking-core';
3
+ import { BatchSpeakingClient, RealtimeSpeakingClient, SpeakingMicrophone, createSpeakingGateway, createSpeakingService, PronunciationClient, } from '@hoanghong/speaking-core';
4
4
  import * as i0 from "@angular/core";
5
- export const SPEAKING_CONFIG = new InjectionToken('FSEL_SPEAKING_CONFIG');
5
+ export const SPEAKING_CONFIG = new InjectionToken('FSEL_SPEAKING_CONFIG', {
6
+ providedIn: 'root', factory: () => createSpeakingService(),
7
+ });
6
8
  /** Provide at the screen/component level to give each speaking screen its own lifecycle. */
7
9
  export class SpeakingService {
8
10
  zone;
9
11
  batch;
12
+ pronunciation;
10
13
  realtime;
11
14
  eventSubject = new Subject();
12
15
  events$ = this.eventSubject.asObservable();
@@ -15,6 +18,7 @@ export class SpeakingService {
15
18
  constructor(config, zone) {
16
19
  this.zone = zone;
17
20
  this.batch = new BatchSpeakingClient(config.batch);
21
+ this.pronunciation = new PronunciationClient(config.pronunciation ?? config.batch);
18
22
  this.realtime = new RealtimeSpeakingClient(config.realtime);
19
23
  this.microphone = new SpeakingMicrophone(this.realtime, config.microphone);
20
24
  this.unsubscribe = this.realtime.onEvent(event => this.zone.run(() => this.eventSubject.next(event)));
@@ -45,5 +49,9 @@ export function provideSpeaking(config) {
45
49
  export function provideSpeakingGateway(options = {}) {
46
50
  return [{ provide: SPEAKING_CONFIG, useFactory: () => createSpeakingGateway(options) }];
47
51
  }
52
+ /** Application identity only; hosted endpoint and short-lived sessions are automatic. */
53
+ export function provideSpeakingApp(options) {
54
+ return [{ provide: SPEAKING_CONFIG, useFactory: () => createSpeakingService(options) }];
55
+ }
48
56
  export * from '@hoanghong/speaking-core';
49
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BlYWtpbmcuc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zcGVha2luZy5zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxNQUFNLEVBQUUsVUFBVSxFQUFFLGNBQWMsRUFBK0IsTUFBTSxlQUFlLENBQUM7QUFDaEcsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUMvQixPQUFPLEVBQ1MsbUJBQW1CLEVBQ2pDLHNCQUFzQixFQUFpQixrQkFBa0IsRUFDakMscUJBQXFCLEdBQzlDLE1BQU0sMEJBQTBCLENBQUM7O0FBUWxDLE1BQU0sQ0FBQyxNQUFNLGVBQWUsR0FBRyxJQUFJLGNBQWMsQ0FBaUIsc0JBQXNCLENBQUMsQ0FBQztBQUUxRiw0RkFBNEY7QUFFNUYsTUFBTSxPQUFPLGVBQWU7SUFRb0Q7SUFQckUsS0FBSyxDQUFzQjtJQUMzQixRQUFRLENBQXlCO0lBQ3pCLFlBQVksR0FBRyxJQUFJLE9BQU8sRUFBaUIsQ0FBQztJQUNwRCxPQUFPLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQyxZQUFZLEVBQUUsQ0FBQztJQUNuQyxVQUFVLENBQXFCO0lBQy9CLFdBQVcsQ0FBYTtJQUV6QyxZQUFxQyxNQUFzQixFQUFtQixJQUFZO1FBQVosU0FBSSxHQUFKLElBQUksQ0FBUTtRQUN4RixJQUFJLENBQUMsS0FBSyxHQUFHLElBQUksbUJBQW1CLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ25ELElBQUksQ0FBQyxRQUFRLEdBQUcsSUFBSSxzQkFBc0IsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDNUQsSUFBSSxDQUFDLFVBQVUsR0FBRyxJQUFJLGtCQUFrQixDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsTUFBTSxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQzNFLElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDeEcsQ0FBQztJQUVELEtBQUssQ0FBQyxPQUFxQjtRQUN6QixPQUFPLElBQUksQ0FBQyxJQUFJLENBQUMsaUJBQWlCLENBQUMsR0FBRyxFQUFFLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQztJQUMzRSxDQUFDO0lBRUQsSUFBSSxLQUE2QixPQUFPLElBQUksQ0FBQyxVQUFVLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ2pFLE1BQU0sS0FBb0IsT0FBTyxJQUFJLENBQUMsVUFBVSxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsQ0FBQztJQUU1RCxXQUFXO1FBQ1QsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO1FBQ25CLElBQUksQ0FBQyxZQUFZLENBQUMsUUFBUSxFQUFFLENBQUM7UUFDN0IsS0FBSyxJQUFJLENBQUMsVUFBVSxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQ2pDLENBQUM7d0dBMUJVLGVBQWUsa0JBUU4sZUFBZTs0R0FSeEIsZUFBZTs7NEZBQWYsZUFBZTtrQkFEM0IsVUFBVTs7MEJBU0ksTUFBTTsyQkFBQyxlQUFlOztBQXFCckMsTUFBTSxVQUFVLGVBQWUsQ0FBQyxNQUFzQjtJQUNwRCxPQUFPLENBQUMsRUFBRSxPQUFPLEVBQUUsZUFBZSxFQUFFLFFBQVEsRUFBRSxNQUFNLEVBQUUsRUFBRSxlQUFlLENBQUMsQ0FBQztBQUMzRSxDQUFDO0FBRUQsdUZBQXVGO0FBQ3ZGLE1BQU0sVUFBVSxzQkFBc0IsQ0FBQyxVQUFrQyxFQUFFO0lBQ3pFLE9BQU8sQ0FBQyxFQUFFLE9BQU8sRUFBRSxlQUFlLEVBQUUsVUFBVSxFQUFFLEdBQUcsRUFBRSxDQUFDLHFCQUFxQixDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUMsQ0FBQztBQUMxRixDQUFDO0FBSUQsY0FBYywwQkFBMEIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdCwgSW5qZWN0YWJsZSwgSW5qZWN0aW9uVG9rZW4sIE5nWm9uZSwgT25EZXN0cm95LCBQcm92aWRlciB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xyXG5pbXBvcnQgeyBTdWJqZWN0IH0gZnJvbSAncnhqcyc7XHJcbmltcG9ydCB7XHJcbiAgQmF0Y2hPcHRpb25zLCBCYXRjaFNwZWFraW5nQ2xpZW50LCBNaWNyb3Bob25lT3B0aW9ucywgUmVhbHRpbWVGaW5hbCwgUmVhbHRpbWVPcHRpb25zLFxyXG4gIFJlYWx0aW1lU3BlYWtpbmdDbGllbnQsIFNwZWFraW5nRXZlbnQsIFNwZWFraW5nTWljcm9waG9uZSwgU3RhcnRPcHRpb25zLFxyXG4gIFNwZWFraW5nR2F0ZXdheU9wdGlvbnMsIGNyZWF0ZVNwZWFraW5nR2F0ZXdheSxcclxufSBmcm9tICdAaG9hbmdob25nL3NwZWFraW5nLWNvcmUnO1xyXG5cclxuZXhwb3J0IGludGVyZmFjZSBTcGVha2luZ0NvbmZpZyB7XHJcbiAgYmF0Y2g6IEJhdGNoT3B0aW9ucztcclxuICByZWFsdGltZTogUmVhbHRpbWVPcHRpb25zO1xyXG4gIG1pY3JvcGhvbmU6IE1pY3JvcGhvbmVPcHRpb25zO1xyXG59XHJcblxyXG5leHBvcnQgY29uc3QgU1BFQUtJTkdfQ09ORklHID0gbmV3IEluamVjdGlvblRva2VuPFNwZWFraW5nQ29uZmlnPignRlNFTF9TUEVBS0lOR19DT05GSUcnKTtcclxuXHJcbi8qKiBQcm92aWRlIGF0IHRoZSBzY3JlZW4vY29tcG9uZW50IGxldmVsIHRvIGdpdmUgZWFjaCBzcGVha2luZyBzY3JlZW4gaXRzIG93biBsaWZlY3ljbGUuICovXHJcbkBJbmplY3RhYmxlKClcclxuZXhwb3J0IGNsYXNzIFNwZWFraW5nU2VydmljZSBpbXBsZW1lbnRzIE9uRGVzdHJveSB7XHJcbiAgcmVhZG9ubHkgYmF0Y2g6IEJhdGNoU3BlYWtpbmdDbGllbnQ7XHJcbiAgcmVhZG9ubHkgcmVhbHRpbWU6IFJlYWx0aW1lU3BlYWtpbmdDbGllbnQ7XHJcbiAgcHJpdmF0ZSByZWFkb25seSBldmVudFN1YmplY3QgPSBuZXcgU3ViamVjdDxTcGVha2luZ0V2ZW50PigpO1xyXG4gIHJlYWRvbmx5IGV2ZW50cyQgPSB0aGlzLmV2ZW50U3ViamVjdC5hc09ic2VydmFibGUoKTtcclxuICBwcml2YXRlIHJlYWRvbmx5IG1pY3JvcGhvbmU6IFNwZWFraW5nTWljcm9waG9uZTtcclxuICBwcml2YXRlIHJlYWRvbmx5IHVuc3Vic2NyaWJlOiAoKSA9PiB2b2lkO1xyXG5cclxuICBjb25zdHJ1Y3RvcihASW5qZWN0KFNQRUFLSU5HX0NPTkZJRykgY29uZmlnOiBTcGVha2luZ0NvbmZpZywgcHJpdmF0ZSByZWFkb25seSB6b25lOiBOZ1pvbmUpIHtcclxuICAgIHRoaXMuYmF0Y2ggPSBuZXcgQmF0Y2hTcGVha2luZ0NsaWVudChjb25maWcuYmF0Y2gpO1xyXG4gICAgdGhpcy5yZWFsdGltZSA9IG5ldyBSZWFsdGltZVNwZWFraW5nQ2xpZW50KGNvbmZpZy5yZWFsdGltZSk7XHJcbiAgICB0aGlzLm1pY3JvcGhvbmUgPSBuZXcgU3BlYWtpbmdNaWNyb3Bob25lKHRoaXMucmVhbHRpbWUsIGNvbmZpZy5taWNyb3Bob25lKTtcclxuICAgIHRoaXMudW5zdWJzY3JpYmUgPSB0aGlzLnJlYWx0aW1lLm9uRXZlbnQoZXZlbnQgPT4gdGhpcy56b25lLnJ1bigoKSA9PiB0aGlzLmV2ZW50U3ViamVjdC5uZXh0KGV2ZW50KSkpO1xyXG4gIH1cclxuXHJcbiAgc3RhcnQob3B0aW9uczogU3RhcnRPcHRpb25zKTogUHJvbWlzZTx2b2lkPiB7XHJcbiAgICByZXR1cm4gdGhpcy56b25lLnJ1bk91dHNpZGVBbmd1bGFyKCgpID0+IHRoaXMubWljcm9waG9uZS5zdGFydChvcHRpb25zKSk7XHJcbiAgfVxyXG5cclxuICBzdG9wKCk6IFByb21pc2U8UmVhbHRpbWVGaW5hbD4geyByZXR1cm4gdGhpcy5taWNyb3Bob25lLnN0b3AoKTsgfVxyXG4gIGNhbmNlbCgpOiBQcm9taXNlPHZvaWQ+IHsgcmV0dXJuIHRoaXMubWljcm9waG9uZS5jYW5jZWwoKTsgfVxyXG5cclxuICBuZ09uRGVzdHJveSgpOiB2b2lkIHtcclxuICAgIHRoaXMudW5zdWJzY3JpYmUoKTtcclxuICAgIHRoaXMuZXZlbnRTdWJqZWN0LmNvbXBsZXRlKCk7XHJcbiAgICB2b2lkIHRoaXMubWljcm9waG9uZS5kaXNwb3NlKCk7XHJcbiAgfVxyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gcHJvdmlkZVNwZWFraW5nKGNvbmZpZzogU3BlYWtpbmdDb25maWcpOiBQcm92aWRlcltdIHtcclxuICByZXR1cm4gW3sgcHJvdmlkZTogU1BFQUtJTkdfQ09ORklHLCB1c2VWYWx1ZTogY29uZmlnIH0sIFNwZWFraW5nU2VydmljZV07XHJcbn1cclxuXHJcbi8qKiBDb25maWd1cmUgb25jZSBhdCBhcHAgYm9vdHN0cmFwOyBldmVyeSBmc2VsLXNwZWFraW5nIGNvbXBvbmVudCBvd25zIGl0cyBzZXNzaW9uLiAqL1xyXG5leHBvcnQgZnVuY3Rpb24gcHJvdmlkZVNwZWFraW5nR2F0ZXdheShvcHRpb25zOiBTcGVha2luZ0dhdGV3YXlPcHRpb25zID0ge30pOiBQcm92aWRlcltdIHtcclxuICByZXR1cm4gW3sgcHJvdmlkZTogU1BFQUtJTkdfQ09ORklHLCB1c2VGYWN0b3J5OiAoKSA9PiBjcmVhdGVTcGVha2luZ0dhdGV3YXkob3B0aW9ucykgfV07XHJcbn1cclxuXHJcblxyXG5cclxuZXhwb3J0ICogZnJvbSAnQGhvYW5naG9uZy9zcGVha2luZy1jb3JlJztcclxuIl19
57
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BlYWtpbmcuc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zcGVha2luZy5zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxNQUFNLEVBQUUsVUFBVSxFQUFFLGNBQWMsRUFBK0IsTUFBTSxlQUFlLENBQUM7QUFDaEcsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUMvQixPQUFPLEVBQ1MsbUJBQW1CLEVBQ2pDLHNCQUFzQixFQUFpQixrQkFBa0IsRUFDakMscUJBQXFCLEVBQzdDLHFCQUFxQixFQUNyQixtQkFBbUIsR0FDcEIsTUFBTSwwQkFBMEIsQ0FBQzs7QUFTbEMsTUFBTSxDQUFDLE1BQU0sZUFBZSxHQUFHLElBQUksY0FBYyxDQUFpQixzQkFBc0IsRUFBRTtJQUN4RixVQUFVLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxHQUFHLEVBQUUsQ0FBQyxxQkFBcUIsRUFBRTtDQUMzRCxDQUFDLENBQUM7QUFFSCw0RkFBNEY7QUFFNUYsTUFBTSxPQUFPLGVBQWU7SUFTb0Q7SUFSckUsS0FBSyxDQUFzQjtJQUMzQixhQUFhLENBQXNCO0lBQ25DLFFBQVEsQ0FBeUI7SUFDekIsWUFBWSxHQUFHLElBQUksT0FBTyxFQUFpQixDQUFDO0lBQ3BELE9BQU8sR0FBRyxJQUFJLENBQUMsWUFBWSxDQUFDLFlBQVksRUFBRSxDQUFDO0lBQ25DLFVBQVUsQ0FBcUI7SUFDL0IsV0FBVyxDQUFhO0lBRXpDLFlBQXFDLE1BQXNCLEVBQW1CLElBQVk7UUFBWixTQUFJLEdBQUosSUFBSSxDQUFRO1FBQ3hGLElBQUksQ0FBQyxLQUFLLEdBQUcsSUFBSSxtQkFBbUIsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDbkQsSUFBSSxDQUFDLGFBQWEsR0FBRyxJQUFJLG1CQUFtQixDQUFDLE1BQU0sQ0FBQyxhQUFhLElBQUksTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ25GLElBQUksQ0FBQyxRQUFRLEdBQUcsSUFBSSxzQkFBc0IsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDNUQsSUFBSSxDQUFDLFVBQVUsR0FBRyxJQUFJLGtCQUFrQixDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsTUFBTSxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQzNFLElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDeEcsQ0FBQztJQUVELEtBQUssQ0FBQyxPQUFxQjtRQUN6QixPQUFPLElBQUksQ0FBQyxJQUFJLENBQUMsaUJBQWlCLENBQUMsR0FBRyxFQUFFLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQztJQUMzRSxDQUFDO0lBRUQsSUFBSSxLQUFxQyxPQUFPLElBQUksQ0FBQyxVQUFVLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ3pFLE1BQU0sS0FBb0IsT0FBTyxJQUFJLENBQUMsVUFBVSxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsQ0FBQztJQUU1RCxXQUFXO1FBQ1QsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO1FBQ25CLElBQUksQ0FBQyxZQUFZLENBQUMsUUFBUSxFQUFFLENBQUM7UUFDN0IsS0FBSyxJQUFJLENBQUMsVUFBVSxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQ2pDLENBQUM7d0dBNUJVLGVBQWUsa0JBU04sZUFBZTs0R0FUeEIsZUFBZTs7NEZBQWYsZUFBZTtrQkFEM0IsVUFBVTs7MEJBVUksTUFBTTsyQkFBQyxlQUFlOztBQXNCckMsTUFBTSxVQUFVLGVBQWUsQ0FBQyxNQUFzQjtJQUNwRCxPQUFPLENBQUMsRUFBRSxPQUFPLEVBQUUsZUFBZSxFQUFFLFFBQVEsRUFBRSxNQUFNLEVBQUUsRUFBRSxlQUFlLENBQUMsQ0FBQztBQUMzRSxDQUFDO0FBRUQsdUZBQXVGO0FBQ3ZGLE1BQU0sVUFBVSxzQkFBc0IsQ0FBQyxVQUFrQyxFQUFFO0lBQ3pFLE9BQU8sQ0FBQyxFQUFFLE9BQU8sRUFBRSxlQUFlLEVBQUUsVUFBVSxFQUFFLEdBQUcsRUFBRSxDQUFDLHFCQUFxQixDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUMsQ0FBQztBQUMxRixDQUFDO0FBRUQseUZBQXlGO0FBQ3pGLE1BQU0sVUFBVSxrQkFBa0IsQ0FBQyxPQUEyQjtJQUM1RCxPQUFPLENBQUMsRUFBRSxPQUFPLEVBQUUsZUFBZSxFQUFFLFVBQVUsRUFBRSxHQUFHLEVBQUUsQ0FBQyxxQkFBcUIsQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDLENBQUM7QUFDMUYsQ0FBQztBQUlELGNBQWMsMEJBQTBCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBJbmplY3QsIEluamVjdGFibGUsIEluamVjdGlvblRva2VuLCBOZ1pvbmUsIE9uRGVzdHJveSwgUHJvdmlkZXIgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcclxuaW1wb3J0IHsgU3ViamVjdCB9IGZyb20gJ3J4anMnO1xyXG5pbXBvcnQge1xyXG4gIEJhdGNoT3B0aW9ucywgQmF0Y2hTcGVha2luZ0NsaWVudCwgTWljcm9waG9uZU9wdGlvbnMsIFJlY29yZGVkU3BlYWtpbmdGaW5hbCwgUmVhbHRpbWVPcHRpb25zLFxyXG4gIFJlYWx0aW1lU3BlYWtpbmdDbGllbnQsIFNwZWFraW5nRXZlbnQsIFNwZWFraW5nTWljcm9waG9uZSwgU3RhcnRPcHRpb25zLFxyXG4gIFNwZWFraW5nR2F0ZXdheU9wdGlvbnMsIGNyZWF0ZVNwZWFraW5nR2F0ZXdheSxcclxuICBjcmVhdGVTcGVha2luZ1NlcnZpY2UsXHJcbiAgUHJvbnVuY2lhdGlvbkNsaWVudCwgUHJvbnVuY2lhdGlvbk9wdGlvbnMsXHJcbn0gZnJvbSAnQGhvYW5naG9uZy9zcGVha2luZy1jb3JlJztcclxuXHJcbmV4cG9ydCBpbnRlcmZhY2UgU3BlYWtpbmdDb25maWcge1xyXG4gIGJhdGNoOiBCYXRjaE9wdGlvbnM7XHJcbiAgcmVhbHRpbWU6IFJlYWx0aW1lT3B0aW9ucztcclxuICBtaWNyb3Bob25lOiBNaWNyb3Bob25lT3B0aW9ucztcclxuICBwcm9udW5jaWF0aW9uPzogUHJvbnVuY2lhdGlvbk9wdGlvbnM7XHJcbn1cclxuXHJcbmV4cG9ydCBjb25zdCBTUEVBS0lOR19DT05GSUcgPSBuZXcgSW5qZWN0aW9uVG9rZW48U3BlYWtpbmdDb25maWc+KCdGU0VMX1NQRUFLSU5HX0NPTkZJRycsIHtcclxuICBwcm92aWRlZEluOiAncm9vdCcsIGZhY3Rvcnk6ICgpID0+IGNyZWF0ZVNwZWFraW5nU2VydmljZSgpLFxyXG59KTtcclxuXHJcbi8qKiBQcm92aWRlIGF0IHRoZSBzY3JlZW4vY29tcG9uZW50IGxldmVsIHRvIGdpdmUgZWFjaCBzcGVha2luZyBzY3JlZW4gaXRzIG93biBsaWZlY3ljbGUuICovXHJcbkBJbmplY3RhYmxlKClcclxuZXhwb3J0IGNsYXNzIFNwZWFraW5nU2VydmljZSBpbXBsZW1lbnRzIE9uRGVzdHJveSB7XHJcbiAgcmVhZG9ubHkgYmF0Y2g6IEJhdGNoU3BlYWtpbmdDbGllbnQ7XHJcbiAgcmVhZG9ubHkgcHJvbnVuY2lhdGlvbjogUHJvbnVuY2lhdGlvbkNsaWVudDtcclxuICByZWFkb25seSByZWFsdGltZTogUmVhbHRpbWVTcGVha2luZ0NsaWVudDtcclxuICBwcml2YXRlIHJlYWRvbmx5IGV2ZW50U3ViamVjdCA9IG5ldyBTdWJqZWN0PFNwZWFraW5nRXZlbnQ+KCk7XHJcbiAgcmVhZG9ubHkgZXZlbnRzJCA9IHRoaXMuZXZlbnRTdWJqZWN0LmFzT2JzZXJ2YWJsZSgpO1xyXG4gIHByaXZhdGUgcmVhZG9ubHkgbWljcm9waG9uZTogU3BlYWtpbmdNaWNyb3Bob25lO1xyXG4gIHByaXZhdGUgcmVhZG9ubHkgdW5zdWJzY3JpYmU6ICgpID0+IHZvaWQ7XHJcblxyXG4gIGNvbnN0cnVjdG9yKEBJbmplY3QoU1BFQUtJTkdfQ09ORklHKSBjb25maWc6IFNwZWFraW5nQ29uZmlnLCBwcml2YXRlIHJlYWRvbmx5IHpvbmU6IE5nWm9uZSkge1xyXG4gICAgdGhpcy5iYXRjaCA9IG5ldyBCYXRjaFNwZWFraW5nQ2xpZW50KGNvbmZpZy5iYXRjaCk7XHJcbiAgICB0aGlzLnByb251bmNpYXRpb24gPSBuZXcgUHJvbnVuY2lhdGlvbkNsaWVudChjb25maWcucHJvbnVuY2lhdGlvbiA/PyBjb25maWcuYmF0Y2gpO1xyXG4gICAgdGhpcy5yZWFsdGltZSA9IG5ldyBSZWFsdGltZVNwZWFraW5nQ2xpZW50KGNvbmZpZy5yZWFsdGltZSk7XHJcbiAgICB0aGlzLm1pY3JvcGhvbmUgPSBuZXcgU3BlYWtpbmdNaWNyb3Bob25lKHRoaXMucmVhbHRpbWUsIGNvbmZpZy5taWNyb3Bob25lKTtcclxuICAgIHRoaXMudW5zdWJzY3JpYmUgPSB0aGlzLnJlYWx0aW1lLm9uRXZlbnQoZXZlbnQgPT4gdGhpcy56b25lLnJ1bigoKSA9PiB0aGlzLmV2ZW50U3ViamVjdC5uZXh0KGV2ZW50KSkpO1xyXG4gIH1cclxuXHJcbiAgc3RhcnQob3B0aW9uczogU3RhcnRPcHRpb25zKTogUHJvbWlzZTx2b2lkPiB7XHJcbiAgICByZXR1cm4gdGhpcy56b25lLnJ1bk91dHNpZGVBbmd1bGFyKCgpID0+IHRoaXMubWljcm9waG9uZS5zdGFydChvcHRpb25zKSk7XHJcbiAgfVxyXG5cclxuICBzdG9wKCk6IFByb21pc2U8UmVjb3JkZWRTcGVha2luZ0ZpbmFsPiB7IHJldHVybiB0aGlzLm1pY3JvcGhvbmUuc3RvcCgpOyB9XHJcbiAgY2FuY2VsKCk6IFByb21pc2U8dm9pZD4geyByZXR1cm4gdGhpcy5taWNyb3Bob25lLmNhbmNlbCgpOyB9XHJcblxyXG4gIG5nT25EZXN0cm95KCk6IHZvaWQge1xyXG4gICAgdGhpcy51bnN1YnNjcmliZSgpO1xyXG4gICAgdGhpcy5ldmVudFN1YmplY3QuY29tcGxldGUoKTtcclxuICAgIHZvaWQgdGhpcy5taWNyb3Bob25lLmRpc3Bvc2UoKTtcclxuICB9XHJcbn1cclxuXHJcbmV4cG9ydCBmdW5jdGlvbiBwcm92aWRlU3BlYWtpbmcoY29uZmlnOiBTcGVha2luZ0NvbmZpZyk6IFByb3ZpZGVyW10ge1xyXG4gIHJldHVybiBbeyBwcm92aWRlOiBTUEVBS0lOR19DT05GSUcsIHVzZVZhbHVlOiBjb25maWcgfSwgU3BlYWtpbmdTZXJ2aWNlXTtcclxufVxyXG5cclxuLyoqIENvbmZpZ3VyZSBvbmNlIGF0IGFwcCBib290c3RyYXA7IGV2ZXJ5IGZzZWwtc3BlYWtpbmcgY29tcG9uZW50IG93bnMgaXRzIHNlc3Npb24uICovXHJcbmV4cG9ydCBmdW5jdGlvbiBwcm92aWRlU3BlYWtpbmdHYXRld2F5KG9wdGlvbnM6IFNwZWFraW5nR2F0ZXdheU9wdGlvbnMgPSB7fSk6IFByb3ZpZGVyW10ge1xyXG4gIHJldHVybiBbeyBwcm92aWRlOiBTUEVBS0lOR19DT05GSUcsIHVzZUZhY3Rvcnk6ICgpID0+IGNyZWF0ZVNwZWFraW5nR2F0ZXdheShvcHRpb25zKSB9XTtcclxufVxyXG5cclxuLyoqIEFwcGxpY2F0aW9uIGlkZW50aXR5IG9ubHk7IGhvc3RlZCBlbmRwb2ludCBhbmQgc2hvcnQtbGl2ZWQgc2Vzc2lvbnMgYXJlIGF1dG9tYXRpYy4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIHByb3ZpZGVTcGVha2luZ0FwcChvcHRpb25zOiB7IGFwcEtleTogc3RyaW5nIH0pOiBQcm92aWRlcltdIHtcclxuICByZXR1cm4gW3sgcHJvdmlkZTogU1BFQUtJTkdfQ09ORklHLCB1c2VGYWN0b3J5OiAoKSA9PiBjcmVhdGVTcGVha2luZ1NlcnZpY2Uob3B0aW9ucykgfV07XHJcbn1cclxuXHJcblxyXG5cclxuZXhwb3J0ICogZnJvbSAnQGhvYW5naG9uZy9zcGVha2luZy1jb3JlJztcclxuIl19
@@ -1,15 +1,18 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { InjectionToken, Inject, Injectable, EventEmitter, inject, DestroyRef, Output, Input, Component } from '@angular/core';
3
3
  import { Subject } from 'rxjs';
4
- import { BatchSpeakingClient, RealtimeSpeakingClient, SpeakingMicrophone, createSpeakingGateway } from '@hoanghong/speaking-core';
4
+ import { createSpeakingService, BatchSpeakingClient, PronunciationClient, RealtimeSpeakingClient, SpeakingMicrophone, createSpeakingGateway } from '@hoanghong/speaking-core';
5
5
  export * from '@hoanghong/speaking-core';
6
6
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
7
7
 
8
- const SPEAKING_CONFIG = new InjectionToken('FSEL_SPEAKING_CONFIG');
8
+ const SPEAKING_CONFIG = new InjectionToken('FSEL_SPEAKING_CONFIG', {
9
+ providedIn: 'root', factory: () => createSpeakingService(),
10
+ });
9
11
  /** Provide at the screen/component level to give each speaking screen its own lifecycle. */
10
12
  class SpeakingService {
11
13
  zone;
12
14
  batch;
15
+ pronunciation;
13
16
  realtime;
14
17
  eventSubject = new Subject();
15
18
  events$ = this.eventSubject.asObservable();
@@ -18,6 +21,7 @@ class SpeakingService {
18
21
  constructor(config, zone) {
19
22
  this.zone = zone;
20
23
  this.batch = new BatchSpeakingClient(config.batch);
24
+ this.pronunciation = new PronunciationClient(config.pronunciation ?? config.batch);
21
25
  this.realtime = new RealtimeSpeakingClient(config.realtime);
22
26
  this.microphone = new SpeakingMicrophone(this.realtime, config.microphone);
23
27
  this.unsubscribe = this.realtime.onEvent(event => this.zone.run(() => this.eventSubject.next(event)));
@@ -48,10 +52,15 @@ function provideSpeaking(config) {
48
52
  function provideSpeakingGateway(options = {}) {
49
53
  return [{ provide: SPEAKING_CONFIG, useFactory: () => createSpeakingGateway(options) }];
50
54
  }
55
+ /** Application identity only; hosted endpoint and short-lived sessions are automatic. */
56
+ function provideSpeakingApp(options) {
57
+ return [{ provide: SPEAKING_CONFIG, useFactory: () => createSpeakingService(options) }];
58
+ }
51
59
 
52
60
  class FselSpeakingComponent {
53
61
  initialLanguage = 'vi';
54
62
  uploadEnabled = true;
63
+ serviceType;
55
64
  completed = new EventEmitter();
56
65
  failed = new EventEmitter();
57
66
  speaking = inject(SpeakingService);
@@ -121,7 +130,7 @@ class FselSpeakingComponent {
121
130
  return;
122
131
  this.text = result.result?.text ?? '';
123
132
  this.status = result.status === 'no_speech' ? 'Không nhận thấy lời nói.' : 'Đã hoàn tất.';
124
- this.completed.emit(result);
133
+ this.completed.emit({ ...result, language: this.language, text: this.text, audio: result.audio, serviceType: 'realtime' });
125
134
  }
126
135
  catch (error) {
127
136
  if (generation === this.generation)
@@ -190,7 +199,8 @@ class FselSpeakingComponent {
190
199
  throw new Error(job.error.message);
191
200
  this.text = job.result?.text ?? '';
192
201
  this.status = job.status === 'no_speech' ? 'Không nhận thấy lời nói.' : job.status === 'cancelled' ? 'Đã hủy.' : 'Đã hoàn tất.';
193
- this.completed.emit(job);
202
+ if (job.status !== 'cancelled')
203
+ this.completed.emit({ ...job, language: this.language, text: this.text, audio: file, serviceType: 'upload' });
194
204
  }
195
205
  catch (error) {
196
206
  if (generation === this.generation)
@@ -204,17 +214,19 @@ class FselSpeakingComponent {
204
214
  }
205
215
  }
206
216
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FselSpeakingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
207
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FselSpeakingComponent, isStandalone: true, selector: "fsel-speaking", inputs: { initialLanguage: "initialLanguage", uploadEnabled: "uploadEnabled" }, outputs: { completed: "completed", failed: "failed" }, providers: [SpeakingService], ngImport: i0, template: `
217
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FselSpeakingComponent, isStandalone: true, selector: "fsel-speaking", inputs: { initialLanguage: "initialLanguage", uploadEnabled: "uploadEnabled", serviceType: "serviceType" }, outputs: { completed: "completed", failed: "failed" }, providers: [SpeakingService], ngImport: i0, template: `
208
218
  <section aria-label="FSEL Speaking">
209
219
  <label>Ngôn ngữ <select [value]="language" [disabled]="busy" (change)="selectLanguage($event)">
210
220
  @for (item of languages; track item.code) { <option [value]="item.code" [selected]="item.code === language">{{ item.name }}</option> }
211
221
  </select></label>
212
222
  <div class="actions">
223
+ @if (serviceType !== 'upload') {
213
224
  <button type="button" (click)="start()" [disabled]="busy">Bắt đầu nói</button>
214
225
  <button type="button" (click)="stop()" [disabled]="!recording">Dừng và lấy kết quả</button>
226
+ }
215
227
  <button type="button" (click)="cancel()" [disabled]="!busy || (uploading && !job)">Hủy</button>
216
228
  </div>
217
- @if (uploadEnabled) { <label>Hoặc chọn file audio (tối đa 25 MB)
229
+ @if (serviceType !== 'realtime' && uploadEnabled) { <label>Chọn file audio (tối đa 25 MB)
218
230
  <input type="file" accept="audio/*,.wav,.mp3,.m4a,.ogg,.flac,.webm" [disabled]="busy" (change)="upload($event)">
219
231
  </label> }
220
232
  <p role="status">{{ status }}</p>
@@ -230,11 +242,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
230
242
  @for (item of languages; track item.code) { <option [value]="item.code" [selected]="item.code === language">{{ item.name }}</option> }
231
243
  </select></label>
232
244
  <div class="actions">
245
+ @if (serviceType !== 'upload') {
233
246
  <button type="button" (click)="start()" [disabled]="busy">Bắt đầu nói</button>
234
247
  <button type="button" (click)="stop()" [disabled]="!recording">Dừng và lấy kết quả</button>
248
+ }
235
249
  <button type="button" (click)="cancel()" [disabled]="!busy || (uploading && !job)">Hủy</button>
236
250
  </div>
237
- @if (uploadEnabled) { <label>Hoặc chọn file audio (tối đa 25 MB)
251
+ @if (serviceType !== 'realtime' && uploadEnabled) { <label>Chọn file audio (tối đa 25 MB)
238
252
  <input type="file" accept="audio/*,.wav,.mp3,.m4a,.ogg,.flac,.webm" [disabled]="busy" (change)="upload($event)">
239
253
  </label> }
240
254
  <p role="status">{{ status }}</p>
@@ -245,6 +259,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
245
259
  type: Input
246
260
  }], uploadEnabled: [{
247
261
  type: Input
262
+ }], serviceType: [{
263
+ type: Input
248
264
  }], completed: [{
249
265
  type: Output
250
266
  }], failed: [{
@@ -255,5 +271,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
255
271
  * Generated bundle index. Do not edit.
256
272
  */
257
273
 
258
- export { FselSpeakingComponent, SPEAKING_CONFIG, SpeakingService, provideSpeaking, provideSpeakingGateway };
274
+ export { FselSpeakingComponent, SPEAKING_CONFIG, SpeakingService, provideSpeaking, provideSpeakingApp, provideSpeakingGateway };
259
275
  //# sourceMappingURL=hoanghong-speaking-angular.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"hoanghong-speaking-angular.mjs","sources":["../../src/speaking.service.ts","../../src/speaking.component.ts","../../src/hoanghong-speaking-angular.ts"],"sourcesContent":["import { Inject, Injectable, InjectionToken, NgZone, OnDestroy, Provider } from '@angular/core';\r\nimport { Subject } from 'rxjs';\r\nimport {\r\n BatchOptions, BatchSpeakingClient, MicrophoneOptions, RealtimeFinal, RealtimeOptions,\r\n RealtimeSpeakingClient, SpeakingEvent, SpeakingMicrophone, StartOptions,\r\n SpeakingGatewayOptions, createSpeakingGateway,\r\n} from '@hoanghong/speaking-core';\r\n\r\nexport interface SpeakingConfig {\r\n batch: BatchOptions;\r\n realtime: RealtimeOptions;\r\n microphone: MicrophoneOptions;\r\n}\r\n\r\nexport const SPEAKING_CONFIG = new InjectionToken<SpeakingConfig>('FSEL_SPEAKING_CONFIG');\r\n\r\n/** Provide at the screen/component level to give each speaking screen its own lifecycle. */\r\n@Injectable()\r\nexport class SpeakingService implements OnDestroy {\r\n readonly batch: BatchSpeakingClient;\r\n readonly realtime: RealtimeSpeakingClient;\r\n private readonly eventSubject = new Subject<SpeakingEvent>();\r\n readonly events$ = this.eventSubject.asObservable();\r\n private readonly microphone: SpeakingMicrophone;\r\n private readonly unsubscribe: () => void;\r\n\r\n constructor(@Inject(SPEAKING_CONFIG) config: SpeakingConfig, private readonly zone: NgZone) {\r\n this.batch = new BatchSpeakingClient(config.batch);\r\n this.realtime = new RealtimeSpeakingClient(config.realtime);\r\n this.microphone = new SpeakingMicrophone(this.realtime, config.microphone);\r\n this.unsubscribe = this.realtime.onEvent(event => this.zone.run(() => this.eventSubject.next(event)));\r\n }\r\n\r\n start(options: StartOptions): Promise<void> {\r\n return this.zone.runOutsideAngular(() => this.microphone.start(options));\r\n }\r\n\r\n stop(): Promise<RealtimeFinal> { return this.microphone.stop(); }\r\n cancel(): Promise<void> { return this.microphone.cancel(); }\r\n\r\n ngOnDestroy(): void {\r\n this.unsubscribe();\r\n this.eventSubject.complete();\r\n void this.microphone.dispose();\r\n }\r\n}\r\n\r\nexport function provideSpeaking(config: SpeakingConfig): Provider[] {\r\n return [{ provide: SPEAKING_CONFIG, useValue: config }, SpeakingService];\r\n}\r\n\r\n/** Configure once at app bootstrap; every fsel-speaking component owns its session. */\r\nexport function provideSpeakingGateway(options: SpeakingGatewayOptions = {}): Provider[] {\r\n return [{ provide: SPEAKING_CONFIG, useFactory: () => createSpeakingGateway(options) }];\r\n}\r\n\r\n\r\n\r\nexport * from '@hoanghong/speaking-core';\r\n","import { Component, DestroyRef, EventEmitter, Input, OnInit, Output, inject } from '@angular/core';\r\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\r\nimport { Job, Language, RealtimeFinal, SpeakingEvent } from '@hoanghong/speaking-core';\r\nimport { SpeakingService } from './speaking.service';\r\n\r\n@Component({\r\n selector: 'fsel-speaking', standalone: true, providers: [SpeakingService],\r\n template: `\r\n <section aria-label=\"FSEL Speaking\">\r\n <label>Ngôn ngữ <select [value]=\"language\" [disabled]=\"busy\" (change)=\"selectLanguage($event)\">\r\n @for (item of languages; track item.code) { <option [value]=\"item.code\" [selected]=\"item.code === language\">{{ item.name }}</option> }\r\n </select></label>\r\n <div class=\"actions\">\r\n <button type=\"button\" (click)=\"start()\" [disabled]=\"busy\">Bắt đầu nói</button>\r\n <button type=\"button\" (click)=\"stop()\" [disabled]=\"!recording\">Dừng và lấy kết quả</button>\r\n <button type=\"button\" (click)=\"cancel()\" [disabled]=\"!busy || (uploading && !job)\">Hủy</button>\r\n </div>\r\n @if (uploadEnabled) { <label>Hoặc chọn file audio (tối đa 25 MB)\r\n <input type=\"file\" accept=\"audio/*,.wav,.mp3,.m4a,.ogg,.flac,.webm\" [disabled]=\"busy\" (change)=\"upload($event)\">\r\n </label> }\r\n <p role=\"status\">{{ status }}</p>\r\n @if (error) { <p role=\"alert\" class=\"error\">{{ error }}</p> }\r\n <label>Văn bản nhận dạng <textarea rows=\"7\" readonly [value]=\"text\"></textarea></label>\r\n </section>`,\r\n styles: [`:host{display:block;font:inherit;color:inherit}section{border:1px solid #b9c1bc;padding:20px;border-radius:4px}label{display:grid;gap:8px;margin-bottom:16px}select,input,textarea,button{font:inherit;box-sizing:border-box;max-width:100%}select,input,textarea{padding:10px;border:1px solid #929c95;border-radius:3px;background:#fff;color:#202723}textarea{width:100%;resize:vertical}.actions{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px}button{min-height:44px;padding:8px 14px;background:#365448;color:white;border:1px solid #365448;border-radius:4px;cursor:pointer}button:disabled{opacity:.5;cursor:default}button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:3px solid #a66a15;outline-offset:2px}.error{color:#a02020;overflow-wrap:anywhere}`],\r\n})\r\nexport class FselSpeakingComponent implements OnInit {\r\n @Input() initialLanguage: Language = 'vi';\r\n @Input() uploadEnabled = true;\r\n @Output() completed = new EventEmitter<RealtimeFinal | Job>();\r\n @Output() failed = new EventEmitter<Error>();\r\n readonly speaking = inject(SpeakingService);\r\n private readonly destroy = inject(DestroyRef);\r\n readonly languages = [{ code: 'en', name: 'Tiếng Anh' }, { code: 'ja', name: 'Tiếng Nhật' }, { code: 'ko', name: 'Tiếng Hàn' }, { code: 'zh', name: 'Tiếng Trung' }, { code: 'vi', name: 'Tiếng Việt' }];\r\n language: Language = 'vi'; busy = false; recording = false; uploading = false;\r\n text = ''; error = ''; status = 'Chọn ngôn ngữ, sau đó bấm nói hoặc chọn file.';\r\n job?: Job; private generation = 0; private abort?: AbortController;\r\n constructor() {\r\n this.speaking.events$.pipe(takeUntilDestroyed(this.destroy)).subscribe((event: SpeakingEvent) => {\r\n if (event.type === 'partial') this.text = event.text;\r\n if (event.type === 'state') this.recording = event.state === 'recording';\r\n if (event.type === 'clientError') { this.report(event.error); this.busy = false; this.recording = false; }\r\n });\r\n this.destroy.onDestroy(() => { ++this.generation; this.abort?.abort(); });\r\n }\r\n ngOnInit(): void { this.language = this.initialLanguage; }\r\n selectLanguage(event: Event): void { this.language = (event.target as HTMLSelectElement).value as Language; }\r\n private report(error: unknown): void {\r\n const value = error instanceof Error ? error : new Error('Speaking failed');\r\n this.error = value.message; this.failed.emit(value);\r\n }\r\n async start(): Promise<void> {\r\n if (this.busy) return;\r\n const generation = ++this.generation;\r\n this.busy = true; this.uploading = false; this.error = ''; this.text = ''; this.status = 'Đang mở microphone và chuẩn bị model…';\r\n try { await this.speaking.start({ language: this.language }); if (generation === this.generation) this.status = 'Đang nghe…'; }\r\n catch (error) { if (generation === this.generation) { this.report(error); this.busy = false; } }\r\n }\r\n async stop(): Promise<void> {\r\n if (!this.recording) return;\r\n const generation = this.generation;\r\n this.recording = false; this.status = 'Đang lấy kết quả…';\r\n try {\r\n const result = await this.speaking.stop();\r\n if (generation !== this.generation) return;\r\n this.text = result.result?.text ?? ''; this.status = result.status === 'no_speech' ? 'Không nhận thấy lời nói.' : 'Đã hoàn tất.'; this.completed.emit(result);\r\n } catch (error) { if (generation === this.generation) this.report(error); }\r\n finally { if (generation === this.generation) this.busy = false; }\r\n }\r\n async cancel(): Promise<void> {\r\n ++this.generation; this.abort?.abort();\r\n try { if (this.uploading && this.job) await this.speaking.batch.cancelJob(this.job.job_id); else await this.speaking.cancel(); this.status = 'Đã hủy.'; }\r\n catch (error) { this.report(error); }\r\n finally { this.busy = false; this.recording = false; this.uploading = false; }\r\n }\r\n async upload(event: Event): Promise<void> {\r\n const input = event.target as HTMLInputElement; const file = input.files?.[0]; input.value = '';\r\n if (!file || this.busy) return;\r\n if (file.size > 25 * 1024 * 1024) { this.report(new Error('File tối đa 25 MB.')); return; }\r\n const generation = ++this.generation; this.abort = new AbortController();\r\n this.busy = true; this.uploading = true; this.job = undefined; this.text = ''; this.error = ''; this.status = 'Đang upload…';\r\n try {\r\n let job = await this.speaking.batch.submitAudio(file, this.language, crypto.randomUUID(), 'short_answer', file.name, this.abort.signal);\r\n const deadline = Date.now() + 10 * 60 * 1000;\r\n while (generation === this.generation) {\r\n this.job = job;\r\n if (['succeeded', 'no_speech', 'failed', 'cancelled'].includes(job.status)) break;\r\n if (Date.now() > deadline) throw new Error('Hết thời gian chờ; job có thể vẫn đang chạy trên server.');\r\n this.status = 'Đang nhận dạng file…';\r\n await new Promise(resolve => setTimeout(resolve, 1500));\r\n if (generation !== this.generation) return;\r\n job = await this.speaking.batch.getResult(job.job_id, this.abort.signal);\r\n }\r\n if (generation !== this.generation) return;\r\n if (job.error) throw new Error(job.error.message);\r\n this.text = job.result?.text ?? ''; this.status = job.status === 'no_speech' ? 'Không nhận thấy lời nói.' : job.status === 'cancelled' ? 'Đã hủy.' : 'Đã hoàn tất.'; this.completed.emit(job);\r\n } catch (error) { if (generation === this.generation) this.report(error); }\r\n finally { if (generation === this.generation) { this.busy = false; this.uploading = false; } }\r\n }\r\n}\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hoanghong-speaking-angular.mjs","sources":["../../src/speaking.service.ts","../../src/speaking.component.ts","../../src/hoanghong-speaking-angular.ts"],"sourcesContent":["import { Inject, Injectable, InjectionToken, NgZone, OnDestroy, Provider } from '@angular/core';\r\nimport { Subject } from 'rxjs';\r\nimport {\r\n BatchOptions, BatchSpeakingClient, MicrophoneOptions, RecordedSpeakingFinal, RealtimeOptions,\r\n RealtimeSpeakingClient, SpeakingEvent, SpeakingMicrophone, StartOptions,\r\n SpeakingGatewayOptions, createSpeakingGateway,\r\n createSpeakingService,\r\n PronunciationClient, PronunciationOptions,\r\n} from '@hoanghong/speaking-core';\r\n\r\nexport interface SpeakingConfig {\r\n batch: BatchOptions;\r\n realtime: RealtimeOptions;\r\n microphone: MicrophoneOptions;\r\n pronunciation?: PronunciationOptions;\r\n}\r\n\r\nexport const SPEAKING_CONFIG = new InjectionToken<SpeakingConfig>('FSEL_SPEAKING_CONFIG', {\r\n providedIn: 'root', factory: () => createSpeakingService(),\r\n});\r\n\r\n/** Provide at the screen/component level to give each speaking screen its own lifecycle. */\r\n@Injectable()\r\nexport class SpeakingService implements OnDestroy {\r\n readonly batch: BatchSpeakingClient;\r\n readonly pronunciation: PronunciationClient;\r\n readonly realtime: RealtimeSpeakingClient;\r\n private readonly eventSubject = new Subject<SpeakingEvent>();\r\n readonly events$ = this.eventSubject.asObservable();\r\n private readonly microphone: SpeakingMicrophone;\r\n private readonly unsubscribe: () => void;\r\n\r\n constructor(@Inject(SPEAKING_CONFIG) config: SpeakingConfig, private readonly zone: NgZone) {\r\n this.batch = new BatchSpeakingClient(config.batch);\r\n this.pronunciation = new PronunciationClient(config.pronunciation ?? config.batch);\r\n this.realtime = new RealtimeSpeakingClient(config.realtime);\r\n this.microphone = new SpeakingMicrophone(this.realtime, config.microphone);\r\n this.unsubscribe = this.realtime.onEvent(event => this.zone.run(() => this.eventSubject.next(event)));\r\n }\r\n\r\n start(options: StartOptions): Promise<void> {\r\n return this.zone.runOutsideAngular(() => this.microphone.start(options));\r\n }\r\n\r\n stop(): Promise<RecordedSpeakingFinal> { return this.microphone.stop(); }\r\n cancel(): Promise<void> { return this.microphone.cancel(); }\r\n\r\n ngOnDestroy(): void {\r\n this.unsubscribe();\r\n this.eventSubject.complete();\r\n void this.microphone.dispose();\r\n }\r\n}\r\n\r\nexport function provideSpeaking(config: SpeakingConfig): Provider[] {\r\n return [{ provide: SPEAKING_CONFIG, useValue: config }, SpeakingService];\r\n}\r\n\r\n/** Configure once at app bootstrap; every fsel-speaking component owns its session. */\r\nexport function provideSpeakingGateway(options: SpeakingGatewayOptions = {}): Provider[] {\r\n return [{ provide: SPEAKING_CONFIG, useFactory: () => createSpeakingGateway(options) }];\r\n}\r\n\r\n/** Application identity only; hosted endpoint and short-lived sessions are automatic. */\r\nexport function provideSpeakingApp(options: { appKey: string }): Provider[] {\r\n return [{ provide: SPEAKING_CONFIG, useFactory: () => createSpeakingService(options) }];\r\n}\r\n\r\n\r\n\r\nexport * from '@hoanghong/speaking-core';\r\n","import { Component, DestroyRef, EventEmitter, Input, OnInit, Output, inject } from '@angular/core';\r\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\r\nimport { Job, Language, SpeakingResult, SpeakingServiceType, SpeakingEvent } from '@hoanghong/speaking-core';\r\nimport { SpeakingService } from './speaking.service';\r\n\r\n@Component({\r\n selector: 'fsel-speaking', standalone: true, providers: [SpeakingService],\r\n template: `\r\n <section aria-label=\"FSEL Speaking\">\r\n <label>Ngôn ngữ <select [value]=\"language\" [disabled]=\"busy\" (change)=\"selectLanguage($event)\">\r\n @for (item of languages; track item.code) { <option [value]=\"item.code\" [selected]=\"item.code === language\">{{ item.name }}</option> }\r\n </select></label>\r\n <div class=\"actions\">\r\n @if (serviceType !== 'upload') {\r\n <button type=\"button\" (click)=\"start()\" [disabled]=\"busy\">Bắt đầu nói</button>\r\n <button type=\"button\" (click)=\"stop()\" [disabled]=\"!recording\">Dừng và lấy kết quả</button>\r\n }\r\n <button type=\"button\" (click)=\"cancel()\" [disabled]=\"!busy || (uploading && !job)\">Hủy</button>\r\n </div>\r\n @if (serviceType !== 'realtime' && uploadEnabled) { <label>Chọn file audio (tối đa 25 MB)\r\n <input type=\"file\" accept=\"audio/*,.wav,.mp3,.m4a,.ogg,.flac,.webm\" [disabled]=\"busy\" (change)=\"upload($event)\">\r\n </label> }\r\n <p role=\"status\">{{ status }}</p>\r\n @if (error) { <p role=\"alert\" class=\"error\">{{ error }}</p> }\r\n <label>Văn bản nhận dạng <textarea rows=\"7\" readonly [value]=\"text\"></textarea></label>\r\n </section>`,\r\n styles: [`:host{display:block;font:inherit;color:inherit}section{border:1px solid #b9c1bc;padding:20px;border-radius:4px}label{display:grid;gap:8px;margin-bottom:16px}select,input,textarea,button{font:inherit;box-sizing:border-box;max-width:100%}select,input,textarea{padding:10px;border:1px solid #929c95;border-radius:3px;background:#fff;color:#202723}textarea{width:100%;resize:vertical}.actions{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px}button{min-height:44px;padding:8px 14px;background:#365448;color:white;border:1px solid #365448;border-radius:4px;cursor:pointer}button:disabled{opacity:.5;cursor:default}button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:3px solid #a66a15;outline-offset:2px}.error{color:#a02020;overflow-wrap:anywhere}`],\r\n})\r\nexport class FselSpeakingComponent implements OnInit {\r\n @Input() initialLanguage: Language = 'vi';\r\n @Input() uploadEnabled = true;\r\n @Input() serviceType?: SpeakingServiceType;\r\n @Output() completed = new EventEmitter<SpeakingResult>();\r\n @Output() failed = new EventEmitter<Error>();\r\n readonly speaking = inject(SpeakingService);\r\n private readonly destroy = inject(DestroyRef);\r\n readonly languages = [{ code: 'en', name: 'Tiếng Anh' }, { code: 'ja', name: 'Tiếng Nhật' }, { code: 'ko', name: 'Tiếng Hàn' }, { code: 'zh', name: 'Tiếng Trung' }, { code: 'vi', name: 'Tiếng Việt' }];\r\n language: Language = 'vi'; busy = false; recording = false; uploading = false;\r\n text = ''; error = ''; status = 'Chọn ngôn ngữ, sau đó bấm nói hoặc chọn file.';\r\n job?: Job; private generation = 0; private abort?: AbortController;\r\n constructor() {\r\n this.speaking.events$.pipe(takeUntilDestroyed(this.destroy)).subscribe((event: SpeakingEvent) => {\r\n if (event.type === 'partial') this.text = event.text;\r\n if (event.type === 'state') this.recording = event.state === 'recording';\r\n if (event.type === 'clientError') { this.report(event.error); this.busy = false; this.recording = false; }\r\n });\r\n this.destroy.onDestroy(() => { ++this.generation; this.abort?.abort(); });\r\n }\r\n ngOnInit(): void { this.language = this.initialLanguage; }\r\n selectLanguage(event: Event): void { this.language = (event.target as HTMLSelectElement).value as Language; }\r\n private report(error: unknown): void {\r\n const value = error instanceof Error ? error : new Error('Speaking failed');\r\n this.error = value.message; this.failed.emit(value);\r\n }\r\n async start(): Promise<void> {\r\n if (this.busy) return;\r\n const generation = ++this.generation;\r\n this.busy = true; this.uploading = false; this.error = ''; this.text = ''; this.status = 'Đang mở microphone và chuẩn bị model…';\r\n try { await this.speaking.start({ language: this.language }); if (generation === this.generation) this.status = 'Đang nghe…'; }\r\n catch (error) { if (generation === this.generation) { this.report(error); this.busy = false; } }\r\n }\r\n async stop(): Promise<void> {\r\n if (!this.recording) return;\r\n const generation = this.generation;\r\n this.recording = false; this.status = 'Đang lấy kết quả…';\r\n try {\r\n const result = await this.speaking.stop();\r\n if (generation !== this.generation) return;\r\n this.text = result.result?.text ?? ''; this.status = result.status === 'no_speech' ? 'Không nhận thấy lời nói.' : 'Đã hoàn tất.';\r\n this.completed.emit({ ...result, language: this.language, text: this.text, audio: result.audio, serviceType: 'realtime' });\r\n } catch (error) { if (generation === this.generation) this.report(error); }\r\n finally { if (generation === this.generation) this.busy = false; }\r\n }\r\n async cancel(): Promise<void> {\r\n ++this.generation; this.abort?.abort();\r\n try { if (this.uploading && this.job) await this.speaking.batch.cancelJob(this.job.job_id); else await this.speaking.cancel(); this.status = 'Đã hủy.'; }\r\n catch (error) { this.report(error); }\r\n finally { this.busy = false; this.recording = false; this.uploading = false; }\r\n }\r\n async upload(event: Event): Promise<void> {\r\n const input = event.target as HTMLInputElement; const file = input.files?.[0]; input.value = '';\r\n if (!file || this.busy) return;\r\n if (file.size > 25 * 1024 * 1024) { this.report(new Error('File tối đa 25 MB.')); return; }\r\n const generation = ++this.generation; this.abort = new AbortController();\r\n this.busy = true; this.uploading = true; this.job = undefined; this.text = ''; this.error = ''; this.status = 'Đang upload…';\r\n try {\r\n let job = await this.speaking.batch.submitAudio(file, this.language, crypto.randomUUID(), 'short_answer', file.name, this.abort.signal);\r\n const deadline = Date.now() + 10 * 60 * 1000;\r\n while (generation === this.generation) {\r\n this.job = job;\r\n if (['succeeded', 'no_speech', 'failed', 'cancelled'].includes(job.status)) break;\r\n if (Date.now() > deadline) throw new Error('Hết thời gian chờ; job có thể vẫn đang chạy trên server.');\r\n this.status = 'Đang nhận dạng file…';\r\n await new Promise(resolve => setTimeout(resolve, 1500));\r\n if (generation !== this.generation) return;\r\n job = await this.speaking.batch.getResult(job.job_id, this.abort.signal);\r\n }\r\n if (generation !== this.generation) return;\r\n if (job.error) throw new Error(job.error.message);\r\n this.text = job.result?.text ?? ''; this.status = job.status === 'no_speech' ? 'Không nhận thấy lời nói.' : job.status === 'cancelled' ? 'Đã hủy.' : 'Đã hoàn tất.';\r\n if (job.status !== 'cancelled') this.completed.emit({ ...job, language: this.language, text: this.text, audio: file, serviceType: 'upload' });\r\n } catch (error) { if (generation === this.generation) this.report(error); }\r\n finally { if (generation === this.generation) { this.busy = false; this.uploading = false; } }\r\n }\r\n}\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hoanghong/speaking-angular",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Ready-to-use online speaking: five languages, microphone realtime and audio upload",
5
5
  "type": "module",
6
6
  "peerDependencies": {
@@ -11,7 +11,7 @@
11
11
  "sideEffects": false,
12
12
  "license": "MIT",
13
13
  "dependencies": {
14
- "@hoanghong/speaking-core": "^0.2.0",
14
+ "@hoanghong/speaking-core": "^0.4.0",
15
15
  "tslib": "^2.3.0"
16
16
  },
17
17
  "publishConfig": {
@@ -1,11 +1,12 @@
1
1
  import { EventEmitter, OnInit } from '@angular/core';
2
- import { Job, Language, RealtimeFinal } from '@hoanghong/speaking-core';
2
+ import { Job, Language, SpeakingResult, SpeakingServiceType } from '@hoanghong/speaking-core';
3
3
  import { SpeakingService } from './speaking.service';
4
4
  import * as i0 from "@angular/core";
5
5
  export declare class FselSpeakingComponent implements OnInit {
6
6
  initialLanguage: Language;
7
7
  uploadEnabled: boolean;
8
- completed: EventEmitter<RealtimeFinal | Job>;
8
+ serviceType?: SpeakingServiceType;
9
+ completed: EventEmitter<SpeakingResult>;
9
10
  failed: EventEmitter<Error>;
10
11
  readonly speaking: SpeakingService;
11
12
  private readonly destroy;
@@ -32,5 +33,5 @@ export declare class FselSpeakingComponent implements OnInit {
32
33
  cancel(): Promise<void>;
33
34
  upload(event: Event): Promise<void>;
34
35
  static ɵfac: i0.ɵɵFactoryDeclaration<FselSpeakingComponent, never>;
35
- static ɵcmp: i0.ɵɵComponentDeclaration<FselSpeakingComponent, "fsel-speaking", never, { "initialLanguage": { "alias": "initialLanguage"; "required": false; }; "uploadEnabled": { "alias": "uploadEnabled"; "required": false; }; }, { "completed": "completed"; "failed": "failed"; }, never, never, true, never>;
36
+ static ɵcmp: i0.ɵɵComponentDeclaration<FselSpeakingComponent, "fsel-speaking", never, { "initialLanguage": { "alias": "initialLanguage"; "required": false; }; "uploadEnabled": { "alias": "uploadEnabled"; "required": false; }; "serviceType": { "alias": "serviceType"; "required": false; }; }, { "completed": "completed"; "failed": "failed"; }, never, never, true, never>;
36
37
  }
@@ -1,16 +1,18 @@
1
1
  import { InjectionToken, NgZone, OnDestroy, Provider } from '@angular/core';
2
- import { BatchOptions, BatchSpeakingClient, MicrophoneOptions, RealtimeFinal, RealtimeOptions, RealtimeSpeakingClient, SpeakingEvent, StartOptions, SpeakingGatewayOptions } from '@hoanghong/speaking-core';
2
+ import { BatchOptions, BatchSpeakingClient, MicrophoneOptions, RecordedSpeakingFinal, RealtimeOptions, RealtimeSpeakingClient, SpeakingEvent, StartOptions, SpeakingGatewayOptions, PronunciationClient, PronunciationOptions } from '@hoanghong/speaking-core';
3
3
  import * as i0 from "@angular/core";
4
4
  export interface SpeakingConfig {
5
5
  batch: BatchOptions;
6
6
  realtime: RealtimeOptions;
7
7
  microphone: MicrophoneOptions;
8
+ pronunciation?: PronunciationOptions;
8
9
  }
9
10
  export declare const SPEAKING_CONFIG: InjectionToken<SpeakingConfig>;
10
11
  /** Provide at the screen/component level to give each speaking screen its own lifecycle. */
11
12
  export declare class SpeakingService implements OnDestroy {
12
13
  private readonly zone;
13
14
  readonly batch: BatchSpeakingClient;
15
+ readonly pronunciation: PronunciationClient;
14
16
  readonly realtime: RealtimeSpeakingClient;
15
17
  private readonly eventSubject;
16
18
  readonly events$: import("rxjs").Observable<SpeakingEvent>;
@@ -18,7 +20,7 @@ export declare class SpeakingService implements OnDestroy {
18
20
  private readonly unsubscribe;
19
21
  constructor(config: SpeakingConfig, zone: NgZone);
20
22
  start(options: StartOptions): Promise<void>;
21
- stop(): Promise<RealtimeFinal>;
23
+ stop(): Promise<RecordedSpeakingFinal>;
22
24
  cancel(): Promise<void>;
23
25
  ngOnDestroy(): void;
24
26
  static ɵfac: i0.ɵɵFactoryDeclaration<SpeakingService, never>;
@@ -27,4 +29,8 @@ export declare class SpeakingService implements OnDestroy {
27
29
  export declare function provideSpeaking(config: SpeakingConfig): Provider[];
28
30
  /** Configure once at app bootstrap; every fsel-speaking component owns its session. */
29
31
  export declare function provideSpeakingGateway(options?: SpeakingGatewayOptions): Provider[];
32
+ /** Application identity only; hosted endpoint and short-lived sessions are automatic. */
33
+ export declare function provideSpeakingApp(options: {
34
+ appKey: string;
35
+ }): Provider[];
30
36
  export * from '@hoanghong/speaking-core';