dictawhisper 0.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/LICENSE +21 -0
- package/README.md +194 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DictaWhisper contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="site/static/logo.png" alt="DictaWhisper logo" width="128" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# DictaWhisper
|
|
6
|
+
|
|
7
|
+
**[dictawhisper.com](https://dictawhisper.com)** · [GitHub](https://github.com/Catalyst-Forge-LLC/dictawhisper)
|
|
8
|
+
|
|
9
|
+
The npm package is a name hold. Clone this repo to run it.
|
|
10
|
+
|
|
11
|
+
**A local voice journal.** Record in the browser, drop a file, or (optionally) sync a phone folder. DictaWhisper transcribes on your GPU with [faster-whisper](https://github.com/SYSTRAN/faster-whisper), then turns the raw speech into readable notes and tags via [ollanet](https://ollanet.dev) on localhost or another box with a cleanup model.
|
|
12
|
+
|
|
13
|
+
The name is **dicta** (dictation, a dictaphone) plus **Whisper**. Audio stays on this computer. The `.json` next to each recording is the note: no database, no account.
|
|
14
|
+
|
|
15
|
+
Open the UI at [http://localhost:7777](http://localhost:7777). On Tailscale, the same page is reachable from your phone.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Why it exists
|
|
20
|
+
|
|
21
|
+
Voice notes are easy to make and hard to keep. Phone recordings pile up as undated blobs. Cloud speech-to-text wants the audio. Desktop Whisper dumps a wall of filler words and repeated phrases. A journal you can reread needs three things:
|
|
22
|
+
|
|
23
|
+
1. **Capture that fits your tools:** record or drop a file in the browser. Syncthing from a phone is optional.
|
|
24
|
+
2. **Accurate, local transcription:** `large-v3` on CUDA, a prompt seeded with your own vocabulary, word times that survive cleanup.
|
|
25
|
+
3. **Usable notes:** cleaned prose, tags, playback that follows the cleaned paragraphs, files you can copy and back up.
|
|
26
|
+
|
|
27
|
+
By default the API binds to `127.0.0.1`. Turn on `http.tailscale` and the UI listens on your tailnet so a phone or laptop on the same Tailscale can open it. File APIs only accept paths under your watch roots. If cleanup is unavailable, you still get the raw transcript.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Daily loop
|
|
32
|
+
|
|
33
|
+
1. Talk. Hit Record, or drag an audio file onto the page.
|
|
34
|
+
2. Browser files start immediately. Optional ffmpeg denoise, then Whisper (model loaded once) writes a sidecar `.json`.
|
|
35
|
+
3. ollanet cleans the text and adds tags onto the same JSON. Raw speech is kept.
|
|
36
|
+
4. Notes are grouped by month. Cleaned text is the default; raw speech is one click away. Playback follows the cleaned paragraphs.
|
|
37
|
+
|
|
38
|
+
If you also watch a phone folder ([Syncthing](https://syncthing.net) or any folder sync), those files wait 30 minutes after the last write so the transfer can finish, then dated names are filed into `YYYY/MM/` on that watch root.
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
Browser record/drop → drop folder → immediately ─────────────┐
|
|
42
|
+
Optional phone folder → watch roots → settle 30m → YYYY/MM/ ┼─→ whisper → sidecar.json → ollanet → UI
|
|
43
|
+
Force / retranscribe ─────────────────────────────────────────┘
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## What it is good at
|
|
49
|
+
|
|
50
|
+
**Files are the database.** Each note is `audio` + `audio.json`. You can rsync the tree, open a sidecar in an editor, or point `pnpm retranscribe` at one month. The file path is the note's identity, so there is nothing to export.
|
|
51
|
+
|
|
52
|
+
**The Whisper model stays loaded.** The worker loads the model once and keeps it for the next file. That is the normal path, not a special command.
|
|
53
|
+
|
|
54
|
+
**Audio never leaves this computer.** Faster-whisper runs here. If you point ollanet at Ollama on another machine on your own network, only the transcript text crosses the network. If ollanet is unreachable, doctor and `/health` report it and you still have the raw words.
|
|
55
|
+
|
|
56
|
+
**UI on the tailnet.** Default bind is localhost. Set `http.tailscale` (or `DICTA_TAILSCALE=1`) and `pnpm dev` advertises `http://<magicdns>:7777` / `http://<100.x>:7777` so you can read and record from a phone on the same Tailscale. Nothing is published to the public internet, and the app adds no login of its own; tailnet membership is the access control.
|
|
57
|
+
|
|
58
|
+
**Playback aligned to word timestamps.** Cleanup drops fillers and collapses repeated phrases, but playback cues map back to Whisper word timestamps rather than being inferred from the cleaned wording. Copy uses the same sections the player shows.
|
|
59
|
+
|
|
60
|
+
**Custom vocabulary and stable tags.** `whisper.promptTerms` (a person, a company, a street) go into Whisper's initial prompt. Tags already in your inventory are fed into cleanup so the model does not spell them a new way every week, and a consolidate preview/apply pass merges near-duplicates when you ask.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Privacy and network boundary
|
|
65
|
+
|
|
66
|
+
- **No telemetry.** Nothing contacts an external analytics or telemetry service.
|
|
67
|
+
- **Audio stays here.** Faster-whisper processes files on this GPU or CPU.
|
|
68
|
+
- **Text only, and only on your network.** If cleanup runs on another machine you already use, only the transcript text is sent there.
|
|
69
|
+
- **Loopback by default.** API (`8008`) and UI (`7777`) bind to `127.0.0.1`. They listen on `0.0.0.0` only if you turn on `http.tailscale`.
|
|
70
|
+
- **Path allowlisting.** File routes resolve realpaths and reject anything outside `watch.roots` and `browserDropFolder` with a `403`.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Requirements
|
|
75
|
+
|
|
76
|
+
- Node 20+ and [pnpm](https://pnpm.io)
|
|
77
|
+
- NVIDIA GPU + CUDA for local Whisper (or set `whisper.device` to `cpu`, which is far slower)
|
|
78
|
+
- Python with [`faster-whisper`](https://github.com/SYSTRAN/faster-whisper): point `whisper.python` at that interpreter
|
|
79
|
+
- `ffmpeg` on PATH if denoise is on
|
|
80
|
+
- [ollanet](https://ollanet.dev) + an Ollama model for cleanup (localhost or another reachable host; optional for raw transcripts)
|
|
81
|
+
- Optional: [Tailscale](https://tailscale.com) if you want the UI from another device on your tailnet
|
|
82
|
+
- Optional: a watched folder ([Syncthing](https://syncthing.net), a shared drive, a dump directory) if you already record outside the browser
|
|
83
|
+
|
|
84
|
+
Record or drag files at [localhost:7777](http://localhost:7777). Run it on a computer you leave turned on.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Setup
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
cp config.example.json config.json
|
|
92
|
+
# edit whisper.python, whisper.promptTerms, ollanet.machine / cleanModel
|
|
93
|
+
# watch.roots is optional: record or drop files in the UI without Syncthing
|
|
94
|
+
pnpm install
|
|
95
|
+
pnpm run doctor
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The UI can record and accept dropped files with no Syncthing. Add `watch.roots` only if you already have a phone folder or dump directory. Then: which Python has CUDA Whisper, and which Ollama (this computer or another on your network) ollanet should use for cleanup.
|
|
99
|
+
|
|
100
|
+
`pnpm run doctor` and startup `/health` run the same probes: Node, config, watch roots, ffmpeg, `faster-whisper` import, CUDA vs CPU, ollanet reachability, and port availability. Failures refuse to start queues. Warnings (CPU mode, ollanet unreachable, port already in use) are reported without blocking startup.
|
|
101
|
+
|
|
102
|
+
Optional sanity check (machine can be this computer or another name ollanet already sees):
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
ollanet prompt YOUR-OLLANET-HOST YOUR-CLEAN-MODEL --format json "ping"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Run
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
pnpm dev # API + UI → http://localhost:7777
|
|
114
|
+
pnpm start # API only on 127.0.0.1:8008
|
|
115
|
+
pnpm ui # UI only
|
|
116
|
+
pnpm turbo # API with large-v3-turbo (bash)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The UI proxies `/socket.io` and API paths, so open **7777** only. API defaults to `127.0.0.1:8008`.
|
|
120
|
+
|
|
121
|
+
To reach the UI from a phone on your Tailscale:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
"http": { "tailscale": true }
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
or `DICTA_TAILSCALE=1`. Doctor and startup print the MagicDNS / `100.x` URL. There is still no login: only devices on that tailnet should be able to open it.
|
|
128
|
+
|
|
129
|
+
`pnpm retranscribe` is for a pile of existing notes, or when a transcript went wrong. Newest first; notes that already have word times are skipped unless you pass `--force`.
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
pnpm retranscribe
|
|
133
|
+
pnpm retranscribe --dir="./notes/2026/08" --limit=5 --reclean
|
|
134
|
+
pnpm retranscribe --force
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`--reclean` runs ollanet again after the new transcript. Without it, cleaned text and tags are kept; only words/times/raw text update.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## HTTP
|
|
142
|
+
|
|
143
|
+
| Method | Path | Purpose |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| GET | `/health` | Shared doctor report, queues, Whisper worker, ollanet reachability (`?fresh=1` to skip cache) |
|
|
146
|
+
| GET | `/status` | pending / raw / done counts |
|
|
147
|
+
| GET | `/notes/index` | Inbox summaries (path, tags, preview, search body) |
|
|
148
|
+
| GET | `/note?file=` | Full sidecar (allowlisted) |
|
|
149
|
+
| GET | `/audio?file=` | Stream allowlisted audio (sidecar or audio path) |
|
|
150
|
+
| POST | `/audio` | Multipart `file` (or `audio`) + optional `clipName` → drop folder, process immediately |
|
|
151
|
+
| POST | `/transcribe/force` | `{ "file": "..." }` retry transcription |
|
|
152
|
+
| POST | `/process/force` | Re-run cleanup on an existing note |
|
|
153
|
+
| POST | `/process/skip` | Leave the raw transcript; skip later cleanup |
|
|
154
|
+
| POST | `/holding/resolve` | `{ "file", "action": "overwrite" \| "rename" \| "unfile" }` |
|
|
155
|
+
| POST | `/tags/consolidate/preview` | Merge plan (`useModel`, default true) |
|
|
156
|
+
| POST | `/tags/consolidate/apply` | `{ "groups": [{ "keep", "drop" }] }` |
|
|
157
|
+
|
|
158
|
+
Force/delete/read only accept realpaths under configured watch roots.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Config and env
|
|
163
|
+
|
|
164
|
+
| Env | Meaning |
|
|
165
|
+
|---|---|
|
|
166
|
+
| `HOST` / `PORT` | API bind (default `127.0.0.1:8008`; Tailscale mode listens on `0.0.0.0`) |
|
|
167
|
+
| `DICTA_TAILSCALE` | `1` / `true`: expose UI + API on the tailnet |
|
|
168
|
+
| `WHISPER_MODEL` | `large-v3` or `turbo` |
|
|
169
|
+
| `WHISPER_PYTHON` | Interpreter with faster-whisper |
|
|
170
|
+
| `WHISPER_DEVICE` | `cuda` or `cpu` |
|
|
171
|
+
| `VOICE_SETTLE_MINUTES` | Phone/watch settle; default 30 |
|
|
172
|
+
| `VOICE_BROWSER_SETTLE_MS` | Browser-drop settle; default 0 |
|
|
173
|
+
| `OLLANET_MACHINE` / `OLLANET_CLEAN_MODEL` | Cleanup host and model |
|
|
174
|
+
| `OLLAMA_PROMPT_TIMEOUT_MS` | ollanet abort; default 900000 (15m) |
|
|
175
|
+
|
|
176
|
+
Useful `config.json` knobs (see `config.example.json`):
|
|
177
|
+
|
|
178
|
+
- `watch.createMissingRoots`: create empty watch roots instead of failing doctor
|
|
179
|
+
- `whisper.promptTerms`: names and terms Whisper should prefer
|
|
180
|
+
- `whisper.computeType`: `float16`, or `int8_float16` if VRAM is tight
|
|
181
|
+
- `audio.preprocess`: ffmpeg denoise
|
|
182
|
+
- `queues.*.concurrency`: keep transcription at 1 on a single GPU
|
|
183
|
+
- `http.tailscale`: bind beyond localhost and allow Tailscale origins for the UI
|
|
184
|
+
- `ollanet.required`: treat missing cleanup host/model as a failure (default false; raw transcripts still work)
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Site
|
|
189
|
+
|
|
190
|
+
Marketing site (FilePress): [`site/`](site/). `pnpm site:dev` / `pnpm site:build` / `pnpm ship`. Live: [dictawhisper.com](https://dictawhisper.com). Page copy follows [aiBreze](https://aibreze.com); see [`docs/aibreze-overlay.md`](docs/aibreze-overlay.md).
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dictawhisper",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Name hold. The voice journal is a git clone (dictawhisper.com). A CLI or library may land here later.",
|
|
6
|
+
"homepage": "https://dictawhisper.com",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Catalyst-Forge-LLC/dictawhisper.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/Catalyst-Forge-LLC/dictawhisper/issues"
|
|
13
|
+
},
|
|
14
|
+
"author": "Catalyst Forge LLC",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"whisper",
|
|
17
|
+
"faster-whisper",
|
|
18
|
+
"transcription",
|
|
19
|
+
"voice-journal",
|
|
20
|
+
"local",
|
|
21
|
+
"ollama",
|
|
22
|
+
"ollanet"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"files": [
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE"
|
|
29
|
+
],
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=20"
|
|
32
|
+
},
|
|
33
|
+
"packageManager": "pnpm@8.14.1",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"async": "^3.2.6",
|
|
36
|
+
"chokidar": "^4.0.3",
|
|
37
|
+
"express": "^4.21.2",
|
|
38
|
+
"multer": "^2.2.0",
|
|
39
|
+
"ollanet": "^0.6.5",
|
|
40
|
+
"socket.io": "^4.8.1",
|
|
41
|
+
"zod": "^3.25.76",
|
|
42
|
+
"zod-to-json-schema": "^3.24.6"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/async": "^3.2.25",
|
|
46
|
+
"@types/express": "^5.0.3",
|
|
47
|
+
"@types/multer": "^2.2.0",
|
|
48
|
+
"@types/node": "^24.6.2",
|
|
49
|
+
"aibreze": "^0.1.2",
|
|
50
|
+
"typescript": "^5.9.3"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"start": "node --experimental-strip-types src/server.ts",
|
|
54
|
+
"dev": "node --experimental-strip-types src/dev.ts",
|
|
55
|
+
"turbo": "WHISPER_MODEL=turbo node --experimental-strip-types src/server.ts",
|
|
56
|
+
"ui": "pnpm --dir client dev",
|
|
57
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
58
|
+
"test": "node --experimental-strip-types --test test/*.test.ts",
|
|
59
|
+
"doctor": "node --experimental-strip-types src/doctor.ts",
|
|
60
|
+
"retranscribe": "node --experimental-strip-types scripts/retranscribe.ts",
|
|
61
|
+
"site:dev": "pnpm --dir site dev",
|
|
62
|
+
"site:build": "pnpm --dir site build",
|
|
63
|
+
"site:deploy": "pnpm --dir site run ship",
|
|
64
|
+
"ship": "pnpm --dir site run ship"
|
|
65
|
+
}
|
|
66
|
+
}
|