fixed runner

This commit is contained in:
Jon Taylor
2024-05-16 18:57:34 +01:00
parent 72da9320da
commit dab01e0d58
37 changed files with 214 additions and 109 deletions

View File

@@ -1,2 +0,0 @@
VITE_SERVER_URL... #optional: if serving frontend independetely from backend (otherwise relative)
VITE_TRANSPORT_ROOM_URL=... #optional: use the same room each time (vs. creating a new one)

View File

@@ -2,6 +2,7 @@ import asyncio
import aiohttp
import os
import sys
import argparse
from PIL import Image
@@ -23,8 +24,6 @@ from pipecat.services.openai import OpenAILLMService
from pipecat.transports.services.daily import DailyParams, DailyTranscriptionSettings, DailyTransport
from pipecat.vad.silero import SileroVAD
from runner import configure
from loguru import logger
from dotenv import load_dotenv
@@ -43,7 +42,8 @@ for i in range(1, 26):
# Get the filename without the extension to use as the dictionary key
# Open the image and convert it to bytes
with Image.open(full_path) as img:
sprites.append(ImageRawFrame(image=img.tobytes(), size=img.size, format=img.format))
sprites.append(ImageRawFrame(image=img.tobytes(),
size=img.size, format=img.format))
flipped = sprites[::-1]
sprites.extend(flipped)
@@ -156,5 +156,9 @@ async def main(room_url: str, token):
if __name__ == "__main__":
(url, token) = configure()
asyncio.run(main(url, token))
parser = argparse.ArgumentParser(description="Daily Storyteller Bot")
parser.add_argument("-u", type=str, help="Room URL")
parser.add_argument("-t", type=str, help="Token")
config = parser.parse_args()
asyncio.run(main(config.u, config.t))

View File

@@ -0,0 +1,166 @@
from daily_helpers import create_room, get_token, check_room_url
import os
import sys
import argparse
import subprocess
import atexit
from pathlib import Path
from typing import Optional
from fastapi import FastAPI, Request, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, JSONResponse
from dotenv import load_dotenv
load_dotenv(override=True)
# Bot sub-process dict for status reporting and concurrency control
bot_procs = {}
def cleanup():
# Clean up function, just to be extra safe
for proc in bot_procs.values():
proc[0].terminate()
proc[0].wait()
atexit.register(cleanup)
# ------------ Configuration ------------ #
MAX_SESSION_TIME = 5 * 1000
BOT_CAN_IDLE = True
SERVE_STATIC = True
STATIC_DIR = "../web-ui/dist"
STATIC_ROUTE = "/static"
STATIC_INDEX = "index.html"
# ----------------- API ----------------- #
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"]
)
# Optionally serve client static files
if SERVE_STATIC:
app.mount(STATIC_ROUTE, StaticFiles(
directory=STATIC_DIR, html=True), name="static")
@app.get("/{path_name:path}", response_class=FileResponse)
async def catch_all(path_name: Optional[str] = ""):
if path_name == "":
return FileResponse(f"{STATIC_DIR}/{STATIC_INDEX}")
file_path = Path(STATIC_DIR) / (path_name or "")
if file_path.is_file():
return file_path
html_file_path = file_path.with_suffix(".html")
if html_file_path.is_file():
return FileResponse(html_file_path)
raise HTTPException(
status_code=404, detail="Page not found")
@app.post("/start_bot")
async def start_bot(request: Request) -> JSONResponse:
try:
data = await request.json()
# Is this a webhook creation request?
if "test" in data:
return JSONResponse({"test": True})
except Exception:
pass
# Use specified room URL, or create a new one if not specified
room_url = os.getenv("DAILY_SAMPLE_ROOM_URL", None)
if not room_url:
try:
room_url, room_name = create_room()
except Exception:
raise HTTPException(
status_code=500,
detail="Unable to provision room")
else:
# Check passed room URL exists
try:
check_room_url(room_url)
except Exception:
raise HTTPException(
status_code=500, detail=f"Room not found: {room_url}")
# Give the agent a token to join the session
token = get_token(room_url)
if not room_url or not token:
raise HTTPException(
status_code=500, detail=f"Failed to get token for room: {room_url}")
# Spawn a new agent, and join the user session
# Note: this is mostly for demonstration purposes (refer to 'deployment' in README)
# @TODO: Spawn a new fly machine here...
try:
proc = subprocess.Popen(
[
f"python3 -m bot -u {room_url} -t {token}"
],
shell=True,
bufsize=1,
cwd=os.path.dirname(os.path.abspath(__file__))
)
bot_procs[proc.pid] = (proc, room_url)
except Exception as e:
raise HTTPException(
status_code=500, detail=f"Failed to start subprocess: {e}")
# Grab a token for the user to join with
user_token = get_token(room_url)
return JSONResponse({"bot_id": proc.pid, "room_url": room_url, "token": user_token})
# ----------------- Main ----------------- #
if __name__ == "__main__":
# Check environment variables
required_env_vars = ['OPENAI_API_KEY', 'DAILY_API_KEY',
'ELEVENLABS_VOICE_ID', 'ELEVENLABS_API_KEY']
for env_var in required_env_vars:
if env_var not in os.environ:
raise Exception(f"Missing environment variable: {env_var}.")
parser = argparse.ArgumentParser(description="Pipecat Bot Runner")
parser.add_argument("--host", type=str,
default=os.getenv("HOST", "localhost"), help="Host address")
parser.add_argument("--port", type=int,
default=os.getenv("PORT", 7860), help="Port number")
parser.add_argument("--reload", action="store_true",
default=True, help="Reload code on change")
config = parser.parse_args()
try:
import uvicorn
uvicorn.run(
"bot_runner:app",
host=config.host,
port=config.port,
reload=config.reload
)
except KeyboardInterrupt:
print("Pipecat runner shutting down...")

View File

@@ -1,4 +1,5 @@
from re import X
import urllib.parse
import os
import time
@@ -9,7 +10,7 @@ from dotenv import load_dotenv
load_dotenv()
daily_api_path = os.getenv("DAILY_API_URL") or "api.daily.co/v1"
daily_api_path = os.getenv("DAILY_API_URL", "api.daily.co/v1")
daily_api_key = os.getenv("DAILY_API_KEY")
@@ -50,6 +51,31 @@ def create_room() -> tuple[str, str]:
return room_url, room_name
def check_room_url(room_url: str) -> bool:
"""
Checks if a room exists in Daily.
# See: https://docs.daily.co/reference/rest-api/rooms/get-room-config
Args:
room_name (str): The url of the room to check for
Returns:
bool: True if 200 OK, Exception otherwise.
"""
room_name = get_name_from_url(room_url)
res: requests.Response = requests.get(
f"https://{daily_api_path}/rooms/{room_name}",
headers={"Authorization": f"Bearer {daily_api_key}"}
)
if res.status_code != 200:
raise Exception(f"Room not found: {room_name}")
return True
def get_name_from_url(room_url: str) -> str:
"""
Extracts the name from a given room URL.

View File

@@ -1,89 +0,0 @@
import os
import argparse
import uvicorn
from typing import Optional
from pathlib import Path
from fastapi import FastAPI, Request, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, JSONResponse
from dotenv import load_dotenv
load_dotenv(override=True)
# ------------ Configuration ------------ #
MAX_SESSION_TIME = 5 * 1000
BOT_CAN_IDLE = True
SERVE_STATIC = True
STATIC_DIR = "client/dist"
STATIC_ROUTE = "/static"
STATIC_INDEX = "index.html"
# ----------------- API ----------------- #
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"]
)
# Optionally serve client static files
if SERVE_STATIC:
app.mount(STATIC_ROUTE, StaticFiles(
directory=STATIC_DIR, html=True), name="static")
@app.get("/{path_name:path}", response_class=FileResponse)
async def catch_all(path_name: Optional[str] = ""):
if path_name == "":
return FileResponse(f"{STATIC_DIR}/{STATIC_INDEX}")
file_path = Path(STATIC_DIR) / (path_name or "")
if file_path.is_file():
return file_path
html_file_path = file_path.with_suffix(".html")
if html_file_path.is_file():
return FileResponse(html_file_path)
raise HTTPException(
status_code=404, detail="Page not found")
@app.post("/start_bot")
async def start_bot(request: Request):
return JSONResponse({"bot_id": 123, "user_token": "abc", "room_url": "https://jpt.daily.co/hello"})
# ----------------- Main ----------------- #
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Pipecat Bot Runner")
parser.add_argument("--host", type=str,
default=os.getenv("HOST", "localhost"), help="Host address")
parser.add_argument("--port", type=int,
default=os.getenv("PORT", 7860), help="Port number")
parser.add_argument("--reload", action="store_true",
default=True, help="Reload code on change")
config = parser.parse_args()
try:
uvicorn.run(
"pipecat:app",
host=config.host,
port=config.port,
reload=config.reload
)
except KeyboardInterrupt:
print("Pipecat runner shutting down...")

View File

@@ -3,4 +3,5 @@ fastapi
uvicorn
requests
python-dotenv
loguru
loguru
requests

View File

@@ -0,0 +1 @@
VITE_SERVER_URL=... #optional: if serving frontend independetely from backend (otherwise relative.)

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -37,14 +37,11 @@ export default function App() {
let data;
try {
const res = await fetch(`${serverUrl}/start_bot`, {
const res = await fetch(`${serverUrl}start_bot`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
room_url: import.meta.env.VITE_TRANSPORT_ROOM_URL || null,
}),
});
data = await res.json();
@@ -52,6 +49,7 @@ export default function App() {
if (!res.ok) {
setError(data.detail);
setState("error");
return;
}
} catch (e) {
setError(
@@ -65,7 +63,7 @@ export default function App() {
await daily.join({
url: data.room_url,
token: data.user_token,
token: data.token,
videoSource: false,
startAudioOff: true,
});
@@ -73,10 +71,10 @@ export default function App() {
setState("connected");
}
/*async function leave() {
async function leave() {
await daily?.leave();
setState("finished");
}*/
setState("idle");
}
if (state === "error") {
return (
@@ -87,7 +85,7 @@ export default function App() {
}
if (state === "connected") {
return <Session />;
return <Session onLeave={() => leave()} />;
}
const status_text = {

View File

Before

Width:  |  Height:  |  Size: 937 B

After

Width:  |  Height:  |  Size: 937 B

View File

@@ -9,7 +9,7 @@ import UserMicBubble from "../UserMicBubble";
import styles from "./styles.module.css";
export const Session: React.FC = () => {
export const Session: React.FC<{ onLeave: () => void }> = ({ onLeave }) => {
const daily = useDaily();
const [showDevices, setShowDevices] = useState(false);
const modalRef = useRef<HTMLDialogElement>(null);
@@ -65,7 +65,7 @@ export const Session: React.FC = () => {
>
<Settings />
</Button>
<Button>
<Button onClick={() => onLeave()}>
<LogOut size={16} />
End
</Button>