npcsh 1.1.12__py3-none-any.whl → 1.1.14__py3-none-any.whl
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.
- npcsh/_state.py +700 -377
- npcsh/alicanto.py +54 -1153
- npcsh/completion.py +206 -0
- npcsh/config.py +163 -0
- npcsh/corca.py +35 -1462
- npcsh/execution.py +185 -0
- npcsh/guac.py +31 -1986
- npcsh/npc_team/jinxs/code/sh.jinx +11 -15
- npcsh/npc_team/jinxs/modes/alicanto.jinx +186 -80
- npcsh/npc_team/jinxs/modes/corca.jinx +243 -22
- npcsh/npc_team/jinxs/modes/guac.jinx +313 -42
- npcsh/npc_team/jinxs/modes/plonk.jinx +209 -48
- npcsh/npc_team/jinxs/modes/pti.jinx +167 -25
- npcsh/npc_team/jinxs/modes/spool.jinx +158 -37
- npcsh/npc_team/jinxs/modes/wander.jinx +179 -74
- npcsh/npc_team/jinxs/modes/yap.jinx +258 -21
- npcsh/npc_team/jinxs/utils/chat.jinx +39 -12
- npcsh/npc_team/jinxs/utils/cmd.jinx +44 -0
- npcsh/npc_team/jinxs/utils/search.jinx +3 -3
- npcsh/npc_team/jinxs/utils/usage.jinx +33 -0
- npcsh/npcsh.py +76 -20
- npcsh/parsing.py +118 -0
- npcsh/plonk.py +41 -329
- npcsh/pti.py +41 -201
- npcsh/spool.py +34 -239
- npcsh/ui.py +199 -0
- npcsh/wander.py +54 -542
- npcsh/yap.py +38 -570
- npcsh-1.1.14.data/data/npcsh/npc_team/alicanto.jinx +194 -0
- npcsh-1.1.14.data/data/npcsh/npc_team/chat.jinx +44 -0
- npcsh-1.1.14.data/data/npcsh/npc_team/cmd.jinx +44 -0
- npcsh-1.1.14.data/data/npcsh/npc_team/corca.jinx +249 -0
- npcsh-1.1.14.data/data/npcsh/npc_team/guac.jinx +317 -0
- npcsh-1.1.14.data/data/npcsh/npc_team/plonk.jinx +214 -0
- npcsh-1.1.14.data/data/npcsh/npc_team/pti.jinx +170 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/search.jinx +3 -3
- npcsh-1.1.14.data/data/npcsh/npc_team/sh.jinx +34 -0
- npcsh-1.1.14.data/data/npcsh/npc_team/spool.jinx +161 -0
- npcsh-1.1.14.data/data/npcsh/npc_team/usage.jinx +33 -0
- npcsh-1.1.14.data/data/npcsh/npc_team/wander.jinx +186 -0
- npcsh-1.1.14.data/data/npcsh/npc_team/yap.jinx +262 -0
- {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/METADATA +1 -1
- npcsh-1.1.14.dist-info/RECORD +135 -0
- npcsh-1.1.12.data/data/npcsh/npc_team/alicanto.jinx +0 -88
- npcsh-1.1.12.data/data/npcsh/npc_team/chat.jinx +0 -17
- npcsh-1.1.12.data/data/npcsh/npc_team/corca.jinx +0 -28
- npcsh-1.1.12.data/data/npcsh/npc_team/guac.jinx +0 -46
- npcsh-1.1.12.data/data/npcsh/npc_team/plonk.jinx +0 -53
- npcsh-1.1.12.data/data/npcsh/npc_team/pti.jinx +0 -28
- npcsh-1.1.12.data/data/npcsh/npc_team/sh.jinx +0 -38
- npcsh-1.1.12.data/data/npcsh/npc_team/spool.jinx +0 -40
- npcsh-1.1.12.data/data/npcsh/npc_team/wander.jinx +0 -81
- npcsh-1.1.12.data/data/npcsh/npc_team/yap.jinx +0 -25
- npcsh-1.1.12.dist-info/RECORD +0 -126
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/agent.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/alicanto.npc +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/alicanto.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/build.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/compile.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/compress.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/corca.npc +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/corca.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/corca_example.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/edit_file.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/foreman.npc +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/frederic.npc +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/frederic4.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/guac.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/help.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/init.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/jinxs.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/kadiefa.npc +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/kadiefa.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/load_file.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/npc-studio.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/npcsh.ctx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/npcsh_sibiji.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/ots.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/plonk.npc +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/plonk.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/plonkjr.npc +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/plonkjr.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/python.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/roll.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sample.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/serve.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/set.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sibiji.npc +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sibiji.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sleep.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/spool.png +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/sql.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/trigger.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/vixynt.jinx +0 -0
- {npcsh-1.1.12.data → npcsh-1.1.14.data}/data/npcsh/npc_team/yap.png +0 -0
- {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/WHEEL +0 -0
- {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/entry_points.txt +0 -0
- {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/licenses/LICENSE +0 -0
- {npcsh-1.1.12.dist-info → npcsh-1.1.14.dist-info}/top_level.txt +0 -0
npcsh/yap.py
CHANGED
|
@@ -1,582 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
yap - Voice chat mode CLI entry point
|
|
1
3
|
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import wave
|
|
8
|
-
import queue
|
|
4
|
+
This is a thin wrapper that executes the yap.jinx through the jinx mechanism.
|
|
5
|
+
"""
|
|
6
|
+
import argparse
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
cleanup_temp_files,
|
|
12
|
-
FORMAT,
|
|
13
|
-
CHANNELS,
|
|
14
|
-
RATE,
|
|
15
|
-
CHUNK,
|
|
16
|
-
transcribe_recording,
|
|
17
|
-
convert_mp3_to_wav,
|
|
18
|
-
)
|
|
19
|
-
import threading
|
|
20
|
-
import tempfile
|
|
21
|
-
import os
|
|
22
|
-
import re
|
|
23
|
-
import time
|
|
24
|
-
import numpy as np
|
|
25
|
-
|
|
10
|
+
from npcsh._state import setup_shell
|
|
26
11
|
|
|
27
|
-
except Exception as e:
|
|
28
|
-
print(
|
|
29
|
-
"Exception: "
|
|
30
|
-
+ str(e)
|
|
31
|
-
+ "\n"
|
|
32
|
-
+ "Could not load the whisper package. If you want to use tts/stt features, please run `pip install npcsh[audio]` and follow the instructions in the npcsh github readme to ensure your OS can handle the audio dependencies."
|
|
33
|
-
)
|
|
34
|
-
from npcpy.data.load import load_csv, load_pdf
|
|
35
|
-
from npcsh._state import (
|
|
36
|
-
NPCSH_CHAT_MODEL,
|
|
37
|
-
NPCSH_CHAT_PROVIDER,
|
|
38
|
-
NPCSH_DB_PATH,
|
|
39
|
-
NPCSH_API_URL,
|
|
40
|
-
NPCSH_STREAM_OUTPUT
|
|
41
|
-
)
|
|
42
12
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
from npcpy.npc_compiler import (
|
|
52
|
-
NPC, Team
|
|
53
|
-
)
|
|
54
|
-
from npcpy.memory.command_history import CommandHistory, save_conversation_message,start_new_conversation
|
|
55
|
-
from typing import Dict, Any, List
|
|
56
|
-
def enter_yap_mode(
|
|
57
|
-
messages: list = None,
|
|
58
|
-
model: str = None,
|
|
59
|
-
provider: str = None ,
|
|
60
|
-
npc = None,
|
|
61
|
-
team = None,
|
|
62
|
-
stream: bool = False,
|
|
63
|
-
api_url: str = None,
|
|
64
|
-
api_key: str=None,
|
|
65
|
-
conversation_id = None,
|
|
66
|
-
tts_model="kokoro",
|
|
67
|
-
voice="af_heart",
|
|
68
|
-
files: List[str] = None,
|
|
69
|
-
rag_similarity_threshold: float = 0.3,
|
|
70
|
-
**kwargs
|
|
71
|
-
) -> Dict[str, Any]:
|
|
72
|
-
running = True
|
|
73
|
-
is_recording = False
|
|
74
|
-
recording_data = []
|
|
75
|
-
buffer_data = []
|
|
76
|
-
last_speech_time = 0
|
|
77
|
-
vad_model, _ = torch.hub.load(
|
|
78
|
-
repo_or_dir="snakers4/silero-vad",
|
|
79
|
-
model="silero_vad",
|
|
80
|
-
force_reload=False,
|
|
81
|
-
onnx=False,
|
|
82
|
-
verbose=False,
|
|
83
|
-
|
|
84
|
-
)
|
|
85
|
-
device = 'cpu'
|
|
86
|
-
vad_model.to(device)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
print("Entering yap mode. Initializing...")
|
|
90
|
-
|
|
91
|
-
concise_instruction = "Please provide brief responses of 1-2 sentences unless the user specifically asks for more detailed information. Keep responses clear and concise."
|
|
92
|
-
|
|
93
|
-
provider = (
|
|
94
|
-
NPCSH_CHAT_PROVIDER if npc is None else npc.provider or NPCSH_CHAT_PROVIDER
|
|
95
|
-
)
|
|
96
|
-
api_url = NPCSH_API_URL if npc is None else npc.api_url or NPCSH_API_URL
|
|
97
|
-
|
|
98
|
-
print(f"\nUsing model: {model} with provider: {provider}")
|
|
99
|
-
|
|
100
|
-
system_message = get_system_message(npc) if npc else "You are a helpful assistant."
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
system_message = system_message + " " + concise_instruction
|
|
104
|
-
|
|
105
|
-
if messages is None or len(messages) == 0:
|
|
106
|
-
messages = [{"role": "system", "content": system_message}]
|
|
107
|
-
elif messages is not None and messages[0]['role'] != 'system':
|
|
108
|
-
messages.insert(0, {"role": "system", "content": system_message})
|
|
109
|
-
|
|
110
|
-
kokoro_pipeline = None
|
|
111
|
-
if tts_model == "kokoro":
|
|
112
|
-
from kokoro import KPipeline
|
|
113
|
-
import soundfile as sf
|
|
114
|
-
|
|
115
|
-
kokoro_pipeline = KPipeline(lang_code="a")
|
|
116
|
-
print("Kokoro TTS model initialized")
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
pyaudio_instance = pyaudio.PyAudio()
|
|
122
|
-
audio_stream = None
|
|
123
|
-
transcription_queue = queue.Queue()
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
is_speaking = threading.Event()
|
|
127
|
-
is_speaking.clear()
|
|
128
|
-
|
|
129
|
-
speech_queue = queue.Queue(maxsize=20)
|
|
130
|
-
speech_thread_active = threading.Event()
|
|
131
|
-
speech_thread_active.set()
|
|
132
|
-
|
|
133
|
-
def speech_playback_thread():
|
|
134
|
-
nonlocal running, audio_stream
|
|
135
|
-
|
|
136
|
-
while running and speech_thread_active.is_set():
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
print('.', end='', flush=True)
|
|
140
|
-
if not speech_queue.empty():
|
|
141
|
-
print('\n')
|
|
142
|
-
text_to_speak = speech_queue.get(timeout=0.1)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if text_to_speak.strip():
|
|
146
|
-
|
|
147
|
-
is_speaking.set()
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
current_audio_stream = audio_stream
|
|
151
|
-
audio_stream = (
|
|
152
|
-
None
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
if current_audio_stream and current_audio_stream.is_active():
|
|
156
|
-
current_audio_stream.stop_stream()
|
|
157
|
-
current_audio_stream.close()
|
|
158
|
-
|
|
159
|
-
print(f"Speaking full response...")
|
|
160
|
-
print(text_to_speak)
|
|
161
|
-
|
|
162
|
-
generate_and_play_speech(text_to_speak)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
time.sleep(0.005 * len(text_to_speak))
|
|
166
|
-
print(len(text_to_speak))
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
is_speaking.clear()
|
|
170
|
-
else:
|
|
171
|
-
time.sleep(0.5)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def safely_close_audio_stream(stream):
|
|
178
|
-
"""Safely close an audio stream with error handling"""
|
|
179
|
-
if stream:
|
|
180
|
-
try:
|
|
181
|
-
if stream.is_active():
|
|
182
|
-
stream.stop_stream()
|
|
183
|
-
stream.close()
|
|
184
|
-
except Exception as e:
|
|
185
|
-
print(f"Error closing audio stream: {e}")
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
speech_thread = threading.Thread(target=speech_playback_thread)
|
|
189
|
-
speech_thread.daemon = True
|
|
190
|
-
speech_thread.start()
|
|
191
|
-
|
|
192
|
-
def generate_and_play_speech(text):
|
|
193
|
-
try:
|
|
194
|
-
|
|
195
|
-
unique_id = str(time.time()).replace(".", "")
|
|
196
|
-
temp_dir = tempfile.gettempdir()
|
|
197
|
-
wav_file = os.path.join(temp_dir, f"temp_{unique_id}.wav")
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if tts_model == "kokoro" and kokoro_pipeline:
|
|
201
|
-
|
|
202
|
-
generator = kokoro_pipeline(text, voice=voice)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
for _, _, audio in generator:
|
|
206
|
-
|
|
207
|
-
import soundfile as sf
|
|
208
|
-
|
|
209
|
-
sf.write(wav_file, audio, 24000)
|
|
210
|
-
break
|
|
211
|
-
else:
|
|
212
|
-
|
|
213
|
-
mp3_file = os.path.join(temp_dir, f"temp_{unique_id}.mp3")
|
|
214
|
-
tts = gTTS(text=text, lang="en", slow=False)
|
|
215
|
-
tts.save(mp3_file)
|
|
216
|
-
convert_mp3_to_wav(mp3_file, wav_file)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
wf = wave.open(wav_file, "rb")
|
|
220
|
-
p = pyaudio.PyAudio()
|
|
221
|
-
|
|
222
|
-
stream = p.open(
|
|
223
|
-
format=p.get_format_from_width(wf.getsampwidth()),
|
|
224
|
-
channels=wf.getnchannels(),
|
|
225
|
-
rate=wf.getframerate(),
|
|
226
|
-
output=True,
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
data = wf.readframes(4096)
|
|
230
|
-
while data and running:
|
|
231
|
-
stream.write(data)
|
|
232
|
-
data = wf.readframes(4096)
|
|
233
|
-
|
|
234
|
-
stream.stop_stream()
|
|
235
|
-
stream.close()
|
|
236
|
-
p.terminate()
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
try:
|
|
240
|
-
if os.path.exists(wav_file):
|
|
241
|
-
os.remove(wav_file)
|
|
242
|
-
if tts_model == "gtts" and "mp3_file" in locals():
|
|
243
|
-
if os.path.exists(mp3_file):
|
|
244
|
-
os.remove(mp3_file)
|
|
245
|
-
except Exception as e:
|
|
246
|
-
print(f"Error removing temp file: {e}")
|
|
247
|
-
|
|
248
|
-
except Exception as e:
|
|
249
|
-
print(f"Error in TTS process: {e}")
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
def speak_text(text):
|
|
253
|
-
speech_queue.put(text)
|
|
254
|
-
|
|
255
|
-
def process_input(user_input, messages):
|
|
256
|
-
|
|
257
|
-
full_response = ""
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
check = check_llm_command(
|
|
261
|
-
user_input,
|
|
262
|
-
npc=npc,
|
|
263
|
-
team=team,
|
|
264
|
-
messages=messages,
|
|
265
|
-
model=model,
|
|
266
|
-
provider=provider,
|
|
267
|
-
stream=False,
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
assistant_reply = check["output"]
|
|
272
|
-
messages = check['messages']
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if stream and not isinstance(assistant_reply,str) and not isinstance(assistant_reply, dict):
|
|
277
|
-
assistant_reply = print_and_process_stream_with_markdown(assistant_reply, model, provider)
|
|
278
|
-
elif isinstance(assistant_reply,dict):
|
|
279
|
-
|
|
280
|
-
assistant_reply = assistant_reply.get('output')
|
|
281
|
-
render_markdown(assistant_reply)
|
|
282
|
-
full_response += assistant_reply
|
|
283
|
-
|
|
284
|
-
print("\n")
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
if full_response.strip():
|
|
288
|
-
processed_text = process_text_for_tts(full_response)
|
|
289
|
-
speak_text(processed_text)
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
messages.append({"role": "assistant", "content": full_response})
|
|
293
|
-
return messages
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
def capture_audio():
|
|
302
|
-
nonlocal is_recording, recording_data, buffer_data, last_speech_time, running, is_speaking
|
|
303
|
-
nonlocal audio_stream, transcription_queue
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if is_speaking.is_set():
|
|
307
|
-
return False
|
|
308
|
-
|
|
309
|
-
try:
|
|
310
|
-
|
|
311
|
-
if audio_stream is None and not is_speaking.is_set():
|
|
312
|
-
audio_stream = pyaudio_instance.open(
|
|
313
|
-
format=FORMAT,
|
|
314
|
-
channels=CHANNELS,
|
|
315
|
-
rate=RATE,
|
|
316
|
-
input=True,
|
|
317
|
-
frames_per_buffer=CHUNK,
|
|
318
|
-
)
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
timeout_counter = 0
|
|
322
|
-
max_timeout = 100
|
|
323
|
-
|
|
324
|
-
print("\nListening for speech...")
|
|
325
|
-
|
|
326
|
-
while (
|
|
327
|
-
running
|
|
328
|
-
and audio_stream
|
|
329
|
-
and audio_stream.is_active()
|
|
330
|
-
and not is_speaking.is_set()
|
|
331
|
-
and timeout_counter < max_timeout
|
|
332
|
-
):
|
|
333
|
-
try:
|
|
334
|
-
|
|
335
|
-
data = audio_stream.read(CHUNK, exception_on_overflow=False)
|
|
336
|
-
|
|
337
|
-
if not data:
|
|
338
|
-
timeout_counter += 1
|
|
339
|
-
time.sleep(0.1)
|
|
340
|
-
continue
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
timeout_counter = 0
|
|
344
|
-
|
|
345
|
-
audio_array = np.frombuffer(data, dtype=np.int16)
|
|
346
|
-
if len(audio_array) == 0:
|
|
347
|
-
continue
|
|
348
|
-
|
|
349
|
-
audio_float = audio_array.astype(np.float32) / 32768.0
|
|
350
|
-
tensor = torch.from_numpy(audio_float).to(device)
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
speech_prob = vad_model(tensor, RATE).item()
|
|
354
|
-
current_time = time.time()
|
|
355
|
-
|
|
356
|
-
if speech_prob > 0.5:
|
|
357
|
-
last_speech_time = current_time
|
|
358
|
-
if not is_recording:
|
|
359
|
-
is_recording = True
|
|
360
|
-
print("\nSpeech detected, listening...")
|
|
361
|
-
recording_data.extend(buffer_data)
|
|
362
|
-
buffer_data = []
|
|
363
|
-
recording_data.append(data)
|
|
364
|
-
else:
|
|
365
|
-
if is_recording:
|
|
366
|
-
if (
|
|
367
|
-
current_time - last_speech_time > 1
|
|
368
|
-
):
|
|
369
|
-
is_recording = False
|
|
370
|
-
print("Speech ended, transcribing...")
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
safely_close_audio_stream(audio_stream)
|
|
374
|
-
audio_stream = None
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
transcription = transcribe_recording(recording_data)
|
|
378
|
-
if transcription:
|
|
379
|
-
transcription_queue.put(transcription)
|
|
380
|
-
recording_data = []
|
|
381
|
-
return True
|
|
382
|
-
else:
|
|
383
|
-
buffer_data.append(data)
|
|
384
|
-
if len(buffer_data) > int(
|
|
385
|
-
0.65 * RATE / CHUNK
|
|
386
|
-
):
|
|
387
|
-
buffer_data.pop(0)
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
if is_speaking.is_set():
|
|
391
|
-
safely_close_audio_stream(audio_stream)
|
|
392
|
-
audio_stream = None
|
|
393
|
-
return False
|
|
394
|
-
|
|
395
|
-
except Exception as e:
|
|
396
|
-
print(f"Error processing audio frame: {e}")
|
|
397
|
-
time.sleep(0.1)
|
|
398
|
-
|
|
399
|
-
except Exception as e:
|
|
400
|
-
print(f"Error in audio capture: {e}")
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
safely_close_audio_stream(audio_stream)
|
|
404
|
-
audio_stream = None
|
|
405
|
-
|
|
406
|
-
return False
|
|
407
|
-
|
|
408
|
-
def process_text_for_tts(text):
|
|
409
|
-
|
|
410
|
-
text = re.sub(r"[*<>{}()\[\]&%#@^_=+~]", "", text)
|
|
411
|
-
text = text.strip()
|
|
412
|
-
|
|
413
|
-
text = re.sub(r"(\w)\.(\w)\.", r"\1 \2 ", text)
|
|
414
|
-
text = re.sub(r"([.!?])(\w)", r"\1 \2", text)
|
|
415
|
-
return text
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
speak_text("Entering yap mode. Please wait.")
|
|
419
|
-
|
|
420
|
-
try:
|
|
421
|
-
loaded_content = {}
|
|
422
|
-
if not conversation_id:
|
|
423
|
-
conversation_id = start_new_conversation()
|
|
424
|
-
command_history = CommandHistory()
|
|
425
|
-
|
|
426
|
-
if files:
|
|
427
|
-
for file in files:
|
|
428
|
-
extension = os.path.splitext(file)[1].lower()
|
|
429
|
-
try:
|
|
430
|
-
if extension == ".pdf":
|
|
431
|
-
content = load_pdf(file)["texts"].iloc[0]
|
|
432
|
-
elif extension == ".csv":
|
|
433
|
-
content = load_csv(file)
|
|
434
|
-
else:
|
|
435
|
-
print(f"Unsupported file type: {file}")
|
|
436
|
-
continue
|
|
437
|
-
loaded_content[file] = content
|
|
438
|
-
print(f"Loaded content from: {file}")
|
|
439
|
-
except Exception as e:
|
|
440
|
-
print(f"Error loading {file}: {str(e)}")
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
while running:
|
|
445
|
-
import select
|
|
446
|
-
import sys
|
|
447
|
-
if not is_speaking.is_set():
|
|
448
|
-
print(
|
|
449
|
-
"🎤🎤🎤🎤\n Speak or type your message (or 'exit' to quit): ",
|
|
450
|
-
end="",
|
|
451
|
-
flush=True,
|
|
452
|
-
)
|
|
453
|
-
rlist, _, _ = select.select([sys.stdin], [], [], 0.1)
|
|
454
|
-
if rlist:
|
|
455
|
-
user_input = sys.stdin.readline().strip()
|
|
456
|
-
if user_input.lower() in ("exit", "quit", "goodbye"):
|
|
457
|
-
print("\nExiting yap mode.")
|
|
458
|
-
break
|
|
459
|
-
if user_input:
|
|
460
|
-
print(f"\nYou (typed): {user_input}")
|
|
461
|
-
|
|
462
|
-
if loaded_content:
|
|
463
|
-
context_content = ""
|
|
464
|
-
for filename, content in loaded_content.items():
|
|
465
|
-
retrieved_docs = rag_search(
|
|
466
|
-
user_input,
|
|
467
|
-
content,
|
|
468
|
-
similarity_threshold=rag_similarity_threshold,
|
|
469
|
-
)
|
|
470
|
-
if retrieved_docs:
|
|
471
|
-
context_content += (
|
|
472
|
-
f"\n\nLoaded content from: {filename}\n{content}\n\n"
|
|
473
|
-
)
|
|
474
|
-
if len(context_content) > 0:
|
|
475
|
-
user_input += f"""
|
|
476
|
-
Here is the loaded content that may be relevant to your query:
|
|
477
|
-
{context_content}
|
|
478
|
-
Please reference it explicitly in your response and use it for answering.
|
|
479
|
-
"""
|
|
480
|
-
message_id = save_conversation_message(
|
|
481
|
-
command_history,
|
|
482
|
-
conversation_id,
|
|
483
|
-
"user",
|
|
484
|
-
user_input,
|
|
485
|
-
wd=os.getcwd(),
|
|
486
|
-
model=model,
|
|
487
|
-
provider=provider,
|
|
488
|
-
npc=npc.name if npc else None,
|
|
489
|
-
)
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
messages= process_input(user_input, messages)
|
|
493
|
-
|
|
494
|
-
message_id = save_conversation_message(
|
|
495
|
-
command_history,
|
|
496
|
-
conversation_id,
|
|
497
|
-
"assistant",
|
|
498
|
-
messages[-1]["content"],
|
|
499
|
-
wd=os.getcwd(),
|
|
500
|
-
model=model,
|
|
501
|
-
provider=provider,
|
|
502
|
-
npc=npc.name if npc else None,
|
|
503
|
-
)
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
continue
|
|
507
|
-
if not is_speaking.is_set():
|
|
508
|
-
print('capturing audio')
|
|
509
|
-
got_speech = capture_audio()
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
if got_speech:
|
|
513
|
-
try:
|
|
514
|
-
transcription = transcription_queue.get_nowait()
|
|
515
|
-
print(f"\nYou (spoke): {transcription}")
|
|
516
|
-
messages = process_input(transcription, messages)
|
|
517
|
-
except queue.Empty:
|
|
518
|
-
pass
|
|
519
|
-
else:
|
|
520
|
-
|
|
521
|
-
time.sleep(0.1)
|
|
522
|
-
|
|
523
|
-
except KeyboardInterrupt:
|
|
524
|
-
print("\nInterrupted by user.")
|
|
13
|
+
def main():
|
|
14
|
+
parser = argparse.ArgumentParser(description="yap - Voice chat mode")
|
|
15
|
+
parser.add_argument("--model", "-m", type=str, help="LLM model to use")
|
|
16
|
+
parser.add_argument("--provider", "-p", type=str, help="LLM provider to use")
|
|
17
|
+
parser.add_argument("--files", "-f", nargs="*", help="Files to load for RAG context")
|
|
18
|
+
parser.add_argument("--tts-model", type=str, default="kokoro", help="TTS model to use")
|
|
19
|
+
parser.add_argument("--voice", type=str, default="af_heart", help="Voice for TTS")
|
|
20
|
+
args = parser.parse_args()
|
|
525
21
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
running = False
|
|
529
|
-
speech_thread_active.clear()
|
|
22
|
+
# Setup shell to get team and default NPC
|
|
23
|
+
command_history, team, default_npc = setup_shell()
|
|
530
24
|
|
|
531
|
-
|
|
532
|
-
|
|
25
|
+
if not team or "yap" not in team.jinxs_dict:
|
|
26
|
+
print("Error: yap jinx not found. Ensure npc_team/jinxs/modes/yap.jinx exists.")
|
|
27
|
+
sys.exit(1)
|
|
533
28
|
|
|
534
|
-
|
|
535
|
-
|
|
29
|
+
# Build context for jinx execution
|
|
30
|
+
context = {
|
|
31
|
+
"npc": default_npc,
|
|
32
|
+
"team": team,
|
|
33
|
+
"messages": [],
|
|
34
|
+
"model": args.model,
|
|
35
|
+
"provider": args.provider,
|
|
36
|
+
"files": ",".join(args.files) if args.files else None,
|
|
37
|
+
"tts_model": args.tts_model,
|
|
38
|
+
"voice": args.voice,
|
|
39
|
+
}
|
|
536
40
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
cleanup_temp_files()
|
|
41
|
+
# Execute the jinx
|
|
42
|
+
yap_jinx = team.jinxs_dict["yap"]
|
|
43
|
+
result = yap_jinx.execute(context=context, npc=default_npc)
|
|
541
44
|
|
|
542
|
-
|
|
45
|
+
if isinstance(result, dict) and result.get("output"):
|
|
46
|
+
print(result["output"])
|
|
543
47
|
|
|
544
|
-
def main():
|
|
545
|
-
|
|
546
|
-
import argparse
|
|
547
|
-
parser = argparse.ArgumentParser(description="Enter yap mode for chatting with an NPC")
|
|
548
|
-
parser.add_argument("--model", default=NPCSH_CHAT_MODEL, help="Model to use")
|
|
549
|
-
parser.add_argument("--provider", default=NPCSH_CHAT_PROVIDER, help="Provider to use")
|
|
550
|
-
parser.add_argument("--files", nargs="*", help="Files to load into context")
|
|
551
|
-
parser.add_argument("--stream", default="true", help="Use streaming mode")
|
|
552
|
-
parser.add_argument("--npc", type=str, default=os.path.expanduser('~/.npcsh/npc_team/sibiji.npc'), help="Path to NPC file")
|
|
553
|
-
args = parser.parse_args()
|
|
554
|
-
npc_db_conn = create_engine(
|
|
555
|
-
f"sqlite:///{NPCSH_DB_PATH}")
|
|
556
|
-
|
|
557
|
-
sibiji = NPC(file=args.npc, db_conn=npc_db_conn)
|
|
558
|
-
|
|
559
|
-
team = Team(team_path = '~/.npcsh/npc_team/', db_conn=npc_db_conn, forenpc= sibiji)
|
|
560
|
-
if sibiji.model is None:
|
|
561
|
-
sibiji.model = args.model
|
|
562
|
-
model = args.model
|
|
563
|
-
else:
|
|
564
|
-
model = sibiji.model
|
|
565
|
-
if sibiji.provider is None:
|
|
566
|
-
sibiji.provider = args.provider
|
|
567
|
-
provider = args.provider
|
|
568
|
-
else:
|
|
569
|
-
provider = sibiji.provider
|
|
570
|
-
|
|
571
|
-
enter_yap_mode(
|
|
572
|
-
messages=None,
|
|
573
|
-
model= model,
|
|
574
|
-
provider = provider,
|
|
575
|
-
npc=sibiji,
|
|
576
|
-
team = team,
|
|
577
|
-
files=args.files,
|
|
578
|
-
stream= args.stream.lower() == "true",
|
|
579
|
-
)
|
|
580
48
|
|
|
581
49
|
if __name__ == "__main__":
|
|
582
|
-
main()
|
|
50
|
+
main()
|