added temporary api
This commit is contained in:
@@ -1,17 +1,35 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { LogOut, Settings } from "lucide-react";
|
import { LogOut, Settings } from "lucide-react";
|
||||||
import { DailyAudio } from "@daily-co/daily-react";
|
import { DailyAudio, useAppMessage, useDaily } from "@daily-co/daily-react";
|
||||||
|
|
||||||
import DeviceSelect from "../DeviceSelect";
|
import DeviceSelect from "../DeviceSelect";
|
||||||
import Agent from "./agent";
|
import Agent from "./agent";
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import styles from "./styles.module.css";
|
|
||||||
import UserMicBubble from "../UserMicBubble";
|
import UserMicBubble from "../UserMicBubble";
|
||||||
|
|
||||||
|
import styles from "./styles.module.css";
|
||||||
|
|
||||||
export const Session: React.FC = () => {
|
export const Session: React.FC = () => {
|
||||||
|
const daily = useDaily();
|
||||||
const [showDevices, setShowDevices] = useState(false);
|
const [showDevices, setShowDevices] = useState(false);
|
||||||
const [canTalk, setCanTalk] = useState(false);
|
|
||||||
const modalRef = useRef<HTMLDialogElement>(null);
|
const modalRef = useRef<HTMLDialogElement>(null);
|
||||||
|
const [talkState, setTalkState] = useState<"user" | "assistant">("assistant");
|
||||||
|
|
||||||
|
useAppMessage({
|
||||||
|
onAppMessage: (e) => {
|
||||||
|
if (!daily || !e.data?.cue) return;
|
||||||
|
|
||||||
|
// Determine the UI state from the cue sent by the bot
|
||||||
|
if (e.data?.cue === "user_turn") {
|
||||||
|
// Delay enabling local mic input to avoid feedback from LLM
|
||||||
|
setTimeout(() => daily.setLocalAudio(true), 500);
|
||||||
|
setTalkState("user");
|
||||||
|
} else {
|
||||||
|
daily.setLocalAudio(false);
|
||||||
|
setTalkState("assistant");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const current = modalRef.current;
|
const current = modalRef.current;
|
||||||
@@ -34,7 +52,7 @@ export const Session: React.FC = () => {
|
|||||||
|
|
||||||
<div className={styles.agentContainer}>
|
<div className={styles.agentContainer}>
|
||||||
<Agent />
|
<Agent />
|
||||||
<UserMicBubble active={canTalk} />
|
<UserMicBubble active={talkState === "user"} />
|
||||||
<DailyAudio />
|
<DailyAudio />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React, { useCallback, useRef, useState, useEffect } from "react";
|
import React, { useCallback, useRef } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useAudioLevel,
|
useAudioLevel,
|
||||||
useAudioTrack,
|
useAudioTrack,
|
||||||
useLocalSessionId,
|
useLocalSessionId,
|
||||||
useAppMessage,
|
//useAppMessage,
|
||||||
} from "@daily-co/daily-react";
|
} from "@daily-co/daily-react";
|
||||||
import { DailyEventObjectAppMessage } from "@daily-co/daily-js";
|
//import { DailyEventObjectAppMessage } from "@daily-co/daily-js";
|
||||||
import { Mic, MicOff } from "lucide-react";
|
import { Mic, MicOff } from "lucide-react";
|
||||||
//import { TypewriterEffect } from "../ui/typewriter";
|
//import { TypewriterEffect } from "../ui/typewriter";
|
||||||
import styles from "./styles.module.css";
|
import styles from "./styles.module.css";
|
||||||
@@ -38,13 +38,13 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function UserMicBubble({ active }: Props) {
|
export default function UserMicBubble({ active }: Props) {
|
||||||
|
/*
|
||||||
const [transcription, setTranscription] = useState<string[]>([]);
|
const [transcription, setTranscription] = useState<string[]>([]);
|
||||||
|
|
||||||
useAppMessage({
|
useAppMessage({
|
||||||
onAppMessage: (e: DailyEventObjectAppMessage<any>) => {
|
onAppMessage: (e: DailyEventObjectAppMessage<any>) => {
|
||||||
if (e.fromId && e.fromId === "transcription") {
|
if (e.fromId && e.fromId === "transcription") {
|
||||||
if (e.data.user_id === "" && e.data.is_final) {
|
if (e.data.user_id === "" && e.data.is_final) {
|
||||||
setTranscription((t) => [...t, ...e.data.text.split(" ")]);
|
//setTranscription((t) => [...t, ...e.data.text.split(" ")]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -54,7 +54,7 @@ export default function UserMicBubble({ active }: Props) {
|
|||||||
if (active) return;
|
if (active) return;
|
||||||
const t = setTimeout(() => setTranscription([]), 4000);
|
const t = setTimeout(() => setTranscription([]), 4000);
|
||||||
return () => clearTimeout(t);
|
return () => clearTimeout(t);
|
||||||
}, [active]);
|
}, [active]);*/
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.bubbleContainer} ${active ? styles.active : ""}`}>
|
<div className={`${styles.bubbleContainer} ${active ? styles.active : ""}`}>
|
||||||
|
|||||||
89
examples/simple-chatbot/pipecat.py
Normal file
89
examples/simple-chatbot/pipecat.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
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...")
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
python-dotenv
|
pipecat-ai[daily,openai,fal]
|
||||||
requests
|
fastapi
|
||||||
fastapi[all]
|
|
||||||
uvicorn
|
uvicorn
|
||||||
pipecat-ai[daily,openai,silero]
|
requests
|
||||||
|
python-dotenv
|
||||||
|
loguru
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
import argparse
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import urllib
|
|
||||||
import requests
|
|
||||||
|
|
||||||
|
|
||||||
def configure():
|
|
||||||
parser = argparse.ArgumentParser(description="Daily AI SDK Bot Sample")
|
|
||||||
parser.add_argument(
|
|
||||||
"-u",
|
|
||||||
"--url",
|
|
||||||
type=str,
|
|
||||||
required=False,
|
|
||||||
help="URL of the Daily room to join")
|
|
||||||
parser.add_argument(
|
|
||||||
"-k",
|
|
||||||
"--apikey",
|
|
||||||
type=str,
|
|
||||||
required=False,
|
|
||||||
help="Daily API Key (needed to create an owner token for the room)",
|
|
||||||
)
|
|
||||||
|
|
||||||
args, unknown = parser.parse_known_args()
|
|
||||||
|
|
||||||
url = args.url or os.getenv("DAILY_SAMPLE_ROOM_URL")
|
|
||||||
key = args.apikey or os.getenv("DAILY_API_KEY")
|
|
||||||
|
|
||||||
if not url:
|
|
||||||
raise Exception(
|
|
||||||
"No Daily room specified. use the -u/--url option from the command line, or set DAILY_SAMPLE_ROOM_URL in your environment to specify a Daily room URL.")
|
|
||||||
|
|
||||||
if not key:
|
|
||||||
raise Exception("No Daily API key specified. use the -k/--apikey option from the command line, or set DAILY_API_KEY in your environment to specify a Daily API key, available from https://dashboard.daily.co/developers.")
|
|
||||||
|
|
||||||
# Create a meeting token for the given room with an expiration 1 hour in
|
|
||||||
# the future.
|
|
||||||
room_name: str = urllib.parse.urlparse(url).path[1:]
|
|
||||||
expiration: float = time.time() + 60 * 60
|
|
||||||
|
|
||||||
res: requests.Response = requests.post(
|
|
||||||
f"https://api.daily.co/v1/meeting-tokens",
|
|
||||||
headers={
|
|
||||||
"Authorization": f"Bearer {key}"},
|
|
||||||
json={
|
|
||||||
"properties": {
|
|
||||||
"room_name": room_name,
|
|
||||||
"is_owner": True,
|
|
||||||
"exp": expiration}},
|
|
||||||
)
|
|
||||||
|
|
||||||
if res.status_code != 200:
|
|
||||||
raise Exception(
|
|
||||||
f"Failed to create meeting token: {res.status_code} {res.text}")
|
|
||||||
|
|
||||||
token: str = res.json()["token"]
|
|
||||||
|
|
||||||
return (url, token)
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
import os
|
|
||||||
import argparse
|
|
||||||
import subprocess
|
|
||||||
import atexit
|
|
||||||
|
|
||||||
from fastapi import FastAPI, Request, HTTPException
|
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
|
||||||
from fastapi.responses import JSONResponse, RedirectResponse
|
|
||||||
|
|
||||||
from utils.daily_helpers import create_room as _create_room, get_token
|
|
||||||
|
|
||||||
MAX_BOTS_PER_ROOM = 1
|
|
||||||
|
|
||||||
# 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.terminate()
|
|
||||||
proc.wait()
|
|
||||||
|
|
||||||
|
|
||||||
atexit.register(cleanup)
|
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI()
|
|
||||||
|
|
||||||
app.add_middleware(
|
|
||||||
CORSMiddleware,
|
|
||||||
allow_origins=["*"],
|
|
||||||
allow_credentials=True,
|
|
||||||
allow_methods=["*"],
|
|
||||||
allow_headers=["*"],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/start")
|
|
||||||
async def start_agent(request: Request):
|
|
||||||
print(f"!!! Creating room")
|
|
||||||
room_url, room_name = _create_room()
|
|
||||||
print(f"!!! Room URL: {room_url}")
|
|
||||||
# Ensure the room property is present
|
|
||||||
if not room_url:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=500,
|
|
||||||
detail="Missing 'room' property in request data. Cannot start agent without a target room!")
|
|
||||||
|
|
||||||
# Check if there is already an existing process running in this room
|
|
||||||
num_bots_in_room = sum(
|
|
||||||
1 for proc in bot_procs.values() if proc[1] == room_url and proc[0].poll() is None)
|
|
||||||
if num_bots_in_room >= MAX_BOTS_PER_ROOM:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=500, detail=f"Max bot limited reach for room: {room_url}")
|
|
||||||
|
|
||||||
# Get the token for the room
|
|
||||||
token = get_token(room_url)
|
|
||||||
|
|
||||||
if 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)
|
|
||||||
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}")
|
|
||||||
|
|
||||||
return RedirectResponse(room_url)
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/status/{pid}")
|
|
||||||
def get_status(pid: int):
|
|
||||||
# Look up the subprocess
|
|
||||||
proc = bot_procs.get(pid)
|
|
||||||
|
|
||||||
# If the subprocess doesn't exist, return an error
|
|
||||||
if not proc:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=404, detail=f"Bot with process id: {pid} not found")
|
|
||||||
|
|
||||||
# Check the status of the subprocess
|
|
||||||
if proc[0].poll() is None:
|
|
||||||
status = "running"
|
|
||||||
else:
|
|
||||||
status = "finished"
|
|
||||||
|
|
||||||
return JSONResponse({"bot_id": pid, "status": status})
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import uvicorn
|
|
||||||
|
|
||||||
default_host = os.getenv("HOST", "0.0.0.0")
|
|
||||||
default_port = int(os.getenv("FAST_API_PORT", "7860"))
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="Daily Storyteller FastAPI server")
|
|
||||||
parser.add_argument("--host", type=str,
|
|
||||||
default=default_host, help="Host address")
|
|
||||||
parser.add_argument("--port", type=int,
|
|
||||||
default=default_port, help="Port number")
|
|
||||||
parser.add_argument("--reload", action="store_true",
|
|
||||||
help="Reload code on change")
|
|
||||||
|
|
||||||
config = parser.parse_args()
|
|
||||||
|
|
||||||
uvicorn.run(
|
|
||||||
"server:app",
|
|
||||||
host=config.host,
|
|
||||||
port=config.port,
|
|
||||||
reload=config.reload,
|
|
||||||
)
|
|
||||||
Reference in New Issue
Block a user