feat: voice stt+tts just first have voice
Browse files- scripts/transcribe.py +21 -19
- scripts/whisper_server.py +80 -0
- src/commands/config/config.tsx +6 -6
- src/commands/config/config.tsx.bak +7 -0
- src/commands/voice/voice.ts +1 -0
- src/components/Settings/Config.tsx +0 -0
- src/components/Settings/Config.tsx.bak +0 -0
- src/components/Settings/Settings.tsx +132 -133
- src/components/Settings/Settings.tsx.bak +137 -0
- src/hooks/useAutoTTS.ts +0 -1
- src/hooks/useVoice.ts +2 -1
- src/screens/REPL.tsx +0 -0
- src/screens/REPL.tsx.bak +0 -0
- src/services/voice/edgeTTS.ts +6 -2
- src/services/voice/whisperSTT.ts +138 -94
- src/utils/processUserInput/processSlashCommand.tsx +0 -0
- src/utils/processUserInput/processSlashCommand.tsx.bak +0 -0
scripts/transcribe.py
CHANGED
|
@@ -11,36 +11,38 @@ def write_wav(path: str, raw_pcm: bytes, sample_rate: int = 16000):
|
|
| 11 |
w.setframerate(sample_rate)
|
| 12 |
w.writeframes(raw_pcm)
|
| 13 |
|
| 14 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
try:
|
| 16 |
-
import
|
| 17 |
-
|
| 18 |
-
|
|
|
|
| 19 |
if language:
|
| 20 |
opts["language"] = language
|
| 21 |
-
|
| 22 |
-
|
|
|
|
| 23 |
except ImportError:
|
| 24 |
pass
|
| 25 |
|
| 26 |
try:
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
local_path = os.path.expanduser(f"~/.cache/huggingface/hub/models--Systran--faster-whisper-{model_size}")
|
| 31 |
-
if os.path.isdir(local_path):
|
| 32 |
-
model_path = local_path
|
| 33 |
-
model = WhisperModel(model_path, device="cpu", compute_type="int8")
|
| 34 |
-
opts = {"beam_size": 1}
|
| 35 |
if language:
|
| 36 |
opts["language"] = language
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
return {"success": True, "text": text.strip(), "language": info.language}
|
| 40 |
except ImportError:
|
| 41 |
pass
|
| 42 |
|
| 43 |
-
return {"success": False, "error": "Neither
|
| 44 |
|
| 45 |
def main():
|
| 46 |
if len(sys.argv) < 2:
|
|
@@ -48,7 +50,7 @@ def main():
|
|
| 48 |
sys.exit(1)
|
| 49 |
|
| 50 |
wav_path = sys.argv[1]
|
| 51 |
-
model_size = "
|
| 52 |
language = None
|
| 53 |
|
| 54 |
i = 2
|
|
|
|
| 11 |
w.setframerate(sample_rate)
|
| 12 |
w.writeframes(raw_pcm)
|
| 13 |
|
| 14 |
+
def get_model_path(name):
|
| 15 |
+
lp = os.path.expanduser(f"~/.cache/huggingface/hub/models--Systran--faster-whisper-{name}")
|
| 16 |
+
if os.path.isdir(lp):
|
| 17 |
+
return lp
|
| 18 |
+
return name
|
| 19 |
+
|
| 20 |
+
def transcribe(wav_path: str, model_size: str = "small", language: str | None = None) -> dict:
|
| 21 |
try:
|
| 22 |
+
from faster_whisper import WhisperModel
|
| 23 |
+
model_path = get_model_path(model_size)
|
| 24 |
+
model = WhisperModel(model_path, device="cuda", compute_type="int8_float16")
|
| 25 |
+
opts = {"beam_size": 5}
|
| 26 |
if language:
|
| 27 |
opts["language"] = language
|
| 28 |
+
segments, info = model.transcribe(wav_path, **opts)
|
| 29 |
+
text = " ".join(seg.text for seg in segments)
|
| 30 |
+
return {"success": True, "text": text.strip(), "language": info.language}
|
| 31 |
except ImportError:
|
| 32 |
pass
|
| 33 |
|
| 34 |
try:
|
| 35 |
+
import whisper
|
| 36 |
+
model = whisper.load_model(model_size, device="cuda", download_root=WHISPER_CACHE)
|
| 37 |
+
opts = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
if language:
|
| 39 |
opts["language"] = language
|
| 40 |
+
result = model.transcribe(wav_path, **opts)
|
| 41 |
+
return {"success": True, "text": result["text"].strip(), "language": result.get("language", "")}
|
|
|
|
| 42 |
except ImportError:
|
| 43 |
pass
|
| 44 |
|
| 45 |
+
return {"success": False, "error": "Neither faster-whisper nor openai-whisper is installed. Run: pip install faster-whisper"}
|
| 46 |
|
| 47 |
def main():
|
| 48 |
if len(sys.argv) < 2:
|
|
|
|
| 50 |
sys.exit(1)
|
| 51 |
|
| 52 |
wav_path = sys.argv[1]
|
| 53 |
+
model_size = "small"
|
| 54 |
language = None
|
| 55 |
|
| 56 |
i = 2
|
scripts/whisper_server.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json, os, sys, wave
|
| 2 |
+
|
| 3 |
+
WHISPER_CACHE = os.path.expanduser("~/.cache/whisper")
|
| 4 |
+
|
| 5 |
+
def write_wav(path, raw_pcm, sample_rate=16000):
|
| 6 |
+
with wave.open(path, 'wb') as w:
|
| 7 |
+
w.setnchannels(1)
|
| 8 |
+
w.setsampwidth(2)
|
| 9 |
+
w.setframerate(sample_rate)
|
| 10 |
+
w.writeframes(raw_pcm)
|
| 11 |
+
|
| 12 |
+
def get_model_path(name):
|
| 13 |
+
lp = os.path.expanduser(f'~/.cache/huggingface/hub/models--Systran--faster-whisper-{name}')
|
| 14 |
+
if os.path.isdir(lp):
|
| 15 |
+
return lp
|
| 16 |
+
return name
|
| 17 |
+
|
| 18 |
+
def main():
|
| 19 |
+
model = None
|
| 20 |
+
model_name = None
|
| 21 |
+
|
| 22 |
+
for line in sys.stdin:
|
| 23 |
+
line = line.strip()
|
| 24 |
+
if not line:
|
| 25 |
+
continue
|
| 26 |
+
try:
|
| 27 |
+
msg = json.loads(line)
|
| 28 |
+
except:
|
| 29 |
+
continue
|
| 30 |
+
|
| 31 |
+
msg_type = msg.get('type')
|
| 32 |
+
|
| 33 |
+
if msg_type == 'load':
|
| 34 |
+
name = msg.get('model', 'small')
|
| 35 |
+
print(json.dumps({'type': 'ready', 'model': name}), flush=True)
|
| 36 |
+
sys.stdout.flush()
|
| 37 |
+
|
| 38 |
+
elif msg_type == 'transcribe':
|
| 39 |
+
wav_path = msg.get('wav')
|
| 40 |
+
language = msg.get('language')
|
| 41 |
+
|
| 42 |
+
if not model or model_name != msg.get('model'):
|
| 43 |
+
model_name = msg.get('model', 'base')
|
| 44 |
+
print(json.dumps({'type': 'status', 'message': f'Loading model {model_name}...'}), flush=True)
|
| 45 |
+
try:
|
| 46 |
+
import whisper
|
| 47 |
+
model = whisper.load_model(model_name, device='cuda', download_root=WHISPER_CACHE)
|
| 48 |
+
print(json.dumps({'type': 'status', 'message': 'Model loaded'}), flush=True)
|
| 49 |
+
except ImportError:
|
| 50 |
+
try:
|
| 51 |
+
from faster_whisper import WhisperModel
|
| 52 |
+
mp = get_model_path(model_name)
|
| 53 |
+
model = WhisperModel(mp, device='cuda', compute_type='int8_float16')
|
| 54 |
+
print(json.dumps({'type': 'status', 'message': 'Model loaded'}), flush=True)
|
| 55 |
+
except (ImportError, RuntimeError) as e:
|
| 56 |
+
print(json.dumps({'type': 'error', 'message': f'No whisper library available: {e}'}), flush=True)
|
| 57 |
+
sys.stdout.flush()
|
| 58 |
+
continue
|
| 59 |
+
|
| 60 |
+
if not wav_path or not os.path.exists(wav_path):
|
| 61 |
+
print(json.dumps({'type': 'error', 'message': 'WAV file not found'}), flush=True)
|
| 62 |
+
sys.stdout.flush()
|
| 63 |
+
continue
|
| 64 |
+
|
| 65 |
+
try:
|
| 66 |
+
opts = {}
|
| 67 |
+
if language:
|
| 68 |
+
opts['language'] = language
|
| 69 |
+
|
| 70 |
+
result = model.transcribe(wav_path, **opts)
|
| 71 |
+
text = result['text'].strip() if isinstance(result, dict) else ''
|
| 72 |
+
lang = result.get('language', language or 'en') if isinstance(result, dict) else (language or 'en')
|
| 73 |
+
|
| 74 |
+
print(json.dumps({'type': 'result', 'text': text, 'language': lang}), flush=True)
|
| 75 |
+
except Exception as e:
|
| 76 |
+
print(json.dumps({'type': 'error', 'message': str(e)}), flush=True)
|
| 77 |
+
sys.stdout.flush()
|
| 78 |
+
|
| 79 |
+
if __name__ == '__main__':
|
| 80 |
+
main()
|
src/commands/config/config.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
-
import * as React from 'react'
|
| 2 |
-
import { Settings } from '../../components/Settings/Settings.js'
|
| 3 |
-
import type { LocalJSXCommandCall } from '../../types/command.js'
|
|
|
|
| 4 |
export const call: LocalJSXCommandCall = async (onDone, context) => {
|
| 5 |
-
return <Settings onClose={onDone} context={context} defaultTab="Config" />
|
| 6 |
-
}
|
| 7 |
-
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIlNldHRpbmdzIiwiTG9jYWxKU1hDb21tYW5kQ2FsbCIsImNhbGwiLCJvbkRvbmUiLCJjb250ZXh0Il0sInNvdXJjZXMiOlsiY29uZmlnLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IFNldHRpbmdzIH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9TZXR0aW5ncy9TZXR0aW5ncy5qcydcbmltcG9ydCB0eXBlIHsgTG9jYWxKU1hDb21tYW5kQ2FsbCB9IGZyb20gJy4uLy4uL3R5cGVzL2NvbW1hbmQuanMnXG5cbmV4cG9ydCBjb25zdCBjYWxsOiBMb2NhbEpTWENvbW1hbmRDYWxsID0gYXN5bmMgKG9uRG9uZSwgY29udGV4dCkgPT4ge1xuICByZXR1cm4gPFNldHRpbmdzIG9uQ2xvc2U9e29uRG9uZX0gY29udGV4dD17Y29udGV4dH0gZGVmYXVsdFRhYj1cIkNvbmZpZ1wiIC8+XG59XG4iXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBS0EsS0FBSyxNQUFNLE9BQU87QUFDOUIsU0FBU0MsUUFBUSxRQUFRLHVDQUF1QztBQUNoRSxjQUFjQyxtQkFBbUIsUUFBUSx3QkFBd0I7QUFFakUsT0FBTyxNQUFNQyxJQUFJLEVBQUVELG1CQUFtQixHQUFHLE1BQUFDLENBQU9DLE1BQU0sRUFBRUMsT0FBTyxLQUFLO0VBQ2xFLE9BQU8sQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLENBQUNELE1BQU0sQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDQyxPQUFPLENBQUMsQ0FBQyxVQUFVLENBQUMsUUFBUSxHQUFHO0FBQzVFLENBQUMiLCJpZ25vcmVMaXN0IjpbXX0=
|
|
|
|
| 1 |
+
import * as React from 'react'
|
| 2 |
+
import { Settings } from '../../components/Settings/Settings.js'
|
| 3 |
+
import type { LocalJSXCommandCall } from '../../types/command.js'
|
| 4 |
+
|
| 5 |
export const call: LocalJSXCommandCall = async (onDone, context) => {
|
| 6 |
+
return <Settings onClose={onDone} context={context} defaultTab="Config" />
|
| 7 |
+
}
|
|
|
src/commands/config/config.tsx.bak
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from 'react';
|
| 2 |
+
import { Settings } from '../../components/Settings/Settings.js';
|
| 3 |
+
import type { LocalJSXCommandCall } from '../../types/command.js';
|
| 4 |
+
export const call: LocalJSXCommandCall = async (onDone, context) => {
|
| 5 |
+
return <Settings onClose={onDone} context={context} defaultTab="Config" />;
|
| 6 |
+
};
|
| 7 |
+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIlNldHRpbmdzIiwiTG9jYWxKU1hDb21tYW5kQ2FsbCIsImNhbGwiLCJvbkRvbmUiLCJjb250ZXh0Il0sInNvdXJjZXMiOlsiY29uZmlnLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IFNldHRpbmdzIH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9TZXR0aW5ncy9TZXR0aW5ncy5qcydcbmltcG9ydCB0eXBlIHsgTG9jYWxKU1hDb21tYW5kQ2FsbCB9IGZyb20gJy4uLy4uL3R5cGVzL2NvbW1hbmQuanMnXG5cbmV4cG9ydCBjb25zdCBjYWxsOiBMb2NhbEpTWENvbW1hbmRDYWxsID0gYXN5bmMgKG9uRG9uZSwgY29udGV4dCkgPT4ge1xuICByZXR1cm4gPFNldHRpbmdzIG9uQ2xvc2U9e29uRG9uZX0gY29udGV4dD17Y29udGV4dH0gZGVmYXVsdFRhYj1cIkNvbmZpZ1wiIC8+XG59XG4iXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBS0EsS0FBSyxNQUFNLE9BQU87QUFDOUIsU0FBU0MsUUFBUSxRQUFRLHVDQUF1QztBQUNoRSxjQUFjQyxtQkFBbUIsUUFBUSx3QkFBd0I7QUFFakUsT0FBTyxNQUFNQyxJQUFJLEVBQUVELG1CQUFtQixHQUFHLE1BQUFDLENBQU9DLE1BQU0sRUFBRUMsT0FBTyxLQUFLO0VBQ2xFLE9BQU8sQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLENBQUNELE1BQU0sQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDQyxPQUFPLENBQUMsQ0FBQyxVQUFVLENBQUMsUUFBUSxHQUFHO0FBQzVFLENBQUMiLCJpZ25vcmVMaXN0IjpbXX0=
|
src/commands/voice/voice.ts
CHANGED
|
@@ -92,6 +92,7 @@ export const call: LocalCommandCall = async () => {
|
|
| 92 |
const result = updateSettingsForSource('userSettings', {
|
| 93 |
voiceEnabled: true,
|
| 94 |
voiceProvider: 'local' as const,
|
|
|
|
| 95 |
})
|
| 96 |
if (result.error) {
|
| 97 |
return {
|
|
|
|
| 92 |
const result = updateSettingsForSource('userSettings', {
|
| 93 |
voiceEnabled: true,
|
| 94 |
voiceProvider: 'local' as const,
|
| 95 |
+
voiceAutoTTS: true,
|
| 96 |
})
|
| 97 |
if (result.error) {
|
| 98 |
return {
|
src/components/Settings/Config.tsx
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/components/Settings/Config.tsx.bak
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/components/Settings/Settings.tsx
CHANGED
|
@@ -1,137 +1,136 @@
|
|
| 1 |
-
import { c as _c } from "react/compiler-runtime";
|
| 2 |
// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered
|
| 3 |
-
import * as React from 'react'
|
| 4 |
-
import { Suspense, useState } from 'react'
|
| 5 |
-
import { useKeybinding } from '../../keybindings/useKeybinding.js'
|
| 6 |
-
import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js'
|
| 7 |
-
import { useTerminalSize } from '../../hooks/useTerminalSize.js'
|
| 8 |
-
import {
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
import {
|
| 13 |
-
import {
|
| 14 |
-
import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
type Props = {
|
| 16 |
-
onClose: (
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
export function Settings(t0) {
|
| 23 |
-
const $ = _c(25);
|
| 24 |
-
const {
|
| 25 |
-
onClose,
|
| 26 |
-
context,
|
| 27 |
-
defaultTab
|
| 28 |
-
} = t0;
|
| 29 |
-
const [selectedTab, setSelectedTab] = useState(defaultTab);
|
| 30 |
-
const [tabsHidden, setTabsHidden] = useState(false);
|
| 31 |
-
const [configOwnsEsc, setConfigOwnsEsc] = useState(false);
|
| 32 |
-
const [gatesOwnsEsc, setGatesOwnsEsc] = useState(false);
|
| 33 |
-
const insideModal = useIsInsideModal();
|
| 34 |
-
const {
|
| 35 |
-
rows
|
| 36 |
-
} = useModalOrTerminalSize(useTerminalSize());
|
| 37 |
-
const contentHeight = insideModal ? rows + 1 : Math.max(15, Math.min(Math.floor(rows * 0.8), 30));
|
| 38 |
-
const [diagnosticsPromise] = useState(_temp2);
|
| 39 |
-
useExitOnCtrlCDWithKeybindings();
|
| 40 |
-
let t1;
|
| 41 |
-
if ($[0] !== onClose || $[1] !== tabsHidden) {
|
| 42 |
-
t1 = () => {
|
| 43 |
-
if (tabsHidden) {
|
| 44 |
-
return;
|
| 45 |
-
}
|
| 46 |
-
onClose("Status dialog dismissed", {
|
| 47 |
-
display: "system"
|
| 48 |
-
});
|
| 49 |
-
};
|
| 50 |
-
$[0] = onClose;
|
| 51 |
-
$[1] = tabsHidden;
|
| 52 |
-
$[2] = t1;
|
| 53 |
-
} else {
|
| 54 |
-
t1 = $[2];
|
| 55 |
-
}
|
| 56 |
-
const handleEscape = t1;
|
| 57 |
-
const t2 = !tabsHidden && !(selectedTab === "Config" && configOwnsEsc) && !(selectedTab === "Gates" && gatesOwnsEsc);
|
| 58 |
-
let t3;
|
| 59 |
-
if ($[3] !== t2) {
|
| 60 |
-
t3 = {
|
| 61 |
-
context: "Settings",
|
| 62 |
-
isActive: t2
|
| 63 |
-
};
|
| 64 |
-
$[3] = t2;
|
| 65 |
-
$[4] = t3;
|
| 66 |
-
} else {
|
| 67 |
-
t3 = $[4];
|
| 68 |
-
}
|
| 69 |
-
useKeybinding("confirm:no", handleEscape, t3);
|
| 70 |
-
let t4;
|
| 71 |
-
if ($[5] !== context || $[6] !== diagnosticsPromise) {
|
| 72 |
-
t4 = <Tab key="status" title="Status"><Status context={context} diagnosticsPromise={diagnosticsPromise} /></Tab>;
|
| 73 |
-
$[5] = context;
|
| 74 |
-
$[6] = diagnosticsPromise;
|
| 75 |
-
$[7] = t4;
|
| 76 |
-
} else {
|
| 77 |
-
t4 = $[7];
|
| 78 |
-
}
|
| 79 |
-
let t5;
|
| 80 |
-
if ($[8] !== contentHeight || $[9] !== context || $[10] !== onClose) {
|
| 81 |
-
t5 = <Tab key="config" title="Config"><Suspense fallback={null}><Config context={context} onClose={onClose} setTabsHidden={setTabsHidden} onIsSearchModeChange={setConfigOwnsEsc} contentHeight={contentHeight} /></Suspense></Tab>;
|
| 82 |
-
$[8] = contentHeight;
|
| 83 |
-
$[9] = context;
|
| 84 |
-
$[10] = onClose;
|
| 85 |
-
$[11] = t5;
|
| 86 |
-
} else {
|
| 87 |
-
t5 = $[11];
|
| 88 |
-
}
|
| 89 |
-
let t6;
|
| 90 |
-
if ($[12] === Symbol.for("react.memo_cache_sentinel")) {
|
| 91 |
-
t6 = <Tab key="usage" title="Usage"><Usage /></Tab>;
|
| 92 |
-
$[12] = t6;
|
| 93 |
-
} else {
|
| 94 |
-
t6 = $[12];
|
| 95 |
-
}
|
| 96 |
-
let t7;
|
| 97 |
-
if ($[13] !== contentHeight) {
|
| 98 |
-
t7 = false ? [<Tab key="gates" title="Gates"><Gates onOwnsEscChange={setGatesOwnsEsc} contentHeight={contentHeight} /></Tab>] : [];
|
| 99 |
-
$[13] = contentHeight;
|
| 100 |
-
$[14] = t7;
|
| 101 |
-
} else {
|
| 102 |
-
t7 = $[14];
|
| 103 |
-
}
|
| 104 |
-
let t8;
|
| 105 |
-
if ($[15] !== t4 || $[16] !== t5 || $[17] !== t7) {
|
| 106 |
-
t8 = [t4, t5, t6, ...t7];
|
| 107 |
-
$[15] = t4;
|
| 108 |
-
$[16] = t5;
|
| 109 |
-
$[17] = t7;
|
| 110 |
-
$[18] = t8;
|
| 111 |
-
} else {
|
| 112 |
-
t8 = $[18];
|
| 113 |
-
}
|
| 114 |
-
const tabs = t8;
|
| 115 |
-
const t9 = defaultTab !== "Config" && defaultTab !== "Gates";
|
| 116 |
-
const t10 = tabsHidden || insideModal ? undefined : contentHeight;
|
| 117 |
-
let t11;
|
| 118 |
-
if ($[19] !== selectedTab || $[20] !== t10 || $[21] !== t9 || $[22] !== tabs || $[23] !== tabsHidden) {
|
| 119 |
-
t11 = <Pane color="permission"><Tabs color="permission" selectedTab={selectedTab} onTabChange={setSelectedTab} hidden={tabsHidden} initialHeaderFocused={t9} contentHeight={t10}>{tabs}</Tabs></Pane>;
|
| 120 |
-
$[19] = selectedTab;
|
| 121 |
-
$[20] = t10;
|
| 122 |
-
$[21] = t9;
|
| 123 |
-
$[22] = tabs;
|
| 124 |
-
$[23] = tabsHidden;
|
| 125 |
-
$[24] = t11;
|
| 126 |
-
} else {
|
| 127 |
-
t11 = $[24];
|
| 128 |
-
}
|
| 129 |
-
return t11;
|
| 130 |
}
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
}
|
| 137 |
-
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIlN1c3BlbnNlIiwidXNlU3RhdGUiLCJ1c2VLZXliaW5kaW5nIiwidXNlRXhpdE9uQ3RybENEV2l0aEtleWJpbmRpbmdzIiwidXNlVGVybWluYWxTaXplIiwidXNlSXNJbnNpZGVNb2RhbCIsInVzZU1vZGFsT3JUZXJtaW5hbFNpemUiLCJQYW5lIiwiVGFicyIsIlRhYiIsIlN0YXR1cyIsImJ1aWxkRGlhZ25vc3RpY3MiLCJDb25maWciLCJVc2FnZSIsIkxvY2FsSlNYQ29tbWFuZENvbnRleHQiLCJDb21tYW5kUmVzdWx0RGlzcGxheSIsIlByb3BzIiwib25DbG9zZSIsInJlc3VsdCIsIm9wdGlvbnMiLCJkaXNwbGF5IiwiY29udGV4dCIsImRlZmF1bHRUYWIiLCJTZXR0aW5ncyIsInQwIiwiJCIsIl9jIiwic2VsZWN0ZWRUYWIiLCJzZXRTZWxlY3RlZFRhYiIsInRhYnNIaWRkZW4iLCJzZXRUYWJzSGlkZGVuIiwiY29uZmlnT3duc0VzYyIsInNldENvbmZpZ093bnNFc2MiLCJnYXRlc093bnNFc2MiLCJzZXRHYXRlc093bnNFc2MiLCJpbnNpZGVNb2RhbCIsInJvd3MiLCJjb250ZW50SGVpZ2h0IiwiTWF0aCIsIm1heCIsIm1pbiIsImZsb29yIiwiZGlhZ25vc3RpY3NQcm9taXNlIiwiX3RlbXAyIiwidDEiLCJoYW5kbGVFc2NhcGUiLCJ0MiIsInQzIiwiaXNBY3RpdmUiLCJ0NCIsInQ1IiwidDYiLCJTeW1ib2wiLCJmb3IiLCJ0NyIsInQ4IiwidGFicyIsInQ5IiwidDEwIiwidW5kZWZpbmVkIiwidDExIiwiY2F0Y2giLCJfdGVtcCJdLCJzb3VyY2VzIjpbIlNldHRpbmdzLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyIvLyBiaW9tZS1pZ25vcmUtYWxsIGFzc2lzdC9zb3VyY2Uvb3JnYW5pemVJbXBvcnRzOiBBTlQtT05MWSBpbXBvcnQgbWFya2VycyBtdXN0IG5vdCBiZSByZW9yZGVyZWRcbmltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgU3VzcGVuc2UsIHVzZVN0YXRlIH0gZnJvbSAncmVhY3QnXG5pbXBvcnQgeyB1c2VLZXliaW5kaW5nIH0gZnJvbSAnLi4vLi4va2V5YmluZGluZ3MvdXNlS2V5YmluZGluZy5qcydcbmltcG9ydCB7IHVzZUV4aXRPbkN0cmxDRFdpdGhLZXliaW5kaW5ncyB9IGZyb20gJy4uLy4uL2hvb2tzL3VzZUV4aXRPbkN0cmxDRFdpdGhLZXliaW5kaW5ncy5qcydcbmltcG9ydCB7IHVzZVRlcm1pbmFsU2l6ZSB9IGZyb20gJy4uLy4uL2hvb2tzL3VzZVRlcm1pbmFsU2l6ZS5qcydcbmltcG9ydCB7XG4gIHVzZUlzSW5zaWRlTW9kYWwsXG4gIHVzZU1vZGFsT3JUZXJtaW5hbFNpemUsXG59IGZyb20gJy4uLy4uL2NvbnRleHQvbW9kYWxDb250ZXh0LmpzJ1xuaW1wb3J0IHsgUGFuZSB9IGZyb20gJy4uL2Rlc2lnbi1zeXN0ZW0vUGFuZS5qcydcbmltcG9ydCB7IFRhYnMsIFRhYiB9IGZyb20gJy4uL2Rlc2lnbi1zeXN0ZW0vVGFicy5qcydcbmltcG9ydCB7IFN0YXR1cywgYnVpbGREaWFnbm9zdGljcyB9IGZyb20gJy4vU3RhdHVzLmpzJ1xuaW1wb3J0IHsgQ29uZmlnIH0gZnJvbSAnLi9Db25maWcuanMnXG5pbXBvcnQgeyBVc2FnZSB9IGZyb20gJy4vVXNhZ2UuanMnXG5pbXBvcnQgdHlwZSB7XG4gIExvY2FsSlNYQ29tbWFuZENvbnRleHQsXG4gIENvbW1hbmRSZXN1bHREaXNwbGF5LFxufSBmcm9tICcuLi8uLi9jb21tYW5kcy5qcydcblxudHlwZSBQcm9wcyA9IHtcbiAgb25DbG9zZTogKFxuICAgIHJlc3VsdD86IHN0cmluZyxcbiAgICBvcHRpb25zPzogeyBkaXNwbGF5PzogQ29tbWFuZFJlc3VsdERpc3BsYXkgfSxcbiAgKSA9PiB2b2lkXG4gIGNvbnRleHQ6IExvY2FsSlNYQ29tbWFuZENvbnRleHRcbiAgZGVmYXVsdFRhYjogJ1N0YXR1cycgfCAnQ29uZmlnJyB8ICdVc2FnZScgfCAnR2F0ZXMnXG59XG5cbmV4cG9ydCBmdW5jdGlvbiBTZXR0aW5ncyh7XG4gIG9uQ2xvc2UsXG4gIGNvbnRleHQsXG4gIGRlZmF1bHRUYWIsXG59OiBQcm9wcyk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IFtzZWxlY3RlZFRhYiwgc2V0U2VsZWN0ZWRUYWJdID0gdXNlU3RhdGU8c3RyaW5nPihkZWZhdWx0VGFiKVxuICBjb25zdCBbdGFic0hpZGRlbiwgc2V0VGFic0hpZGRlbl0gPSB1c2VTdGF0ZShmYWxzZSlcbiAgLy8gVHJ1ZSB3aGlsZSBDb25maWcncyBvd24gRXNjIGhhbmRsZXIgaXMgYWN0aXZlIChzZWFyY2ggbW9kZSB3aXRoIGNvbnRlbnRcbiAgLy8gZm9jdXNlZCkuIFNldHRpbmdzIG11c3QgY2VkZSBFc2Mgc28gc2VhcmNoIGNhbiBjbGVhci9leGl0IGZpcnN0LlxuICBjb25zdCBbY29uZmlnT3duc0VzYywgc2V0Q29uZmlnT3duc0VzY10gPSB1c2VTdGF0ZShmYWxzZSlcbiAgY29uc3QgW2dhdGVzT3duc0VzYywgc2V0R2F0ZXNPd25zRXNjXSA9IHVzZVN0YXRlKGZhbHNlKVxuICAvLyBGaXhlZCBjb250ZW50IGhlaWdodCBzbyBzd2l0Y2hpbmcgdGFicyBkb2Vzbid0IHNoaWZ0IHRoZSBwYW5lIGhlaWdodC5cbiAgLy8gT3V0c2lkZSBtb2RhbHMgY2FwIGF0IG1pbig4MCUgdmlld3BvcnQsIDMwKS4gSW5zaWRlIGEgTW9kYWwgdGhlIG1vZGFsJ3NcbiAgLy8gaW5uZXJTaXplLnJvd3MgSVMgdGhlIFNjcm9sbEJveCB2aWV3cG9ydCDigJQgdGhlIDAuOCBtdWx0aXBsaWVyIG92ZXItXG4gIC8vIHNocmlua3MsIGxlYXZpbmcgZW1wdHkgcm93cyB3aGlsZSBDb25maWcgc2hvd3MgXCLihpMgTiBtb3JlIGJlbG93XCIuXG4gIC8vXG4gIC8vIEluc2lkZS1tb2RhbCBtYXRoOiBDb25maWcncyBwYW5lQ2FwLTEwIGNocm9tZSBlc3RpbWF0ZSB3YXMgdHVuZWQgZm9yXG4gIC8vIG1hcmdpblk9ezF9ICgyIHJvd3MpIHdoaWNoIGlzIHN0cmlwcGVkIGluc2lkZSBtb2RhbHMg4oaSICsyIHRvIHJlY292ZXIuXG4gIC8vIFRoZW4gLTIgZm9yIFRhYnMnIGhlYWRlciByb3cgKyBpdHMgbWFyZ2luVG9wPTEuIFBsdXMgKzEgb2JzZXJ2ZWQgZ2FwXG4gIC8vIGZyb20gdGhlIHBhbmVDYXAtMTAgZXN0aW1hdGUgYmVpbmcgc2xpZ2h0bHkgZ2VuZXJvdXMuIE5ldDogcm93cyArIDEuXG4gIGNvbnN0IGluc2lkZU1vZGFsID0gdXNlSXNJbnNpZGVNb2RhbCgpXG4gIGNvbnN0IHsgcm93cyB9ID0gdXNlTW9kYWxPclRlcm1pbmFsU2l6ZSh1c2VUZXJtaW5hbFNpemUoKSlcbiAgY29uc3QgY29udGVudEhlaWdodCA9IGluc2lkZU1vZGFsXG4gICAgPyByb3dzICsgMVxuICAgIDogTWF0aC5tYXgoMTUsIE1hdGgubWluKE1hdGguZmxvb3Iocm93cyAqIDAuOCksIDMwKSlcbiAgLy8gS2ljayBvZmYgZGlhZ25vc3RpY3Mgb25jZSB3aGVuIHRoZSBwYW5lIG9wZW5zLiBTdGF0dXMgdXNlKClzIHRoaXMgc29cbiAgLy8gaXQgcmVzb2x2ZXMgb25jZSBwZXIgL2NvbmZpZyBpbnZvY2F0aW9uIOKAlCBubyByZS1mZXRjaCBmbGFzaCB3aGVuXG4gIC8vIHRhYmJpbmcgYmFjayB0byBTdGF0dXMgKFRhYiB1bm1vdW50cyBjaGlsZHJlbiB3aGVuIG5vdCBzZWxlY3RlZCkuXG4gIGNvbnN0IFtkaWFnbm9zdGljc1Byb21pc2VdID0gdXNlU3RhdGUoKCkgPT5cbiAgICBidWlsZERpYWdub3N0aWNzKCkuY2F0Y2goKCkgPT4gW10pLFxuICApXG5cbiAgdXNlRXhpdE9uQ3RybENEV2l0aEtleWJpbmRpbmdzKClcblxuICAvLyBIYW5kbGUgZXNjYXBlIHZpYSBrZXliaW5kaW5nIC0gb25seSB3aGVuIG5vdCBpbiBzdWJtZW51XG4gIGNvbnN0IGhhbmRsZUVzY2FwZSA9ICgpID0+IHtcbiAgICAvLyBEb24ndCBoYW5kbGUgZXNjYXBlIHdoZW4gYSBzdWJtZW51IGlzIHNob3dpbmcgKHRhYnNIaWRkZW4gbWVhbnMgc3VibWVudSBpcyBvcGVuKVxuICAgIC8vIExldCB0aGUgc3VibWVudSBoYW5kbGUgZXNjYXBlIHRvIHJldHVybiB0byB0aGUgbWFpbiBtZW51XG4gICAgaWYgKHRhYnNIaWRkZW4pIHtcbiAgICAgIHJldHVyblxuICAgIH1cbiAgICAvLyBUT0RPOiBVcGRhdGUgdG8gXCJTZXR0aW5nc1wiIGRpYWxvZyBvbmNlIHdlIGRlZmluZSAnL3NldHRpbmdzJy5cbiAgICBvbkNsb3NlKCdTdGF0dXMgZGlhbG9nIGRpc21pc3NlZCcsIHsgZGlzcGxheTogJ3N5c3RlbScgfSlcbiAgfVxuXG4gIC8vIERpc2FibGUgd2hlbiBzdWJtZW51IGlzIG9wZW4gc28gdGhlIHN1Ym1lbnUncyBEaWFsb2cgY2FuIGhhbmRsZSBFU0MsXG4gIC8vIGFuZCB3aGVuIENvbmZpZydzIHNlYXJjaCBtb2RlIGlzIGFjdGl2ZSBzbyBpdHMgdXNlSW5wdXQgaGFuZGxlclxuICAvLyAoY2xlYXIgcXVlcnkg4oaSIGV4aXQgc2VhcmNoKSBwcm9jZXNzZXMgRXNjYXBlIGZpcnN0LlxuICB1c2VLZXliaW5kaW5nKCdjb25maXJtOm5vJywgaGFuZGxlRXNjYXBlLCB7XG4gICAgY29udGV4dDogJ1NldHRpbmdzJyxcbiAgICBpc0FjdGl2ZTpcbiAgICAgICF0YWJzSGlkZGVuICYmXG4gICAgICAhKHNlbGVjdGVkVGFiID09PSAnQ29uZmlnJyAmJiBjb25maWdPd25zRXNjKSAmJlxuICAgICAgIShzZWxlY3RlZFRhYiA9PT0gJ0dhdGVzJyAmJiBnYXRlc093bnNFc2MpLFxuICB9KVxuXG4gIGNvbnN0IHRhYnMgPSBbXG4gICAgPFRhYiBrZXk9XCJzdGF0dXNcIiB0aXRsZT1cIlN0YXR1c1wiPlxuICAgICAgPFN0YXR1cyBjb250ZXh0PXtjb250ZXh0fSBkaWFnbm9zdGljc1Byb21pc2U9e2RpYWdub3N0aWNzUHJvbWlzZX0gLz5cbiAgICA8L1RhYj4sXG4gICAgPFRhYiBrZXk9XCJjb25maWdcIiB0aXRsZT1cIkNvbmZpZ1wiPlxuICAgICAgPFN1c3BlbnNlIGZhbGxiYWNrPXtudWxsfT5cbiAgICAgICAgPENvbmZpZ1xuICAgICAgICAgIGNvbnRleHQ9e2NvbnRleHR9XG4gICAgICAgICAgb25DbG9zZT17b25DbG9zZX1cbiAgICAgICAgICBzZXRUYWJzSGlkZGVuPXtzZXRUYWJzSGlkZGVufVxuICAgICAgICAgIG9uSXNTZWFyY2hNb2RlQ2hhbmdlPXtzZXRDb25maWdPd25zRXNjfVxuICAgICAgICAgIGNvbnRlbnRIZWlnaHQ9e2NvbnRlbnRIZWlnaHR9XG4gICAgICAgIC8+XG4gICAgICA8L1N1c3BlbnNlPlxuICAgIDwvVGFiPixcbiAgICA8VGFiIGtleT1cInVzYWdlXCIgdGl0bGU9XCJVc2FnZVwiPlxuICAgICAgPFVzYWdlIC8+XG4gICAgPC9UYWI+LFxuICAgIC4uLihcImV4dGVybmFsXCIgPT09ICdhbnQnXG4gICAgICA/IFtcbiAgICAgICAgICA8VGFiIGtleT1cImdhdGVzXCIgdGl0bGU9XCJHYXRlc1wiPlxuICAgICAgICAgICAgPEdhdGVzXG4gICAgICAgICAgICAgIG9uT3duc0VzY0NoYW5nZT17c2V0R2F0ZXNPd25zRXNjfVxuICAgICAgICAgICAgICBjb250ZW50SGVpZ2h0PXtjb250ZW50SGVpZ2h0fVxuICAgICAgICAgICAgLz5cbiAgICAgICAgICA8L1RhYj4sXG4gICAgICAgIF1cbiAgICAgIDogW10pLFxuICBdXG5cbiAgcmV0dXJuIChcbiAgICA8UGFuZSBjb2xvcj1cInBlcm1pc3Npb25cIj5cbiAgICAgIDxUYWJzXG4gICAgICAgIGNvbG9yPVwicGVybWlzc2lvblwiXG4gICAgICAgIHNlbGVjdGVkVGFiPXtzZWxlY3RlZFRhYn1cbiAgICAgICAgb25UYWJDaGFuZ2U9e3NldFNlbGVjdGVkVGFifVxuICAgICAgICBoaWRkZW49e3RhYnNIaWRkZW59XG4gICAgICAgIC8vIENvbmZpZyBoYXMgaW50ZXJhY3RpdmUgY29udGVudCDigJQgc3RhcnQgd2l0aCBoZWFkZXIgdW5mb2N1c2VkIHNvXG4gICAgICAgIC8vIGxlZnQvcmlnaHQvdGFiIGN5Y2xlIG9wdGlvbiB2YWx1ZXMgaW5zdGVhZCBvZiBzd2l0Y2hpbmcgdGFicy5cbiAgICAgICAgaW5pdGlhbEhlYWRlckZvY3VzZWQ9e2RlZmF1bHRUYWIgIT09ICdDb25maWcnICYmIGRlZmF1bHRUYWIgIT09ICdHYXRlcyd9XG4gICAgICAgIC8vIEluc2lkZSBhIE1vZGFsLCBza2lwIHRoZSBUYWJzLWxldmVsIGNhcCBzbyB0YWxsIHRhYnMgKFN0YXR1cydzXG4gICAgICAgIC8vIE1DUCBsaXN0KSBmbG93IHRvIHRoZWlyIG5hdHVyYWwgaGVpZ2h0IGZvciB0aGUgTW9kYWwncyBTY3JvbGxCb3hcbiAgICAgICAgLy8gdG8gc2Nyb2xsLiBDb25maWcvR2F0ZXMgc3RpbGwgZ2V0IGNvbnRlbnRIZWlnaHQgYWJvdmUg4oCUIHRoZXlcbiAgICAgICAgLy8gcGFnaW5hdGUgaW50ZXJuYWxseSBzbyB0aGlzIG9ubHkgYWZmZWN0cyBTdGF0dXMvVXNhZ2UuXG4gICAgICAgIGNvbnRlbnRIZWlnaHQ9e3RhYnNIaWRkZW4gfHwgaW5zaWRlTW9kYWwgPyB1bmRlZmluZWQgOiBjb250ZW50SGVpZ2h0fVxuICAgICAgPlxuICAgICAgICB7dGFic31cbiAgICAgIDwvVGFicz5cbiAgICA8L1BhbmU+XG4gIClcbn1cbiJdLCJtYXBwaW5ncyI6IjtBQUFBO0FBQ0EsT0FBTyxLQUFLQSxLQUFLLE1BQU0sT0FBTztBQUM5QixTQUFTQyxRQUFRLEVBQUVDLFFBQVEsUUFBUSxPQUFPO0FBQzFDLFNBQVNDLGFBQWEsUUFBUSxvQ0FBb0M7QUFDbEUsU0FBU0MsOEJBQThCLFFBQVEsK0NBQStDO0FBQzlGLFNBQVNDLGVBQWUsUUFBUSxnQ0FBZ0M7QUFDaEUsU0FDRUMsZ0JBQWdCLEVBQ2hCQyxzQkFBc0IsUUFDakIsK0JBQStCO0FBQ3RDLFNBQVNDLElBQUksUUFBUSwwQkFBMEI7QUFDL0MsU0FBU0MsSUFB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
|
|
|
|
|
|
|
| 1 |
// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered
|
| 2 |
+
import * as React from 'react'
|
| 3 |
+
import { Suspense, useState } from 'react'
|
| 4 |
+
import { useKeybinding } from '../../keybindings/useKeybinding.js'
|
| 5 |
+
import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js'
|
| 6 |
+
import { useTerminalSize } from '../../hooks/useTerminalSize.js'
|
| 7 |
+
import {
|
| 8 |
+
useIsInsideModal,
|
| 9 |
+
useModalOrTerminalSize,
|
| 10 |
+
} from '../../context/modalContext.js'
|
| 11 |
+
import { Pane } from '../design-system/Pane.js'
|
| 12 |
+
import { Tabs, Tab } from '../design-system/Tabs.js'
|
| 13 |
+
import { Status, buildDiagnostics } from './Status.js'
|
| 14 |
+
import { Config } from './Config.js'
|
| 15 |
+
import { Usage } from './Usage.js'
|
| 16 |
+
import type {
|
| 17 |
+
LocalJSXCommandContext,
|
| 18 |
+
CommandResultDisplay,
|
| 19 |
+
} from '../../commands.js'
|
| 20 |
+
|
| 21 |
type Props = {
|
| 22 |
+
onClose: (
|
| 23 |
+
result?: string,
|
| 24 |
+
options?: { display?: CommandResultDisplay },
|
| 25 |
+
) => void
|
| 26 |
+
context: LocalJSXCommandContext
|
| 27 |
+
defaultTab: 'Status' | 'Config' | 'Usage' | 'Gates'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
}
|
| 29 |
+
|
| 30 |
+
export function Settings({
|
| 31 |
+
onClose,
|
| 32 |
+
context,
|
| 33 |
+
defaultTab,
|
| 34 |
+
}: Props): React.ReactNode {
|
| 35 |
+
const [selectedTab, setSelectedTab] = useState<string>(defaultTab)
|
| 36 |
+
const [tabsHidden, setTabsHidden] = useState(false)
|
| 37 |
+
// True while Config's own Esc handler is active (search mode with content
|
| 38 |
+
// focused). Settings must cede Esc so search can clear/exit first.
|
| 39 |
+
const [configOwnsEsc, setConfigOwnsEsc] = useState(false)
|
| 40 |
+
const [gatesOwnsEsc, setGatesOwnsEsc] = useState(false)
|
| 41 |
+
// Fixed content height so switching tabs doesn't shift the pane height.
|
| 42 |
+
// Outside modals cap at min(80% viewport, 30). Inside a Modal the modal's
|
| 43 |
+
// innerSize.rows IS the ScrollBox viewport — the 0.8 multiplier over-
|
| 44 |
+
// shrinks, leaving empty rows while Config shows "↓ N more below".
|
| 45 |
+
//
|
| 46 |
+
// Inside-modal math: Config's paneCap-10 chrome estimate was tuned for
|
| 47 |
+
// marginY={1} (2 rows) which is stripped inside modals → +2 to recover.
|
| 48 |
+
// Then -2 for Tabs' header row + its marginTop=1. Plus +1 observed gap
|
| 49 |
+
// from the paneCap-10 estimate being slightly generous. Net: rows + 1.
|
| 50 |
+
const insideModal = useIsInsideModal()
|
| 51 |
+
const { rows } = useModalOrTerminalSize(useTerminalSize())
|
| 52 |
+
const contentHeight = insideModal
|
| 53 |
+
? rows + 1
|
| 54 |
+
: Math.max(15, Math.min(Math.floor(rows * 0.8), 30))
|
| 55 |
+
// Kick off diagnostics once when the pane opens. Status use()s this so
|
| 56 |
+
// it resolves once per /config invocation — no re-fetch flash when
|
| 57 |
+
// tabbing back to Status (Tab unmounts children when not selected).
|
| 58 |
+
const [diagnosticsPromise] = useState(() =>
|
| 59 |
+
buildDiagnostics().catch(() => []),
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
useExitOnCtrlCDWithKeybindings()
|
| 63 |
+
|
| 64 |
+
// Handle escape via keybinding - only when not in submenu
|
| 65 |
+
const handleEscape = () => {
|
| 66 |
+
// Don't handle escape when a submenu is showing (tabsHidden means submenu is open)
|
| 67 |
+
// Let the submenu handle escape to return to the main menu
|
| 68 |
+
if (tabsHidden) {
|
| 69 |
+
return
|
| 70 |
+
}
|
| 71 |
+
// TODO: Update to "Settings" dialog once we define '/settings'.
|
| 72 |
+
onClose('Status dialog dismissed', { display: 'system' })
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
// Disable when submenu is open so the submenu's Dialog can handle ESC,
|
| 76 |
+
// and when Config's search mode is active so its useInput handler
|
| 77 |
+
// (clear query → exit search) processes Escape first.
|
| 78 |
+
useKeybinding('confirm:no', handleEscape, {
|
| 79 |
+
context: 'Settings',
|
| 80 |
+
isActive:
|
| 81 |
+
!tabsHidden &&
|
| 82 |
+
!(selectedTab === 'Config' && configOwnsEsc) &&
|
| 83 |
+
!(selectedTab === 'Gates' && gatesOwnsEsc),
|
| 84 |
+
})
|
| 85 |
+
|
| 86 |
+
const tabs = [
|
| 87 |
+
<Tab key="status" title="Status">
|
| 88 |
+
<Status context={context} diagnosticsPromise={diagnosticsPromise} />
|
| 89 |
+
</Tab>,
|
| 90 |
+
<Tab key="config" title="Config">
|
| 91 |
+
<Suspense fallback={null}>
|
| 92 |
+
<Config
|
| 93 |
+
context={context}
|
| 94 |
+
onClose={onClose}
|
| 95 |
+
setTabsHidden={setTabsHidden}
|
| 96 |
+
onIsSearchModeChange={setConfigOwnsEsc}
|
| 97 |
+
contentHeight={contentHeight}
|
| 98 |
+
/>
|
| 99 |
+
</Suspense>
|
| 100 |
+
</Tab>,
|
| 101 |
+
<Tab key="usage" title="Usage">
|
| 102 |
+
<Usage />
|
| 103 |
+
</Tab>,
|
| 104 |
+
...("external" === 'ant'
|
| 105 |
+
? [
|
| 106 |
+
<Tab key="gates" title="Gates">
|
| 107 |
+
<Gates
|
| 108 |
+
onOwnsEscChange={setGatesOwnsEsc}
|
| 109 |
+
contentHeight={contentHeight}
|
| 110 |
+
/>
|
| 111 |
+
</Tab>,
|
| 112 |
+
]
|
| 113 |
+
: []),
|
| 114 |
+
]
|
| 115 |
+
|
| 116 |
+
return (
|
| 117 |
+
<Pane color="permission">
|
| 118 |
+
<Tabs
|
| 119 |
+
color="permission"
|
| 120 |
+
selectedTab={selectedTab}
|
| 121 |
+
onTabChange={setSelectedTab}
|
| 122 |
+
hidden={tabsHidden}
|
| 123 |
+
// Config has interactive content — start with header unfocused so
|
| 124 |
+
// left/right/tab cycle option values instead of switching tabs.
|
| 125 |
+
initialHeaderFocused={defaultTab !== 'Config' && defaultTab !== 'Gates'}
|
| 126 |
+
// Inside a Modal, skip the Tabs-level cap so tall tabs (Status's
|
| 127 |
+
// MCP list) flow to their natural height for the Modal's ScrollBox
|
| 128 |
+
// to scroll. Config/Gates still get contentHeight above — they
|
| 129 |
+
// paginate internally so this only affects Status/Usage.
|
| 130 |
+
contentHeight={tabsHidden || insideModal ? undefined : contentHeight}
|
| 131 |
+
>
|
| 132 |
+
{tabs}
|
| 133 |
+
</Tabs>
|
| 134 |
+
</Pane>
|
| 135 |
+
)
|
| 136 |
}
|
|
|
src/components/Settings/Settings.tsx.bak
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { c as _c } from "react/compiler-runtime";
|
| 2 |
+
// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered
|
| 3 |
+
import * as React from 'react';
|
| 4 |
+
import { Suspense, useState } from 'react';
|
| 5 |
+
import { useKeybinding } from '../../keybindings/useKeybinding.js';
|
| 6 |
+
import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js';
|
| 7 |
+
import { useTerminalSize } from '../../hooks/useTerminalSize.js';
|
| 8 |
+
import { useIsInsideModal, useModalOrTerminalSize } from '../../context/modalContext.js';
|
| 9 |
+
import { Pane } from '../design-system/Pane.js';
|
| 10 |
+
import { Tabs, Tab } from '../design-system/Tabs.js';
|
| 11 |
+
import { Status, buildDiagnostics } from './Status.js';
|
| 12 |
+
import { Config } from './Config.js';
|
| 13 |
+
import { Usage } from './Usage.js';
|
| 14 |
+
import type { LocalJSXCommandContext, CommandResultDisplay } from '../../commands.js';
|
| 15 |
+
type Props = {
|
| 16 |
+
onClose: (result?: string, options?: {
|
| 17 |
+
display?: CommandResultDisplay;
|
| 18 |
+
}) => void;
|
| 19 |
+
context: LocalJSXCommandContext;
|
| 20 |
+
defaultTab: 'Status' | 'Config' | 'Usage' | 'Gates';
|
| 21 |
+
};
|
| 22 |
+
export function Settings(t0) {
|
| 23 |
+
const $ = _c(25);
|
| 24 |
+
const {
|
| 25 |
+
onClose,
|
| 26 |
+
context,
|
| 27 |
+
defaultTab
|
| 28 |
+
} = t0;
|
| 29 |
+
const [selectedTab, setSelectedTab] = useState(defaultTab);
|
| 30 |
+
const [tabsHidden, setTabsHidden] = useState(false);
|
| 31 |
+
const [configOwnsEsc, setConfigOwnsEsc] = useState(false);
|
| 32 |
+
const [gatesOwnsEsc, setGatesOwnsEsc] = useState(false);
|
| 33 |
+
const insideModal = useIsInsideModal();
|
| 34 |
+
const {
|
| 35 |
+
rows
|
| 36 |
+
} = useModalOrTerminalSize(useTerminalSize());
|
| 37 |
+
const contentHeight = insideModal ? rows + 1 : Math.max(15, Math.min(Math.floor(rows * 0.8), 30));
|
| 38 |
+
const [diagnosticsPromise] = useState(_temp2);
|
| 39 |
+
useExitOnCtrlCDWithKeybindings();
|
| 40 |
+
let t1;
|
| 41 |
+
if ($[0] !== onClose || $[1] !== tabsHidden) {
|
| 42 |
+
t1 = () => {
|
| 43 |
+
if (tabsHidden) {
|
| 44 |
+
return;
|
| 45 |
+
}
|
| 46 |
+
onClose("Status dialog dismissed", {
|
| 47 |
+
display: "system"
|
| 48 |
+
});
|
| 49 |
+
};
|
| 50 |
+
$[0] = onClose;
|
| 51 |
+
$[1] = tabsHidden;
|
| 52 |
+
$[2] = t1;
|
| 53 |
+
} else {
|
| 54 |
+
t1 = $[2];
|
| 55 |
+
}
|
| 56 |
+
const handleEscape = t1;
|
| 57 |
+
const t2 = !tabsHidden && !(selectedTab === "Config" && configOwnsEsc) && !(selectedTab === "Gates" && gatesOwnsEsc);
|
| 58 |
+
let t3;
|
| 59 |
+
if ($[3] !== t2) {
|
| 60 |
+
t3 = {
|
| 61 |
+
context: "Settings",
|
| 62 |
+
isActive: t2
|
| 63 |
+
};
|
| 64 |
+
$[3] = t2;
|
| 65 |
+
$[4] = t3;
|
| 66 |
+
} else {
|
| 67 |
+
t3 = $[4];
|
| 68 |
+
}
|
| 69 |
+
useKeybinding("confirm:no", handleEscape, t3);
|
| 70 |
+
let t4;
|
| 71 |
+
if ($[5] !== context || $[6] !== diagnosticsPromise) {
|
| 72 |
+
t4 = <Tab key="status" title="Status"><Status context={context} diagnosticsPromise={diagnosticsPromise} /></Tab>;
|
| 73 |
+
$[5] = context;
|
| 74 |
+
$[6] = diagnosticsPromise;
|
| 75 |
+
$[7] = t4;
|
| 76 |
+
} else {
|
| 77 |
+
t4 = $[7];
|
| 78 |
+
}
|
| 79 |
+
let t5;
|
| 80 |
+
if ($[8] !== contentHeight || $[9] !== context || $[10] !== onClose) {
|
| 81 |
+
t5 = <Tab key="config" title="Config"><Suspense fallback={null}><Config context={context} onClose={onClose} setTabsHidden={setTabsHidden} onIsSearchModeChange={setConfigOwnsEsc} contentHeight={contentHeight} /></Suspense></Tab>;
|
| 82 |
+
$[8] = contentHeight;
|
| 83 |
+
$[9] = context;
|
| 84 |
+
$[10] = onClose;
|
| 85 |
+
$[11] = t5;
|
| 86 |
+
} else {
|
| 87 |
+
t5 = $[11];
|
| 88 |
+
}
|
| 89 |
+
let t6;
|
| 90 |
+
if ($[12] === Symbol.for("react.memo_cache_sentinel")) {
|
| 91 |
+
t6 = <Tab key="usage" title="Usage"><Usage /></Tab>;
|
| 92 |
+
$[12] = t6;
|
| 93 |
+
} else {
|
| 94 |
+
t6 = $[12];
|
| 95 |
+
}
|
| 96 |
+
let t7;
|
| 97 |
+
if ($[13] !== contentHeight) {
|
| 98 |
+
t7 = false ? [<Tab key="gates" title="Gates"><Gates onOwnsEscChange={setGatesOwnsEsc} contentHeight={contentHeight} /></Tab>] : [];
|
| 99 |
+
$[13] = contentHeight;
|
| 100 |
+
$[14] = t7;
|
| 101 |
+
} else {
|
| 102 |
+
t7 = $[14];
|
| 103 |
+
}
|
| 104 |
+
let t8;
|
| 105 |
+
if ($[15] !== t4 || $[16] !== t5 || $[17] !== t7) {
|
| 106 |
+
t8 = [t4, t5, t6, ...t7];
|
| 107 |
+
$[15] = t4;
|
| 108 |
+
$[16] = t5;
|
| 109 |
+
$[17] = t7;
|
| 110 |
+
$[18] = t8;
|
| 111 |
+
} else {
|
| 112 |
+
t8 = $[18];
|
| 113 |
+
}
|
| 114 |
+
const tabs = t8;
|
| 115 |
+
const t9 = defaultTab !== "Config" && defaultTab !== "Gates";
|
| 116 |
+
const t10 = tabsHidden || insideModal ? undefined : contentHeight;
|
| 117 |
+
let t11;
|
| 118 |
+
if ($[19] !== selectedTab || $[20] !== t10 || $[21] !== t9 || $[22] !== tabs || $[23] !== tabsHidden) {
|
| 119 |
+
t11 = <Pane color="permission"><Tabs color="permission" selectedTab={selectedTab} onTabChange={setSelectedTab} hidden={tabsHidden} initialHeaderFocused={t9} contentHeight={t10}>{tabs}</Tabs></Pane>;
|
| 120 |
+
$[19] = selectedTab;
|
| 121 |
+
$[20] = t10;
|
| 122 |
+
$[21] = t9;
|
| 123 |
+
$[22] = tabs;
|
| 124 |
+
$[23] = tabsHidden;
|
| 125 |
+
$[24] = t11;
|
| 126 |
+
} else {
|
| 127 |
+
t11 = $[24];
|
| 128 |
+
}
|
| 129 |
+
return t11;
|
| 130 |
+
}
|
| 131 |
+
function _temp2() {
|
| 132 |
+
return buildDiagnostics().catch(_temp);
|
| 133 |
+
}
|
| 134 |
+
function _temp() {
|
| 135 |
+
return [];
|
| 136 |
+
}
|
| 137 |
+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIlN1c3BlbnNlIiwidXNlU3RhdGUiLCJ1c2VLZXliaW5kaW5nIiwidXNlRXhpdE9uQ3RybENEV2l0aEtleWJpbmRpbmdzIiwidXNlVGVybWluYWxTaXplIiwidXNlSXNJbnNpZGVNb2RhbCIsInVzZU1vZGFsT3JUZXJtaW5hbFNpemUiLCJQYW5lIiwiVGFicyIsIlRhYiIsIlN0YXR1cyIsImJ1aWxkRGlhZ25vc3RpY3MiLCJDb25maWciLCJVc2FnZSIsIkxvY2FsSlNYQ29tbWFuZENvbnRleHQiLCJDb21tYW5kUmVzdWx0RGlzcGxheSIsIlByb3BzIiwib25DbG9zZSIsInJlc3VsdCIsIm9wdGlvbnMiLCJkaXNwbGF5IiwiY29udGV4dCIsImRlZmF1bHRUYWIiLCJTZXR0aW5ncyIsInQwIiwiJCIsIl9jIiwic2VsZWN0ZWRUYWIiLCJzZXRTZWxlY3RlZFRhYiIsInRhYnNIaWRkZW4iLCJzZXRUYWJzSGlkZGVuIiwiY29uZmlnT3duc0VzYyIsInNldENvbmZpZ093bnNFc2MiLCJnYXRlc093bnNFc2MiLCJzZXRHYXRlc093bnNFc2MiLCJpbnNpZGVNb2RhbCIsInJvd3MiLCJjb250ZW50SGVpZ2h0IiwiTWF0aCIsIm1heCIsIm1pbiIsImZsb29yIiwiZGlhZ25vc3RpY3NQcm9taXNlIiwiX3RlbXAyIiwidDEiLCJoYW5kbGVFc2NhcGUiLCJ0MiIsInQzIiwiaXNBY3RpdmUiLCJ0NCIsInQ1IiwidDYiLCJTeW1ib2wiLCJmb3IiLCJ0NyIsInQ4IiwidGFicyIsInQ5IiwidDEwIiwidW5kZWZpbmVkIiwidDExIiwiY2F0Y2giLCJfdGVtcCJdLCJzb3VyY2VzIjpbIlNldHRpbmdzLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyIvLyBiaW9tZS1pZ25vcmUtYWxsIGFzc2lzdC9zb3VyY2Uvb3JnYW5pemVJbXBvcnRzOiBBTlQtT05MWSBpbXBvcnQgbWFya2VycyBtdXN0IG5vdCBiZSByZW9yZGVyZWRcbmltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgU3VzcGVuc2UsIHVzZVN0YXRlIH0gZnJvbSAncmVhY3QnXG5pbXBvcnQgeyB1c2VLZXliaW5kaW5nIH0gZnJvbSAnLi4vLi4va2V5YmluZGluZ3MvdXNlS2V5YmluZGluZy5qcydcbmltcG9ydCB7IHVzZUV4aXRPbkN0cmxDRFdpdGhLZXliaW5kaW5ncyB9IGZyb20gJy4uLy4uL2hvb2tzL3VzZUV4aXRPbkN0cmxDRFdpdGhLZXliaW5kaW5ncy5qcydcbmltcG9ydCB7IHVzZVRlcm1pbmFsU2l6ZSB9IGZyb20gJy4uLy4uL2hvb2tzL3VzZVRlcm1pbmFsU2l6ZS5qcydcbmltcG9ydCB7XG4gIHVzZUlzSW5zaWRlTW9kYWwsXG4gIHVzZU1vZGFsT3JUZXJtaW5hbFNpemUsXG59IGZyb20gJy4uLy4uL2NvbnRleHQvbW9kYWxDb250ZXh0LmpzJ1xuaW1wb3J0IHsgUGFuZSB9IGZyb20gJy4uL2Rlc2lnbi1zeXN0ZW0vUGFuZS5qcydcbmltcG9ydCB7IFRhYnMsIFRhYiB9IGZyb20gJy4uL2Rlc2lnbi1zeXN0ZW0vVGFicy5qcydcbmltcG9ydCB7IFN0YXR1cywgYnVpbGREaWFnbm9zdGljcyB9IGZyb20gJy4vU3RhdHVzLmpzJ1xuaW1wb3J0IHsgQ29uZmlnIH0gZnJvbSAnLi9Db25maWcuanMnXG5pbXBvcnQgeyBVc2FnZSB9IGZyb20gJy4vVXNhZ2UuanMnXG5pbXBvcnQgdHlwZSB7XG4gIExvY2FsSlNYQ29tbWFuZENvbnRleHQsXG4gIENvbW1hbmRSZXN1bHREaXNwbGF5LFxufSBmcm9tICcuLi8uLi9jb21tYW5kcy5qcydcblxudHlwZSBQcm9wcyA9IHtcbiAgb25DbG9zZTogKFxuICAgIHJlc3VsdD86IHN0cmluZyxcbiAgICBvcHRpb25zPzogeyBkaXNwbGF5PzogQ29tbWFuZFJlc3VsdERpc3BsYXkgfSxcbiAgKSA9PiB2b2lkXG4gIGNvbnRleHQ6IExvY2FsSlNYQ29tbWFuZENvbnRleHRcbiAgZGVmYXVsdFRhYjogJ1N0YXR1cycgfCAnQ29uZmlnJyB8ICdVc2FnZScgfCAnR2F0ZXMnXG59XG5cbmV4cG9ydCBmdW5jdGlvbiBTZXR0aW5ncyh7XG4gIG9uQ2xvc2UsXG4gIGNvbnRleHQsXG4gIGRlZmF1bHRUYWIsXG59OiBQcm9wcyk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IFtzZWxlY3RlZFRhYiwgc2V0U2VsZWN0ZWRUYWJdID0gdXNlU3RhdGU8c3RyaW5nPihkZWZhdWx0VGFiKVxuICBjb25zdCBbdGFic0hpZGRlbiwgc2V0VGFic0hpZGRlbl0gPSB1c2VTdGF0ZShmYWxzZSlcbiAgLy8gVHJ1ZSB3aGlsZSBDb25maWcncyBvd24gRXNjIGhhbmRsZXIgaXMgYWN0aXZlIChzZWFyY2ggbW9kZSB3aXRoIGNvbnRlbnRcbiAgLy8gZm9jdXNlZCkuIFNldHRpbmdzIG11c3QgY2VkZSBFc2Mgc28gc2VhcmNoIGNhbiBjbGVhci9leGl0IGZpcnN0LlxuICBjb25zdCBbY29uZmlnT3duc0VzYywgc2V0Q29uZmlnT3duc0VzY10gPSB1c2VTdGF0ZShmYWxzZSlcbiAgY29uc3QgW2dhdGVzT3duc0VzYywgc2V0R2F0ZXNPd25zRXNjXSA9IHVzZVN0YXRlKGZhbHNlKVxuICAvLyBGaXhlZCBjb250ZW50IGhlaWdodCBzbyBzd2l0Y2hpbmcgdGFicyBkb2Vzbid0IHNoaWZ0IHRoZSBwYW5lIGhlaWdodC5cbiAgLy8gT3V0c2lkZSBtb2RhbHMgY2FwIGF0IG1pbig4MCUgdmlld3BvcnQsIDMwKS4gSW5zaWRlIGEgTW9kYWwgdGhlIG1vZGFsJ3NcbiAgLy8gaW5uZXJTaXplLnJvd3MgSVMgdGhlIFNjcm9sbEJveCB2aWV3cG9ydCDigJQgdGhlIDAuOCBtdWx0aXBsaWVyIG92ZXItXG4gIC8vIHNocmlua3MsIGxlYXZpbmcgZW1wdHkgcm93cyB3aGlsZSBDb25maWcgc2hvd3MgXCLihpMgTiBtb3JlIGJlbG93XCIuXG4gIC8vXG4gIC8vIEluc2lkZS1tb2RhbCBtYXRoOiBDb25maWcncyBwYW5lQ2FwLTEwIGNocm9tZSBlc3RpbWF0ZSB3YXMgdHVuZWQgZm9yXG4gIC8vIG1hcmdpblk9ezF9ICgyIHJvd3MpIHdoaWNoIGlzIHN0cmlwcGVkIGluc2lkZSBtb2RhbHMg4oaSICsyIHRvIHJlY292ZXIuXG4gIC8vIFRoZW4gLTIgZm9yIFRhYnMnIGhlYWRlciByb3cgKyBpdHMgbWFyZ2luVG9wPTEuIFBsdXMgKzEgb2JzZXJ2ZWQgZ2FwXG4gIC8vIGZyb20gdGhlIHBhbmVDYXAtMTAgZXN0aW1hdGUgYmVpbmcgc2xpZ2h0bHkgZ2VuZXJvdXMuIE5ldDogcm93cyArIDEuXG4gIGNvbnN0IGluc2lkZU1vZGFsID0gdXNlSXNJbnNpZGVNb2RhbCgpXG4gIGNvbnN0IHsgcm93cyB9ID0gdXNlTW9kYWxPclRlcm1pbmFsU2l6ZSh1c2VUZXJtaW5hbFNpemUoKSlcbiAgY29uc3QgY29udGVudEhlaWdodCA9IGluc2lkZU1vZGFsXG4gICAgPyByb3dzICsgMVxuICAgIDogTWF0aC5tYXgoMTUsIE1hdGgubWluKE1hdGguZmxvb3Iocm93cyAqIDAuOCksIDMwKSlcbiAgLy8gS2ljayBvZmYgZGlhZ25vc3RpY3Mgb25jZSB3aGVuIHRoZSBwYW5lIG9wZW5zLiBTdGF0dXMgdXNlKClzIHRoaXMgc29cbiAgLy8gaXQgcmVzb2x2ZXMgb25jZSBwZXIgL2NvbmZpZyBpbnZvY2F0aW9uIOKAlCBubyByZS1mZXRjaCBmbGFzaCB3aGVuXG4gIC8vIHRhYmJpbmcgYmFjayB0byBTdGF0dXMgKFRhYiB1bm1vdW50cyBjaGlsZHJlbiB3aGVuIG5vdCBzZWxlY3RlZCkuXG4gIGNvbnN0IFtkaWFnbm9zdGljc1Byb21pc2VdID0gdXNlU3RhdGUoKCkgPT5cbiAgICBidWlsZERpYWdub3N0aWNzKCkuY2F0Y2goKCkgPT4gW10pLFxuICApXG5cbiAgdXNlRXhpdE9uQ3RybENEV2l0aEtleWJpbmRpbmdzKClcblxuICAvLyBIYW5kbGUgZXNjYXBlIHZpYSBrZXliaW5kaW5nIC0gb25seSB3aGVuIG5vdCBpbiBzdWJtZW51XG4gIGNvbnN0IGhhbmRsZUVzY2FwZSA9ICgpID0+IHtcbiAgICAvLyBEb24ndCBoYW5kbGUgZXNjYXBlIHdoZW4gYSBzdWJtZW51IGlzIHNob3dpbmcgKHRhYnNIaWRkZW4gbWVhbnMgc3VibWVudSBpcyBvcGVuKVxuICAgIC8vIExldCB0aGUgc3VibWVudSBoYW5kbGUgZXNjYXBlIHRvIHJldHVybiB0byB0aGUgbWFpbiBtZW51XG4gICAgaWYgKHRhYnNIaWRkZW4pIHtcbiAgICAgIHJldHVyblxuICAgIH1cbiAgICAvLyBUT0RPOiBVcGRhdGUgdG8gXCJTZXR0aW5nc1wiIGRpYWxvZyBvbmNlIHdlIGRlZmluZSAnL3NldHRpbmdzJy5cbiAgICBvbkNsb3NlKCdTdGF0dXMgZGlhbG9nIGRpc21pc3NlZCcsIHsgZGlzcGxheTogJ3N5c3RlbScgfSlcbiAgfVxuXG4gIC8vIERpc2FibGUgd2hlbiBzdWJtZW51IGlzIG9wZW4gc28gdGhlIHN1Ym1lbnUncyBEaWFsb2cgY2FuIGhhbmRsZSBFU0MsXG4gIC8vIGFuZCB3aGVuIENvbmZpZydzIHNlYXJjaCBtb2RlIGlzIGFjdGl2ZSBzbyBpdHMgdXNlSW5wdXQgaGFuZGxlclxuICAvLyAoY2xlYXIgcXVlcnkg4oaSIGV4aXQgc2VhcmNoKSBwcm9jZXNzZXMgRXNjYXBlIGZpcnN0LlxuICB1c2VLZXliaW5kaW5nKCdjb25maXJtOm5vJywgaGFuZGxlRXNjYXBlLCB7XG4gICAgY29udGV4dDogJ1NldHRpbmdzJyxcbiAgICBpc0FjdGl2ZTpcbiAgICAgICF0YWJzSGlkZGVuICYmXG4gICAgICAhKHNlbGVjdGVkVGFiID09PSAnQ29uZmlnJyAmJiBjb25maWdPd25zRXNjKSAmJlxuICAgICAgIShzZWxlY3RlZFRhYiA9PT0gJ0dhdGVzJyAmJiBnYXRlc093bnNFc2MpLFxuICB9KVxuXG4gIGNvbnN0IHRhYnMgPSBbXG4gICAgPFRhYiBrZXk9XCJzdGF0dXNcIiB0aXRsZT1cIlN0YXR1c1wiPlxuICAgICAgPFN0YXR1cyBjb250ZXh0PXtjb250ZXh0fSBkaWFnbm9zdGljc1Byb21pc2U9e2RpYWdub3N0aWNzUHJvbWlzZX0gLz5cbiAgICA8L1RhYj4sXG4gICAgPFRhYiBrZXk9XCJjb25maWdcIiB0aXRsZT1cIkNvbmZpZ1wiPlxuICAgICAgPFN1c3BlbnNlIGZhbGxiYWNrPXtudWxsfT5cbiAgICAgICAgPENvbmZpZ1xuICAgICAgICAgIGNvbnRleHQ9e2NvbnRleHR9XG4gICAgICAgICAgb25DbG9zZT17b25DbG9zZX1cbiAgICAgICAgICBzZXRUYWJzSGlkZGVuPXtzZXRUYWJzSGlkZGVufVxuICAgICAgICAgIG9uSXNTZWFyY2hNb2RlQ2hhbmdlPXtzZXRDb25maWdPd25zRXNjfVxuICAgICAgICAgIGNvbnRlbnRIZWlnaHQ9e2NvbnRlbnRIZWlnaHR9XG4gICAgICAgIC8+XG4gICAgICA8L1N1c3BlbnNlPlxuICAgIDwvVGFiPixcbiAgICA8VGFiIGtleT1cInVzYWdlXCIgdGl0bGU9XCJVc2FnZVwiPlxuICAgICAgPFVzYWdlIC8+XG4gICAgPC9UYWI+LFxuICAgIC4uLihcImV4dGVybmFsXCIgPT09ICdhbnQnXG4gICAgICA/IFtcbiAgICAgICAgICA8VGFiIGtleT1cImdhdGVzXCIgdGl0bGU9XCJHYXRlc1wiPlxuICAgICAgICAgICAgPEdhdGVzXG4gICAgICAgICAgICAgIG9uT3duc0VzY0NoYW5nZT17c2V0R2F0ZXNPd25zRXNjfVxuICAgICAgICAgICAgICBjb250ZW50SGVpZ2h0PXtjb250ZW50SGVpZ2h0fVxuICAgICAgICAgICAgLz5cbiAgICAgICAgICA8L1RhYj4sXG4gICAgICAgIF1cbiAgICAgIDogW10pLFxuICBdXG5cbiAgcmV0dXJuIChcbiAgICA8UGFuZSBjb2xvcj1cInBlcm1pc3Npb25cIj5cbiAgICAgIDxUYWJzXG4gICAgICAgIGNvbG9yPVwicGVybWlzc2lvblwiXG4gICAgICAgIHNlbGVjdGVkVGFiPXtzZWxlY3RlZFRhYn1cbiAgICAgICAgb25UYWJDaGFuZ2U9e3NldFNlbGVjdGVkVGFifVxuICAgICAgICBoaWRkZW49e3RhYnNIaWRkZW59XG4gICAgICAgIC8vIENvbmZpZyBoYXMgaW50ZXJhY3RpdmUgY29udGVudCDigJQgc3RhcnQgd2l0aCBoZWFkZXIgdW5mb2N1c2VkIHNvXG4gICAgICAgIC8vIGxlZnQvcmlnaHQvdGFiIGN5Y2xlIG9wdGlvbiB2YWx1ZXMgaW5zdGVhZCBvZiBzd2l0Y2hpbmcgdGFicy5cbiAgICAgICAgaW5pdGlhbEhlYWRlckZvY3VzZWQ9e2RlZmF1bHRUYWIgIT09ICdDb25maWcnICYmIGRlZmF1bHRUYWIgIT09ICdHYXRlcyd9XG4gICAgICAgIC8vIEluc2lkZSBhIE1vZGFsLCBza2lwIHRoZSBUYWJzLWxldmVsIGNhcCBzbyB0YWxsIHRhYnMgKFN0YXR1cydzXG4gICAgICAgIC8vIE1DUCBsaXN0KSBmbG93IHRvIHRoZWlyIG5hdHVyYWwgaGVpZ2h0IGZvciB0aGUgTW9kYWwncyBTY3JvbGxCb3hcbiAgICAgICAgLy8gdG8gc2Nyb2xsLiBDb25maWcvR2F0ZXMgc3RpbGwgZ2V0IGNvbnRlbnRIZWlnaHQgYWJvdmUg4oCUIHRoZXlcbiAgICAgICAgLy8gcGFnaW5hdGUgaW50ZXJuYWxseSBzbyB0aGlzIG9ubHkgYWZmZWN0cyBTdGF0dXMvVXNhZ2UuXG4gICAgICAgIGNvbnRlbnRIZWlnaHQ9e3RhYnNIaWRkZW4gfHwgaW5zaWRlTW9kYWwgPyB1bmRlZmluZWQgOiBjb250ZW50SGVpZ2h0fVxuICAgICAgPlxuICAgICAgICB7dGFic31cbiAgICAgIDwvVGFicz5cbiAgICA8L1BhbmU+XG4gIClcbn1cbiJdLCJtYXBwaW5ncyI6IjtBQUFBO0FBQ0EsT0FBTyxLQUFLQSxLQUFLLE1BQU0sT0FBTztBQUM5QixTQUFTQyxRQUFRLEVBQUVDLFFBQVEsUUFBUSxPQUFPO0FBQzFDLFNBQVNDLGFBQWEsUUFBUSxvQ0FBb0M7QUFDbEUsU0FBU0MsOEJBQThCLFFBQVEsK0NBQStDO0FBQzlGLFNBQVNDLGVBQWUsUUFBUSxnQ0FBZ0M7QUFDaEUsU0FDRUMsZ0JBQWdCLEVBQ2hCQyxzQkFBc0IsUUFDakIsK0JBQStCO0FBQ3RDLFNBQVNDLElBQUksUUFBUSwwQkFBMEI7QUFDL0MsU0FBU0MsSUFB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
|
src/hooks/useAutoTTS.ts
CHANGED
|
@@ -5,7 +5,6 @@ import type { RenderableMessage } from '../types/message.js'
|
|
| 5 |
|
| 6 |
export function useAutoTTS(messages: RenderableMessage[]): void {
|
| 7 |
const triggeredIdsRef = useRef<Set<string>>(new Set())
|
| 8 |
-
const pendingPlayRef = useRef<Promise<void> | null>(null)
|
| 9 |
|
| 10 |
useEffect(() => {
|
| 11 |
const settings = getInitialSettings()
|
|
|
|
| 5 |
|
| 6 |
export function useAutoTTS(messages: RenderableMessage[]): void {
|
| 7 |
const triggeredIdsRef = useRef<Set<string>>(new Set())
|
|
|
|
| 8 |
|
| 9 |
useEffect(() => {
|
| 10 |
const settings = getInitialSettings()
|
src/hooks/useVoice.ts
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
| 22 |
type VoiceStreamConnection,
|
| 23 |
} from '../services/voiceStreamSTT.js'
|
| 24 |
import { connectDoubaoStream } from '../services/doubaoSTT.js'
|
| 25 |
-
import { connectLocalWhisperStream } from '../services/voice/whisperSTT.js'
|
| 26 |
import { logForDebugging } from '../utils/debug.js'
|
| 27 |
import { toError } from '../utils/errors.js'
|
| 28 |
import { getSystemLocaleLanguage } from '../utils/intl.js'
|
|
@@ -804,6 +804,7 @@ export function useVoice({
|
|
| 804 |
opts: { language: string; keyterms: string[] },
|
| 805 |
) => Promise<VoiceStreamConnection | null>
|
| 806 |
if (isLocalProvider()) {
|
|
|
|
| 807 |
connectFn = (cbs, _opts) =>
|
| 808 |
connectLocalWhisperStream(cbs, { language: stt.code })
|
| 809 |
} else if (isDoubaoProvider()) {
|
|
|
|
| 22 |
type VoiceStreamConnection,
|
| 23 |
} from '../services/voiceStreamSTT.js'
|
| 24 |
import { connectDoubaoStream } from '../services/doubaoSTT.js'
|
| 25 |
+
import { connectLocalWhisperStream, preloadWhisperModel } from '../services/voice/whisperSTT.js'
|
| 26 |
import { logForDebugging } from '../utils/debug.js'
|
| 27 |
import { toError } from '../utils/errors.js'
|
| 28 |
import { getSystemLocaleLanguage } from '../utils/intl.js'
|
|
|
|
| 804 |
opts: { language: string; keyterms: string[] },
|
| 805 |
) => Promise<VoiceStreamConnection | null>
|
| 806 |
if (isLocalProvider()) {
|
| 807 |
+
void preloadWhisperModel({ language: stt.code })
|
| 808 |
connectFn = (cbs, _opts) =>
|
| 809 |
connectLocalWhisperStream(cbs, { language: stt.code })
|
| 810 |
} else if (isDoubaoProvider()) {
|
src/screens/REPL.tsx
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/screens/REPL.tsx.bak
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/services/voice/edgeTTS.ts
CHANGED
|
@@ -81,8 +81,12 @@ export async function speakWithEdgeTTS(
|
|
| 81 |
}
|
| 82 |
|
| 83 |
try {
|
| 84 |
-
const
|
| 85 |
-
resolve(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
} catch {
|
| 87 |
resolve({
|
| 88 |
success: false,
|
|
|
|
| 81 |
}
|
| 82 |
|
| 83 |
try {
|
| 84 |
+
const raw = JSON.parse(stdout)
|
| 85 |
+
resolve({
|
| 86 |
+
success: raw.success,
|
| 87 |
+
audioPath: raw.audio_path ?? raw.audioPath,
|
| 88 |
+
error: raw.error,
|
| 89 |
+
})
|
| 90 |
} catch {
|
| 91 |
resolve({
|
| 92 |
success: false,
|
src/services/voice/whisperSTT.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import { spawn } from 'child_process'
|
| 2 |
import { existsSync, mkdtempSync, writeFileSync, unlinkSync, rmdirSync } from 'fs'
|
| 3 |
import { tmpdir } from 'os'
|
| 4 |
import { join, dirname } from 'path'
|
|
@@ -21,14 +21,7 @@ const PROJECT_ROOT = findProjectRoot()
|
|
| 21 |
const SCRIPTS_DIR = join(PROJECT_ROOT, 'scripts')
|
| 22 |
const VENV_PYTHON = join(PROJECT_ROOT, '.venv', 'bin', 'python')
|
| 23 |
|
| 24 |
-
|
| 25 |
-
function log(...args: unknown[]) {
|
| 26 |
-
const line = `[${new Date().toISOString()}] ${args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ')}\n`
|
| 27 |
-
try { writeFileSync(LOG, line, { flag: 'a' }) } catch {}
|
| 28 |
-
}
|
| 29 |
-
|
| 30 |
-
function resolvePythonPath(customPath?: string): string {
|
| 31 |
-
if (customPath) return customPath
|
| 32 |
if (existsSync(VENV_PYTHON)) return VENV_PYTHON
|
| 33 |
return 'python3'
|
| 34 |
}
|
|
@@ -39,91 +32,159 @@ type WhisperOptions = {
|
|
| 39 |
pythonPath?: string
|
| 40 |
}
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
export function connectLocalWhisperStream(
|
| 43 |
callbacks: VoiceStreamCallbacks,
|
| 44 |
options?: WhisperOptions,
|
| 45 |
): Promise<VoiceStreamConnection | null> {
|
| 46 |
-
return new Promise(resolve => {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
const chunks: Buffer[] = []
|
| 48 |
let finalized = false
|
| 49 |
let tmpDir: string | null = null
|
| 50 |
|
| 51 |
-
const python = resolvePythonPath(options?.pythonPath)
|
| 52 |
-
const model = options?.model ?? 'large-v3-turbo'
|
| 53 |
-
const language = options?.language
|
| 54 |
-
log('projectRoot', PROJECT_ROOT, 'python', python, 'model', model, 'language', language)
|
| 55 |
-
|
| 56 |
const connection: VoiceStreamConnection = {
|
| 57 |
send(chunk: Buffer) {
|
| 58 |
if (finalized) return
|
| 59 |
chunks.push(Buffer.from(chunk))
|
| 60 |
-
if (chunks.length === 1) log('first chunk', chunk.length, 'bytes')
|
| 61 |
},
|
| 62 |
|
| 63 |
async finalize(): Promise<FinalizeSource> {
|
| 64 |
if (finalized) return 'ws_already_closed'
|
| 65 |
finalized = true
|
| 66 |
-
log('finalize called, chunks', chunks.length)
|
| 67 |
|
| 68 |
if (chunks.length === 0) {
|
| 69 |
-
log('no chunks, returning no_data_timeout')
|
| 70 |
callbacks.onClose()
|
| 71 |
return 'no_data_timeout'
|
| 72 |
}
|
| 73 |
|
| 74 |
const audioBuf = Buffer.concat(chunks)
|
| 75 |
-
log('total audio', audioBuf.length, 'bytes')
|
| 76 |
|
| 77 |
try {
|
| 78 |
tmpDir = mkdtempSync(join(tmpdir(), 'vc-whisper-'))
|
| 79 |
const wavPath = join(tmpDir, 'input.wav')
|
| 80 |
-
|
| 81 |
writeWavHeader(wavPath, audioBuf, 16000)
|
| 82 |
|
| 83 |
-
const
|
| 84 |
-
if (
|
| 85 |
-
args.push('--language', language)
|
| 86 |
-
}
|
| 87 |
-
log('spawning', python, args.join(' '))
|
| 88 |
-
|
| 89 |
-
const proc = spawn(python, args, { stdio: ['ignore', 'pipe', 'pipe'] })
|
| 90 |
-
|
| 91 |
-
let stdout = ''
|
| 92 |
-
let stderr = ''
|
| 93 |
-
|
| 94 |
-
proc.stdout.on('data', (data: Buffer) => {
|
| 95 |
-
stdout += data.toString()
|
| 96 |
-
})
|
| 97 |
-
|
| 98 |
-
proc.stderr.on('data', (data: Buffer) => {
|
| 99 |
-
stderr += data.toString()
|
| 100 |
-
})
|
| 101 |
-
|
| 102 |
-
const exitCode = await new Promise<number>(resolveExit => {
|
| 103 |
-
proc.on('close', resolveExit)
|
| 104 |
-
})
|
| 105 |
-
|
| 106 |
-
log('exitCode', exitCode, 'stdout', stdout.slice(0, 500), 'stderr', stderr.slice(0, 500))
|
| 107 |
-
|
| 108 |
-
if (exitCode !== 0) {
|
| 109 |
-
callbacks.onError(`Whisper transcription failed: ${stderr || 'unknown error'}`, {
|
| 110 |
-
fatal: true,
|
| 111 |
-
})
|
| 112 |
-
callbacks.onClose()
|
| 113 |
-
return 'ws_close'
|
| 114 |
-
}
|
| 115 |
-
|
| 116 |
-
const result = JSON.parse(stdout)
|
| 117 |
-
log('result', result)
|
| 118 |
-
if (result.success && result.text) {
|
| 119 |
callbacks.onTranscript(result.text, true)
|
| 120 |
-
} else if (result.success && !result.text) {
|
| 121 |
-
callbacks.onTranscript('', true)
|
| 122 |
} else {
|
| 123 |
-
callbacks.
|
| 124 |
-
result.error ?? 'Transcription failed',
|
| 125 |
-
{ fatal: true },
|
| 126 |
-
)
|
| 127 |
}
|
| 128 |
} catch (err) {
|
| 129 |
callbacks.onError(
|
|
@@ -132,12 +193,8 @@ export function connectLocalWhisperStream(
|
|
| 132 |
)
|
| 133 |
} finally {
|
| 134 |
if (tmpDir) {
|
| 135 |
-
try {
|
| 136 |
-
|
| 137 |
-
} catch {}
|
| 138 |
-
try {
|
| 139 |
-
rmdirSync(tmpDir)
|
| 140 |
-
} catch {}
|
| 141 |
}
|
| 142 |
callbacks.onClose()
|
| 143 |
}
|
|
@@ -160,29 +217,20 @@ export function connectLocalWhisperStream(
|
|
| 160 |
})
|
| 161 |
}
|
| 162 |
|
| 163 |
-
export async function checkLocalWhisperAvailable(
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
'-c',
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
'
|
| 172 |
-
'
|
| 173 |
-
], { stdio: ['ignore', 'pipe', 'pipe'] })
|
| 174 |
-
|
| 175 |
-
let stdout = ''
|
| 176 |
-
proc.stdout.on('data', (data: Buffer) => { stdout += data.toString() })
|
| 177 |
-
proc.on('close', () => {
|
| 178 |
-
try {
|
| 179 |
-
const result = JSON.parse(stdout)
|
| 180 |
-
resolve(result.ok === true)
|
| 181 |
-
} catch {
|
| 182 |
-
resolve(false)
|
| 183 |
-
}
|
| 184 |
})
|
| 185 |
-
}
|
|
|
|
|
|
|
| 186 |
}
|
| 187 |
|
| 188 |
function writeWavHeader(path: string, pcmData: Buffer, sampleRate: number): void {
|
|
@@ -193,11 +241,9 @@ function writeWavHeader(path: string, pcmData: Buffer, sampleRate: number): void
|
|
| 193 |
const dataSize = pcmData.length
|
| 194 |
|
| 195 |
const header = Buffer.alloc(44)
|
| 196 |
-
|
| 197 |
header.write('RIFF', 0)
|
| 198 |
header.writeUInt32LE(36 + dataSize, 4)
|
| 199 |
header.write('WAVE', 8)
|
| 200 |
-
|
| 201 |
header.write('fmt ', 12)
|
| 202 |
header.writeUInt32LE(16, 16)
|
| 203 |
header.writeUInt16LE(1, 20)
|
|
@@ -206,9 +252,7 @@ function writeWavHeader(path: string, pcmData: Buffer, sampleRate: number): void
|
|
| 206 |
header.writeUInt32LE(byteRate, 28)
|
| 207 |
header.writeUInt16LE(blockAlign, 32)
|
| 208 |
header.writeUInt16LE(bitsPerSample, 34)
|
| 209 |
-
|
| 210 |
header.write('data', 36)
|
| 211 |
header.writeUInt32LE(dataSize, 40)
|
| 212 |
-
|
| 213 |
writeFileSync(path, Buffer.concat([header, pcmData]))
|
| 214 |
}
|
|
|
|
| 1 |
+
import { spawn, type ChildProcess } from 'child_process'
|
| 2 |
import { existsSync, mkdtempSync, writeFileSync, unlinkSync, rmdirSync } from 'fs'
|
| 3 |
import { tmpdir } from 'os'
|
| 4 |
import { join, dirname } from 'path'
|
|
|
|
| 21 |
const SCRIPTS_DIR = join(PROJECT_ROOT, 'scripts')
|
| 22 |
const VENV_PYTHON = join(PROJECT_ROOT, '.venv', 'bin', 'python')
|
| 23 |
|
| 24 |
+
function resolvePythonPath(): string {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
if (existsSync(VENV_PYTHON)) return VENV_PYTHON
|
| 26 |
return 'python3'
|
| 27 |
}
|
|
|
|
| 32 |
pythonPath?: string
|
| 33 |
}
|
| 34 |
|
| 35 |
+
let serverProc: ChildProcess | null = null
|
| 36 |
+
let serverLoaded = false
|
| 37 |
+
let serverLoadingResolve: (() => void) | null = null
|
| 38 |
+
let serverLoadingReject: ((err: Error) => void) | null = null
|
| 39 |
+
|
| 40 |
+
function getServer(): ChildProcess {
|
| 41 |
+
if (serverProc && serverProc.exitCode === null) {
|
| 42 |
+
return serverProc
|
| 43 |
+
}
|
| 44 |
+
const python = resolvePythonPath()
|
| 45 |
+
serverProc = spawn(python, [join(SCRIPTS_DIR, 'whisper_server.py')], {
|
| 46 |
+
stdio: ['pipe', 'pipe', 'pipe'],
|
| 47 |
+
})
|
| 48 |
+
|
| 49 |
+
let buf = ''
|
| 50 |
+
serverProc.stdout!.on('data', (data: Buffer) => {
|
| 51 |
+
buf += data.toString()
|
| 52 |
+
const lines = buf.split('\n')
|
| 53 |
+
buf = lines.pop() ?? ''
|
| 54 |
+
for (const line of lines) {
|
| 55 |
+
if (!line) continue
|
| 56 |
+
try {
|
| 57 |
+
const msg = JSON.parse(line)
|
| 58 |
+
if (msg.type === 'ready') {
|
| 59 |
+
serverLoaded = true
|
| 60 |
+
serverLoadingResolve?.()
|
| 61 |
+
serverLoadingResolve = null
|
| 62 |
+
serverLoadingReject = null
|
| 63 |
+
} else if (msg.type === 'error') {
|
| 64 |
+
serverLoaded = false
|
| 65 |
+
serverLoadingReject?.(new Error(msg.message))
|
| 66 |
+
serverLoadingResolve = null
|
| 67 |
+
serverLoadingReject = null
|
| 68 |
+
}
|
| 69 |
+
} catch {}
|
| 70 |
+
}
|
| 71 |
+
})
|
| 72 |
+
|
| 73 |
+
serverProc.on('error', () => {
|
| 74 |
+
serverProc = null
|
| 75 |
+
serverLoaded = false
|
| 76 |
+
serverLoadingReject?.(new Error('Server process error'))
|
| 77 |
+
})
|
| 78 |
+
serverProc.on('close', () => {
|
| 79 |
+
serverProc = null
|
| 80 |
+
serverLoaded = false
|
| 81 |
+
})
|
| 82 |
+
|
| 83 |
+
return serverProc
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
export async function preloadWhisperModel(options?: WhisperOptions): Promise<void> {
|
| 87 |
+
if (serverLoaded) return
|
| 88 |
+
|
| 89 |
+
const proc = getServer()
|
| 90 |
+
const model = options?.model ?? 'small'
|
| 91 |
+
|
| 92 |
+
const responseReady = new Promise<void>((resolve, reject) => {
|
| 93 |
+
serverLoadingResolve = resolve
|
| 94 |
+
serverLoadingReject = reject
|
| 95 |
+
proc.stdin!.write(JSON.stringify({ type: 'load', model }) + '\n')
|
| 96 |
+
})
|
| 97 |
+
|
| 98 |
+
const timeout = new Promise<never>((_, reject) => {
|
| 99 |
+
setTimeout(() => reject(new Error('Model preload timeout')), 30000)
|
| 100 |
+
})
|
| 101 |
+
|
| 102 |
+
await Promise.race([responseReady, timeout])
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
async function transcribeWithServer(
|
| 106 |
+
wavPath: string,
|
| 107 |
+
language?: string | null,
|
| 108 |
+
): Promise<{ text: string; language: string }> {
|
| 109 |
+
const proc = getServer()
|
| 110 |
+
|
| 111 |
+
const result = await new Promise<{ text: string; language: string }>((resolve, reject) => {
|
| 112 |
+
const timeout = setTimeout(() => {
|
| 113 |
+
cleanup()
|
| 114 |
+
reject(new Error('Transcription timeout'))
|
| 115 |
+
}, 30000)
|
| 116 |
+
|
| 117 |
+
let buf = ''
|
| 118 |
+
const cleanup = () => {
|
| 119 |
+
clearTimeout(timeout)
|
| 120 |
+
proc.stdout!.removeAllListeners('data')
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
proc.stdout!.on('data', (data: Buffer) => {
|
| 124 |
+
buf += data.toString()
|
| 125 |
+
const lines = buf.split('\n')
|
| 126 |
+
buf = lines.pop() ?? ''
|
| 127 |
+
for (const line of lines) {
|
| 128 |
+
if (!line) continue
|
| 129 |
+
try {
|
| 130 |
+
const msg = JSON.parse(line)
|
| 131 |
+
if (msg.type === 'result') {
|
| 132 |
+
cleanup()
|
| 133 |
+
resolve({ text: msg.text, language: msg.language })
|
| 134 |
+
} else if (msg.type === 'error') {
|
| 135 |
+
cleanup()
|
| 136 |
+
reject(new Error(msg.message))
|
| 137 |
+
}
|
| 138 |
+
} catch {}
|
| 139 |
+
}
|
| 140 |
+
})
|
| 141 |
+
|
| 142 |
+
proc.stdin!.write(JSON.stringify({ type: 'transcribe', wav: wavPath, language }) + '\n')
|
| 143 |
+
})
|
| 144 |
+
|
| 145 |
+
return result
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
export function connectLocalWhisperStream(
|
| 149 |
callbacks: VoiceStreamCallbacks,
|
| 150 |
options?: WhisperOptions,
|
| 151 |
): Promise<VoiceStreamConnection | null> {
|
| 152 |
+
return new Promise(async resolve => {
|
| 153 |
+
if (!serverLoaded) {
|
| 154 |
+
await preloadWhisperModel(options)
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
const chunks: Buffer[] = []
|
| 158 |
let finalized = false
|
| 159 |
let tmpDir: string | null = null
|
| 160 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
const connection: VoiceStreamConnection = {
|
| 162 |
send(chunk: Buffer) {
|
| 163 |
if (finalized) return
|
| 164 |
chunks.push(Buffer.from(chunk))
|
|
|
|
| 165 |
},
|
| 166 |
|
| 167 |
async finalize(): Promise<FinalizeSource> {
|
| 168 |
if (finalized) return 'ws_already_closed'
|
| 169 |
finalized = true
|
|
|
|
| 170 |
|
| 171 |
if (chunks.length === 0) {
|
|
|
|
| 172 |
callbacks.onClose()
|
| 173 |
return 'no_data_timeout'
|
| 174 |
}
|
| 175 |
|
| 176 |
const audioBuf = Buffer.concat(chunks)
|
|
|
|
| 177 |
|
| 178 |
try {
|
| 179 |
tmpDir = mkdtempSync(join(tmpdir(), 'vc-whisper-'))
|
| 180 |
const wavPath = join(tmpDir, 'input.wav')
|
|
|
|
| 181 |
writeWavHeader(wavPath, audioBuf, 16000)
|
| 182 |
|
| 183 |
+
const result = await transcribeWithServer(wavPath, options?.language)
|
| 184 |
+
if (result.text) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
callbacks.onTranscript(result.text, true)
|
|
|
|
|
|
|
| 186 |
} else {
|
| 187 |
+
callbacks.onTranscript('', true)
|
|
|
|
|
|
|
|
|
|
| 188 |
}
|
| 189 |
} catch (err) {
|
| 190 |
callbacks.onError(
|
|
|
|
| 193 |
)
|
| 194 |
} finally {
|
| 195 |
if (tmpDir) {
|
| 196 |
+
try { unlinkSync(join(tmpDir, 'input.wav')) } catch {}
|
| 197 |
+
try { rmdirSync(tmpDir) } catch {}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
}
|
| 199 |
callbacks.onClose()
|
| 200 |
}
|
|
|
|
| 217 |
})
|
| 218 |
}
|
| 219 |
|
| 220 |
+
export async function checkLocalWhisperAvailable(): Promise<boolean> {
|
| 221 |
+
try {
|
| 222 |
+
const python = resolvePythonPath()
|
| 223 |
+
return await new Promise(resolve => {
|
| 224 |
+
const proc = spawn(python, ['-c', 'import whisper; print("ok")'], {
|
| 225 |
+
stdio: ['ignore', 'pipe', 'pipe'],
|
| 226 |
+
})
|
| 227 |
+
let out = ''
|
| 228 |
+
proc.stdout.on('data', d => { out += d.toString() })
|
| 229 |
+
proc.on('close', () => { resolve(out.trim() === 'ok') })
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
})
|
| 231 |
+
} catch {
|
| 232 |
+
return false
|
| 233 |
+
}
|
| 234 |
}
|
| 235 |
|
| 236 |
function writeWavHeader(path: string, pcmData: Buffer, sampleRate: number): void {
|
|
|
|
| 241 |
const dataSize = pcmData.length
|
| 242 |
|
| 243 |
const header = Buffer.alloc(44)
|
|
|
|
| 244 |
header.write('RIFF', 0)
|
| 245 |
header.writeUInt32LE(36 + dataSize, 4)
|
| 246 |
header.write('WAVE', 8)
|
|
|
|
| 247 |
header.write('fmt ', 12)
|
| 248 |
header.writeUInt32LE(16, 16)
|
| 249 |
header.writeUInt16LE(1, 20)
|
|
|
|
| 252 |
header.writeUInt32LE(byteRate, 28)
|
| 253 |
header.writeUInt16LE(blockAlign, 32)
|
| 254 |
header.writeUInt16LE(bitsPerSample, 34)
|
|
|
|
| 255 |
header.write('data', 36)
|
| 256 |
header.writeUInt32LE(dataSize, 40)
|
|
|
|
| 257 |
writeFileSync(path, Buffer.concat([header, pcmData]))
|
| 258 |
}
|
src/utils/processUserInput/processSlashCommand.tsx
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/utils/processUserInput/processSlashCommand.tsx.bak
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|