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.
@@ -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
+ }