fixed runner
This commit is contained in:
@@ -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)
|
|
||||||
@@ -2,6 +2,7 @@ import asyncio
|
|||||||
import aiohttp
|
import aiohttp
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import argparse
|
||||||
|
|
||||||
from PIL import Image
|
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.transports.services.daily import DailyParams, DailyTranscriptionSettings, DailyTransport
|
||||||
from pipecat.vad.silero import SileroVAD
|
from pipecat.vad.silero import SileroVAD
|
||||||
|
|
||||||
from runner import configure
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
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
|
# Get the filename without the extension to use as the dictionary key
|
||||||
# Open the image and convert it to bytes
|
# Open the image and convert it to bytes
|
||||||
with Image.open(full_path) as img:
|
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]
|
flipped = sprites[::-1]
|
||||||
sprites.extend(flipped)
|
sprites.extend(flipped)
|
||||||
@@ -156,5 +156,9 @@ async def main(room_url: str, token):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
(url, token) = configure()
|
parser = argparse.ArgumentParser(description="Daily Storyteller Bot")
|
||||||
asyncio.run(main(url, token))
|
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))
|
||||||
|
|||||||
166
examples/simple-chatbot/bot_runner.py
Normal file
166
examples/simple-chatbot/bot_runner.py
Normal 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...")
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
|
|
||||||
|
from re import X
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
@@ -9,7 +10,7 @@ from dotenv import load_dotenv
|
|||||||
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")
|
daily_api_key = os.getenv("DAILY_API_KEY")
|
||||||
|
|
||||||
|
|
||||||
@@ -50,6 +51,31 @@ def create_room() -> tuple[str, str]:
|
|||||||
return room_url, room_name
|
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:
|
def get_name_from_url(room_url: str) -> str:
|
||||||
"""
|
"""
|
||||||
Extracts the name from a given room URL.
|
Extracts the name from a given room URL.
|
||||||
@@ -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...")
|
|
||||||
@@ -3,4 +3,5 @@ fastapi
|
|||||||
uvicorn
|
uvicorn
|
||||||
requests
|
requests
|
||||||
python-dotenv
|
python-dotenv
|
||||||
loguru
|
loguru
|
||||||
|
requests
|
||||||
1
examples/web-ui/env.example
Normal file
1
examples/web-ui/env.example
Normal file
@@ -0,0 +1 @@
|
|||||||
|
VITE_SERVER_URL=... #optional: if serving frontend independetely from backend (otherwise relative.)
|
||||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -37,14 +37,11 @@ export default function App() {
|
|||||||
let data;
|
let data;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${serverUrl}/start_bot`, {
|
const res = await fetch(`${serverUrl}start_bot`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
|
||||||
room_url: import.meta.env.VITE_TRANSPORT_ROOM_URL || null,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
data = await res.json();
|
data = await res.json();
|
||||||
@@ -52,6 +49,7 @@ export default function App() {
|
|||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
setError(data.detail);
|
setError(data.detail);
|
||||||
setState("error");
|
setState("error");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(
|
setError(
|
||||||
@@ -65,7 +63,7 @@ export default function App() {
|
|||||||
|
|
||||||
await daily.join({
|
await daily.join({
|
||||||
url: data.room_url,
|
url: data.room_url,
|
||||||
token: data.user_token,
|
token: data.token,
|
||||||
videoSource: false,
|
videoSource: false,
|
||||||
startAudioOff: true,
|
startAudioOff: true,
|
||||||
});
|
});
|
||||||
@@ -73,10 +71,10 @@ export default function App() {
|
|||||||
setState("connected");
|
setState("connected");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*async function leave() {
|
async function leave() {
|
||||||
await daily?.leave();
|
await daily?.leave();
|
||||||
setState("finished");
|
setState("idle");
|
||||||
}*/
|
}
|
||||||
|
|
||||||
if (state === "error") {
|
if (state === "error") {
|
||||||
return (
|
return (
|
||||||
@@ -87,7 +85,7 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state === "connected") {
|
if (state === "connected") {
|
||||||
return <Session />;
|
return <Session onLeave={() => leave()} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const status_text = {
|
const status_text = {
|
||||||
|
Before Width: | Height: | Size: 937 B After Width: | Height: | Size: 937 B |
@@ -9,7 +9,7 @@ import UserMicBubble from "../UserMicBubble";
|
|||||||
|
|
||||||
import styles from "./styles.module.css";
|
import styles from "./styles.module.css";
|
||||||
|
|
||||||
export const Session: React.FC = () => {
|
export const Session: React.FC<{ onLeave: () => void }> = ({ onLeave }) => {
|
||||||
const daily = useDaily();
|
const daily = useDaily();
|
||||||
const [showDevices, setShowDevices] = useState(false);
|
const [showDevices, setShowDevices] = useState(false);
|
||||||
const modalRef = useRef<HTMLDialogElement>(null);
|
const modalRef = useRef<HTMLDialogElement>(null);
|
||||||
@@ -65,7 +65,7 @@ export const Session: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<Settings />
|
<Settings />
|
||||||
</Button>
|
</Button>
|
||||||
<Button>
|
<Button onClick={() => onLeave()}>
|
||||||
<LogOut size={16} />
|
<LogOut size={16} />
|
||||||
End
|
End
|
||||||
</Button>
|
</Button>
|
||||||
Reference in New Issue
Block a user