opencode-interrupt-plugin 0.4.33 → 0.4.34
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/dist/config.d.ts +2 -0
- package/dist/config.js +20 -0
- package/dist/index.js +27 -12
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface PluginConfig {
|
|
|
10
10
|
tts?: boolean;
|
|
11
11
|
ttsVoice?: string;
|
|
12
12
|
ttsRate?: string;
|
|
13
|
+
whisperModel?: string;
|
|
13
14
|
}
|
|
14
15
|
export interface ResolvedConfig {
|
|
15
16
|
licenseKey?: string;
|
|
@@ -25,6 +26,7 @@ export interface ResolvedConfig {
|
|
|
25
26
|
tts: boolean;
|
|
26
27
|
ttsVoice: string;
|
|
27
28
|
ttsRate: string;
|
|
29
|
+
whisperModel: string;
|
|
28
30
|
}
|
|
29
31
|
export declare const FREE_DEFAULTS: ResolvedConfig;
|
|
30
32
|
export declare const SENSITIVITY_PRESETS: Record<string, Partial<ResolvedConfig>>;
|
package/dist/config.js
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
const DEFAULT_TTS_VOICE = "en-US-AvaNeural";
|
|
2
2
|
const DEFAULT_TTS_RATE = "+25%";
|
|
3
|
+
const MODEL_NAMES = {
|
|
4
|
+
base: "ggml-base.bin",
|
|
5
|
+
small: "ggml-small.bin",
|
|
6
|
+
medium: "ggml-medium.bin",
|
|
7
|
+
large: "ggml-large-v3.bin",
|
|
8
|
+
};
|
|
9
|
+
function resolveModelPath(model) {
|
|
10
|
+
const home = process.env.HOME || "/tmp";
|
|
11
|
+
const envModel = process.env.WHISPER_MODEL;
|
|
12
|
+
if (envModel)
|
|
13
|
+
return envModel;
|
|
14
|
+
const filename = MODEL_NAMES[model];
|
|
15
|
+
if (filename)
|
|
16
|
+
return `${home}/.local/bin/${filename}`;
|
|
17
|
+
return model;
|
|
18
|
+
}
|
|
3
19
|
export const FREE_DEFAULTS = {
|
|
4
20
|
isLicensed: false,
|
|
5
21
|
micThreshold: 0.008,
|
|
@@ -13,6 +29,7 @@ export const FREE_DEFAULTS = {
|
|
|
13
29
|
tts: true,
|
|
14
30
|
ttsVoice: DEFAULT_TTS_VOICE,
|
|
15
31
|
ttsRate: DEFAULT_TTS_RATE,
|
|
32
|
+
whisperModel: resolveModelPath("base"),
|
|
16
33
|
};
|
|
17
34
|
export const SENSITIVITY_PRESETS = {
|
|
18
35
|
low: {
|
|
@@ -49,6 +66,9 @@ export function resolveConfig(userConfig, isLicensed) {
|
|
|
49
66
|
tts: userConfig.tts ?? FREE_DEFAULTS.tts,
|
|
50
67
|
ttsVoice: userConfig.ttsVoice ?? FREE_DEFAULTS.ttsVoice,
|
|
51
68
|
ttsRate: userConfig.ttsRate ?? FREE_DEFAULTS.ttsRate,
|
|
69
|
+
whisperModel: userConfig.whisperModel
|
|
70
|
+
? resolveModelPath(userConfig.whisperModel)
|
|
71
|
+
: FREE_DEFAULTS.whisperModel,
|
|
52
72
|
};
|
|
53
73
|
const preset = SENSITIVITY_PRESETS[base.sensitivity];
|
|
54
74
|
if (!userConfig.timingWindowMs)
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,6 @@ import { readFileSync, existsSync, unlinkSync } from "node:fs";
|
|
|
12
12
|
let activeSessionId = null;
|
|
13
13
|
let pendingInterrupt = null;
|
|
14
14
|
const RECORDING_FILE = "/tmp/interrupt-ptt.wav";
|
|
15
|
-
const WHISPER_MODEL = process.env.WHISPER_MODEL || `${process.env.HOME}/.local/bin/ggml-base.bin`;
|
|
16
15
|
let recordingProcess = null;
|
|
17
16
|
let pttActive = false;
|
|
18
17
|
function pttStartRecording() {
|
|
@@ -33,7 +32,7 @@ function pttStopRecording() {
|
|
|
33
32
|
recordingProcess = null;
|
|
34
33
|
}
|
|
35
34
|
const TXT_FILE = "/tmp/interrupt-ptt.txt";
|
|
36
|
-
async function transcribeLocal() {
|
|
35
|
+
async function transcribeLocal(modelPath) {
|
|
37
36
|
try {
|
|
38
37
|
execSync("whisper --help", { stdio: "ignore", timeout: 3000 });
|
|
39
38
|
}
|
|
@@ -45,7 +44,7 @@ async function transcribeLocal() {
|
|
|
45
44
|
}
|
|
46
45
|
catch { /* ignore */ }
|
|
47
46
|
try {
|
|
48
|
-
execSync(`whisper -f "${RECORDING_FILE}" -m "${
|
|
47
|
+
execSync(`whisper -f "${RECORDING_FILE}" -m "${modelPath}" -otxt -of /tmp/interrupt-ptt`, { stdio: "ignore", timeout: 30000 });
|
|
49
48
|
}
|
|
50
49
|
catch {
|
|
51
50
|
return null;
|
|
@@ -83,17 +82,25 @@ async function transcribeAPI() {
|
|
|
83
82
|
return null;
|
|
84
83
|
}
|
|
85
84
|
}
|
|
86
|
-
async function transcribeAndSend(sessionID, directory, api) {
|
|
85
|
+
async function transcribeAndSend(sessionID, directory, api, modelPath) {
|
|
87
86
|
pttStopRecording();
|
|
88
87
|
if (!existsSync(RECORDING_FILE)) {
|
|
89
88
|
api.ui.toast({ variant: "warning", title: "PTT", message: "No audio captured" });
|
|
90
89
|
return;
|
|
91
90
|
}
|
|
92
|
-
|
|
91
|
+
const hasApiKey = !!process.env.OPENAI_API_KEY;
|
|
92
|
+
api.ui.toast({ variant: "info", title: "PTT", message: hasApiKey ? "⏳ Transcribing via OpenAI API..." : "⏳ Transcribing with Whisper..." });
|
|
93
93
|
let text = null;
|
|
94
|
-
|
|
95
|
-
if (!text)
|
|
94
|
+
if (hasApiKey) {
|
|
96
95
|
text = await transcribeAPI();
|
|
96
|
+
if (!text)
|
|
97
|
+
text = await transcribeLocal(modelPath);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
text = await transcribeLocal(modelPath);
|
|
101
|
+
if (!text)
|
|
102
|
+
text = await transcribeAPI();
|
|
103
|
+
}
|
|
97
104
|
if (!text) {
|
|
98
105
|
api.ui.toast({ variant: "error", title: "PTT", message: "❌ Install whisper: run scripts/install-whisper.sh, or set OPENAI_API_KEY" });
|
|
99
106
|
try {
|
|
@@ -115,17 +122,25 @@ async function transcribeAndSend(sessionID, directory, api) {
|
|
|
115
122
|
const preview = text.length > 80 ? text.slice(0, 77) + "..." : text;
|
|
116
123
|
api.ui.toast({ variant: "success", title: "PTT", message: `✅ Sent: "${preview}"` });
|
|
117
124
|
}
|
|
118
|
-
async function transcribeAndSendV1(sessionID, client) {
|
|
125
|
+
async function transcribeAndSendV1(sessionID, client, modelPath) {
|
|
119
126
|
pttStopRecording();
|
|
120
127
|
if (!existsSync(RECORDING_FILE)) {
|
|
121
128
|
await client.tui.showToast({ body: { title: "PTT", message: "⚠️ No audio captured — try again", variant: "warning" } });
|
|
122
129
|
return;
|
|
123
130
|
}
|
|
124
|
-
|
|
131
|
+
const hasApiKey = !!process.env.OPENAI_API_KEY;
|
|
132
|
+
await client.tui.showToast({ body: { title: "PTT", message: hasApiKey ? "⏳ Transcribing via OpenAI API..." : "⏳ Transcribing with Whisper...", variant: "info" } });
|
|
125
133
|
let text = null;
|
|
126
|
-
|
|
127
|
-
if (!text)
|
|
134
|
+
if (hasApiKey) {
|
|
128
135
|
text = await transcribeAPI();
|
|
136
|
+
if (!text)
|
|
137
|
+
text = await transcribeLocal(modelPath);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
text = await transcribeLocal(modelPath);
|
|
141
|
+
if (!text)
|
|
142
|
+
text = await transcribeAPI();
|
|
143
|
+
}
|
|
129
144
|
if (!text) {
|
|
130
145
|
await client.tui.showToast({ body: { title: "PTT", message: "❌ Install whisper: run scripts/install-whisper.sh, or set OPENAI_API_KEY", variant: "error", duration: 8000 } });
|
|
131
146
|
try {
|
|
@@ -330,7 +345,7 @@ export const InterruptPlugin = (userConfig = {}) => {
|
|
|
330
345
|
const sessionID = cmdInput.sessionID;
|
|
331
346
|
if (pttActive) {
|
|
332
347
|
pttActive = false;
|
|
333
|
-
await transcribeAndSendV1(sessionID, client);
|
|
348
|
+
await transcribeAndSendV1(sessionID, client, config.whisperModel);
|
|
334
349
|
}
|
|
335
350
|
else {
|
|
336
351
|
pttActive = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-interrupt-plugin",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.34",
|
|
4
4
|
"description": "Streaming TTS + voice interruption for OpenCode. Speaks responses as they arrive and detects when you talk over it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|