@hoanghong/speaking-angular 0.4.3 → 0.6.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 +11 -0
- package/README.md +166 -106
- package/package.json +9 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## 0.6.0
|
|
2
|
+
|
|
3
|
+
- Versioned overall local pronunciation score (harmonic mean of accuracy and completeness).
|
|
4
|
+
- Explicit score scale, availability, components, exclusions and calibration status.
|
|
5
|
+
- New clients accept older servers without inventing an overall score.
|
|
6
|
+
|
|
7
|
+
## 0.5.0
|
|
8
|
+
|
|
9
|
+
- Optional multi-criteria local pronunciation diagnostics with methods, limitations and unavailable scores.
|
|
10
|
+
- Compatible with older service responses.
|
|
11
|
+
|
|
1
12
|
## 0.4.3
|
|
2
13
|
|
|
3
14
|
- Remove service addresses from public integration examples; no runtime changes.
|
package/README.md
CHANGED
|
@@ -1,168 +1,228 @@
|
|
|
1
1
|
# @hoanghong/speaking-angular
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Angular 18 / RxJS 7.8 SDK for realtime transcription, audio upload and US English pronunciation diagnostics. Service connections, sessions and worklet loading are automatic; no service URL or service token is required. Models run on the service. MIT license.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Install from npm
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install
|
|
8
|
+
npm install @hoanghong/speaking-core@0.6.0 @hoanghong/speaking-angular@0.6.0
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## 1. Install from the version 0.6.0 release ZIP
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
Chưa publish 0.4.3 lên registry. Các tính năng nhận dạng cũ vẫn dùng được.
|
|
13
|
+
Copy both npm tarballs into your application's vendor directory:
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
```bash
|
|
16
|
+
npm install ./vendor/hoanghong-speaking-core-0.6.0.tgz ./vendor/hoanghong-speaking-angular-0.6.0.tgz
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
For a new application, first run npx @angular/cli@18 new speaking-demo --standalone --routing=false --style=css, then cd speaking-demo. These instructions use local archives and do not assume registry publication.
|
|
20
|
+
|
|
21
|
+
## 2. Configure an application key, if supplied
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
`@hoanghong/speaking-angular`, rồi gọi trong class component:
|
|
23
|
+
Use the following src/app/app.config.ts when FSEL has provided a key. Without a key, use an empty providers array for the basic anonymous quota.
|
|
20
24
|
|
|
21
25
|
```ts
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
filename: file.name,
|
|
29
|
-
});
|
|
30
|
-
console.log(result.phoneme_similarity, result.comparisons, result.warnings);
|
|
31
|
-
}
|
|
26
|
+
import { ApplicationConfig } from '@angular/core';
|
|
27
|
+
import { provideSpeakingApp } from '@hoanghong/speaking-angular';
|
|
28
|
+
|
|
29
|
+
export const appConfig: ApplicationConfig = {
|
|
30
|
+
providers: [provideSpeakingApp({ appKey: 'YOUR_APP_KEY' })],
|
|
31
|
+
};
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
Use src/main.ts:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { bootstrapApplication } from '@angular/platform-browser';
|
|
38
|
+
import { AppComponent } from './app/app.component';
|
|
39
|
+
import { appConfig } from './app/app.config';
|
|
40
|
+
|
|
41
|
+
bootstrapApplication(AppComponent, appConfig).catch(console.error);
|
|
42
|
+
```
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
không nhập service token. Dịch vụ đánh giá phát âm 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
|
-
Không đưa service credential vào ứng dụng.
|
|
44
|
+
Application keys are visible application identifiers, not administrative credentials. Key quotas/status are managed server-side. An invalid/disabled key returns INVALID_APP_KEY rather than falling back to anonymous access. Never embed service credentials.
|
|
46
45
|
|
|
47
|
-
##
|
|
46
|
+
## 3. Complete pronunciation example
|
|
48
47
|
|
|
49
|
-
|
|
48
|
+
Replace src/app/app.component.ts below. The learner reads the displayed passage, selects a recording and clicks Assess pronunciation. Alternatively, record with the existing speaking component; its completed callback supplies the same audio File. Assessment is a separate explicit action.
|
|
50
49
|
|
|
51
50
|
```ts
|
|
52
51
|
import { Component, OnDestroy } from '@angular/core';
|
|
53
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
FselSpeakingComponent, PronunciationAssessment, SpeakingError,
|
|
54
|
+
SpeakingResult, SpeakingService,
|
|
55
|
+
} from '@hoanghong/speaking-angular';
|
|
54
56
|
|
|
55
57
|
@Component({
|
|
56
|
-
selector: 'app-root',
|
|
58
|
+
selector: 'app-root',
|
|
59
|
+
standalone: true,
|
|
57
60
|
imports: [FselSpeakingComponent],
|
|
61
|
+
providers: [SpeakingService],
|
|
58
62
|
template: `
|
|
59
|
-
<main
|
|
60
|
-
<h1>
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
<
|
|
63
|
+
<main>
|
|
64
|
+
<h1>US English pronunciation practice</h1>
|
|
65
|
+
<p>Read this passage: {{ referenceText }}</p>
|
|
66
|
+
<label for="recording">Choose an audio recording (maximum 120 seconds)</label>
|
|
67
|
+
<input id="recording" type="file" accept="audio/*"
|
|
68
|
+
[disabled]="busy" (change)="selectFile($event)" />
|
|
69
|
+
<p>Or record the passage, then stop before 120 seconds:</p>
|
|
70
|
+
@if (!busy) {
|
|
71
|
+
<fsel-speaking serviceType="realtime" initialLanguage="en"
|
|
72
|
+
(completed)="onCompleted($event)" (failed)="onFailed($event)" />
|
|
73
|
+
}
|
|
64
74
|
@if (audioUrl) {
|
|
65
75
|
<audio controls [src]="audioUrl"></audio>
|
|
66
|
-
|
|
76
|
+
}
|
|
77
|
+
<button type="button" [disabled]="!audio || busy" (click)="assess()">
|
|
78
|
+
Assess pronunciation
|
|
79
|
+
</button>
|
|
80
|
+
<button type="button" [disabled]="!busy" (click)="cancel()">Cancel</button>
|
|
81
|
+
@if (busy) { <p role="status">Assessing pronunciation...</p> }
|
|
82
|
+
@if (assessment) {
|
|
83
|
+
<p>Phoneme similarity: {{ assessment.phoneme_similarity }} (uncalibrated)</p>
|
|
84
|
+
<p>Phoneme accuracy: {{ accuracy }}</p>
|
|
85
|
+
<p>Overall local pronunciation score: {{ overall }}</p>
|
|
86
|
+
@for (warning of assessment.warnings; track $index) { <p>{{ warning }}</p> }
|
|
67
87
|
}
|
|
68
88
|
<p role="alert">{{ error }}</p>
|
|
69
89
|
</main>
|
|
70
90
|
`,
|
|
71
91
|
})
|
|
72
92
|
export class AppComponent implements OnDestroy {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
93
|
+
readonly referenceText = 'The cat is sitting on the mat.';
|
|
94
|
+
audio: File | null = null;
|
|
95
|
+
audioUrl = '';
|
|
96
|
+
assessment: PronunciationAssessment | null = null;
|
|
97
|
+
error = '';
|
|
98
|
+
busy = false;
|
|
99
|
+
private controller?: AbortController;
|
|
100
|
+
private destroyed = false;
|
|
101
|
+
|
|
102
|
+
constructor(private readonly speaking: SpeakingService) {}
|
|
103
|
+
|
|
104
|
+
get overall(): string {
|
|
105
|
+
const value = this.assessment?.overall;
|
|
106
|
+
return value?.status === 'estimated' && value.score != null
|
|
107
|
+
? value.score.toFixed(1) + ' / ' + value.scale + ' (local estimate)'
|
|
108
|
+
: value?.reason ?? 'Unavailable: upgrade the server if needed';
|
|
80
109
|
}
|
|
81
|
-
onFailed(error: Error): void { this.error = error.message; }
|
|
82
|
-
ngOnDestroy(): void { if (this.audioUrl) URL.revokeObjectURL(this.audioUrl); }
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
110
|
|
|
86
|
-
|
|
111
|
+
get accuracy(): string {
|
|
112
|
+
const value = this.assessment?.breakdown?.accuracy;
|
|
113
|
+
return value?.status === 'estimated' && value.score != null
|
|
114
|
+
? value.score.toFixed(1) + ' / 100 (estimate)'
|
|
115
|
+
: 'Unavailable';
|
|
116
|
+
}
|
|
87
117
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
bootstrapApplication(AppComponent).catch(console.error);
|
|
92
|
-
```
|
|
118
|
+
selectFile(event: Event): void {
|
|
119
|
+
this.setAudio((event.target as HTMLInputElement).files?.[0] ?? null);
|
|
120
|
+
}
|
|
93
121
|
|
|
94
|
-
|
|
122
|
+
onCompleted(result: SpeakingResult): void { this.setAudio(result.audio); }
|
|
123
|
+
onFailed(error: Error): void { this.error = error.message; }
|
|
95
124
|
|
|
96
|
-
|
|
125
|
+
private setAudio(file: File | null): void {
|
|
126
|
+
if (this.audioUrl) URL.revokeObjectURL(this.audioUrl);
|
|
127
|
+
this.audio = file;
|
|
128
|
+
this.audioUrl = file ? URL.createObjectURL(file) : '';
|
|
129
|
+
this.assessment = null;
|
|
130
|
+
this.error = '';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async assess(): Promise<void> {
|
|
134
|
+
if (!this.audio || this.busy) return;
|
|
135
|
+
this.busy = true;
|
|
136
|
+
this.error = '';
|
|
137
|
+
this.assessment = null;
|
|
138
|
+
this.controller = new AbortController();
|
|
139
|
+
try {
|
|
140
|
+
const result = await this.speaking.pronunciation.assessAudio(this.audio, {
|
|
141
|
+
referenceText: this.referenceText,
|
|
142
|
+
filename: this.audio.name,
|
|
143
|
+
signal: this.controller.signal,
|
|
144
|
+
});
|
|
145
|
+
if (!this.destroyed) this.assessment = result;
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (!this.destroyed) {
|
|
148
|
+
this.error = error instanceof SpeakingError
|
|
149
|
+
? error.code + ': ' + error.message
|
|
150
|
+
: 'Pronunciation assessment failed.';
|
|
151
|
+
}
|
|
152
|
+
} finally {
|
|
153
|
+
if (!this.destroyed) this.busy = false;
|
|
154
|
+
this.controller = undefined;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
97
157
|
|
|
98
|
-
|
|
158
|
+
cancel(): void { this.controller?.abort(); }
|
|
99
159
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
160
|
+
ngOnDestroy(): void {
|
|
161
|
+
this.destroyed = true;
|
|
162
|
+
this.cancel();
|
|
163
|
+
if (this.audioUrl) URL.revokeObjectURL(this.audioUrl);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
106
166
|
```
|
|
107
167
|
|
|
108
|
-
|
|
168
|
+
Run npx ng serve and open the local address printed by Angular. Microphone capture requires HTTPS or localhost and user permission. The SDK does not automatically record on page load. The component currently uses its built-in interface labels; the example's own labels and comments are English.
|
|
109
169
|
|
|
110
|
-
|
|
111
|
-
import { bootstrapApplication } from '@angular/platform-browser';
|
|
112
|
-
import { AppComponent } from './app/app.component';
|
|
113
|
-
import { appConfig } from './app/app.config';
|
|
114
|
-
bootstrapApplication(AppComponent, appConfig).catch(console.error);
|
|
115
|
-
```
|
|
170
|
+
SpeakingService must be provided at the screen/component level as shown. provideSpeakingApp configures application identity; it does not replace that provider. The fsel-speaking component owns its recording/transcription lifecycle. Assessment consumes an additional session after recording/transcription. For upload-only pronunciation, the file input calls the pronunciation client directly without first creating a transcription job.
|
|
116
171
|
|
|
117
|
-
|
|
172
|
+
## 4. Reference text and optional phonemes
|
|
118
173
|
|
|
119
|
-
|
|
174
|
+
Pronunciation supports en-US only. referenceText is the exact word, sentence or paragraph being read (1–4096 characters). Omit expectedPhonemes for normal passages; server-side text-to-phoneme support must be installed.
|
|
120
175
|
|
|
121
|
-
|
|
122
|
-
npx ng serve
|
|
123
|
-
```
|
|
176
|
+
For a specific word, add expectedPhonemes: ['k', 'æ', 't'] and set referenceText: 'cat' in the assessment options. These are complete model vocabulary tokens, not individual IPA characters. Do not include enclosing slashes or reuse this sequence for another passage.
|
|
124
177
|
|
|
125
|
-
|
|
178
|
+
Audio must be nonempty, at most 120 seconds and 25 MiB. WAV PCM16 mono at 16 kHz is suitable; the service validates duration/formats. Realtime transcription itself permits 300 seconds, so stop within 120 seconds for pronunciation. The assessment request timeout defaults to 11 minutes. AbortSignal cancels the client request; it does not refund quota or guarantee server inference cancellation. There is no automatic retry.
|
|
126
179
|
|
|
127
|
-
##
|
|
180
|
+
## 5. Read the assessment
|
|
128
181
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
182
|
+
| Field | Use |
|
|
183
|
+
| --- | --- |
|
|
184
|
+
| phoneme_similarity / phoneme_error_rate | Edit-based phoneme comparison, not an official grade |
|
|
185
|
+
| comparisons | match/substitution/deletion/insertion; expected/observed tokens and approximate seconds |
|
|
186
|
+
| observed_phonemes | Recognized tokens and approximate times |
|
|
187
|
+
| breakdown | Optional criteria, errors, signal and limitations |
|
|
188
|
+
| acoustic_frames / psd_frequencies_hz | Formants, pitch, intensity and spectral data |
|
|
189
|
+
| warnings | Interpretation and model limitations |
|
|
133
190
|
|
|
134
|
-
|
|
191
|
+
breakdown may be absent or null with older services. Each criterion has score (0–100 or null), status (estimated/unavailable), method and reason. Display unavailable as "Unavailable", never zero. Display methods/reasons/limitations alongside estimates. Missing sounds can have null timestamps; counts refer to phonemes, not words.
|
|
135
192
|
|
|
136
|
-
|
|
193
|
+
Accuracy = 100*M/(M+S+I); completeness = 100*(N-D)/N, with matches/substitutions/omissions/insertions M/S/D/I and reference count N. Completeness includes substitutions as covered. Similarity = 100*max(0, 1-(S+D+I)/N) and differs from accuracy. Fluency is an acoustic energy-continuity proxy affected by noise/intentional pauses. Prosody has pitch measurements without a score. Overall pronunciation is the harmonic mean of Accuracy and Completeness. Vocabulary, grammar and topic scores are unavailable. calibrated=false; approximate timing is not forced alignment. Do not use these diagnostics as educational grades, Azure scores or pass/fail thresholds.
|
|
137
194
|
|
|
138
|
-
|
|
195
|
+
## 6. Existing transcription component
|
|
139
196
|
|
|
140
|
-
|
|
141
|
-
| --- | --- |
|
|
142
|
-
| `text` | String nhận dạng cuối; rỗng khi không có lời nói |
|
|
143
|
-
| `audio` | File, dùng createObjectURL/FormData/arrayBuffer; realtime có tên speaking.wav |
|
|
144
|
-
| `language` | en/ja/ko/zh/vi |
|
|
145
|
-
| `serviceType` | realtime/upload |
|
|
146
|
-
| `status` | succeeded/no_speech; các metadata gốc vẫn được giữ |
|
|
147
|
-
| `result` | Transcript chi tiết từ server nếu có |
|
|
197
|
+
Use serviceType="realtime" for microphone transcription; serviceType="upload" for audio-file transcription. Omitting serviceType displays both; [uploadEnabled]="false" hides upload. initialLanguage supports en/ja/ko/zh/vi (default vi).
|
|
148
198
|
|
|
149
|
-
|
|
199
|
+
completed emits SpeakingResult with text, audio (File), language, serviceType, status and optional detailed result. Realtime audio is WAV PCM16 mono at 16 kHz; upload preserves the selected file. An empty transcript may indicate no_speech. failed emits an Error; cancellation/failure does not emit successful audio. Use object URLs for playback/download and revoke them when no longer needed. Use FormData to store audio; files are not automatically persisted to permanent storage.
|
|
150
200
|
|
|
151
|
-
##
|
|
201
|
+
## 7. Quotas and troubleshooting
|
|
152
202
|
|
|
153
|
-
|
|
203
|
+
Basic quota defaults to 5 sessions per IP/day and 100 per instance/day (UTC), subject to operator changes. New sessions consume quota even on processing failure. Application-key quota is managed by FSEL. Realtime buffers approximately 9.6 MB PCM; upload UI accepts up to 25 MiB and polls up to 10 minutes. Leaving the component stops capture/polling, although accepted server jobs may continue. There is no automatic reconnect/replay.
|
|
154
204
|
|
|
155
|
-
|
|
205
|
+
Handle SpeakingError.code/status/message: INVALID_REFERENCE/INVALID_AUDIO for input, INVALID_APP_KEY/401 for key issues, QUOTA_EXCEEDED/429 for quota, 415 for unsupported formats, 422 for validation/no recognized phonemes, 404 for missing functionality and 503 for service/model readiness. Check browser microphone permissions and application network/CSP policies for capture/connection failures.
|
|
156
206
|
|
|
157
|
-
|
|
207
|
+
The bundle installs clients; it does not deploy models or prove hosted readiness. Physical microphone/playback and real learner scoring require separate device/service verification.
|
|
158
208
|
|
|
159
209
|
|
|
160
|
-
##
|
|
210
|
+
## Overall pronunciation output (0.6.0)
|
|
161
211
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
212
|
+
Use `result.overall?.score` for the overall chart.
|
|
213
|
+
It is a local estimate, the harmonic mean of unrounded Accuracy and Completeness:
|
|
214
|
+
`2*A*C/(A+C)`, or 0 if A+C=0. Fluency and Prosody are excluded. This is not an
|
|
215
|
+
Azure or calibrated educational grade. `phoneme_similarity` is a separate legacy
|
|
216
|
+
edit-distance diagnostic and can be zero while the overall score is positive.
|
|
165
217
|
|
|
166
|
-
|
|
167
|
-
|
|
218
|
+
The overall object includes `version=local-accuracy-completeness-v1`, `scale=100`,
|
|
219
|
+
`calibrated=false`, `status`, `method`, `reason`, `components` and
|
|
220
|
+
`excluded_components` (Flutter: `excludedComponents`).
|
|
221
|
+
Render null/unavailable scores as unavailable; preserve valid zero values.
|
|
222
|
+
An older server can omit `overall`; the SDK never computes a replacement score.
|
|
223
|
+
`breakdown.pronunciation` mirrors the overall criterion for compatibility.
|
|
224
|
+
The server must be upgraded as well as the SDK.
|
|
225
|
+
|
|
226
|
+
```json
|
|
227
|
+
{"overall":{"score":50,"status":"estimated","method":"accuracy_completeness_hmean: 2*A*C/(A+C); zero if A+C=0","reason":null,"version":"local-accuracy-completeness-v1","scale":100,"calibrated":false,"components":["accuracy","completeness"],"excluded_components":["fluency","prosody"]}}
|
|
168
228
|
```
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hoanghong/speaking-angular",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Angular 18 SDK for speech transcription, audio upload and versioned local pronunciation assessment with typed overall and component scores.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"peerDependencies": {
|
|
7
7
|
"@angular/common": ">=18 <19",
|
|
@@ -11,13 +11,19 @@
|
|
|
11
11
|
"sideEffects": false,
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@hoanghong/speaking-core": "^0.
|
|
14
|
+
"@hoanghong/speaking-core": "^0.6.0",
|
|
15
15
|
"tslib": "^2.3.0"
|
|
16
16
|
},
|
|
17
17
|
"publishConfig": {
|
|
18
18
|
"access": "public",
|
|
19
19
|
"registry": "https://registry.npmjs.org/"
|
|
20
20
|
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"speech",
|
|
23
|
+
"pronunciation-assessment",
|
|
24
|
+
"speech-to-text",
|
|
25
|
+
"sdk"
|
|
26
|
+
],
|
|
21
27
|
"module": "fesm2022/hoanghong-speaking-angular.mjs",
|
|
22
28
|
"typings": "index.d.ts",
|
|
23
29
|
"exports": {
|