Spaces:
Runtime error
Runtime error
remove old files
Browse files- Agent/character_properties.py +0 -9
- Agent/character_state.py +0 -26
- debug_000.py +0 -74
- debug_001.py +0 -64
- debug_app.py +0 -106
- legacy_to_delete/chat_pipeline.py +0 -96
- legacy_to_delete/debug.py +0 -157
- legacy_to_delete/pipeline.py +0 -112
- tests/test_pipeline.py +0 -86
Agent/character_properties.py
DELETED
|
@@ -1,9 +0,0 @@
|
|
| 1 |
-
class CharacterProperties:
|
| 2 |
-
def __init__(self, happiness, energy, confidence, fear, excitement, wanderlust, restful):
|
| 3 |
-
self.happiness = happiness
|
| 4 |
-
self.energy = energy
|
| 5 |
-
self.confidence = confidence
|
| 6 |
-
self.fear = fear
|
| 7 |
-
self.excitement = excitement
|
| 8 |
-
self.wanderlust = wanderlust
|
| 9 |
-
self.restful = restful
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Agent/character_state.py
DELETED
|
@@ -1,26 +0,0 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
from Agent.character_properties import CharacterProperties
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
class CharacterState:
|
| 7 |
-
def __init__(self, name, fixed_traits, properties):
|
| 8 |
-
self.name = name
|
| 9 |
-
self.fixed_traits = fixed_traits
|
| 10 |
-
self.properties = properties
|
| 11 |
-
|
| 12 |
-
charles_properties = CharacterProperties(
|
| 13 |
-
happiness = 0.85,
|
| 14 |
-
energy = 0.9,
|
| 15 |
-
confidence = 0.8,
|
| 16 |
-
fear = 0.2,
|
| 17 |
-
excitement = 0.75,
|
| 18 |
-
wanderlust = 0.95,
|
| 19 |
-
restful = 0.8
|
| 20 |
-
)
|
| 21 |
-
|
| 22 |
-
charles_state = CharacterState(
|
| 23 |
-
name = 'Charles Petrescu',
|
| 24 |
-
fixed_traits = ['quirky', 'honest', 'inquisitive', 'adventurous', 'friendly', 'random', 'knowledgeable', 'humorous'],
|
| 25 |
-
properties = charles_properties
|
| 26 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
debug_000.py
DELETED
|
@@ -1,74 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import subprocess
|
| 3 |
-
from elevenlabs import generate, play
|
| 4 |
-
from elevenlabs import set_api_key
|
| 5 |
-
from elevenlabs import generate, stream
|
| 6 |
-
from dotenv import load_dotenv
|
| 7 |
-
load_dotenv()
|
| 8 |
-
|
| 9 |
-
account_sid = os.environ["ELEVENLABS_API_KEY"]
|
| 10 |
-
voice_id="2OviOUQc1JsQRQgNkVBj"
|
| 11 |
-
model_id="eleven_monolingual_v1"
|
| 12 |
-
set_api_key(account_sid)
|
| 13 |
-
|
| 14 |
-
def stream_tts(prompt):
|
| 15 |
-
audio_stream = generate(
|
| 16 |
-
text=prompt,
|
| 17 |
-
voice=voice_id,
|
| 18 |
-
model=model_id,
|
| 19 |
-
stream_chunk_size=4096,
|
| 20 |
-
stream=True,
|
| 21 |
-
)
|
| 22 |
-
return audio_stream
|
| 23 |
-
|
| 24 |
-
prompts=[
|
| 25 |
-
"erm",
|
| 26 |
-
"Cabbages, my dear friend!",
|
| 27 |
-
"Did you know that the world's largest cabbage weighed 62.71 kilograms?",
|
| 28 |
-
"Simply remarkable!",
|
| 29 |
-
"How are you today?",
|
| 30 |
-
]
|
| 31 |
-
|
| 32 |
-
mpv_command = ["mpv", "--no-cache", "--no-terminal", "--", "fd://0"]
|
| 33 |
-
mpv_process = subprocess.Popen(
|
| 34 |
-
mpv_command,
|
| 35 |
-
stdin=subprocess.PIPE,
|
| 36 |
-
stdout=subprocess.DEVNULL,
|
| 37 |
-
stderr=subprocess.DEVNULL,
|
| 38 |
-
)
|
| 39 |
-
|
| 40 |
-
load_chunks = False
|
| 41 |
-
load_chunks = os.path.exists("chunks.pkl")
|
| 42 |
-
|
| 43 |
-
# check if chunks.pkl exists
|
| 44 |
-
if load_chunks:
|
| 45 |
-
# try open chunks
|
| 46 |
-
with open("chunks.pkl", "rb") as f:
|
| 47 |
-
import pickle
|
| 48 |
-
chunks = pickle.load(f)
|
| 49 |
-
for chunk in chunks:
|
| 50 |
-
mpv_process.stdin.write(chunk)
|
| 51 |
-
mpv_process.stdin.flush()
|
| 52 |
-
|
| 53 |
-
else:
|
| 54 |
-
chunks = []
|
| 55 |
-
|
| 56 |
-
for prompt in prompts:
|
| 57 |
-
for chunk in stream_tts(prompt):
|
| 58 |
-
if chunk is not None:
|
| 59 |
-
chunks.append(chunk)
|
| 60 |
-
mpv_process.stdin.write(chunk) # type: ignore
|
| 61 |
-
mpv_process.stdin.flush() # type: ignore
|
| 62 |
-
|
| 63 |
-
# save chunks to file as a pickled list of bytes
|
| 64 |
-
with open("chunks.pkl", "wb") as f:
|
| 65 |
-
import pickle
|
| 66 |
-
pickle.dump(chunks, f)
|
| 67 |
-
with open("chunks.mp3", "wb") as f:
|
| 68 |
-
for chunk in chunks:
|
| 69 |
-
f.write(chunk)
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
if mpv_process.stdin:
|
| 73 |
-
mpv_process.stdin.close()
|
| 74 |
-
mpv_process.wait()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
debug_001.py
DELETED
|
@@ -1,64 +0,0 @@
|
|
| 1 |
-
import io
|
| 2 |
-
import os
|
| 3 |
-
import subprocess
|
| 4 |
-
import av
|
| 5 |
-
import numpy as np
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
mpv_command = ["mpv", "--no-cache", "--no-terminal", "--", "fd://0"]
|
| 9 |
-
mpv_process = subprocess.Popen(
|
| 10 |
-
mpv_command,
|
| 11 |
-
stdin=subprocess.PIPE,
|
| 12 |
-
stdout=subprocess.DEVNULL,
|
| 13 |
-
stderr=subprocess.DEVNULL,
|
| 14 |
-
)
|
| 15 |
-
|
| 16 |
-
load_chunks = False
|
| 17 |
-
load_chunks = os.path.exists("chunks.pkl")
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
audio_frames = []
|
| 21 |
-
|
| 22 |
-
# try open chunks
|
| 23 |
-
with open("chunks.pkl", "rb") as f:
|
| 24 |
-
import pickle
|
| 25 |
-
chunks = pickle.load(f)
|
| 26 |
-
append = False
|
| 27 |
-
for chunk in chunks:
|
| 28 |
-
mpv_process.stdin.write(chunk)
|
| 29 |
-
mpv_process.stdin.flush()
|
| 30 |
-
# np_chunk = np.frombuffer(chunk, dtype=np.int16)
|
| 31 |
-
# aa = av.AudioFrame.from_ndarray(chunk)
|
| 32 |
-
try:
|
| 33 |
-
if append:
|
| 34 |
-
bytes_io.write(chunk)
|
| 35 |
-
append = False
|
| 36 |
-
bytes_io.seek(0)
|
| 37 |
-
else:
|
| 38 |
-
bytes_io = io.BytesIO(chunk)
|
| 39 |
-
container = av.open(bytes_io, 'r', format='mp3')
|
| 40 |
-
audio_stream = next(s for s in container.streams if s.type == 'audio')
|
| 41 |
-
for frame in container.decode(audio_stream):
|
| 42 |
-
# Convert the audio frame to a NumPy array
|
| 43 |
-
array = frame.to_ndarray()
|
| 44 |
-
|
| 45 |
-
# Now you can use av.AudioFrame.from_ndarray
|
| 46 |
-
audio_frame = av.AudioFrame.from_ndarray(array, format='flt', layout='mono')
|
| 47 |
-
audio_frame.sample_rate = 44100
|
| 48 |
-
|
| 49 |
-
audio_frames.append(audio_frame)
|
| 50 |
-
|
| 51 |
-
except Exception as e:
|
| 52 |
-
print (e)
|
| 53 |
-
append = True
|
| 54 |
-
bytes_io.seek(0, io.SEEK_END)
|
| 55 |
-
continue
|
| 56 |
-
|
| 57 |
-
# with open("frames.pkl", "wb") as f:
|
| 58 |
-
# import pickle
|
| 59 |
-
# pickle.dump(audio_frames, f)
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
if mpv_process.stdin:
|
| 63 |
-
mpv_process.stdin.close()
|
| 64 |
-
mpv_process.wait()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
debug_app.py
DELETED
|
@@ -1,106 +0,0 @@
|
|
| 1 |
-
import logging
|
| 2 |
-
import traceback
|
| 3 |
-
from typing import List
|
| 4 |
-
|
| 5 |
-
import av
|
| 6 |
-
import numpy as np
|
| 7 |
-
import streamlit as st
|
| 8 |
-
from streamlit_webrtc import WebRtcMode, webrtc_streamer
|
| 9 |
-
import pydub
|
| 10 |
-
|
| 11 |
-
from dotenv import load_dotenv
|
| 12 |
-
from ffmpeg_converter_actor import FFMpegConverterActor
|
| 13 |
-
load_dotenv()
|
| 14 |
-
from sample_utils.turn import get_ice_servers
|
| 15 |
-
|
| 16 |
-
logger = logging.getLogger(__name__)
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
import os
|
| 20 |
-
|
| 21 |
-
import ray
|
| 22 |
-
from ray.util.queue import Queue
|
| 23 |
-
if not ray.is_initialized():
|
| 24 |
-
# Try to connect to a running Ray cluster
|
| 25 |
-
ray_address = os.getenv('RAY_ADDRESS')
|
| 26 |
-
if ray_address:
|
| 27 |
-
ray.init(ray_address, namespace="project_charles")
|
| 28 |
-
else:
|
| 29 |
-
ray.init(namespace="project_charles")
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
def video_frame_callback(
|
| 35 |
-
frame: av.VideoFrame,
|
| 36 |
-
) -> av.VideoFrame:
|
| 37 |
-
return frame
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
with open("chunks.pkl", "rb") as f:
|
| 41 |
-
import pickle
|
| 42 |
-
debug_chunks = pickle.load(f)
|
| 43 |
-
|
| 44 |
-
converter_queue = Queue(maxsize=100)
|
| 45 |
-
converter_actor = FFMpegConverterActor.remote(converter_queue)
|
| 46 |
-
ray.get(converter_actor.start_process.remote())
|
| 47 |
-
converter_actor.run.remote()
|
| 48 |
-
for chunk in debug_chunks:
|
| 49 |
-
ray.get(converter_actor.push_chunk.remote(chunk))
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
# emptry array of type int16
|
| 53 |
-
sample_buffer = np.zeros((0), dtype=np.int16)
|
| 54 |
-
|
| 55 |
-
def process_frame(old_frame):
|
| 56 |
-
|
| 57 |
-
try:
|
| 58 |
-
output_channels = 2
|
| 59 |
-
output_sample_rate = 44100
|
| 60 |
-
required_samples = old_frame.samples
|
| 61 |
-
|
| 62 |
-
if not converter_queue.empty():
|
| 63 |
-
frame_as_bytes = converter_queue.get()
|
| 64 |
-
# print(f"frame_as_bytes: {len(frame_as_bytes)}")
|
| 65 |
-
samples = np.frombuffer(frame_as_bytes, dtype=np.int16)
|
| 66 |
-
else:
|
| 67 |
-
# create a byte array of zeros
|
| 68 |
-
samples = np.zeros((required_samples * 2 * 1), dtype=np.int16)
|
| 69 |
-
|
| 70 |
-
# Duplicate mono channel for stereo
|
| 71 |
-
if output_channels == 2:
|
| 72 |
-
samples = np.vstack((samples, samples)).reshape((-1,), order='F')
|
| 73 |
-
|
| 74 |
-
samples = samples.reshape(1, -1)
|
| 75 |
-
|
| 76 |
-
layout = 'stereo' if output_channels == 2 else 'mono'
|
| 77 |
-
new_frame = av.AudioFrame.from_ndarray(samples, format='s16', layout=layout)
|
| 78 |
-
new_frame.sample_rate = old_frame.sample_rate
|
| 79 |
-
new_frame.pts = old_frame.pts
|
| 80 |
-
return new_frame
|
| 81 |
-
except Exception as e:
|
| 82 |
-
print (e)
|
| 83 |
-
traceback.print_exc()
|
| 84 |
-
raise(e)
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
def audio_frame_callback(old_frame: av.AudioFrame) -> av.AudioFrame:
|
| 89 |
-
new_frame = process_frame(old_frame)
|
| 90 |
-
|
| 91 |
-
# print (f"frame: {old_frame}, pts: {old_frame.pts}")
|
| 92 |
-
# print (f"new_frame: {new_frame}, pts: {new_frame.pts}")
|
| 93 |
-
|
| 94 |
-
return new_frame
|
| 95 |
-
# return old_frame
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
webrtc_streamer(
|
| 99 |
-
key="delay",
|
| 100 |
-
mode=WebRtcMode.SENDRECV,
|
| 101 |
-
rtc_configuration={"iceServers": get_ice_servers()},
|
| 102 |
-
|
| 103 |
-
video_frame_callback=video_frame_callback,
|
| 104 |
-
audio_frame_callback=audio_frame_callback,
|
| 105 |
-
|
| 106 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
legacy_to_delete/chat_pipeline.py
DELETED
|
@@ -1,96 +0,0 @@
|
|
| 1 |
-
import asyncio
|
| 2 |
-
import time
|
| 3 |
-
from clip_transform import CLIPTransform
|
| 4 |
-
from chat_service import ChatService
|
| 5 |
-
from dotenv import load_dotenv
|
| 6 |
-
from text_to_speech_service import TextToSpeechService
|
| 7 |
-
from concurrent.futures import ThreadPoolExecutor
|
| 8 |
-
from local_speaker_service import LocalSpeakerService
|
| 9 |
-
from chat_service import ChatService
|
| 10 |
-
from legacy_to_delete.pipeline import Pipeline, Node, Job
|
| 11 |
-
from typing import List
|
| 12 |
-
|
| 13 |
-
class ChatJob(Job):
|
| 14 |
-
def __init__(self, data, chat_service: ChatService):
|
| 15 |
-
super().__init__(data)
|
| 16 |
-
self.chat_service = chat_service
|
| 17 |
-
|
| 18 |
-
class Node1(Node):
|
| 19 |
-
next_id = 0
|
| 20 |
-
|
| 21 |
-
async def process_job(self, job: ChatJob):
|
| 22 |
-
# input job.data is the input string
|
| 23 |
-
# output job.data is the next sentance
|
| 24 |
-
async for sentence in job.chat_service.get_responses_as_sentances_async(job.data):
|
| 25 |
-
if job.chat_service.ignore_sentence(sentence):
|
| 26 |
-
continue
|
| 27 |
-
print(f"{sentence}")
|
| 28 |
-
new_job = ChatJob(sentence, job.chat_service)
|
| 29 |
-
new_job.id = self.next_id
|
| 30 |
-
self.next_id += 1
|
| 31 |
-
yield new_job
|
| 32 |
-
|
| 33 |
-
class Node2(Node):
|
| 34 |
-
next_id = 0
|
| 35 |
-
|
| 36 |
-
async def process_job(self, job: ChatJob):
|
| 37 |
-
# input job.data is the sentance
|
| 38 |
-
# output job.data is the streamed speech bytes
|
| 39 |
-
async for chunk in job.chat_service.get_speech_chunks_async(job.data):
|
| 40 |
-
new_job = ChatJob(chunk, job.chat_service)
|
| 41 |
-
new_job.id = self.next_id
|
| 42 |
-
self.next_id += 1
|
| 43 |
-
yield new_job
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
class Node3(Node):
|
| 47 |
-
# sync_size = 64
|
| 48 |
-
# sync = []
|
| 49 |
-
|
| 50 |
-
async def process_job(self, job: ChatJob):
|
| 51 |
-
# input job.data is the streamed speech bytes
|
| 52 |
-
# Node3.sync.append(job.data)
|
| 53 |
-
job.chat_service.enqueue_speech_bytes_to_play([job.data])
|
| 54 |
-
yield job
|
| 55 |
-
# if len(Node3.sync) >= Node3.sync_size:
|
| 56 |
-
# audio_chunks = Node3.sync[:Node3.sync_size]
|
| 57 |
-
# Node3.sync = Node3.sync[Node3.sync_size:]
|
| 58 |
-
# job.chat_service.enqueue_speech_bytes_to_play(audio_chunks)
|
| 59 |
-
# yield job
|
| 60 |
-
|
| 61 |
-
class ChatPipeline():
|
| 62 |
-
def __init__(self):
|
| 63 |
-
load_dotenv()
|
| 64 |
-
self.pipeline = Pipeline()
|
| 65 |
-
self.audio_processor = LocalSpeakerService()
|
| 66 |
-
self.chat_service = ChatService(self.audio_processor, voice_id="2OviOUQc1JsQRQgNkVBj") # Chales003
|
| 67 |
-
|
| 68 |
-
def __enter__(self):
|
| 69 |
-
return self
|
| 70 |
-
|
| 71 |
-
def __exit__(self, exc_type, exc_value, traceback):
|
| 72 |
-
self.audio_processor.close()
|
| 73 |
-
self.audio_processor = None
|
| 74 |
-
|
| 75 |
-
def __del__(self):
|
| 76 |
-
if self.audio_processor:
|
| 77 |
-
self.audio_processor.close()
|
| 78 |
-
self.audio_processor = None
|
| 79 |
-
|
| 80 |
-
async def start(self):
|
| 81 |
-
self.node1_queue = asyncio.Queue()
|
| 82 |
-
self.node2_queue = asyncio.Queue()
|
| 83 |
-
self.node3_queue = asyncio.Queue()
|
| 84 |
-
self.sync = []
|
| 85 |
-
await self.pipeline.add_node(Node1, 1, self.node1_queue, self.node2_queue, sequential_node=True)
|
| 86 |
-
await self.pipeline.add_node(Node2, 1, self.node2_queue, self.node3_queue, sequential_node=True)
|
| 87 |
-
await self.pipeline.add_node(Node3, 1, self.node3_queue, None, sequential_node=True)
|
| 88 |
-
|
| 89 |
-
async def enqueue(self, prompt):
|
| 90 |
-
job = ChatJob(prompt, self.chat_service)
|
| 91 |
-
await self.pipeline.enqueue_job(job)
|
| 92 |
-
|
| 93 |
-
async def wait_until_all_jobs_idle(self):
|
| 94 |
-
# TODO - implement this
|
| 95 |
-
while True:
|
| 96 |
-
await asyncio.sleep(0.1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
legacy_to_delete/debug.py
DELETED
|
@@ -1,157 +0,0 @@
|
|
| 1 |
-
import asyncio
|
| 2 |
-
import time
|
| 3 |
-
import traceback
|
| 4 |
-
from chat_pipeline import ChatPipeline
|
| 5 |
-
from clip_transform import CLIPTransform
|
| 6 |
-
from chat_service import ChatService
|
| 7 |
-
from dotenv import load_dotenv
|
| 8 |
-
from text_to_speech_service import TextToSpeechService
|
| 9 |
-
from concurrent.futures import ThreadPoolExecutor
|
| 10 |
-
from local_speaker_service import LocalSpeakerService
|
| 11 |
-
from chat_service import ChatService
|
| 12 |
-
|
| 13 |
-
def time_sentance_lenghts():
|
| 14 |
-
load_dotenv()
|
| 15 |
-
|
| 16 |
-
print ("Initializing Chat")
|
| 17 |
-
# audio_processor = AudioStreamProcessor()
|
| 18 |
-
user_speech_service0 = TextToSpeechService(voice_id="Adam")
|
| 19 |
-
prompts = [
|
| 20 |
-
"hello, i am a long sentance, how are you today? Tell me about your shadow self?",
|
| 21 |
-
"a shorter sentance",
|
| 22 |
-
"Jung believed that the process of self-discovery and personal growth involves confronting and integrating the shadow self into the conscious mind.",
|
| 23 |
-
"By doing so, we become more self-aware and more fully actualized individuals.",
|
| 24 |
-
]
|
| 25 |
-
|
| 26 |
-
print ("Timing prompts\n")
|
| 27 |
-
for prompt in prompts:
|
| 28 |
-
start_time = time.time()
|
| 29 |
-
start_stream_time = time.time()
|
| 30 |
-
stream = user_speech_service0.stream(prompt)
|
| 31 |
-
audio = b""
|
| 32 |
-
for chunk in stream:
|
| 33 |
-
if chunk is not None:
|
| 34 |
-
audio += chunk
|
| 35 |
-
end_stream_time = time.time()
|
| 36 |
-
from elevenlabs import play
|
| 37 |
-
start_speech_time = time.time()
|
| 38 |
-
play(audio)
|
| 39 |
-
end_speech_time = time.time()
|
| 40 |
-
end_time = time.time()
|
| 41 |
-
total_time = (end_time - start_time)
|
| 42 |
-
stream_time = (end_stream_time - start_stream_time)
|
| 43 |
-
speech_time = (end_speech_time - start_speech_time)
|
| 44 |
-
stream_multiple = speech_time / stream_time
|
| 45 |
-
print(f"Stream time: {stream_time:.4f}, Acutual audio time: {speech_time:.4f}, a multiple of {stream_multiple:.2f}. for prompt: {prompt}")
|
| 46 |
-
|
| 47 |
-
print ("\nChat success")
|
| 48 |
-
|
| 49 |
-
def test_sentance_lenghts():
|
| 50 |
-
load_dotenv()
|
| 51 |
-
|
| 52 |
-
print ("Initializing Chat")
|
| 53 |
-
audio_processor = LocalSpeakerService()
|
| 54 |
-
user_speech_service0 = TextToSpeechService(voice_id="Adam")
|
| 55 |
-
user_speech_service1 = TextToSpeechService(voice_id="Adam")
|
| 56 |
-
user_speech_service2 = TextToSpeechService(voice_id="Adam")
|
| 57 |
-
user_speech_service3 = TextToSpeechService(voice_id="Adam")
|
| 58 |
-
|
| 59 |
-
prompts = [
|
| 60 |
-
"hello, i am a long sentance, how are you today? Tell me about your shadow self?",
|
| 61 |
-
"a shorter sentance",
|
| 62 |
-
"Jung believed that the process of self-discovery and personal growth involves confronting and integrating the shadow self into the conscious mind.",
|
| 63 |
-
"By doing so, we become more self-aware and more fully actualized individuals.",
|
| 64 |
-
]
|
| 65 |
-
first = True
|
| 66 |
-
stream1 = user_speech_service1.stream(prompts[1])
|
| 67 |
-
stream0 = user_speech_service0.stream(prompts[0])
|
| 68 |
-
time.sleep(5)
|
| 69 |
-
stream2 = user_speech_service2.stream(prompts[2])
|
| 70 |
-
stream3 = user_speech_service3.stream(prompts[3])
|
| 71 |
-
audio_processor.add_audio_stream(stream0)
|
| 72 |
-
audio_processor.add_audio_stream(stream1)
|
| 73 |
-
audio_processor.add_audio_stream(stream2)
|
| 74 |
-
audio_processor.add_audio_stream(stream3)
|
| 75 |
-
audio_processor.close()
|
| 76 |
-
from elevenlabs import generate, play
|
| 77 |
-
speech0 = generate(prompts[0], voice="Adam")
|
| 78 |
-
speech1 = generate(prompts[1], voice="Adam")
|
| 79 |
-
speech2 = generate(prompts[2], voice="Adam")
|
| 80 |
-
speech3 = generate(prompts[3], voice="Adam")
|
| 81 |
-
play(speech0)
|
| 82 |
-
play(speech1)
|
| 83 |
-
play(speech2)
|
| 84 |
-
play(speech1)
|
| 85 |
-
play(speech3)
|
| 86 |
-
play(speech1)
|
| 87 |
-
# for prompt in prompts:
|
| 88 |
-
# stream = user_speech_service.stream(prompt)
|
| 89 |
-
# if first:
|
| 90 |
-
# first = False
|
| 91 |
-
# time.sleep(5)
|
| 92 |
-
# audio_processor.add_audio_stream(stream)
|
| 93 |
-
audio_processor.close()
|
| 94 |
-
print ("Chat success")
|
| 95 |
-
|
| 96 |
-
def run_debug_code():
|
| 97 |
-
load_dotenv()
|
| 98 |
-
|
| 99 |
-
# print ("Initializing CLIP templates")
|
| 100 |
-
# clip_transform = CLIPTransform()
|
| 101 |
-
# print ("CLIP success")
|
| 102 |
-
|
| 103 |
-
print ("Initializing Chat")
|
| 104 |
-
# chat_service = ChatService()
|
| 105 |
-
audio_processor = LocalSpeakerService()
|
| 106 |
-
chat_service = ChatService(audio_processor, voice_id="2OviOUQc1JsQRQgNkVBj") # Chales003
|
| 107 |
-
|
| 108 |
-
user_speech_service = TextToSpeechService(voice_id="Adam")
|
| 109 |
-
|
| 110 |
-
# user_speech_service.print_voices() # if you want to see your custom voices
|
| 111 |
-
|
| 112 |
-
prompts = [
|
| 113 |
-
"hello, how are you today?",
|
| 114 |
-
"tell me about your shadow self?",
|
| 115 |
-
"hmm, interesting, tell me more about that.",
|
| 116 |
-
"wait, that is so interesting, what else?",
|
| 117 |
-
]
|
| 118 |
-
for prompt in prompts:
|
| 119 |
-
print ("")
|
| 120 |
-
print (f'prompt: "{prompt}"')
|
| 121 |
-
stream = user_speech_service.stream(prompt)
|
| 122 |
-
audio_processor.add_audio_stream(stream)
|
| 123 |
-
|
| 124 |
-
print ("")
|
| 125 |
-
print (f'response:')
|
| 126 |
-
response = chat_service.respond_to(prompt)
|
| 127 |
-
|
| 128 |
-
audio_processor.close()
|
| 129 |
-
print ("Chat success")
|
| 130 |
-
|
| 131 |
-
async def run_pipeline():
|
| 132 |
-
load_dotenv()
|
| 133 |
-
|
| 134 |
-
try:
|
| 135 |
-
chat_pipeline = ChatPipeline()
|
| 136 |
-
await chat_pipeline.start()
|
| 137 |
-
prompts = [
|
| 138 |
-
"hello, how are you today?",
|
| 139 |
-
"tell me about your shadow self?",
|
| 140 |
-
"hmm, interesting, tell me more about that.",
|
| 141 |
-
"wait, that is so interesting, what else?",
|
| 142 |
-
]
|
| 143 |
-
for prompt in prompts:
|
| 144 |
-
await chat_pipeline.enqueue(prompt)
|
| 145 |
-
await chat_pipeline.wait_until_all_jobs_idle()
|
| 146 |
-
except KeyboardInterrupt:
|
| 147 |
-
print("Pipeline interrupted by user")
|
| 148 |
-
except Exception as e:
|
| 149 |
-
traceback.print_exc()
|
| 150 |
-
print(f"An error occurred: {e}")
|
| 151 |
-
|
| 152 |
-
if __name__ == '__main__':
|
| 153 |
-
# time_sentance_lenghts()
|
| 154 |
-
# test_sentance_lenghts()
|
| 155 |
-
# run_debug_code()
|
| 156 |
-
asyncio.run(run_pipeline())
|
| 157 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
legacy_to_delete/pipeline.py
DELETED
|
@@ -1,112 +0,0 @@
|
|
| 1 |
-
import asyncio
|
| 2 |
-
import traceback
|
| 3 |
-
|
| 4 |
-
class Job:
|
| 5 |
-
def __init__(self, data):
|
| 6 |
-
self._id = None
|
| 7 |
-
self.data = data
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
class Node:
|
| 11 |
-
# def __init__(self, worker_id: int, input_queue, output_queue, buffer=None, job_sync=None):
|
| 12 |
-
def __init__(self, worker_id: int, input_queue, output_queue=None, job_sync=None, sequential_node=False ):
|
| 13 |
-
self.worker_id = worker_id
|
| 14 |
-
self.input_queue = input_queue
|
| 15 |
-
self.output_queue = output_queue
|
| 16 |
-
self.buffer = {}
|
| 17 |
-
self.job_sync = job_sync
|
| 18 |
-
self.sequential_node = sequential_node
|
| 19 |
-
self.next_i = 0
|
| 20 |
-
self._jobs_dequeued = 0
|
| 21 |
-
self._jobs_processed = 0
|
| 22 |
-
# throw an error if job_sync is not None and sequential_node is False
|
| 23 |
-
if self.job_sync is not None and self.sequential_node == False:
|
| 24 |
-
raise ValueError('job_sync is not None and sequential_node is False')
|
| 25 |
-
|
| 26 |
-
async def run(self):
|
| 27 |
-
try:
|
| 28 |
-
while True:
|
| 29 |
-
job: Job = await self.input_queue.get()
|
| 30 |
-
self._jobs_dequeued += 1
|
| 31 |
-
if self.sequential_node == False:
|
| 32 |
-
async for job in self.process_job(job):
|
| 33 |
-
if self.output_queue is not None:
|
| 34 |
-
await self.output_queue.put(job)
|
| 35 |
-
if self.job_sync is not None:
|
| 36 |
-
self.job_sync.append(job)
|
| 37 |
-
self._jobs_processed += 1
|
| 38 |
-
else:
|
| 39 |
-
# ensure that jobs are processed in order
|
| 40 |
-
self.buffer[job.id] = job
|
| 41 |
-
while self.next_i in self.buffer:
|
| 42 |
-
job = self.buffer.pop(self.next_i)
|
| 43 |
-
async for job in self.process_job(job):
|
| 44 |
-
if self.output_queue is not None:
|
| 45 |
-
await self.output_queue.put(job)
|
| 46 |
-
if self.job_sync is not None:
|
| 47 |
-
self.job_sync.append(job)
|
| 48 |
-
self._jobs_processed += 1
|
| 49 |
-
self.next_i += 1
|
| 50 |
-
except Exception as e:
|
| 51 |
-
print(f"An error occurred in node: {self.__class__.__name__} worker: {self.worker_id}: {e}")
|
| 52 |
-
traceback.print_exc()
|
| 53 |
-
raise # Re-raises the last exception.
|
| 54 |
-
|
| 55 |
-
async def process_job(self, job: Job):
|
| 56 |
-
raise NotImplementedError()
|
| 57 |
-
|
| 58 |
-
class Pipeline:
|
| 59 |
-
def __init__(self):
|
| 60 |
-
self.input_queues = []
|
| 61 |
-
self.root_queue = None
|
| 62 |
-
# self.output_queues = []
|
| 63 |
-
# self.job_sysncs = []
|
| 64 |
-
self.nodes= []
|
| 65 |
-
self.node_workers = {}
|
| 66 |
-
self.tasks = []
|
| 67 |
-
self._job_id = 0
|
| 68 |
-
|
| 69 |
-
async def add_node(self, node: Node, num_workers=1, input_queue=None, output_queue=None, job_sync=None, sequential_node=False ):
|
| 70 |
-
# input_queue must not be None
|
| 71 |
-
if input_queue is None:
|
| 72 |
-
raise ValueError('input_queue is None')
|
| 73 |
-
# job_sync nodes must be sequential_nodes
|
| 74 |
-
if job_sync is not None and sequential_node == False:
|
| 75 |
-
raise ValueError('job_sync is not None and sequential_node is False')
|
| 76 |
-
# sequential_nodes should one have 1 worker
|
| 77 |
-
if sequential_node == True and num_workers != 1:
|
| 78 |
-
raise ValueError('sequentaial nodes can only have one node (sequential_node is True and num_workers is not 1)')
|
| 79 |
-
# output queue must not equal input_queue
|
| 80 |
-
if output_queue == input_queue:
|
| 81 |
-
raise ValueError('output_queue must not be the same as input_queue')
|
| 82 |
-
|
| 83 |
-
node_name = node.__name__
|
| 84 |
-
if node_name not in self.nodes:
|
| 85 |
-
self.nodes.append(node_name)
|
| 86 |
-
|
| 87 |
-
# if input_queue is None then this is the root node
|
| 88 |
-
if len(self.input_queues) == 0:
|
| 89 |
-
self.root_queue = input_queue
|
| 90 |
-
|
| 91 |
-
self.input_queues.append(input_queue)
|
| 92 |
-
|
| 93 |
-
for i in range(num_workers):
|
| 94 |
-
worker_id = i
|
| 95 |
-
node_worker = node(worker_id, input_queue, output_queue, job_sync, sequential_node)
|
| 96 |
-
if node_name not in self.node_workers:
|
| 97 |
-
self.node_workers[node_name] = []
|
| 98 |
-
self.node_workers[node_name].append(node_worker)
|
| 99 |
-
task = asyncio.create_task(node_worker.run())
|
| 100 |
-
self.tasks.append(task)
|
| 101 |
-
|
| 102 |
-
async def enqueue_job(self, job: Job):
|
| 103 |
-
job.id = self._job_id
|
| 104 |
-
self._job_id += 1
|
| 105 |
-
await self.root_queue.put(job)
|
| 106 |
-
|
| 107 |
-
async def close(self):
|
| 108 |
-
for task in self.tasks:
|
| 109 |
-
task.cancel()
|
| 110 |
-
await asyncio.gather(*self.tasks, return_exceptions=True)
|
| 111 |
-
|
| 112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tests/test_pipeline.py
DELETED
|
@@ -1,86 +0,0 @@
|
|
| 1 |
-
import asyncio
|
| 2 |
-
import random
|
| 3 |
-
import time
|
| 4 |
-
import unittest
|
| 5 |
-
import sys
|
| 6 |
-
import os
|
| 7 |
-
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 8 |
-
|
| 9 |
-
from legacy_to_delete.pipeline import Pipeline, Node, Job
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
class Node1(Node):
|
| 13 |
-
async def process_job(self, job: Job):
|
| 14 |
-
job.data += f' (processed by node 1, worker {self.worker_id})'
|
| 15 |
-
yield job
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
class Node2(Node):
|
| 19 |
-
async def process_job(self, job: Job):
|
| 20 |
-
sleep_duration = 0.08 + 0.04 * random.random()
|
| 21 |
-
await asyncio.sleep(sleep_duration)
|
| 22 |
-
job.data += f' (processed by node 2, worker {self.worker_id})'
|
| 23 |
-
yield job
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
class Node3(Node):
|
| 27 |
-
async def process_job(self, job: Job):
|
| 28 |
-
job.data += f' (processed by node 3, worker {self.worker_id})'
|
| 29 |
-
print(f'{job.id} - {job.data}')
|
| 30 |
-
yield job
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
class TestPipeline(unittest.TestCase):
|
| 34 |
-
def setUp(self):
|
| 35 |
-
pass
|
| 36 |
-
|
| 37 |
-
async def _test_pipeline_edge_cases(self):
|
| 38 |
-
# must have a input queue
|
| 39 |
-
with self.assertRaises(ValueError):
|
| 40 |
-
await self.pipeline.add_node(Node1, 1, None, None)
|
| 41 |
-
# too output queue must not equal from input queue
|
| 42 |
-
node1_queue = asyncio.Queue()
|
| 43 |
-
with self.assertRaises(ValueError):
|
| 44 |
-
await self.pipeline.add_node(Node1, 1, node1_queue, node1_queue)
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
async def _test_pipeline(self, num_jobs):
|
| 48 |
-
node1_queue = asyncio.Queue()
|
| 49 |
-
node2_queue = asyncio.Queue()
|
| 50 |
-
node3_queue = asyncio.Queue()
|
| 51 |
-
await self.pipeline.add_node(Node1, 1, node1_queue, node2_queue)
|
| 52 |
-
await self.pipeline.add_node(Node2, 5, node2_queue, node3_queue)
|
| 53 |
-
await self.pipeline.add_node(Node3, 1, node3_queue, job_sync=self.job_sync, sequential_node=True)
|
| 54 |
-
for i in range(num_jobs):
|
| 55 |
-
job = Job("")
|
| 56 |
-
await self.pipeline.enqueue_job(job)
|
| 57 |
-
while True:
|
| 58 |
-
if len(self.job_sync) == num_jobs:
|
| 59 |
-
break
|
| 60 |
-
await asyncio.sleep(0.1)
|
| 61 |
-
await self.pipeline.close()
|
| 62 |
-
|
| 63 |
-
def test_pipeline_edge_cases(self):
|
| 64 |
-
self.pipeline = Pipeline()
|
| 65 |
-
self.job_sync = []
|
| 66 |
-
asyncio.run(self._test_pipeline_edge_cases())
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
def test_pipeline_keeps_order(self):
|
| 70 |
-
self.pipeline = Pipeline()
|
| 71 |
-
self.job_sync = []
|
| 72 |
-
num_jobs = 100
|
| 73 |
-
start_time = time.time()
|
| 74 |
-
asyncio.run(self._test_pipeline(num_jobs))
|
| 75 |
-
end_time = time.time()
|
| 76 |
-
print(f"Pipeline processed in {end_time - start_time} seconds.")
|
| 77 |
-
self.assertEqual(len(self.job_sync), num_jobs)
|
| 78 |
-
for i, job in enumerate(self.job_sync):
|
| 79 |
-
self.assertEqual(i, job.id)
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
if __name__ == '__main__':
|
| 83 |
-
unittest.main()
|
| 84 |
-
# test = TestPipeline()
|
| 85 |
-
# test.setUp()
|
| 86 |
-
# test.test_pipeline()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|