Re-lint after rebase

pull/223/head
Davy Peter Braun 9 months ago
parent 403a29f0d6
commit 79ee710064

@ -10,4 +10,3 @@ In the coming months, we're going to release:
- [ ] An open-source language model for computer control - [ ] An open-source language model for computer control
- [ ] A react-native app for your phone - [ ] A react-native app for your phone
- [ ] A hand-held device that runs fully offline. - [ ] A hand-held device that runs fully offline.

@ -1,4 +1,3 @@
_archive _archive
__pycache__ __pycache__
.idea .idea

@ -26,4 +26,3 @@ And build and upload the firmware with a simple command:
```bash ```bash
pio run --target upload pio run --target upload
``` ```

@ -2,9 +2,11 @@ from ..base_device import Device
device = Device() device = Device()
def main(server_url): def main(server_url):
device.server_url = server_url device.server_url = server_url
device.start() device.start()
if __name__ == "__main__": if __name__ == "__main__":
main() main()

@ -2,9 +2,11 @@ from ..base_device import Device
device = Device() device = Device()
def main(server_url): def main(server_url):
device.server_url = server_url device.server_url = server_url
device.start() device.start()
if __name__ == "__main__": if __name__ == "__main__":
main() main()

@ -2,8 +2,10 @@ from ..base_device import Device
device = Device() device = Device()
def main(): def main():
device.start() device.start()
if __name__ == "__main__": if __name__ == "__main__":
main() main()

@ -2,9 +2,11 @@ from ..base_device import Device
device = Device() device = Device()
def main(server_url): def main(server_url):
device.server_url = server_url device.server_url = server_url
device.start() device.start()
if __name__ == "__main__": if __name__ == "__main__":
main() main()

@ -1,4 +1,5 @@
from dotenv import load_dotenv from dotenv import load_dotenv
load_dotenv() # take environment variables from .env. load_dotenv() # take environment variables from .env.
import os import os
@ -8,7 +9,7 @@ from pathlib import Path
### LLM SETUP ### LLM SETUP
# Define the path to a llamafile # Define the path to a llamafile
llamafile_path = Path(__file__).parent / 'model.llamafile' llamafile_path = Path(__file__).parent / "model.llamafile"
# Check if the new llamafile exists, if not download it # Check if the new llamafile exists, if not download it
if not os.path.exists(llamafile_path): if not os.path.exists(llamafile_path):

@ -1,6 +1,5 @@
class Llm: class Llm:
def __init__(self, config): def __init__(self, config):
# Litellm is used by OI by default, so we just modify OI # Litellm is used by OI by default, so we just modify OI
interpreter = config["interpreter"] interpreter = config["interpreter"]
@ -10,6 +9,3 @@ class Llm:
setattr(interpreter, key.replace("-", "_"), value) setattr(interpreter, key.replace("-", "_"), value)
self.llm = interpreter.llm.completions self.llm = interpreter.llm.completions

@ -3,29 +3,54 @@ import subprocess
import requests import requests
import json import json
class Llm: class Llm:
def __init__(self, config): def __init__(self, config):
self.install(config["service_directory"]) self.install(config["service_directory"])
def install(self, service_directory): def install(self, service_directory):
LLM_FOLDER_PATH = service_directory LLM_FOLDER_PATH = service_directory
self.llm_directory = os.path.join(LLM_FOLDER_PATH, 'llm') self.llm_directory = os.path.join(LLM_FOLDER_PATH, "llm")
if not os.path.isdir(self.llm_directory): # Check if the LLM directory exists if not os.path.isdir(self.llm_directory): # Check if the LLM directory exists
os.makedirs(LLM_FOLDER_PATH, exist_ok=True) os.makedirs(LLM_FOLDER_PATH, exist_ok=True)
# Install WasmEdge # Install WasmEdge
subprocess.run(['curl', '-sSf', 'https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh', '|', 'bash', '-s', '--', '--plugin', 'wasi_nn-ggml']) subprocess.run(
[
"curl",
"-sSf",
"https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh",
"|",
"bash",
"-s",
"--",
"--plugin",
"wasi_nn-ggml",
]
)
# Download the Qwen1.5-0.5B-Chat model GGUF file # Download the Qwen1.5-0.5B-Chat model GGUF file
MODEL_URL = "https://huggingface.co/second-state/Qwen1.5-0.5B-Chat-GGUF/resolve/main/Qwen1.5-0.5B-Chat-Q5_K_M.gguf" MODEL_URL = "https://huggingface.co/second-state/Qwen1.5-0.5B-Chat-GGUF/resolve/main/Qwen1.5-0.5B-Chat-Q5_K_M.gguf"
subprocess.run(['curl', '-LO', MODEL_URL], cwd=self.llm_directory) subprocess.run(["curl", "-LO", MODEL_URL], cwd=self.llm_directory)
# Download the llama-api-server.wasm app # Download the llama-api-server.wasm app
APP_URL = "https://github.com/LlamaEdge/LlamaEdge/releases/latest/download/llama-api-server.wasm" APP_URL = "https://github.com/LlamaEdge/LlamaEdge/releases/latest/download/llama-api-server.wasm"
subprocess.run(['curl', '-LO', APP_URL], cwd=self.llm_directory) subprocess.run(["curl", "-LO", APP_URL], cwd=self.llm_directory)
# Run the API server # Run the API server
subprocess.run(['wasmedge', '--dir', '.:.', '--nn-preload', 'default:GGML:AUTO:Qwen1.5-0.5B-Chat-Q5_K_M.gguf', 'llama-api-server.wasm', '-p', 'llama-2-chat'], cwd=self.llm_directory) subprocess.run(
[
"wasmedge",
"--dir",
".:.",
"--nn-preload",
"default:GGML:AUTO:Qwen1.5-0.5B-Chat-Q5_K_M.gguf",
"llama-api-server.wasm",
"-p",
"llama-2-chat",
],
cwd=self.llm_directory,
)
print("LLM setup completed.") print("LLM setup completed.")
else: else:
@ -33,17 +58,11 @@ class Llm:
def llm(self, messages): def llm(self, messages):
url = "http://localhost:8080/v1/chat/completions" url = "http://localhost:8080/v1/chat/completions"
headers = { headers = {"accept": "application/json", "Content-Type": "application/json"}
'accept': 'application/json', data = {"messages": messages, "model": "llama-2-chat"}
'Content-Type': 'application/json' with requests.post(
} url, headers=headers, data=json.dumps(data), stream=True
data = { ) as response:
"messages": messages,
"model": "llama-2-chat"
}
with requests.post(url, headers=headers, data=json.dumps(data), stream=True) as response:
for line in response.iter_lines(): for line in response.iter_lines():
if line: if line:
yield json.loads(line) yield json.loads(line)

@ -6,7 +6,6 @@ class Stt:
return stt(audio_file_path) return stt(audio_file_path)
from datetime import datetime from datetime import datetime
import os import os
import contextlib import contextlib
@ -19,6 +18,7 @@ from openai import OpenAI
client = OpenAI() client = OpenAI()
def convert_mime_type_to_format(mime_type: str) -> str: def convert_mime_type_to_format(mime_type: str) -> str:
if mime_type == "audio/x-wav" or mime_type == "audio/wav": if mime_type == "audio/x-wav" or mime_type == "audio/wav":
return "wav" return "wav"
@ -29,30 +29,37 @@ def convert_mime_type_to_format(mime_type: str) -> str:
return mime_type return mime_type
@contextlib.contextmanager @contextlib.contextmanager
def export_audio_to_wav_ffmpeg(audio: bytearray, mime_type: str) -> str: def export_audio_to_wav_ffmpeg(audio: bytearray, mime_type: str) -> str:
temp_dir = tempfile.gettempdir() temp_dir = tempfile.gettempdir()
# Create a temporary file with the appropriate extension # Create a temporary file with the appropriate extension
input_ext = convert_mime_type_to_format(mime_type) input_ext = convert_mime_type_to_format(mime_type)
input_path = os.path.join(temp_dir, f"input_{datetime.now().strftime('%Y%m%d%H%M%S%f')}.{input_ext}") input_path = os.path.join(
with open(input_path, 'wb') as f: temp_dir, f"input_{datetime.now().strftime('%Y%m%d%H%M%S%f')}.{input_ext}"
)
with open(input_path, "wb") as f:
f.write(audio) f.write(audio)
# Check if the input file exists # Check if the input file exists
assert os.path.exists(input_path), f"Input file does not exist: {input_path}" assert os.path.exists(input_path), f"Input file does not exist: {input_path}"
# Export to wav # Export to wav
output_path = os.path.join(temp_dir, f"output_{datetime.now().strftime('%Y%m%d%H%M%S%f')}.wav") output_path = os.path.join(
temp_dir, f"output_{datetime.now().strftime('%Y%m%d%H%M%S%f')}.wav"
)
if mime_type == "audio/raw": if mime_type == "audio/raw":
ffmpeg.input( ffmpeg.input(
input_path, input_path,
f='s16le', f="s16le",
ar='16000', ar="16000",
ac=1, ac=1,
).output(output_path, loglevel='panic').run() ).output(output_path, loglevel="panic").run()
else: else:
ffmpeg.input(input_path).output(output_path, acodec='pcm_s16le', ac=1, ar='16k', loglevel='panic').run() ffmpeg.input(input_path).output(
output_path, acodec="pcm_s16le", ac=1, ar="16k", loglevel="panic"
).run()
try: try:
yield output_path yield output_path
@ -60,39 +67,49 @@ def export_audio_to_wav_ffmpeg(audio: bytearray, mime_type: str) -> str:
os.remove(input_path) os.remove(input_path)
os.remove(output_path) os.remove(output_path)
def run_command(command): def run_command(command):
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) result = subprocess.run(
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
)
return result.stdout, result.stderr return result.stdout, result.stderr
def get_transcription_file(wav_file_path: str):
local_path = os.path.join(os.path.dirname(__file__), 'local_service')
whisper_rust_path = os.path.join(os.path.dirname(__file__), 'whisper-rust', 'target', 'release')
model_name = os.getenv('WHISPER_MODEL_NAME', 'ggml-tiny.en.bin')
output, error = run_command([ def get_transcription_file(wav_file_path: str):
os.path.join(whisper_rust_path, 'whisper-rust'), local_path = os.path.join(os.path.dirname(__file__), "local_service")
'--model-path', os.path.join(local_path, model_name), whisper_rust_path = os.path.join(
'--file-path', wav_file_path os.path.dirname(__file__), "whisper-rust", "target", "release"
]) )
model_name = os.getenv("WHISPER_MODEL_NAME", "ggml-tiny.en.bin")
output, error = run_command(
[
os.path.join(whisper_rust_path, "whisper-rust"),
"--model-path",
os.path.join(local_path, model_name),
"--file-path",
wav_file_path,
]
)
return output return output
def get_transcription_bytes(audio_bytes: bytearray, mime_type): def get_transcription_bytes(audio_bytes: bytearray, mime_type):
with export_audio_to_wav_ffmpeg(audio_bytes, mime_type) as wav_file_path: with export_audio_to_wav_ffmpeg(audio_bytes, mime_type) as wav_file_path:
return get_transcription_file(wav_file_path) return get_transcription_file(wav_file_path)
def stt_bytes(audio_bytes: bytearray, mime_type="audio/wav"): def stt_bytes(audio_bytes: bytearray, mime_type="audio/wav"):
with export_audio_to_wav_ffmpeg(audio_bytes, mime_type) as wav_file_path: with export_audio_to_wav_ffmpeg(audio_bytes, mime_type) as wav_file_path:
return stt_wav(wav_file_path) return stt_wav(wav_file_path)
def stt_wav(wav_file_path: str):
def stt_wav(wav_file_path: str):
audio_file = open(wav_file_path, "rb") audio_file = open(wav_file_path, "rb")
try: try:
transcript = client.audio.transcriptions.create( transcript = client.audio.transcriptions.create(
model="whisper-1", model="whisper-1", file=audio_file, response_format="text"
file=audio_file,
response_format="text"
) )
except openai.BadRequestError as e: except openai.BadRequestError as e:
print(f"openai.BadRequestError: {e}") print(f"openai.BadRequestError: {e}")
@ -100,10 +117,13 @@ def stt_wav(wav_file_path: str):
return transcript return transcript
def stt(input_data, mime_type="audio/wav"): def stt(input_data, mime_type="audio/wav"):
if isinstance(input_data, str): if isinstance(input_data, str):
return stt_wav(input_data) return stt_wav(input_data)
elif isinstance(input_data, bytearray): elif isinstance(input_data, bytearray):
return stt_bytes(input_data, mime_type) return stt_bytes(input_data, mime_type)
else: else:
raise ValueError("Input data should be either a path to a wav file (str) or audio bytes (bytearray)") raise ValueError(
"Input data should be either a path to a wav file (str) or audio bytes (bytearray)"
)

@ -13,26 +13,40 @@ class Tts:
self.install(config["service_directory"]) self.install(config["service_directory"])
def tts(self, text): def tts(self, text):
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_file: with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_file:
output_file = temp_file.name output_file = temp_file.name
piper_dir = self.piper_directory piper_dir = self.piper_directory
subprocess.run([ subprocess.run(
os.path.join(piper_dir, 'piper'), [
'--model', os.path.join(piper_dir, os.getenv('PIPER_VOICE_NAME', 'en_US-lessac-medium.onnx')), os.path.join(piper_dir, "piper"),
'--output_file', output_file "--model",
], input=text, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) os.path.join(
piper_dir,
os.getenv("PIPER_VOICE_NAME", "en_US-lessac-medium.onnx"),
),
"--output_file",
output_file,
],
input=text,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
# TODO: hack to format audio correctly for device # TODO: hack to format audio correctly for device
outfile = tempfile.gettempdir() + "/" + "raw.dat" outfile = tempfile.gettempdir() + "/" + "raw.dat"
ffmpeg.input(temp_file.name).output(outfile, f="s16le", ar="16000", ac="1", loglevel='panic').run() ffmpeg.input(temp_file.name).output(
outfile, f="s16le", ar="16000", ac="1", loglevel="panic"
).run()
return outfile return outfile
def install(self, service_directory): def install(self, service_directory):
PIPER_FOLDER_PATH = service_directory PIPER_FOLDER_PATH = service_directory
self.piper_directory = os.path.join(PIPER_FOLDER_PATH, 'piper') self.piper_directory = os.path.join(PIPER_FOLDER_PATH, "piper")
if not os.path.isdir(self.piper_directory): # Check if the Piper directory exists if not os.path.isdir(
self.piper_directory
): # Check if the Piper directory exists
os.makedirs(PIPER_FOLDER_PATH, exist_ok=True) os.makedirs(PIPER_FOLDER_PATH, exist_ok=True)
# Determine OS and architecture # Determine OS and architecture
@ -60,51 +74,91 @@ class Tts:
asset_url = f"{PIPER_URL}{PIPER_ASSETNAME}" asset_url = f"{PIPER_URL}{PIPER_ASSETNAME}"
if OS == "windows": if OS == "windows":
asset_url = asset_url.replace(".tar.gz", ".zip") asset_url = asset_url.replace(".tar.gz", ".zip")
# Download and extract Piper # Download and extract Piper
urllib.request.urlretrieve(asset_url, os.path.join(PIPER_FOLDER_PATH, PIPER_ASSETNAME)) urllib.request.urlretrieve(
asset_url, os.path.join(PIPER_FOLDER_PATH, PIPER_ASSETNAME)
)
# Extract the downloaded file # Extract the downloaded file
if OS == "windows": if OS == "windows":
import zipfile import zipfile
with zipfile.ZipFile(os.path.join(PIPER_FOLDER_PATH, PIPER_ASSETNAME), 'r') as zip_ref:
with zipfile.ZipFile(
os.path.join(PIPER_FOLDER_PATH, PIPER_ASSETNAME), "r"
) as zip_ref:
zip_ref.extractall(path=PIPER_FOLDER_PATH) zip_ref.extractall(path=PIPER_FOLDER_PATH)
else: else:
with tarfile.open(os.path.join(PIPER_FOLDER_PATH, PIPER_ASSETNAME), 'r:gz') as tar: with tarfile.open(
os.path.join(PIPER_FOLDER_PATH, PIPER_ASSETNAME), "r:gz"
) as tar:
tar.extractall(path=PIPER_FOLDER_PATH) tar.extractall(path=PIPER_FOLDER_PATH)
PIPER_VOICE_URL = os.getenv('PIPER_VOICE_URL', PIPER_VOICE_URL = os.getenv(
'https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/lessac/medium/') "PIPER_VOICE_URL",
PIPER_VOICE_NAME = os.getenv('PIPER_VOICE_NAME', 'en_US-lessac-medium.onnx') "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/lessac/medium/",
)
PIPER_VOICE_NAME = os.getenv("PIPER_VOICE_NAME", "en_US-lessac-medium.onnx")
# Download voice model and its json file # Download voice model and its json file
urllib.request.urlretrieve(f"{PIPER_VOICE_URL}{PIPER_VOICE_NAME}", urllib.request.urlretrieve(
os.path.join(self.piper_directory, PIPER_VOICE_NAME)) f"{PIPER_VOICE_URL}{PIPER_VOICE_NAME}",
urllib.request.urlretrieve(f"{PIPER_VOICE_URL}{PIPER_VOICE_NAME}.json", os.path.join(self.piper_directory, PIPER_VOICE_NAME),
os.path.join(self.piper_directory, f"{PIPER_VOICE_NAME}.json")) )
urllib.request.urlretrieve(
f"{PIPER_VOICE_URL}{PIPER_VOICE_NAME}.json",
os.path.join(self.piper_directory, f"{PIPER_VOICE_NAME}.json"),
)
# Additional setup for macOS # Additional setup for macOS
if OS == "macos": if OS == "macos":
if ARCH == "x64": if ARCH == "x64":
subprocess.run(['softwareupdate', '--install-rosetta', '--agree-to-license']) subprocess.run(
["softwareupdate", "--install-rosetta", "--agree-to-license"]
)
PIPER_PHONEMIZE_ASSETNAME = f"piper-phonemize_{OS}_{ARCH}.tar.gz" PIPER_PHONEMIZE_ASSETNAME = f"piper-phonemize_{OS}_{ARCH}.tar.gz"
PIPER_PHONEMIZE_URL = "https://github.com/rhasspy/piper-phonemize/releases/latest/download/" PIPER_PHONEMIZE_URL = "https://github.com/rhasspy/piper-phonemize/releases/latest/download/"
urllib.request.urlretrieve(f"{PIPER_PHONEMIZE_URL}{PIPER_PHONEMIZE_ASSETNAME}", urllib.request.urlretrieve(
os.path.join(self.piper_directory, PIPER_PHONEMIZE_ASSETNAME)) f"{PIPER_PHONEMIZE_URL}{PIPER_PHONEMIZE_ASSETNAME}",
os.path.join(self.piper_directory, PIPER_PHONEMIZE_ASSETNAME),
with tarfile.open(os.path.join(self.piper_directory, PIPER_PHONEMIZE_ASSETNAME), 'r:gz') as tar: )
with tarfile.open(
os.path.join(self.piper_directory, PIPER_PHONEMIZE_ASSETNAME),
"r:gz",
) as tar:
tar.extractall(path=self.piper_directory) tar.extractall(path=self.piper_directory)
PIPER_DIR = self.piper_directory PIPER_DIR = self.piper_directory
subprocess.run(['install_name_tool', '-change', '@rpath/libespeak-ng.1.dylib', subprocess.run(
f"{PIPER_DIR}/piper-phonemize/lib/libespeak-ng.1.dylib", f"{PIPER_DIR}/piper"]) [
subprocess.run(['install_name_tool', '-change', '@rpath/libonnxruntime.1.14.1.dylib', "install_name_tool",
f"{PIPER_DIR}/piper-phonemize/lib/libonnxruntime.1.14.1.dylib", f"{PIPER_DIR}/piper"]) "-change",
subprocess.run(['install_name_tool', '-change', '@rpath/libpiper_phonemize.1.dylib', "@rpath/libespeak-ng.1.dylib",
f"{PIPER_DIR}/piper-phonemize/lib/libpiper_phonemize.1.dylib", f"{PIPER_DIR}/piper"]) f"{PIPER_DIR}/piper-phonemize/lib/libespeak-ng.1.dylib",
f"{PIPER_DIR}/piper",
]
)
subprocess.run(
[
"install_name_tool",
"-change",
"@rpath/libonnxruntime.1.14.1.dylib",
f"{PIPER_DIR}/piper-phonemize/lib/libonnxruntime.1.14.1.dylib",
f"{PIPER_DIR}/piper",
]
)
subprocess.run(
[
"install_name_tool",
"-change",
"@rpath/libpiper_phonemize.1.dylib",
f"{PIPER_DIR}/piper-phonemize/lib/libpiper_phonemize.1.dylib",
f"{PIPER_DIR}/piper",
]
)
print("Piper setup completed.") print("Piper setup completed.")
else: else:

@ -131,4 +131,6 @@ print(output)
Remember: You can run Python code outside a function only to run a Python function; all other code must go in a in Python function if you first write a Python function. ALL imports must go inside the function. Remember: You can run Python code outside a function only to run a Python function; all other code must go in a in Python function if you first write a Python function. ALL imports must go inside the function.
""".strip().replace("OI_SKILLS_DIR", os.path.abspath(os.path.join(os.path.dirname(__file__), "skills"))) """.strip().replace(
"OI_SKILLS_DIR", os.path.abspath(os.path.join(os.path.dirname(__file__), "skills"))
)

@ -1,12 +1,14 @@
import subprocess import subprocess
import re import re
import shutil
import pyqrcode import pyqrcode
import time import time
from ..utils.print_markdown import print_markdown from ..utils.print_markdown import print_markdown
def create_tunnel(tunnel_method='ngrok', server_host='localhost', server_port=10001, qr=False):
print_markdown(f"Exposing server to the internet...") def create_tunnel(
tunnel_method="ngrok", server_host="localhost", server_port=10001, qr=False
):
print_markdown("Exposing server to the internet...")
server_url = "" server_url = ""
if tunnel_method == "bore": if tunnel_method == "bore":
@ -35,9 +37,11 @@ def create_tunnel(tunnel_method='ngrok', server_host='localhost', server_port=10
if not line: if not line:
break break
if "listening at bore.pub:" in line: if "listening at bore.pub:" in line:
remote_port = re.search('bore.pub:([0-9]*)', line).group(1) remote_port = re.search("bore.pub:([0-9]*)", line).group(1)
server_url = f"bore.pub:{remote_port}" server_url = f"bore.pub:{remote_port}"
print_markdown(f"Your server is being hosted at the following URL: bore.pub:{remote_port}") print_markdown(
f"Your server is being hosted at the following URL: bore.pub:{remote_port}"
)
break break
elif tunnel_method == "localtunnel": elif tunnel_method == "localtunnel":
@ -69,9 +73,11 @@ def create_tunnel(tunnel_method='ngrok', server_host='localhost', server_port=10
match = url_pattern.search(line) match = url_pattern.search(line)
if match: if match:
found_url = True found_url = True
remote_url = match.group(0).replace('your url is: ', '') remote_url = match.group(0).replace("your url is: ", "")
server_url = remote_url server_url = remote_url
print(f"\nYour server is being hosted at the following URL: {remote_url}") print(
f"\nYour server is being hosted at the following URL: {remote_url}"
)
break # Exit the loop once the URL is found break # Exit the loop once the URL is found
if not found_url: if not found_url:
@ -93,7 +99,11 @@ def create_tunnel(tunnel_method='ngrok', server_host='localhost', server_port=10
# If ngrok is installed, start it on the specified port # If ngrok is installed, start it on the specified port
# process = subprocess.Popen(f'ngrok http {server_port} --log=stdout', shell=True, stdout=subprocess.PIPE) # process = subprocess.Popen(f'ngrok http {server_port} --log=stdout', shell=True, stdout=subprocess.PIPE)
process = subprocess.Popen(f'ngrok http {server_port} --scheme http,https --domain=marten-advanced-dragon.ngrok-free.app --log=stdout', shell=True, stdout=subprocess.PIPE) process = subprocess.Popen(
f"ngrok http {server_port} --scheme http,https --domain=marten-advanced-dragon.ngrok-free.app --log=stdout",
shell=True,
stdout=subprocess.PIPE,
)
# Initially, no URL is found # Initially, no URL is found
found_url = False found_url = False
@ -110,15 +120,18 @@ def create_tunnel(tunnel_method='ngrok', server_host='localhost', server_port=10
found_url = True found_url = True
remote_url = match.group(0) remote_url = match.group(0)
server_url = remote_url server_url = remote_url
print(f"\nYour server is being hosted at the following URL: {remote_url}") print(
f"\nYour server is being hosted at the following URL: {remote_url}"
)
break # Exit the loop once the URL is found break # Exit the loop once the URL is found
if not found_url: if not found_url:
print("Failed to extract the ngrok tunnel URL. Please check ngrok's output for details.") print(
"Failed to extract the ngrok tunnel URL. Please check ngrok's output for details."
)
if server_url and qr: if server_url and qr:
text = pyqrcode.create(remote_url) text = pyqrcode.create(remote_url)
print(text.terminal(quiet_zone=1)) print(text.terminal(quiet_zone=1))
return server_url return server_url

@ -5,6 +5,7 @@ import tempfile
import ffmpeg import ffmpeg
import subprocess import subprocess
def convert_mime_type_to_format(mime_type: str) -> str: def convert_mime_type_to_format(mime_type: str) -> str:
if mime_type == "audio/x-wav" or mime_type == "audio/wav": if mime_type == "audio/x-wav" or mime_type == "audio/wav":
return "wav" return "wav"
@ -15,39 +16,49 @@ def convert_mime_type_to_format(mime_type: str) -> str:
return mime_type return mime_type
@contextlib.contextmanager @contextlib.contextmanager
def export_audio_to_wav_ffmpeg(audio: bytearray, mime_type: str) -> str: def export_audio_to_wav_ffmpeg(audio: bytearray, mime_type: str) -> str:
temp_dir = tempfile.gettempdir() temp_dir = tempfile.gettempdir()
# Create a temporary file with the appropriate extension # Create a temporary file with the appropriate extension
input_ext = convert_mime_type_to_format(mime_type) input_ext = convert_mime_type_to_format(mime_type)
input_path = os.path.join(temp_dir, f"input_{datetime.now().strftime('%Y%m%d%H%M%S%f')}.{input_ext}") input_path = os.path.join(
with open(input_path, 'wb') as f: temp_dir, f"input_{datetime.now().strftime('%Y%m%d%H%M%S%f')}.{input_ext}"
)
with open(input_path, "wb") as f:
f.write(audio) f.write(audio)
# Check if the input file exists # Check if the input file exists
assert os.path.exists(input_path), f"Input file does not exist: {input_path}" assert os.path.exists(input_path), f"Input file does not exist: {input_path}"
# Export to wav # Export to wav
output_path = os.path.join(temp_dir, f"output_{datetime.now().strftime('%Y%m%d%H%M%S%f')}.wav") output_path = os.path.join(
temp_dir, f"output_{datetime.now().strftime('%Y%m%d%H%M%S%f')}.wav"
)
print(mime_type, input_path, output_path) print(mime_type, input_path, output_path)
if mime_type == "audio/raw": if mime_type == "audio/raw":
ffmpeg.input( ffmpeg.input(
input_path, input_path,
f='s16le', f="s16le",
ar='16000', ar="16000",
ac=1, ac=1,
).output(output_path, loglevel='panic').run() ).output(output_path, loglevel="panic").run()
else: else:
ffmpeg.input(input_path).output(output_path, acodec='pcm_s16le', ac=1, ar='16k', loglevel='panic').run() ffmpeg.input(input_path).output(
output_path, acodec="pcm_s16le", ac=1, ar="16k", loglevel="panic"
).run()
try: try:
yield output_path yield output_path
finally: finally:
os.remove(input_path) os.remove(input_path)
def run_command(command): def run_command(command):
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) result = subprocess.run(
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
)
return result.stdout, result.stderr return result.stdout, result.stderr

@ -1,4 +1,5 @@
from dotenv import load_dotenv from dotenv import load_dotenv
load_dotenv() # take environment variables from .env. load_dotenv() # take environment variables from .env.
import asyncio import asyncio
@ -7,8 +8,10 @@ import platform
from .logs import setup_logging from .logs import setup_logging
from .logs import logger from .logs import logger
setup_logging() setup_logging()
def get_kernel_messages(): def get_kernel_messages():
""" """
Is this the way to do this? Is this the way to do this?
@ -16,20 +19,23 @@ def get_kernel_messages():
current_platform = platform.system() current_platform = platform.system()
if current_platform == "Darwin": if current_platform == "Darwin":
process = subprocess.Popen(['syslog'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) process = subprocess.Popen(
["syslog"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL
)
output, _ = process.communicate() output, _ = process.communicate()
return output.decode('utf-8') return output.decode("utf-8")
elif current_platform == "Linux": elif current_platform == "Linux":
with open('/var/log/dmesg', 'r') as file: with open("/var/log/dmesg", "r") as file:
return file.read() return file.read()
else: else:
logger.info("Unsupported platform.") logger.info("Unsupported platform.")
def custom_filter(message): def custom_filter(message):
# Check for {TO_INTERPRETER{ message here }TO_INTERPRETER} pattern # Check for {TO_INTERPRETER{ message here }TO_INTERPRETER} pattern
if '{TO_INTERPRETER{' in message and '}TO_INTERPRETER}' in message: if "{TO_INTERPRETER{" in message and "}TO_INTERPRETER}" in message:
start = message.find('{TO_INTERPRETER{') + len('{TO_INTERPRETER{') start = message.find("{TO_INTERPRETER{") + len("{TO_INTERPRETER{")
end = message.find('}TO_INTERPRETER}', start) end = message.find("}TO_INTERPRETER}", start)
return message[start:end] return message[start:end]
# Check for USB mention # Check for USB mention
# elif 'USB' in message: # elif 'USB' in message:
@ -41,8 +47,10 @@ def custom_filter(message):
else: else:
return None return None
last_messages = "" last_messages = ""
def check_filtered_kernel(): def check_filtered_kernel():
messages = get_kernel_messages() messages = get_kernel_messages()
if messages is None: if messages is None:
@ -66,11 +74,25 @@ async def put_kernel_messages_into_queue(queue):
if text: if text:
if isinstance(queue, asyncio.Queue): if isinstance(queue, asyncio.Queue):
await queue.put({"role": "computer", "type": "console", "start": True}) await queue.put({"role": "computer", "type": "console", "start": True})
await queue.put({"role": "computer", "type": "console", "format": "output", "content": text}) await queue.put(
{
"role": "computer",
"type": "console",
"format": "output",
"content": text,
}
)
await queue.put({"role": "computer", "type": "console", "end": True}) await queue.put({"role": "computer", "type": "console", "end": True})
else: else:
queue.put({"role": "computer", "type": "console", "start": True}) queue.put({"role": "computer", "type": "console", "start": True})
queue.put({"role": "computer", "type": "console", "format": "output", "content": text}) queue.put(
{
"role": "computer",
"type": "console",
"format": "output",
"content": text,
}
)
queue.put({"role": "computer", "type": "console", "end": True}) queue.put({"role": "computer", "type": "console", "end": True})
await asyncio.sleep(5) await asyncio.sleep(5)

@ -1,4 +1,5 @@
from dotenv import load_dotenv from dotenv import load_dotenv
load_dotenv() # take environment variables from .env. load_dotenv() # take environment variables from .env.
import os import os
@ -9,9 +10,7 @@ root_logger: logging.Logger = logging.getLogger()
def _basic_config() -> None: def _basic_config() -> None:
logging.basicConfig( logging.basicConfig(format="%(message)s")
format="%(message)s"
)
def setup_logging() -> None: def setup_logging() -> None:

@ -6,7 +6,6 @@ class Accumulator:
def accumulate(self, chunk): def accumulate(self, chunk):
# print(str(chunk)[:100]) # print(str(chunk)[:100])
if type(chunk) == dict: if type(chunk) == dict:
if "format" in chunk and chunk["format"] == "active_line": if "format" in chunk and chunk["format"] == "active_line":
# We don't do anything with these # We don't do anything with these
return None return None
@ -17,15 +16,20 @@ class Accumulator:
return None return None
if "content" in chunk: if "content" in chunk:
if any(
if any(self.message[key] != chunk[key] for key in self.message if key != "content"): self.message[key] != chunk[key]
for key in self.message
if key != "content"
):
self.message = chunk self.message = chunk
if "content" not in self.message: if "content" not in self.message:
self.message["content"] = chunk["content"] self.message["content"] = chunk["content"]
else: else:
if type(chunk["content"]) == dict: if type(chunk["content"]) == dict:
# dict concatenation cannot happen, so we see if chunk is a dict # dict concatenation cannot happen, so we see if chunk is a dict
self.message["content"]["content"] += chunk["content"]["content"] self.message["content"]["content"] += chunk["content"][
"content"
]
else: else:
self.message["content"] += chunk["content"] self.message["content"] += chunk["content"]
return None return None
@ -41,5 +45,3 @@ class Accumulator:
self.message["content"] = b"" self.message["content"] = b""
self.message["content"] += chunk self.message["content"] += chunk
return None return None

@ -1,6 +1,7 @@
from rich.console import Console from rich.console import Console
from rich.markdown import Markdown from rich.markdown import Markdown
def print_markdown(markdown_text): def print_markdown(markdown_text):
console = Console() console = Console()
md = Markdown(markdown_text) md = Markdown(markdown_text)

@ -16,34 +16,63 @@ app = typer.Typer()
@app.command() @app.command()
def run( def run(
server: bool = typer.Option(False, "--server", help="Run server"), server: bool = typer.Option(False, "--server", help="Run server"),
server_host: str = typer.Option("0.0.0.0", "--server-host", help="Specify the server host where the server will deploy"), server_host: str = typer.Option(
server_port: int = typer.Option(10001, "--server-port", help="Specify the server port where the server will deploy"), "0.0.0.0",
"--server-host",
tunnel_service: str = typer.Option("ngrok", "--tunnel-service", help="Specify the tunnel service"), help="Specify the server host where the server will deploy",
),
server_port: int = typer.Option(
10001,
"--server-port",
help="Specify the server port where the server will deploy",
),
tunnel_service: str = typer.Option(
"ngrok", "--tunnel-service", help="Specify the tunnel service"
),
expose: bool = typer.Option(False, "--expose", help="Expose server to internet"), expose: bool = typer.Option(False, "--expose", help="Expose server to internet"),
client: bool = typer.Option(False, "--client", help="Run client"), client: bool = typer.Option(False, "--client", help="Run client"),
server_url: str = typer.Option(None, "--server-url", help="Specify the server URL that the client should expect. Defaults to server-host and server-port"), server_url: str = typer.Option(
client_type: str = typer.Option("auto", "--client-type", help="Specify the client type"), None,
"--server-url",
llm_service: str = typer.Option("litellm", "--llm-service", help="Specify the LLM service"), help="Specify the server URL that the client should expect. Defaults to server-host and server-port",
),
client_type: str = typer.Option(
"auto", "--client-type", help="Specify the client type"
),
llm_service: str = typer.Option(
"litellm", "--llm-service", help="Specify the LLM service"
),
model: str = typer.Option("gpt-4", "--model", help="Specify the model"), model: str = typer.Option("gpt-4", "--model", help="Specify the model"),
llm_supports_vision: bool = typer.Option(False, "--llm-supports-vision", help="Specify if the LLM service supports vision"), llm_supports_vision: bool = typer.Option(
llm_supports_functions: bool = typer.Option(False, "--llm-supports-functions", help="Specify if the LLM service supports functions"), False,
context_window: int = typer.Option(2048, "--context-window", help="Specify the context window size"), "--llm-supports-vision",
max_tokens: int = typer.Option(4096, "--max-tokens", help="Specify the maximum number of tokens"), help="Specify if the LLM service supports vision",
temperature: float = typer.Option(0.8, "--temperature", help="Specify the temperature for generation"), ),
llm_supports_functions: bool = typer.Option(
tts_service: str = typer.Option("openai", "--tts-service", help="Specify the TTS service"), False,
"--llm-supports-functions",
stt_service: str = typer.Option("openai", "--stt-service", help="Specify the STT service"), help="Specify if the LLM service supports functions",
),
local: bool = typer.Option(False, "--local", help="Use recommended local services for LLM, STT, and TTS"), context_window: int = typer.Option(
2048, "--context-window", help="Specify the context window size"
qr: bool = typer.Option(False, "--qr", help="Print the QR code for the server URL") ),
max_tokens: int = typer.Option(
4096, "--max-tokens", help="Specify the maximum number of tokens"
),
temperature: float = typer.Option(
0.8, "--temperature", help="Specify the temperature for generation"
),
tts_service: str = typer.Option(
"openai", "--tts-service", help="Specify the TTS service"
),
stt_service: str = typer.Option(
"openai", "--stt-service", help="Specify the STT service"
),
local: bool = typer.Option(
False, "--local", help="Use recommended local services for LLM, STT, and TTS"
),
qr: bool = typer.Option(False, "--qr", help="Print the QR code for the server URL"),
): ):
_run( _run(
server=server, server=server,
server_host=server_host, server_host=server_host,
@ -63,7 +92,7 @@ def run(
tts_service=tts_service, tts_service=tts_service,
stt_service=stt_service, stt_service=stt_service,
local=local, local=local,
qr=qr qr=qr,
) )
@ -86,7 +115,7 @@ def _run(
tts_service: str = "openai", tts_service: str = "openai",
stt_service: str = "openai", stt_service: str = "openai",
local: bool = False, local: bool = False,
qr: bool = False qr: bool = False,
): ):
if local: if local:
tts_service = "piper" tts_service = "piper"
@ -130,7 +159,9 @@ def _run(
server_thread.start() server_thread.start()
if expose: if expose:
tunnel_thread = threading.Thread(target=create_tunnel, args=[tunnel_service, server_host, server_port, qr]) tunnel_thread = threading.Thread(
target=create_tunnel, args=[tunnel_service, server_host, server_port, qr]
)
tunnel_thread.start() tunnel_thread.start()
if client: if client:

Loading…
Cancel
Save