`bash OS/01/start.sh`
This commit is contained in:
parent
5c9c0d780f
commit
699a69321b
|
@ -12,6 +12,7 @@ import json
|
||||||
import time
|
import time
|
||||||
import queue
|
import queue
|
||||||
import os
|
import os
|
||||||
|
from queue import Queue
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
import uvicorn
|
import uvicorn
|
||||||
import re
|
import re
|
||||||
|
@ -28,9 +29,7 @@ interpreter = create_interpreter()
|
||||||
|
|
||||||
conversation_history_path = Path(__file__).parent / 'conversations' / 'user.json'
|
conversation_history_path = Path(__file__).parent / 'conversations' / 'user.json'
|
||||||
|
|
||||||
# Create Queue objects
|
|
||||||
to_user = queue.Queue()
|
|
||||||
to_assistant = queue.Queue()
|
|
||||||
|
|
||||||
# This is so we only say() full sentences
|
# This is so we only say() full sentences
|
||||||
def is_full_sentence(text):
|
def is_full_sentence(text):
|
||||||
|
@ -41,32 +40,65 @@ def split_into_sentences(text):
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
@app.post("/computer")
|
|
||||||
async def read_computer(item: dict):
|
|
||||||
to_assistant.put(item)
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
|
# Global queues
|
||||||
|
receive_queue = queue.Queue()
|
||||||
|
send_queue = queue.Queue()
|
||||||
|
|
||||||
|
|
||||||
@app.websocket("/user")
|
@app.websocket("/user")
|
||||||
async def websocket_endpoint(websocket: WebSocket):
|
async def websocket_endpoint(websocket: WebSocket):
|
||||||
await websocket.accept()
|
await websocket.accept()
|
||||||
|
receive_task = asyncio.create_task(receive_messages(websocket))
|
||||||
|
send_task = asyncio.create_task(send_messages(websocket))
|
||||||
|
await asyncio.gather(receive_task, send_task)
|
||||||
|
|
||||||
|
async def receive_messages(websocket: WebSocket):
|
||||||
while True:
|
while True:
|
||||||
try:
|
data = await websocket.receive_text()
|
||||||
data = await websocket.receive_json()
|
receive_queue.put(data)
|
||||||
to_assistant.put(data)
|
|
||||||
while not to_user.empty():
|
async def send_messages(websocket: WebSocket):
|
||||||
message = to_user.get()
|
while True:
|
||||||
print("sending a message!")
|
message = await asyncio.get_event_loop().run_in_executor(None, send_queue.get)
|
||||||
await websocket.send_json(message)
|
print(message)
|
||||||
except WebSocketDisconnect:
|
await websocket.send_json(message)
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/computer")
|
||||||
|
async def read_computer(item: dict):
|
||||||
|
await asyncio.get_event_loop().run_in_executor(None, receive_queue.put, item)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def queue_listener():
|
def queue_listener():
|
||||||
audio_file = bytearray()
|
audio_file = bytearray()
|
||||||
while True:
|
while True:
|
||||||
# Check 10x a second for new messages
|
# Check 10x a second for new messages
|
||||||
while to_assistant.empty():
|
while receive_queue.empty():
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
message = to_assistant.get()
|
message = receive_queue.get()
|
||||||
|
|
||||||
|
message = json.loads(message)
|
||||||
|
|
||||||
# Hold the audio in a buffer. If it's ready (we got end flag, stt it)
|
# Hold the audio in a buffer. If it's ready (we got end flag, stt it)
|
||||||
if message["type"] == "audio":
|
if message["type"] == "audio":
|
||||||
|
@ -97,7 +129,7 @@ def queue_listener():
|
||||||
for chunk in interpreter.chat(messages, stream=True):
|
for chunk in interpreter.chat(messages, stream=True):
|
||||||
|
|
||||||
# Send it to the user
|
# Send it to the user
|
||||||
to_user.put(chunk)
|
send_queue.put(chunk)
|
||||||
|
|
||||||
# Speak full sentences out loud
|
# Speak full sentences out loud
|
||||||
if chunk["role"] == "assistant" and "content" in chunk:
|
if chunk["role"] == "assistant" and "content" in chunk:
|
||||||
|
@ -122,16 +154,16 @@ def queue_listener():
|
||||||
print("Accumulated text is now the last sentence: ", accumulated_text)
|
print("Accumulated text is now the last sentence: ", accumulated_text)
|
||||||
|
|
||||||
# If we have a new message, save our progress and go back to the top
|
# If we have a new message, save our progress and go back to the top
|
||||||
if not to_assistant.empty():
|
if not receive_queue.empty():
|
||||||
with open(conversation_history_path, 'w') as file:
|
with open(conversation_history_path, 'w') as file:
|
||||||
json.dump(interpreter.messages, file)
|
json.dump(interpreter.messages, file)
|
||||||
break
|
break
|
||||||
|
|
||||||
def stream_tts_to_user(sentence):
|
def stream_tts_to_user(sentence):
|
||||||
to_user.put({"role": "assistant", "type": "audio", "format": "audio/mp3", "start": True})
|
send_queue.put({"role": "assistant", "type": "audio", "format": "audio/mp3", "start": True})
|
||||||
audio_bytes = tts(sentence)
|
audio_bytes = tts(sentence)
|
||||||
to_user.put({"role": "assistant", "type": "audio", "format": "audio/mp3", "content": str(audio_bytes)})
|
send_queue.put({"role": "assistant", "type": "audio", "format": "audio/mp3", "content": str(audio_bytes)})
|
||||||
to_user.put({"role": "assistant", "type": "audio", "format": "audio/mp3", "end": True})
|
send_queue.put({"role": "assistant", "type": "audio", "format": "audio/mp3", "end": True})
|
||||||
|
|
||||||
# Create a thread for the queue listener
|
# Create a thread for the queue listener
|
||||||
queue_thread = Thread(target=queue_listener)
|
queue_thread = Thread(target=queue_listener)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
[]
|
[{"role": "user", "type": "message", "content": "Disgusting.\n"}]
|
|
@ -1,11 +1,7 @@
|
||||||
import threading
|
import threading
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import json
|
import json
|
||||||
import time
|
import subprocess
|
||||||
import redis
|
|
||||||
|
|
||||||
# Connect to Redis
|
|
||||||
r = redis.Redis()
|
|
||||||
|
|
||||||
def add_message_to_queue(message):
|
def add_message_to_queue(message):
|
||||||
|
|
||||||
|
@ -16,9 +12,7 @@ def add_message_to_queue(message):
|
||||||
"format": "output",
|
"format": "output",
|
||||||
"content": message
|
"content": message
|
||||||
})
|
})
|
||||||
|
subprocess.run(['logger', '{TO_INTERPRETER{' + message_json + '}TO_INTERPRETER}'])
|
||||||
# Add the message to the 'to_main' queue
|
|
||||||
r.rpush('to_main', message_json)
|
|
||||||
|
|
||||||
def schedule(dt, message):
|
def schedule(dt, message):
|
||||||
# Calculate the delay in seconds
|
# Calculate the delay in seconds
|
||||||
|
|
|
@ -4,6 +4,8 @@ Defines a function which takes text and returns a path to an audio file.
|
||||||
|
|
||||||
import tempfile
|
import tempfile
|
||||||
from openai import OpenAI
|
from openai import OpenAI
|
||||||
|
from pydub import AudioSegment
|
||||||
|
from pydub.playback import play
|
||||||
|
|
||||||
client = OpenAI()
|
client = OpenAI()
|
||||||
|
|
||||||
|
@ -16,4 +18,10 @@ def tts(text):
|
||||||
)
|
)
|
||||||
with tempfile.NamedTemporaryFile() as temp_file:
|
with tempfile.NamedTemporaryFile() as temp_file:
|
||||||
response.stream_to_file(temp_file.name)
|
response.stream_to_file(temp_file.name)
|
||||||
|
|
||||||
|
# audio = AudioSegment.from_file(temp_file.name, format="mp3")
|
||||||
|
# # Gradual fade in and out over 0.2 seconds
|
||||||
|
# audio = audio.fade_in(200).fade_out(200)
|
||||||
|
# play(audio)
|
||||||
|
|
||||||
return temp_file.read()
|
return temp_file.read()
|
||||||
|
|
|
@ -9,87 +9,21 @@ import requests
|
||||||
import platform
|
import platform
|
||||||
import os
|
import os
|
||||||
|
|
||||||
class Device:
|
def get_kernel_messages():
|
||||||
def __init__(self, device_type, device_info):
|
|
||||||
self.device_type = device_type
|
|
||||||
self.device_info = device_info
|
|
||||||
|
|
||||||
def get_device_info(self):
|
|
||||||
info = f"Device Type: {self.device_type}\n"
|
|
||||||
for key, value in self.device_info.items():
|
|
||||||
info += f"{key}: {value}\n"
|
|
||||||
return info
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if isinstance(other, Device):
|
|
||||||
return self.device_type == other.device_type and self.device_info == other.device_info
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def get_connected_devices():
|
|
||||||
"""
|
|
||||||
Get all connected devices on macOS using system_profiler
|
|
||||||
"""
|
|
||||||
devices = []
|
|
||||||
usb_output = subprocess.check_output(['system_profiler', 'SPUSBDataType'])
|
|
||||||
network_output = subprocess.check_output(['system_profiler', 'SPNetworkDataType'])
|
|
||||||
|
|
||||||
usb_lines = usb_output.decode('utf-8').split('\n')
|
|
||||||
network_lines = network_output.decode('utf-8').split('\n')
|
|
||||||
|
|
||||||
device_info = {}
|
|
||||||
for line in usb_lines:
|
|
||||||
if 'Product ID:' in line or 'Serial Number:' in line or 'Manufacturer:' in line:
|
|
||||||
key, value = line.strip().split(':')
|
|
||||||
device_info[key.strip()] = value.strip()
|
|
||||||
if 'Manufacturer:' in line:
|
|
||||||
devices.append(Device('USB', device_info))
|
|
||||||
device_info = {}
|
|
||||||
|
|
||||||
for line in network_lines:
|
|
||||||
if 'Type:' in line or 'Hardware:' in line or 'BSD Device Name:' in line:
|
|
||||||
key, value = line.strip().split(':')
|
|
||||||
device_info[key.strip()] = value.strip()
|
|
||||||
if 'BSD Device Name:' in line:
|
|
||||||
devices.append(Device('Network', device_info))
|
|
||||||
device_info = {}
|
|
||||||
|
|
||||||
return devices
|
|
||||||
|
|
||||||
|
|
||||||
def run_kernel_watch_darwin():
|
|
||||||
prev_connected_devices = None
|
|
||||||
while True:
|
|
||||||
messages_to_send = []
|
|
||||||
connected_devices = get_connected_devices()
|
|
||||||
if prev_connected_devices is not None:
|
|
||||||
for device in connected_devices:
|
|
||||||
if device not in prev_connected_devices:
|
|
||||||
messages_to_send.append(f'New device connected: {device.get_device_info()}')
|
|
||||||
for device in prev_connected_devices:
|
|
||||||
if device not in connected_devices:
|
|
||||||
messages_to_send.append(f'Device disconnected: {device.get_device_info()}')
|
|
||||||
|
|
||||||
if messages_to_send:
|
|
||||||
requests.post('http://localhost:8000/computer', json = {'messages': messages_to_send})
|
|
||||||
prev_connected_devices = connected_devices
|
|
||||||
|
|
||||||
time.sleep(2)
|
|
||||||
|
|
||||||
|
|
||||||
def get_dmesg(after):
|
|
||||||
"""
|
"""
|
||||||
Is this the way to do this?
|
Is this the way to do this?
|
||||||
"""
|
"""
|
||||||
messages = []
|
current_platform = platform.system()
|
||||||
with open('/var/log/dmesg', 'r') as file:
|
|
||||||
lines = file.readlines()
|
if current_platform == "Darwin":
|
||||||
for line in lines:
|
process = subprocess.Popen(['syslog'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
||||||
timestamp = float(line.split(' ')[0].strip('[]'))
|
output, _ = process.communicate()
|
||||||
if timestamp > after:
|
return output.decode('utf-8')
|
||||||
messages.append(line)
|
elif current_platform == "Linux":
|
||||||
return messages
|
with open('/var/log/dmesg', 'r') as file:
|
||||||
|
return file.read()
|
||||||
|
else:
|
||||||
|
print("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
|
||||||
|
@ -107,12 +41,12 @@ def custom_filter(message):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def run_kernel_watch_linux():
|
def main():
|
||||||
last_timestamp = time.time()
|
last_messages = ""
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
messages = get_dmesg(after=last_timestamp)
|
messages = get_kernel_messages()
|
||||||
last_timestamp = time.time()
|
messages.replace(last_messages, "")
|
||||||
|
messages = messages.split("\n")
|
||||||
|
|
||||||
messages_for_core = []
|
messages_for_core = []
|
||||||
for message in messages:
|
for message in messages:
|
||||||
|
@ -121,14 +55,7 @@ def run_kernel_watch_linux():
|
||||||
if messages_for_core:
|
if messages_for_core:
|
||||||
port = os.getenv('ASSISTANT_PORT', 8000)
|
port = os.getenv('ASSISTANT_PORT', 8000)
|
||||||
requests.post(f'http://localhost:{port}/computer', json = {'messages': messages_for_core})
|
requests.post(f'http://localhost:{port}/computer', json = {'messages': messages_for_core})
|
||||||
time.sleep(2)
|
time.sleep(5)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
current_platform = platform.system()
|
main()
|
||||||
if current_platform == "Darwin":
|
|
||||||
run_kernel_watch_darwin()
|
|
||||||
elif current_platform == "Linux":
|
|
||||||
run_kernel_watch_linux()
|
|
||||||
else:
|
|
||||||
print("Unsupported platform. Exiting.")
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import threading
|
import threading
|
||||||
import websockets
|
|
||||||
import os
|
import os
|
||||||
import pyaudio
|
import pyaudio
|
||||||
|
from starlette.websockets import WebSocket
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from pynput import keyboard
|
from pynput import keyboard
|
||||||
import json
|
import json
|
||||||
|
import websockets
|
||||||
|
import queue
|
||||||
import pydub
|
import pydub
|
||||||
import ast
|
import ast
|
||||||
|
|
||||||
|
@ -24,8 +26,6 @@ WS_URL = f"ws://localhost:{PORT}/user"
|
||||||
# Initialize PyAudio
|
# Initialize PyAudio
|
||||||
p = pyaudio.PyAudio()
|
p = pyaudio.PyAudio()
|
||||||
|
|
||||||
# Queue for sending data
|
|
||||||
data_queue = Queue()
|
|
||||||
|
|
||||||
import wave
|
import wave
|
||||||
import tempfile
|
import tempfile
|
||||||
|
@ -59,10 +59,10 @@ def record_audio():
|
||||||
with open(wav_path, 'rb') as audio_file:
|
with open(wav_path, 'rb') as audio_file:
|
||||||
byte_data = audio_file.read(CHUNK)
|
byte_data = audio_file.read(CHUNK)
|
||||||
while byte_data:
|
while byte_data:
|
||||||
data_queue.put({"role": "user", "type": "audio", "format": "audio/wav", "content": str(byte_data)})
|
send_queue.put({"role": "user", "type": "audio", "format": "audio/wav", "content": str(byte_data)})
|
||||||
byte_data = audio_file.read(CHUNK)
|
byte_data = audio_file.read(CHUNK)
|
||||||
|
|
||||||
data_queue.put({"role": "user", "type": "audio", "format": "audio/wav", "end": True})
|
send_queue.put({"role": "user", "type": "audio", "format": "audio/wav", "end": True})
|
||||||
|
|
||||||
|
|
||||||
def toggle_recording(state):
|
def toggle_recording(state):
|
||||||
|
@ -77,48 +77,6 @@ def toggle_recording(state):
|
||||||
SPACEBAR_PRESSED = False
|
SPACEBAR_PRESSED = False
|
||||||
RECORDING = False
|
RECORDING = False
|
||||||
|
|
||||||
async def websocket_communication():
|
|
||||||
"""Handle WebSocket communication and listen for incoming messages."""
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
async with websockets.connect(WS_URL) as websocket:
|
|
||||||
|
|
||||||
print("Press the spacebar to start/stop recording. Press ESC to exit.")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
# Send data from the queue to the server
|
|
||||||
while not data_queue.empty():
|
|
||||||
data = data_queue.get_nowait()
|
|
||||||
await websocket.send(json.dumps(data))
|
|
||||||
|
|
||||||
# Listen for incoming messages from the server
|
|
||||||
try:
|
|
||||||
chunk = await asyncio.wait_for(websocket.recv(), timeout=1.0)
|
|
||||||
print(f"Received from server: {str(chunk)[:100]}")
|
|
||||||
|
|
||||||
if chunk["type"] == "audio":
|
|
||||||
if "start" in chunk:
|
|
||||||
audio_chunks = bytearray()
|
|
||||||
if "content" in chunk:
|
|
||||||
audio_chunks.extend(bytes(ast.literal_eval(chunk["content"])))
|
|
||||||
if "end" in chunk:
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".mp3") as f:
|
|
||||||
f.write(audio_chunks)
|
|
||||||
f.seek(0)
|
|
||||||
seg = pydub.AudioSegment.from_mp3(f.name)
|
|
||||||
pydub.playback.play(seg)
|
|
||||||
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
# No message received within timeout period
|
|
||||||
pass
|
|
||||||
|
|
||||||
await asyncio.sleep(0.1)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Websocket not ready, retrying... ({e})")
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def on_press(key):
|
def on_press(key):
|
||||||
"""Detect spacebar press."""
|
"""Detect spacebar press."""
|
||||||
if key == keyboard.Key.space:
|
if key == keyboard.Key.space:
|
||||||
|
@ -132,14 +90,37 @@ def on_release(key):
|
||||||
print("Exiting...")
|
print("Exiting...")
|
||||||
os._exit(0)
|
os._exit(0)
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
send_queue = queue.Queue()
|
||||||
|
|
||||||
|
async def message_sender(websocket):
|
||||||
|
while True:
|
||||||
|
message = await asyncio.get_event_loop().run_in_executor(None, send_queue.get)
|
||||||
|
await websocket.send(json.dumps(message))
|
||||||
|
send_queue.task_done()
|
||||||
|
|
||||||
|
async def websocket_communication(WS_URL):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
async with websockets.connect(WS_URL) as websocket:
|
||||||
|
print("Press the spacebar to start/stop recording. Press ESC to exit.")
|
||||||
|
asyncio.create_task(message_sender(websocket))
|
||||||
|
|
||||||
|
async for message in websocket:
|
||||||
|
print(message)
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
except:
|
||||||
|
print("Connecting...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# Start the WebSocket communication in a separate asyncio event loop
|
# Start the WebSocket communication in a separate asyncio event loop
|
||||||
ws_thread = threading.Thread(target=lambda: asyncio.run(websocket_communication()), daemon=True)
|
ws_thread = threading.Thread(target=lambda: asyncio.run(websocket_communication(WS_URL)), daemon=True)
|
||||||
ws_thread.start()
|
ws_thread.start()
|
||||||
|
|
||||||
# Keyboard listener for spacebar press/release
|
# Keyboard listener for spacebar press/release
|
||||||
with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
|
with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
|
||||||
print("In a moment, press the spacebar to start/stop recording. Press ESC to exit.")
|
|
||||||
listener.join()
|
listener.join()
|
||||||
|
|
||||||
p.terminate()
|
p.terminate()
|
||||||
|
|
Loading…
Reference in New Issue