handy-remote-server 1.1.0 β 1.2.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/README.md +55 -14
- package/dist/index.js +36 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,30 +10,71 @@ The easiest way to run the external inference server is using `npx`:
|
|
|
10
10
|
npx handy-remote-server
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
_(You must have Node.js and
|
|
13
|
+
_(You must have Node.js and Rust/Cargo installed)_
|
|
14
14
|
|
|
15
15
|
## Usage
|
|
16
16
|
|
|
17
|
-
When you run the server for the first time, it will
|
|
17
|
+
When you run the server for the first time, it will:
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
1. **Download the GigaAM v3 model** (~100 MB) with a progress bar:
|
|
20
20
|
|
|
21
|
-
```
|
|
22
|
-
|
|
21
|
+
```
|
|
22
|
+
π₯ Downloading model...
|
|
23
|
+
URL: https://blob.handy.computer/giga-am-v3.int8.onnx
|
|
24
|
+
Dest: /path/to/models/gigaam.onnx
|
|
25
|
+
|
|
26
|
+
ββββββββββββββββββββββββββββββββββββββββ 52.3% 52.10 MB / 99.60 MB 12.5 MB/s
|
|
27
|
+
|
|
28
|
+
β
Download complete in 8.2s
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
2. **Generate a persistent API key** saved to `~/.handy/api_key`:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
======================================================
|
|
35
|
+
Generated a new API KEY (saved to /Users/you/.handy/api_key)
|
|
36
|
+
Your API KEY is: xxxxx...xxxxx
|
|
37
|
+
======================================================
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The key persists across restarts. On the next launch, it will be loaded automatically.
|
|
41
|
+
|
|
42
|
+
3. **Start the server** and log every request in detail:
|
|
43
|
+
|
|
44
|
+
```
|
|
23
45
|
Handy Remote Server is running on port 3000
|
|
46
|
+
|
|
47
|
+
[2026-03-07T12:00:00.000Z] ββ REQUEST #1 ββββββββββββββββββββββ
|
|
48
|
+
Method: POST /transcribe
|
|
49
|
+
From: 192.168.1.5
|
|
50
|
+
Auth: OK
|
|
51
|
+
[#1] Audio received: 156.3 KB
|
|
52
|
+
[#1] Queued for inference (queue length: 0)
|
|
53
|
+
[2026-03-07T12:00:01.234Z] ββ RESPONSE #1 βββββββββββββββββββββ
|
|
54
|
+
Status: 200
|
|
55
|
+
Duration: 1.23s
|
|
56
|
+
Result: "ΠΡΠΈΠ²Π΅Ρ, ΠΊΠ°ΠΊ Π΄Π΅Π»Π°?"
|
|
24
57
|
```
|
|
25
58
|
|
|
59
|
+
### Connecting from Handy
|
|
60
|
+
|
|
26
61
|
1. Open **Handy** on your client machine.
|
|
27
|
-
2. Go to **Settings >
|
|
28
|
-
3.
|
|
29
|
-
|
|
30
|
-
|
|
62
|
+
2. Go to **Settings > Models**, select **Remote Server**.
|
|
63
|
+
3. Go to **Settings > General**, fill in:
|
|
64
|
+
- **Remote Server URL**: `http://<your-server-ip>:3000`
|
|
65
|
+
- **API Token**: the generated token
|
|
66
|
+
4. All transcriptions will now be processed by the server!
|
|
31
67
|
|
|
32
|
-
##
|
|
68
|
+
## Environment Variables
|
|
33
69
|
|
|
34
|
-
|
|
70
|
+
| Variable | Default | Description |
|
|
71
|
+
| ---------------- | ------------------------------------------- | ------------------------------- |
|
|
72
|
+
| `PORT` | `3000` | Server port |
|
|
73
|
+
| `API_KEY` | auto-generated, saved to `~/.handy/api_key` | Bearer token for authentication |
|
|
74
|
+
| `INFER_CLI_PATH` | auto-detected | Path to the `rust-infer` binary |
|
|
35
75
|
|
|
36
|
-
|
|
76
|
+
## How It Works
|
|
77
|
+
|
|
78
|
+
The `handy-remote-server` spins up a tiny Express server alongside a heavily optimized Rust CLI (`rust-infer`) powered by `transcribe-rs`. Audio files are dispatched sequentially from the Node server directly into the Rust engine.
|
|
37
79
|
|
|
38
|
-
|
|
39
|
-
- `API_KEY` - defaults to an auto-generated token in development. Set this to a permanent token for production.
|
|
80
|
+
Currently the server uses the **GigaAM v3** model (Russian-language, fast inference, ~100 MB).
|
package/dist/index.js
CHANGED
|
@@ -135,14 +135,46 @@ const gigaamModelPath = path_1.default.join(modelsDir, 'gigaam.onnx');
|
|
|
135
135
|
async function downloadFile(url, dest) {
|
|
136
136
|
if (fs_1.default.existsSync(dest))
|
|
137
137
|
return;
|
|
138
|
-
console.log(
|
|
138
|
+
console.log(`\nπ₯ Downloading model...`);
|
|
139
|
+
console.log(` URL: ${url}`);
|
|
140
|
+
console.log(` Dest: ${dest}\n`);
|
|
139
141
|
fs_1.default.mkdirSync(path_1.default.dirname(dest), { recursive: true });
|
|
140
142
|
const response = await fetch(url);
|
|
141
143
|
if (!response.ok)
|
|
142
144
|
throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
145
|
+
const totalBytes = parseInt(response.headers.get('content-length') || '0', 10);
|
|
146
|
+
let downloadedBytes = 0;
|
|
147
|
+
const startTime = Date.now();
|
|
148
|
+
const fileStream = fs_1.default.createWriteStream(dest);
|
|
149
|
+
const reader = response.body?.getReader();
|
|
150
|
+
if (!reader)
|
|
151
|
+
throw new Error('Response body is not readable');
|
|
152
|
+
const barWidth = 40;
|
|
153
|
+
while (true) {
|
|
154
|
+
const { done, value } = await reader.read();
|
|
155
|
+
if (done)
|
|
156
|
+
break;
|
|
157
|
+
fileStream.write(Buffer.from(value));
|
|
158
|
+
downloadedBytes += value.length;
|
|
159
|
+
// Draw progress bar
|
|
160
|
+
const percent = totalBytes > 0 ? downloadedBytes / totalBytes : 0;
|
|
161
|
+
const filled = Math.round(barWidth * percent);
|
|
162
|
+
const empty = barWidth - filled;
|
|
163
|
+
const bar = 'β'.repeat(filled) + 'β'.repeat(empty);
|
|
164
|
+
const pct = (percent * 100).toFixed(1).padStart(5);
|
|
165
|
+
const dl = formatBytes(downloadedBytes);
|
|
166
|
+
const tot = totalBytes > 0 ? formatBytes(totalBytes) : '?';
|
|
167
|
+
const elapsed = (Date.now() - startTime) / 1000;
|
|
168
|
+
const speed = elapsed > 0 ? formatBytes(downloadedBytes / elapsed) + '/s' : '';
|
|
169
|
+
process.stdout.write(`\r ${bar} ${pct}% ${dl} / ${tot} ${speed} `);
|
|
170
|
+
}
|
|
171
|
+
await new Promise((resolve, reject) => {
|
|
172
|
+
fileStream.end(() => resolve());
|
|
173
|
+
fileStream.on('error', reject);
|
|
174
|
+
});
|
|
175
|
+
const totalTime = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
176
|
+
process.stdout.write('\n');
|
|
177
|
+
console.log(`\nβ
Download complete in ${totalTime}s\n`);
|
|
146
178
|
}
|
|
147
179
|
async function ensureModels() {
|
|
148
180
|
await downloadFile(GIGAAM_MODEL_URL, gigaamModelPath);
|