webtalk 1.0.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/.gitattributes +35 -0
- package/.github/workflows/publish.yml +26 -0
- package/README.md +1 -0
- package/app.html +519 -0
- package/assets/index-ClpvH5Vn.js +40 -0
- package/assets/index-DUYekU7u.css +1 -0
- package/assets/worker-BPxxCWVT.js +2679 -0
- package/config.js +36 -0
- package/debug.js +21 -0
- package/download-lock.js +26 -0
- package/hot-reload.js +78 -0
- package/middleware.js +62 -0
- package/package.json +33 -0
- package/persistent-state.js +62 -0
- package/sdk.js +22 -0
- package/serve-static.js +45 -0
- package/server.js +177 -0
- package/setup-npm-publishing.sh +140 -0
- package/stt.js +141 -0
- package/test.mp3 +0 -0
- package/tts/EventEmitter.js +59 -0
- package/tts/PCMPlayerWorklet.js +563 -0
- package/tts/inference-worker.js +1121 -0
- package/tts/onnx-streaming.js +721 -0
- package/tts-models.js +97 -0
- package/tts-utils.js +52 -0
- package/tts.js +167 -0
- package/whisper-models.js +161 -0
- package/worker-patch.js +32 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
echo "======================================"
|
|
5
|
+
echo "NPM Publishing Setup for webtalk"
|
|
6
|
+
echo "======================================"
|
|
7
|
+
echo ""
|
|
8
|
+
echo "This script will:"
|
|
9
|
+
echo " 1. Validate your npm token"
|
|
10
|
+
echo " 2. Create NPM_TOKEN GitHub secret"
|
|
11
|
+
echo " 3. Verify workflow setup"
|
|
12
|
+
echo ""
|
|
13
|
+
|
|
14
|
+
# Check prerequisites
|
|
15
|
+
echo "Checking prerequisites..."
|
|
16
|
+
MISSING=0
|
|
17
|
+
|
|
18
|
+
if ! command -v gh &> /dev/null; then
|
|
19
|
+
echo "❌ GitHub CLI (gh) not found"
|
|
20
|
+
MISSING=1
|
|
21
|
+
else
|
|
22
|
+
echo "✓ GitHub CLI found"
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
if ! command -v npm &> /dev/null; then
|
|
26
|
+
echo "❌ npm not found"
|
|
27
|
+
MISSING=1
|
|
28
|
+
else
|
|
29
|
+
echo "✓ npm found"
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
if [ $MISSING -eq 1 ]; then
|
|
33
|
+
echo ""
|
|
34
|
+
echo "Install GitHub CLI: https://cli.github.com"
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
echo ""
|
|
39
|
+
|
|
40
|
+
# Step 1: npm token input
|
|
41
|
+
echo "========================================"
|
|
42
|
+
echo "STEP 1: Provide npm Automation Token"
|
|
43
|
+
echo "========================================"
|
|
44
|
+
echo ""
|
|
45
|
+
echo "How to generate your token:"
|
|
46
|
+
echo " 1. Visit: https://www.npmjs.com/settings/tokens"
|
|
47
|
+
echo " 2. Click 'Generate New Token'"
|
|
48
|
+
echo " 3. Select 'Automation' type"
|
|
49
|
+
echo " 4. Optionally add description: 'GitHub Actions - webtalk'"
|
|
50
|
+
echo " 5. Click 'Create'"
|
|
51
|
+
echo " 6. COPY the token immediately (shown only once)"
|
|
52
|
+
echo ""
|
|
53
|
+
read -sp "Paste your npm token and press Enter: " NPM_TOKEN
|
|
54
|
+
echo ""
|
|
55
|
+
echo ""
|
|
56
|
+
|
|
57
|
+
if [ -z "$NPM_TOKEN" ]; then
|
|
58
|
+
echo "❌ No token provided. Exiting."
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# Validate token format (should be ~40-50 chars, alphanumeric)
|
|
63
|
+
if ! [[ "$NPM_TOKEN" =~ ^[a-zA-Z0-9_-]+$ ]] || [ ${#NPM_TOKEN} -lt 20 ]; then
|
|
64
|
+
echo "❌ Token format invalid. Should be alphanumeric string 20+ chars."
|
|
65
|
+
exit 1
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
echo "✓ Token format valid"
|
|
69
|
+
echo ""
|
|
70
|
+
|
|
71
|
+
# Step 2: Verify GitHub authentication
|
|
72
|
+
echo "========================================"
|
|
73
|
+
echo "STEP 2: Verify GitHub Authentication"
|
|
74
|
+
echo "========================================"
|
|
75
|
+
echo ""
|
|
76
|
+
|
|
77
|
+
if ! gh auth status 2>/dev/null | grep -q "Logged in"; then
|
|
78
|
+
echo "❌ Not authenticated to GitHub. Please run: gh auth login"
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
GITHUB_USER=$(gh auth status 2>&1 | grep "Logged in to" | head -1 | awk '{print $NF}' | tr -d '()')
|
|
83
|
+
echo "✓ Authenticated as: $GITHUB_USER"
|
|
84
|
+
echo ""
|
|
85
|
+
|
|
86
|
+
# Step 3: Set GitHub secret
|
|
87
|
+
echo "========================================"
|
|
88
|
+
echo "STEP 3: Creating GitHub Secret"
|
|
89
|
+
echo "========================================"
|
|
90
|
+
echo ""
|
|
91
|
+
|
|
92
|
+
REPO="AnEntrypoint/realtime-whisper-webgpu"
|
|
93
|
+
SECRET_NAME="NPM_TOKEN"
|
|
94
|
+
|
|
95
|
+
echo "Repository: https://github.com/$REPO"
|
|
96
|
+
echo "Secret name: $SECRET_NAME"
|
|
97
|
+
echo ""
|
|
98
|
+
|
|
99
|
+
# Create or update the secret
|
|
100
|
+
if echo "$NPM_TOKEN" | gh secret set "$SECRET_NAME" --repo "$REPO" 2>&1; then
|
|
101
|
+
echo "✓ Secret set successfully"
|
|
102
|
+
else
|
|
103
|
+
echo "❌ Failed to set secret"
|
|
104
|
+
exit 1
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
echo ""
|
|
108
|
+
|
|
109
|
+
# Step 4: Verify secret exists
|
|
110
|
+
echo "========================================"
|
|
111
|
+
echo "STEP 4: Verifying Secret"
|
|
112
|
+
echo "========================================"
|
|
113
|
+
echo ""
|
|
114
|
+
|
|
115
|
+
if gh secret list --repo "$REPO" 2>/dev/null | grep -q "$SECRET_NAME"; then
|
|
116
|
+
echo "✓ $SECRET_NAME is available in GitHub Actions"
|
|
117
|
+
echo ""
|
|
118
|
+
echo "View it at:"
|
|
119
|
+
echo "https://github.com/$REPO/settings/secrets/actions"
|
|
120
|
+
else
|
|
121
|
+
echo "⚠ Warning: Could not verify secret in list"
|
|
122
|
+
echo "Check GitHub manually at:"
|
|
123
|
+
echo "https://github.com/$REPO/settings/secrets/actions"
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
echo ""
|
|
127
|
+
echo "========================================"
|
|
128
|
+
echo "Setup Complete!"
|
|
129
|
+
echo "========================================"
|
|
130
|
+
echo ""
|
|
131
|
+
echo "Your webtalk npm package is now ready to publish automatically."
|
|
132
|
+
echo ""
|
|
133
|
+
echo "To publish a new version:"
|
|
134
|
+
echo " 1. Update version in package.json"
|
|
135
|
+
echo " 2. git add . && git commit -m 'bump version X.X.X'"
|
|
136
|
+
echo " 3. git push origin main"
|
|
137
|
+
echo " 4. GitHub Actions will publish to npm automatically"
|
|
138
|
+
echo ""
|
|
139
|
+
echo "Monitor at: https://github.com/$REPO/actions"
|
|
140
|
+
echo ""
|
package/stt.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const STATUS = {
|
|
2
|
+
LOADING: 'loading',
|
|
3
|
+
READY: 'ready',
|
|
4
|
+
RECORDING: 'recording',
|
|
5
|
+
TRANSCRIBING: 'transcribing',
|
|
6
|
+
ERROR: 'error'
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
class STT {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.language = options.language || 'en';
|
|
12
|
+
this.onTranscript = options.onTranscript || null;
|
|
13
|
+
this.onStatus = options.onStatus || null;
|
|
14
|
+
this.onPartial = options.onPartial || null;
|
|
15
|
+
this.basePath = options.basePath || '';
|
|
16
|
+
this.workerFile = options.workerFile || 'worker-BPxxCWVT.js';
|
|
17
|
+
this.worker = null;
|
|
18
|
+
this.ready = false;
|
|
19
|
+
this.recorder = null;
|
|
20
|
+
this.recordChunks = [];
|
|
21
|
+
this._resolveStop = null;
|
|
22
|
+
this._currentTranscript = '';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async init() {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
try {
|
|
28
|
+
const workerPath = this.basePath + '/assets/' + this.workerFile;
|
|
29
|
+
this.worker = new Worker(workerPath);
|
|
30
|
+
this.worker.onmessage = (e) => this._handleMessage(e.data);
|
|
31
|
+
this.worker.onerror = (e) => reject(e);
|
|
32
|
+
this._initResolve = resolve;
|
|
33
|
+
this.worker.postMessage({ type: 'load' });
|
|
34
|
+
} catch (err) {
|
|
35
|
+
reject(err);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_handleMessage(msg) {
|
|
41
|
+
switch (msg.status) {
|
|
42
|
+
case 'loading':
|
|
43
|
+
this.onStatus?.(STATUS.LOADING, typeof msg.data === 'string' ? msg.data : 'Loading...');
|
|
44
|
+
break;
|
|
45
|
+
case 'ready':
|
|
46
|
+
this.ready = true;
|
|
47
|
+
this.onStatus?.(STATUS.READY, 'Ready');
|
|
48
|
+
this._initResolve?.();
|
|
49
|
+
this._initResolve = null;
|
|
50
|
+
break;
|
|
51
|
+
case 'start':
|
|
52
|
+
this.onStatus?.(STATUS.TRANSCRIBING, 'Transcribing...');
|
|
53
|
+
break;
|
|
54
|
+
case 'update': {
|
|
55
|
+
const text = this._extractText(msg.output);
|
|
56
|
+
this._currentTranscript = text;
|
|
57
|
+
this.onPartial?.(text);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case 'complete': {
|
|
61
|
+
const text = this._extractText(msg.output);
|
|
62
|
+
this._currentTranscript = text;
|
|
63
|
+
this.onTranscript?.(text);
|
|
64
|
+
this.onStatus?.(STATUS.READY, 'Ready');
|
|
65
|
+
this._resolveStop?.(text);
|
|
66
|
+
this._resolveStop = null;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
_extractText(output) {
|
|
73
|
+
if (!output) return '';
|
|
74
|
+
if (Array.isArray(output)) return output.map(o => o.text || o).join('');
|
|
75
|
+
return output.text || String(output);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async startRecording() {
|
|
79
|
+
if (!this.ready) throw new Error('STT not initialized');
|
|
80
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
81
|
+
this.recordChunks = [];
|
|
82
|
+
this.recorder = new MediaRecorder(stream);
|
|
83
|
+
this.recorder.ondataavailable = (e) => this.recordChunks.push(e.data);
|
|
84
|
+
this._stream = stream;
|
|
85
|
+
this.recorder.start();
|
|
86
|
+
this.onStatus?.(STATUS.RECORDING, 'Recording...');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
stopRecording() {
|
|
90
|
+
return new Promise((resolve) => {
|
|
91
|
+
if (!this.recorder || this.recorder.state === 'inactive') {
|
|
92
|
+
resolve(this._currentTranscript);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
this._resolveStop = resolve;
|
|
96
|
+
this.recorder.onstop = async () => {
|
|
97
|
+
this._stream?.getTracks().forEach(t => t.stop());
|
|
98
|
+
const blob = new Blob(this.recordChunks, { type: 'audio/webm' });
|
|
99
|
+
await this._processAudio(blob);
|
|
100
|
+
};
|
|
101
|
+
this.recorder.stop();
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async transcribeBlob(blob) {
|
|
106
|
+
return new Promise((resolve) => {
|
|
107
|
+
this._resolveStop = resolve;
|
|
108
|
+
this._processAudio(blob);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async _processAudio(blob) {
|
|
113
|
+
if (!this.worker || !this.ready) return;
|
|
114
|
+
this.onStatus?.(STATUS.TRANSCRIBING, 'Transcribing...');
|
|
115
|
+
const arrayBuf = await blob.arrayBuffer();
|
|
116
|
+
const ctx = new AudioContext({ sampleRate: 16000 });
|
|
117
|
+
const decoded = await ctx.decodeAudioData(arrayBuf);
|
|
118
|
+
const audio = decoded.getChannelData(0);
|
|
119
|
+
ctx.close();
|
|
120
|
+
this.worker.postMessage({ type: 'generate', data: { audio, language: this.language } });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getStatus() {
|
|
124
|
+
return {
|
|
125
|
+
ready: this.ready,
|
|
126
|
+
recording: this.recorder?.state === 'recording',
|
|
127
|
+
language: this.language,
|
|
128
|
+
workerFile: this.workerFile,
|
|
129
|
+
hasWorker: !!this.worker
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
destroy() {
|
|
134
|
+
this.worker?.terminate();
|
|
135
|
+
this.worker = null;
|
|
136
|
+
this.ready = false;
|
|
137
|
+
this._stream?.getTracks().forEach(t => t.stop());
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export { STT };
|
package/test.mp3
ADDED
|
Binary file
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export class EventEmitter {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.events = {};
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
addEventListener(event, listener, options = {}) {
|
|
7
|
+
if (!this.events[event]) {
|
|
8
|
+
this.events[event] = [];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const wrappedListener = {
|
|
12
|
+
callback: listener,
|
|
13
|
+
once: options.once || false
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
this.events[event].push(wrappedListener);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
removeEventListener(event, listener) {
|
|
20
|
+
if (!this.events[event]) return;
|
|
21
|
+
|
|
22
|
+
this.events[event] = this.events[event].filter(
|
|
23
|
+
wrappedListener => wrappedListener.callback !== listener
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
dispatchEvent(event) {
|
|
28
|
+
const eventName = event.type;
|
|
29
|
+
if (!this.events[eventName]) return;
|
|
30
|
+
|
|
31
|
+
this.events[eventName] = this.events[eventName].filter(wrappedListener => {
|
|
32
|
+
wrappedListener.callback.call(this, event);
|
|
33
|
+
return !wrappedListener.once;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
emit(eventName, data) {
|
|
38
|
+
const event = new CustomEvent(eventName, { detail: data });
|
|
39
|
+
this.dispatchEvent(event);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class CustomEvent {
|
|
44
|
+
constructor(type, options = {}) {
|
|
45
|
+
this.type = type;
|
|
46
|
+
this.detail = options.detail;
|
|
47
|
+
this.target = null;
|
|
48
|
+
this.currentTarget = null;
|
|
49
|
+
this.defaultPrevented = false;
|
|
50
|
+
this.bubbles = options.bubbles || false;
|
|
51
|
+
this.cancelable = options.cancelable || false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
preventDefault() {
|
|
55
|
+
if (this.cancelable) {
|
|
56
|
+
this.defaultPrevented = true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|