Compare commits
6 Commits
hush/backg
...
cb/multi-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
052b2439a5 | ||
|
|
d39e46a9e1 | ||
|
|
031879423a | ||
|
|
df1aac0cc8 | ||
|
|
676f0a7b64 | ||
|
|
f948a144f8 |
@@ -9,8 +9,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- Added a new iOS client option to the `SmallWebRTCTransport` **video-transform** example.
|
||||
|
||||
- Added new processors `ProducerProcessor` and `ConsumerProcessor`. The
|
||||
producer processor processes frames from the pipeline and decides whether the
|
||||
consumers should consume it or not. If so, the same frame that is received by
|
||||
|
||||
8
examples/multi-transport-chatbot/Dockerfile
Normal file
8
examples/multi-transport-chatbot/Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
||||
FROM dailyco/pipecat-base:latest
|
||||
RUN apt-get update && apt-get install ffmpeg -y
|
||||
|
||||
COPY ./requirements.txt requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
||||
|
||||
COPY ./bot.py bot.py
|
||||
65
examples/multi-transport-chatbot/README.md
Normal file
65
examples/multi-transport-chatbot/README.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Multi-Transport Chatbot for Pipecat and Pipecat Cloud
|
||||
|
||||
This project demonstrates a bot architecture that allows you to use different transports with the same bot, depending on how you run the botfile. This can be really useful for starting with one transport for early development and then transitioning to a different transport in production.
|
||||
|
||||
Here's how to use this bot with each of the supported transports.
|
||||
|
||||
## Step 1: Local development with SmallWebRTCTransport
|
||||
|
||||
To get started, let's run the bot with SmallWebRTCTransport, which makes a direct peer-to-peer WebRTC connection between your browser and the bot.
|
||||
|
||||
```bash
|
||||
# Start with the standard venv setup:
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Rename the env example and add your keys:
|
||||
mv example.env .env
|
||||
|
||||
# Now run the included webserver:
|
||||
python server.py
|
||||
```
|
||||
|
||||
Open a browser pointed at `http://localhost:7860` and click the **Connect** button to talk to the bot.
|
||||
|
||||
`server.py` helps set up the WebRTC connection, and then calls the `local_webrtc` function in bot.py with this line of code:
|
||||
|
||||
```python
|
||||
background_tasks.add_task(local_webrtc, pipecat_connection)
|
||||
```
|
||||
|
||||
In `bot.py`, you can see that the `local_webrtc` function creates a `SmallWebRTCTransport` instance and passes it to the `main()` function.
|
||||
|
||||
## Step 2: Local development with Daily
|
||||
|
||||
After step 1, you can run the same bot using the Daily transport. Add a `DAILY_API_KEY` to your .env file. If you have a Daily account already, you can get your API key from https://dashboard.daily.co/developers. If you have a Pipecat Cloud account, you have a Daily API key available at https://pipecat.daily.co/<your-org-slug>/settings/daily.
|
||||
|
||||
Run the bot using a different entrypoint:
|
||||
|
||||
```bash
|
||||
LOCAL_RUN=1 python bot.py
|
||||
```
|
||||
|
||||
This uses the `local_daily()` function in `bot.py`, which creates a `DailyTransport`.
|
||||
|
||||
### Step 3: Deploy to Pipecat Cloud
|
||||
|
||||
This repo already includes a Dockerfile you can use to build an image that works with Pipecat Cloud. You can do it in a few steps. First, edit `build.sh` and `pcc-deploy.toml` and replace `your-dockerhub-username` with, well, your DockerHub username. Then:
|
||||
|
||||
```bash
|
||||
#
|
||||
./build.sh
|
||||
pcc deploy
|
||||
|
||||
# Then start a session with your bot
|
||||
pcc agent start multi-transport-chatbot --use-daily
|
||||
```
|
||||
|
||||
This will give you a URL you can open in your browser to talk to the bot using Daily Prebuilt.
|
||||
|
||||
Behind the scenes, Pipecat Cloud loads your botfile and calls its `bot()` function. Since you used the `--use-daily` option, the `args` argument is a `DailySessionArguments` instance that includes the Daily room URL and token, so the bot uses a `DailyTransport`.
|
||||
|
||||
## Step 4: Use a Twilio phone number and websocket
|
||||
|
||||
Follow the [Pipecat Cloud Twilio docs](https://docs.pipecat.daily.co/pipecat-in-production/twilio-mediastreams) to configure a TwiML Bin that points one of your phone numbers to Pipecat Cloud. When you dial that number, Pipecat Cloud will start a session with your bot that includes a `WebsocketArguments` object, so the `bot()` function will start your bot with a `FastAPIWebsocketTransport`.
|
||||
288
examples/multi-transport-chatbot/bot.py
Normal file
288
examples/multi-transport-chatbot/bot.py
Normal file
@@ -0,0 +1,288 @@
|
||||
#
|
||||
# Copyright (c) 2024–2025, Daily
|
||||
#
|
||||
# SPDX-License-Identifier: BSD 2-Clause License
|
||||
#
|
||||
|
||||
"""OpenAI Bot Implementation.
|
||||
|
||||
This module implements a chatbot using OpenAI's GPT-4 model for natural language
|
||||
processing. It includes:
|
||||
- Real-time audio/video interaction through Daily
|
||||
- Animated robot avatar
|
||||
- Text-to-speech using ElevenLabs
|
||||
- Support for both English and Spanish
|
||||
|
||||
The bot runs as part of a pipeline that processes audio/video frames and manages
|
||||
the conversation flow.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import aiohttp
|
||||
from dotenv import load_dotenv
|
||||
from loguru import logger
|
||||
from PIL import Image
|
||||
from pipecatcloud.agent import DailySessionArguments, SessionArguments, WebSocketSessionArguments
|
||||
|
||||
from pipecat.adapters.schemas.function_schema import FunctionSchema
|
||||
from pipecat.adapters.schemas.tools_schema import ToolsSchema
|
||||
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
||||
from pipecat.frames.frames import (
|
||||
BotStartedSpeakingFrame,
|
||||
BotStoppedSpeakingFrame,
|
||||
Frame,
|
||||
OutputImageRawFrame,
|
||||
SpriteFrame,
|
||||
TTSSpeakFrame,
|
||||
)
|
||||
from pipecat.pipeline.pipeline import Pipeline
|
||||
from pipecat.pipeline.runner import PipelineRunner
|
||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||
from pipecat.processors.frameworks.rtvi import RTVIConfig, RTVIObserver, RTVIProcessor
|
||||
from pipecat.services.cartesia.tts import CartesiaTTSService
|
||||
from pipecat.services.gladia.stt import GladiaSTTService
|
||||
from pipecat.services.openai.llm import OpenAILLMService
|
||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
||||
from pipecat.transports.network.fastapi_websocket import (
|
||||
FastAPIWebsocketParams,
|
||||
FastAPIWebsocketTransport,
|
||||
)
|
||||
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
||||
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
||||
|
||||
load_dotenv(override=True)
|
||||
|
||||
# Check if we're in local development mode
|
||||
LOCAL_RUN = os.getenv("LOCAL_RUN")
|
||||
if LOCAL_RUN:
|
||||
import asyncio
|
||||
import webbrowser
|
||||
|
||||
try:
|
||||
from local_runner import configure
|
||||
except ImportError:
|
||||
logger.error("Could not import local_runner module. Local development mode may not work.")
|
||||
|
||||
# Logger for local dev
|
||||
# logger.add(sys.stderr, level="DEBUG")
|
||||
|
||||
|
||||
async def fetch_weather_from_api(function_name, tool_call_id, args, llm, context, result_callback):
|
||||
"""Fetch weather data dummy function.
|
||||
|
||||
This function simulates fetching weather data from an external API.
|
||||
It demonstrates how to call an external service from the language model.
|
||||
"""
|
||||
await llm.push_frame(TTSSpeakFrame("Let me check on that."))
|
||||
await result_callback({"conditions": "nice", "temperature": "75"})
|
||||
|
||||
|
||||
async def main(transport: BaseTransport):
|
||||
"""Main bot execution function.
|
||||
|
||||
Sets up and runs the bot pipeline including:
|
||||
- Speech-to-text and text-to-speech services
|
||||
- Language model integration
|
||||
- Animation processing
|
||||
- RTVI event handling
|
||||
|
||||
Uses the transport defined by the calling function.
|
||||
See below for various ways to start the bot with different transports.
|
||||
"""
|
||||
tts = CartesiaTTSService(
|
||||
api_key=os.getenv("CARTESIA_API_KEY"),
|
||||
voice_id="c45bc5ec-dc68-4feb-8829-6e6b2748095d", # Movieman
|
||||
)
|
||||
|
||||
stt = GladiaSTTService(api_key=os.getenv("GLADIA_API_KEY"))
|
||||
|
||||
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||
|
||||
# Register your function call providing the function name and callback
|
||||
llm.register_function("get_current_weather", fetch_weather_from_api)
|
||||
|
||||
# Define your function call using the FunctionSchema
|
||||
# Learn more about function calling in Pipecat:
|
||||
# https://docs.pipecat.ai/guides/features/function-calling
|
||||
weather_function = FunctionSchema(
|
||||
name="get_current_weather",
|
||||
description="Get the current weather",
|
||||
properties={
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "The city and state, e.g. San Francisco, CA",
|
||||
},
|
||||
"format": {
|
||||
"type": "string",
|
||||
"enum": ["celsius", "fahrenheit"],
|
||||
"description": "The temperature unit to use. Infer this from the user's location.",
|
||||
},
|
||||
},
|
||||
required=["location", "format"],
|
||||
)
|
||||
|
||||
# Set up the tools schema with your weather function call
|
||||
tools = ToolsSchema(standard_tools=[weather_function])
|
||||
|
||||
# Set up initial messages for the bot
|
||||
messages = [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "You are Chatbot, a friendly, helpful robot. Your goal is to demonstrate your capabilities in a succinct way. Your output will be converted to audio so don't include special characters in your answers. Respond to what the user said in a creative and helpful way, but keep your responses brief. Start by introducing yourself.",
|
||||
},
|
||||
]
|
||||
|
||||
# Set up conversation context and management
|
||||
# The context_aggregator will automatically collect conversation context
|
||||
# Pass your initial messages and tools to the context to initialize the context
|
||||
context = OpenAILLMContext(messages, tools)
|
||||
context_aggregator = llm.create_context_aggregator(context)
|
||||
|
||||
# RTVI events for Pipecat client UI
|
||||
rtvi = RTVIProcessor(config=RTVIConfig(config=[]))
|
||||
|
||||
# Add your processors to the pipeline
|
||||
pipeline = Pipeline(
|
||||
[
|
||||
transport.input(),
|
||||
stt,
|
||||
rtvi,
|
||||
context_aggregator.user(),
|
||||
llm,
|
||||
tts,
|
||||
transport.output(),
|
||||
context_aggregator.assistant(),
|
||||
]
|
||||
)
|
||||
|
||||
# Create a PipelineTask to manage the pipeline
|
||||
task = PipelineTask(
|
||||
pipeline,
|
||||
params=PipelineParams(
|
||||
allow_interruptions=True,
|
||||
enable_metrics=True,
|
||||
enable_usage_metrics=True,
|
||||
),
|
||||
observers=[RTVIObserver(rtvi)],
|
||||
)
|
||||
|
||||
@rtvi.event_handler("on_client_ready")
|
||||
async def on_client_ready(rtvi):
|
||||
# Notify the client that the bot is ready
|
||||
await rtvi.set_bot_ready()
|
||||
|
||||
@transport.event_handler("on_client_connected")
|
||||
async def on_client_connected(transport, client):
|
||||
# Kick off the conversation by pushing a context frame to the pipeline
|
||||
await task.queue_frames([context_aggregator.user().get_context_frame()])
|
||||
|
||||
@transport.event_handler("on_client_disconnected")
|
||||
async def on_client_disconnected(transport, client):
|
||||
# Cancel the PipelineTask to stop processing
|
||||
await task.cancel()
|
||||
|
||||
runner = PipelineRunner()
|
||||
|
||||
await runner.run(task)
|
||||
|
||||
|
||||
shared_params = {
|
||||
"audio_in_enabled": True,
|
||||
"audio_out_enabled": True,
|
||||
"video_in_enabled": False,
|
||||
"video_out_enabled": False,
|
||||
"vad_enabled": True,
|
||||
"vad_analyzer": SileroVADAnalyzer(),
|
||||
"vad_audio_passthrough": True,
|
||||
}
|
||||
|
||||
|
||||
async def bot(args: SessionArguments):
|
||||
"""Bot entry point compatible with Pipecat Cloud. SessionArguments
|
||||
will be a different subclass depending on how the session is started.
|
||||
|
||||
args: either DailySessionArguments or WebsocketSessionArguments
|
||||
DailySessionArguments:
|
||||
room_url: The Daily room URL
|
||||
token: The Daily room token
|
||||
body: The configuration object from the request body
|
||||
session_id: The session ID for logging
|
||||
|
||||
WebsocketSessionArguments:
|
||||
websocket: The websocket for connecting to Twilio
|
||||
"""
|
||||
logger.info(f"Starting PCC bot. args: {args}")
|
||||
|
||||
if isinstance(args, WebSocketSessionArguments):
|
||||
logger.debug("Starting WebSocket bot")
|
||||
|
||||
start_data = args.websocket.iter_text()
|
||||
await start_data.__anext__()
|
||||
call_data = json.loads(await start_data.__anext__())
|
||||
stream_sid = call_data["start"]["streamSid"]
|
||||
transport = FastAPIWebsocketTransport(
|
||||
websocket=args.websocket,
|
||||
params=FastAPIWebsocketParams(
|
||||
**shared_params,
|
||||
serializer=TwilioFrameSerializer(stream_sid),
|
||||
),
|
||||
)
|
||||
elif isinstance(args, DailySessionArguments):
|
||||
logger.debug("Starting Daily bot")
|
||||
transport = DailyTransport(
|
||||
args.room_url,
|
||||
args.token,
|
||||
"Respond bot",
|
||||
DailyParams(**shared_params, transcription_enabled=False),
|
||||
)
|
||||
try:
|
||||
await main(transport)
|
||||
logger.info("Bot process completed")
|
||||
except Exception as e:
|
||||
logger.exception(f"Error in bot process: {str(e)}")
|
||||
raise
|
||||
|
||||
|
||||
# Local development
|
||||
async def local_daily():
|
||||
"""This is an entrypoint for running your bot locally but using Daily
|
||||
for the transport. To use this, you'll need to have DAILY_API_KEY set in your .env file.
|
||||
"""
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
(room_url, token) = await configure(session)
|
||||
logger.warning(f"Talk to your voice agent here: {room_url}")
|
||||
webbrowser.open(room_url)
|
||||
transport = DailyTransport(
|
||||
room_url=room_url,
|
||||
token=token,
|
||||
bot_name="Bot",
|
||||
params=DailyParams(**shared_params, transcription_enabled=False),
|
||||
)
|
||||
await main(transport)
|
||||
except Exception as e:
|
||||
logger.exception(f"Error in local development mode: {e}")
|
||||
|
||||
|
||||
async def local_webrtc(webrtc_connection):
|
||||
"""An entrypoint for using the SmallWebRTCTransport, which doesn't require a Daily
|
||||
account or API key. You'll need to run the web client and small API server included
|
||||
with this example to use this transport. Run `python server.py` to use it.
|
||||
"""
|
||||
transport = SmallWebRTCTransport(
|
||||
webrtc_connection=webrtc_connection, params=TransportParams(**shared_params)
|
||||
)
|
||||
await main(transport)
|
||||
|
||||
|
||||
# Local development entry point
|
||||
if LOCAL_RUN and __name__ == "__main__":
|
||||
try:
|
||||
# Change this line to run whichever entrypoint you want to use for your bot.
|
||||
asyncio.run(local_daily())
|
||||
except Exception as e:
|
||||
logger.exception(f"Failed to run in local mode: {e}")
|
||||
19
examples/multi-transport-chatbot/build.sh
Executable file
19
examples/multi-transport-chatbot/build.sh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
VERSION="0.1"
|
||||
DOCKER_USERNAME="your-dockerhub-username"
|
||||
AGENT_NAME="multi-transport-chatbot"
|
||||
|
||||
# Build the Docker image with the correct context
|
||||
echo "Building Docker image..."
|
||||
docker build --platform=linux/arm64 -t "$DOCKER_USERNAME/$AGENT_NAME:$VERSION" -t "$DOCKER_USERNAME/$AGENT_NAME:latest" .
|
||||
|
||||
# Push the Docker images
|
||||
echo "Pushing Docker image $DOCKER_USERNAME/$AGENT_NAME:$VERSION..."
|
||||
docker push "$DOCKER_USERNAME/$AGENT_NAME:$VERSION"
|
||||
|
||||
echo "Pushing Docker image $DOCKER_USERNAME/$AGENT_NAME:latest..."
|
||||
docker push "$DOCKER_USERNAME/$AGENT_NAME:latest"
|
||||
|
||||
echo "Successfully built and pushed $DOCKER_USERNAME/$AGENT_NAME:$VERSION and $DOCKER_USERNAME/$AGENT_NAME:latest"
|
||||
3
examples/multi-transport-chatbot/env.example
Normal file
3
examples/multi-transport-chatbot/env.example
Normal file
@@ -0,0 +1,3 @@
|
||||
OPENAI_API_KEY=sk-PL...
|
||||
CARTESIA_API_KEY=aeb...
|
||||
GLADIA_API_KEY=54e...
|
||||
100
examples/multi-transport-chatbot/index.html
Normal file
100
examples/multi-transport-chatbot/index.html
Normal file
@@ -0,0 +1,100 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WebRTC Voice Agent</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; }
|
||||
#status { font-size: 20px; margin: 20px; }
|
||||
button { padding: 10px 20px; font-size: 16px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>WebRTC Voice Agent</h1>
|
||||
<p id="status">Disconnected</p>
|
||||
<button id="connect-btn">Connect</button>
|
||||
<audio id="audio-el" autoplay></audio>
|
||||
|
||||
<script>
|
||||
const statusEl = document.getElementById("status")
|
||||
const buttonEl = document.getElementById("connect-btn")
|
||||
const audioEl = document.getElementById("audio-el")
|
||||
|
||||
let connected = false
|
||||
let peerConnection = null
|
||||
|
||||
/*const waitForIceGatheringComplete = async (pc) => {
|
||||
if (pc.iceGatheringState === 'complete') return;
|
||||
return new Promise((resolve) => {
|
||||
const checkState = () => {
|
||||
if (pc.iceGatheringState === 'complete') {
|
||||
pc.removeEventListener('icegatheringstatechange', checkState);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
pc.addEventListener('icegatheringstatechange', checkState);
|
||||
});
|
||||
}*/
|
||||
|
||||
const createSmallWebRTCConnection = async (audioTrack) => {
|
||||
const pc = new RTCPeerConnection()
|
||||
pc.ontrack = e => audioEl.srcObject = e.streams[0]
|
||||
pc.addTransceiver(audioTrack, { direction: 'sendrecv' })
|
||||
await pc.setLocalDescription(await pc.createOffer())
|
||||
//await waitForIceGatheringComplete(pc)
|
||||
const offer = pc.localDescription
|
||||
const response = await fetch('/api/offer', {
|
||||
body: JSON.stringify({ sdp: offer.sdp, type: offer.type}),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
method: 'POST',
|
||||
});
|
||||
const answer = await response.json()
|
||||
await pc.setRemoteDescription(answer)
|
||||
return pc
|
||||
}
|
||||
|
||||
const connect = async () => {
|
||||
const audioStream = await navigator.mediaDevices.getUserMedia({audio: true})
|
||||
peerConnection= await createSmallWebRTCConnection(audioStream.getAudioTracks()[0])
|
||||
peerConnection.onconnectionstatechange = () => {
|
||||
let connectionState = peerConnection?.connectionState
|
||||
if (connectionState === 'connected') {
|
||||
_onConnected()
|
||||
} else if (connectionState === 'disconnected') {
|
||||
_onDisconnected()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const _onConnected = () => {
|
||||
statusEl.textContent = "Connected"
|
||||
buttonEl.textContent = "Disconnect"
|
||||
connected = true
|
||||
}
|
||||
|
||||
const _onDisconnected = () => {
|
||||
statusEl.textContent = "Disconnected"
|
||||
buttonEl.textContent = "Connect"
|
||||
connected = false
|
||||
}
|
||||
|
||||
const disconnect = () => {
|
||||
if (!peerConnection) {
|
||||
return
|
||||
}
|
||||
peerConnection.close()
|
||||
peerConnection = null
|
||||
_onDisconnected()
|
||||
}
|
||||
|
||||
buttonEl.addEventListener("click", async () => {
|
||||
if (!connected) {
|
||||
await connect()
|
||||
} else {
|
||||
disconnect()
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
46
examples/multi-transport-chatbot/local_runner.py
Normal file
46
examples/multi-transport-chatbot/local_runner.py
Normal file
@@ -0,0 +1,46 @@
|
||||
#
|
||||
# Copyright (c) 2024–2025, Daily
|
||||
#
|
||||
# SPDX-License-Identifier: BSD 2-Clause License
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
import aiohttp
|
||||
|
||||
from pipecat.transports.services.helpers.daily_rest import DailyRESTHelper, DailyRoomParams
|
||||
|
||||
|
||||
async def configure(aiohttp_session: aiohttp.ClientSession):
|
||||
(url, token) = await configure_with_args(aiohttp_session)
|
||||
return (url, token)
|
||||
|
||||
|
||||
async def configure_with_args(aiohttp_session: aiohttp.ClientSession = None):
|
||||
key = os.getenv("DAILY_API_KEY")
|
||||
if not key:
|
||||
raise Exception(
|
||||
"No Daily API key specified. set DAILY_API_KEY in your environment to specify a Daily API key, available from https://dashboard.daily.co/developers."
|
||||
)
|
||||
|
||||
daily_rest_helper = DailyRESTHelper(
|
||||
daily_api_key=key,
|
||||
daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"),
|
||||
aiohttp_session=aiohttp_session,
|
||||
)
|
||||
|
||||
room = await daily_rest_helper.create_room(
|
||||
DailyRoomParams(properties={"enable_prejoin_ui": False})
|
||||
)
|
||||
if not room.url:
|
||||
raise HTTPException(status_code=500, detail="Failed to create room")
|
||||
|
||||
url = room.url
|
||||
|
||||
# Create a meeting token for the given room with an expiration 1 hour in
|
||||
# the future.
|
||||
expiry_time: float = 60 * 60
|
||||
|
||||
token = await daily_rest_helper.get_token(url, expiry_time)
|
||||
|
||||
return (url, token)
|
||||
7
examples/multi-transport-chatbot/pcc-deploy.toml
Normal file
7
examples/multi-transport-chatbot/pcc-deploy.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
agent_name = "multi-transport-chatbot"
|
||||
image = "your-dockerhub-username/multi-transport-chatbot:0.1"
|
||||
secret_set = "pcc-transport-chatbot-secrets"
|
||||
|
||||
[scaling]
|
||||
min_instances = 0
|
||||
max_instances = 2
|
||||
5
examples/multi-transport-chatbot/requirements.txt
Normal file
5
examples/multi-transport-chatbot/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
python-dotenv
|
||||
fastapi[all]
|
||||
uvicorn
|
||||
pipecat-ai[cartesia,daily,gladia,openai,silero,webrtc]
|
||||
pipecatcloud
|
||||
81
examples/multi-transport-chatbot/server.py
Normal file
81
examples/multi-transport-chatbot/server.py
Normal file
@@ -0,0 +1,81 @@
|
||||
import argparse
|
||||
import asyncio
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Dict
|
||||
|
||||
import uvicorn
|
||||
from bot import local_webrtc
|
||||
from dotenv import load_dotenv
|
||||
from fastapi import BackgroundTasks, FastAPI
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv(override=True)
|
||||
|
||||
logger = logging.getLogger("pc")
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
# Store connections by pc_id
|
||||
pcs_map: Dict[str, SmallWebRTCConnection] = {}
|
||||
|
||||
|
||||
@app.post("/api/offer")
|
||||
async def offer(request: dict, background_tasks: BackgroundTasks):
|
||||
pc_id = request.get("pc_id")
|
||||
|
||||
if pc_id and pc_id in pcs_map:
|
||||
pipecat_connection = pcs_map[pc_id]
|
||||
logger.info(f"Reusing existing connection for pc_id: {pc_id}")
|
||||
await pipecat_connection.renegotiate(sdp=request["sdp"], type=request["type"])
|
||||
else:
|
||||
pipecat_connection = SmallWebRTCConnection()
|
||||
await pipecat_connection.initialize(sdp=request["sdp"], type=request["type"])
|
||||
|
||||
@pipecat_connection.event_handler("closed")
|
||||
async def handle_disconnected(webrtc_connection: SmallWebRTCConnection):
|
||||
logger.info(f"Discarding peer connection for pc_id: {webrtc_connection.pc_id}")
|
||||
pcs_map.pop(webrtc_connection.pc_id, None)
|
||||
|
||||
background_tasks.add_task(local_webrtc, pipecat_connection)
|
||||
|
||||
answer = pipecat_connection.get_answer()
|
||||
# Updating the peer connection inside the map
|
||||
pcs_map[answer["pc_id"]] = pipecat_connection
|
||||
|
||||
return answer
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def serve_index():
|
||||
return FileResponse("index.html")
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
yield # Run app
|
||||
coros = [pc.close() for pc in pcs_map.values()]
|
||||
await asyncio.gather(*coros)
|
||||
pcs_map.clear()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="WebRTC demo")
|
||||
parser.add_argument(
|
||||
"--host", default="localhost", help="Host for HTTP server (default: localhost)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--port", type=int, default=7860, help="Port for HTTP server (default: 7860)"
|
||||
)
|
||||
parser.add_argument("--verbose", "-v", action="count")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verbose:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
else:
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
uvicorn.run(app, host=args.host, port=args.port)
|
||||
@@ -1,2 +0,0 @@
|
||||
/SimpleChatbot.xcodeproj/xcuserdata/
|
||||
/SimpleChatbot.xcodeproj/project.xcworkspace/xcuserdata/
|
||||
@@ -1,18 +0,0 @@
|
||||
# iOS implementation
|
||||
|
||||
Basic implementation using the [Pipecat iOS SDK](https://docs.pipecat.ai/client/ios/introduction).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Run the bot server. See the [server README](../../server).
|
||||
2. Install [Xcode](https://developer.apple.com/xcode/), and set up your device [to run your own applications](https://developer.apple.com/documentation/xcode/distributing-your-app-to-registered-devices).
|
||||
|
||||
## Running locally
|
||||
|
||||
1. Clone this repository locally.
|
||||
2. Open the SimpleChatbot.xcodeproj in Xcode.
|
||||
3. Tell Xcode to update its Package Dependencies by clicking File -> Packages -> Update to Latest Package Versions.
|
||||
4. Build the project.
|
||||
5. Run the project on your device.
|
||||
6. Connect to the URL you are testing.
|
||||
|
||||
@@ -1,727 +0,0 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 56;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
90031FA72C616EE700408370 /* SimpleChatbotApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90031FA62C616EE700408370 /* SimpleChatbotApp.swift */; };
|
||||
90031FA92C616EE700408370 /* PreJoinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90031FA82C616EE700408370 /* PreJoinView.swift */; };
|
||||
90031FAB2C616EE800408370 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 90031FAA2C616EE800408370 /* Assets.xcassets */; };
|
||||
90031FAE2C616EE800408370 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 90031FAD2C616EE800408370 /* Preview Assets.xcassets */; };
|
||||
90031FB82C616EE900408370 /* SimpleChatbotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90031FB72C616EE900408370 /* SimpleChatbotTests.swift */; };
|
||||
90031FC22C616EE900408370 /* SimpleChatbotUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90031FC12C616EE900408370 /* SimpleChatbotUITests.swift */; };
|
||||
90031FC42C616EE900408370 /* SimpleChatbotUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90031FC32C616EE900408370 /* SimpleChatbotUITestsLaunchTests.swift */; };
|
||||
90031FDC2C6D5DD700408370 /* ToastModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90031FDB2C6D5DD700408370 /* ToastModifier.swift */; };
|
||||
90383A912D9C357F00D0DDA3 /* PipecatClientIOSSmallWebrtc in Frameworks */ = {isa = PBXBuildFile; productRef = 90383A902D9C357F00D0DDA3 /* PipecatClientIOSSmallWebrtc */; };
|
||||
90383A932D9C35B300D0DDA3 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90383A922D9C35B300D0DDA3 /* ChatView.swift */; };
|
||||
90383A962D9C35BD00D0DDA3 /* LiveMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90383A942D9C35BD00D0DDA3 /* LiveMessage.swift */; };
|
||||
90383A982D9D85E700D0DDA3 /* CameraButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90383A972D9D85E700D0DDA3 /* CameraButtonView.swift */; };
|
||||
90383A9B2DA4620800D0DDA3 /* PipecatClientIOSSmallWebrtc in Frameworks */ = {isa = PBXBuildFile; productRef = 90383A9A2DA4620800D0DDA3 /* PipecatClientIOSSmallWebrtc */; };
|
||||
90ABB98E2C735ED6000D9CC7 /* MeetingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90ABB98D2C735ED6000D9CC7 /* MeetingView.swift */; };
|
||||
90ABB9932C73820D000D9CC7 /* MicrophoneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90ABB9922C73820D000D9CC7 /* MicrophoneView.swift */; };
|
||||
90ABB9982C738356000D9CC7 /* CustomColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90ABB9972C738356000D9CC7 /* CustomColors.swift */; };
|
||||
90ABB99A2C73A6A9000D9CC7 /* MockCallContainerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90ABB9992C73A6A9000D9CC7 /* MockCallContainerModel.swift */; };
|
||||
90ABB99D2C73C2D1000D9CC7 /* CallContainerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90ABB99C2C73C2D1000D9CC7 /* CallContainerModel.swift */; };
|
||||
90ABB9A32C74E1CE000D9CC7 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90ABB9A22C74E1CE000D9CC7 /* SettingsView.swift */; };
|
||||
90ABB9A62C74EA8A000D9CC7 /* SettingsPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90ABB9A52C74EA8A000D9CC7 /* SettingsPreference.swift */; };
|
||||
90ABB9A82C74EAB1000D9CC7 /* SettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90ABB9A72C74EAB1000D9CC7 /* SettingsManager.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
90031FB42C616EE900408370 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 90031F9B2C616EE700408370 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 90031FA22C616EE700408370;
|
||||
remoteInfo = SimpleChatbot;
|
||||
};
|
||||
90031FBE2C616EE900408370 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 90031F9B2C616EE700408370 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 90031FA22C616EE700408370;
|
||||
remoteInfo = SimpleChatbot;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
90031FA32C616EE700408370 /* SimpleChatbot.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SimpleChatbot.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
90031FA62C616EE700408370 /* SimpleChatbotApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleChatbotApp.swift; sourceTree = "<group>"; };
|
||||
90031FA82C616EE700408370 /* PreJoinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreJoinView.swift; sourceTree = "<group>"; };
|
||||
90031FAA2C616EE800408370 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
90031FAD2C616EE800408370 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
90031FB32C616EE900408370 /* SimpleChatbotTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SimpleChatbotTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
90031FB72C616EE900408370 /* SimpleChatbotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleChatbotTests.swift; sourceTree = "<group>"; };
|
||||
90031FBD2C616EE900408370 /* SimpleChatbotUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SimpleChatbotUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
90031FC12C616EE900408370 /* SimpleChatbotUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleChatbotUITests.swift; sourceTree = "<group>"; };
|
||||
90031FC32C616EE900408370 /* SimpleChatbotUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleChatbotUITestsLaunchTests.swift; sourceTree = "<group>"; };
|
||||
90031FD62C63FD6A00408370 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
90031FDB2C6D5DD700408370 /* ToastModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastModifier.swift; sourceTree = "<group>"; };
|
||||
90383A922D9C35B300D0DDA3 /* ChatView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = "<group>"; };
|
||||
90383A942D9C35BD00D0DDA3 /* LiveMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveMessage.swift; sourceTree = "<group>"; };
|
||||
90383A972D9D85E700D0DDA3 /* CameraButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraButtonView.swift; sourceTree = "<group>"; };
|
||||
90ABB98D2C735ED6000D9CC7 /* MeetingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingView.swift; sourceTree = "<group>"; };
|
||||
90ABB9922C73820D000D9CC7 /* MicrophoneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrophoneView.swift; sourceTree = "<group>"; };
|
||||
90ABB9972C738356000D9CC7 /* CustomColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomColors.swift; sourceTree = "<group>"; };
|
||||
90ABB9992C73A6A9000D9CC7 /* MockCallContainerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCallContainerModel.swift; sourceTree = "<group>"; };
|
||||
90ABB99C2C73C2D1000D9CC7 /* CallContainerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallContainerModel.swift; sourceTree = "<group>"; };
|
||||
90ABB9A22C74E1CE000D9CC7 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||
90ABB9A52C74EA8A000D9CC7 /* SettingsPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPreference.swift; sourceTree = "<group>"; };
|
||||
90ABB9A72C74EAB1000D9CC7 /* SettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsManager.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
90031FA02C616EE700408370 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
90383A912D9C357F00D0DDA3 /* PipecatClientIOSSmallWebrtc in Frameworks */,
|
||||
90383A9B2DA4620800D0DDA3 /* PipecatClientIOSSmallWebrtc in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
90031FB02C616EE900408370 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
90031FBA2C616EE900408370 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
90031F9A2C616EE700408370 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
90031FA52C616EE700408370 /* SimpleChatbot */,
|
||||
90031FB62C616EE900408370 /* SimpleChatbotTests */,
|
||||
90031FC02C616EE900408370 /* SimpleChatbotUITests */,
|
||||
90031FA42C616EE700408370 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
90031FA42C616EE700408370 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
90031FA32C616EE700408370 /* SimpleChatbot.app */,
|
||||
90031FB32C616EE900408370 /* SimpleChatbotTests.xctest */,
|
||||
90031FBD2C616EE900408370 /* SimpleChatbotUITests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
90031FA52C616EE700408370 /* SimpleChatbot */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
90383A952D9C35BD00D0DDA3 /* types */,
|
||||
90ABB99B2C73C2C5000D9CC7 /* model */,
|
||||
90031FDD2C6D61E000408370 /* views */,
|
||||
90031FD62C63FD6A00408370 /* Info.plist */,
|
||||
90031FA62C616EE700408370 /* SimpleChatbotApp.swift */,
|
||||
90031FAA2C616EE800408370 /* Assets.xcassets */,
|
||||
90031FAC2C616EE800408370 /* Preview Content */,
|
||||
);
|
||||
path = SimpleChatbot;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
90031FAC2C616EE800408370 /* Preview Content */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
90031FAD2C616EE800408370 /* Preview Assets.xcassets */,
|
||||
);
|
||||
path = "Preview Content";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
90031FB62C616EE900408370 /* SimpleChatbotTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
90031FB72C616EE900408370 /* SimpleChatbotTests.swift */,
|
||||
);
|
||||
path = SimpleChatbotTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
90031FC02C616EE900408370 /* SimpleChatbotUITests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
90031FC12C616EE900408370 /* SimpleChatbotUITests.swift */,
|
||||
90031FC32C616EE900408370 /* SimpleChatbotUITestsLaunchTests.swift */,
|
||||
);
|
||||
path = SimpleChatbotUITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
90031FDD2C6D61E000408370 /* views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
90ABB99E2C73C3A9000D9CC7 /* components */,
|
||||
90ABB9962C738346000D9CC7 /* extensions */,
|
||||
90ABB9A42C74EA52000D9CC7 /* settings */,
|
||||
90031FA82C616EE700408370 /* PreJoinView.swift */,
|
||||
90ABB98D2C735ED6000D9CC7 /* MeetingView.swift */,
|
||||
);
|
||||
path = views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
90383A952D9C35BD00D0DDA3 /* types */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
90383A942D9C35BD00D0DDA3 /* LiveMessage.swift */,
|
||||
);
|
||||
path = types;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
90ABB9962C738346000D9CC7 /* extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
90ABB9972C738356000D9CC7 /* CustomColors.swift */,
|
||||
);
|
||||
path = extensions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
90ABB99B2C73C2C5000D9CC7 /* model */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
90ABB9992C73A6A9000D9CC7 /* MockCallContainerModel.swift */,
|
||||
90ABB99C2C73C2D1000D9CC7 /* CallContainerModel.swift */,
|
||||
);
|
||||
path = model;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
90ABB99E2C73C3A9000D9CC7 /* components */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
90383A972D9D85E700D0DDA3 /* CameraButtonView.swift */,
|
||||
90383A922D9C35B300D0DDA3 /* ChatView.swift */,
|
||||
90ABB9922C73820D000D9CC7 /* MicrophoneView.swift */,
|
||||
90031FDB2C6D5DD700408370 /* ToastModifier.swift */,
|
||||
);
|
||||
path = components;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
90ABB9A42C74EA52000D9CC7 /* settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
90ABB9A52C74EA8A000D9CC7 /* SettingsPreference.swift */,
|
||||
90ABB9A72C74EAB1000D9CC7 /* SettingsManager.swift */,
|
||||
90ABB9A22C74E1CE000D9CC7 /* SettingsView.swift */,
|
||||
);
|
||||
path = settings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
90031FA22C616EE700408370 /* SimpleChatbot */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 90031FC72C616EE900408370 /* Build configuration list for PBXNativeTarget "SimpleChatbot" */;
|
||||
buildPhases = (
|
||||
90031F9F2C616EE700408370 /* Sources */,
|
||||
90031FA02C616EE700408370 /* Frameworks */,
|
||||
90031FA12C616EE700408370 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = SimpleChatbot;
|
||||
packageProductDependencies = (
|
||||
90383A902D9C357F00D0DDA3 /* PipecatClientIOSSmallWebrtc */,
|
||||
90383A9A2DA4620800D0DDA3 /* PipecatClientIOSSmallWebrtc */,
|
||||
);
|
||||
productName = SimpleChatbot;
|
||||
productReference = 90031FA32C616EE700408370 /* SimpleChatbot.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
90031FB22C616EE900408370 /* SimpleChatbotTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 90031FCA2C616EE900408370 /* Build configuration list for PBXNativeTarget "SimpleChatbotTests" */;
|
||||
buildPhases = (
|
||||
90031FAF2C616EE900408370 /* Sources */,
|
||||
90031FB02C616EE900408370 /* Frameworks */,
|
||||
90031FB12C616EE900408370 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
90031FB52C616EE900408370 /* PBXTargetDependency */,
|
||||
);
|
||||
name = SimpleChatbotTests;
|
||||
productName = SimpleChatbotTests;
|
||||
productReference = 90031FB32C616EE900408370 /* SimpleChatbotTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
90031FBC2C616EE900408370 /* SimpleChatbotUITests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 90031FCD2C616EE900408370 /* Build configuration list for PBXNativeTarget "SimpleChatbotUITests" */;
|
||||
buildPhases = (
|
||||
90031FB92C616EE900408370 /* Sources */,
|
||||
90031FBA2C616EE900408370 /* Frameworks */,
|
||||
90031FBB2C616EE900408370 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
90031FBF2C616EE900408370 /* PBXTargetDependency */,
|
||||
);
|
||||
name = SimpleChatbotUITests;
|
||||
productName = SimpleChatbotUITests;
|
||||
productReference = 90031FBD2C616EE900408370 /* SimpleChatbotUITests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.ui-testing";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
90031F9B2C616EE700408370 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1540;
|
||||
LastUpgradeCheck = 1540;
|
||||
TargetAttributes = {
|
||||
90031FA22C616EE700408370 = {
|
||||
CreatedOnToolsVersion = 15.4;
|
||||
};
|
||||
90031FB22C616EE900408370 = {
|
||||
CreatedOnToolsVersion = 15.4;
|
||||
TestTargetID = 90031FA22C616EE700408370;
|
||||
};
|
||||
90031FBC2C616EE900408370 = {
|
||||
CreatedOnToolsVersion = 15.4;
|
||||
TestTargetID = 90031FA22C616EE700408370;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 90031F9E2C616EE700408370 /* Build configuration list for PBXProject "SimpleChatbot" */;
|
||||
compatibilityVersion = "Xcode 14.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 90031F9A2C616EE700408370;
|
||||
packageReferences = (
|
||||
90383A992DA4620800D0DDA3 /* XCRemoteSwiftPackageReference "pipecat-client-ios-small-webrtc" */,
|
||||
);
|
||||
productRefGroup = 90031FA42C616EE700408370 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
90031FA22C616EE700408370 /* SimpleChatbot */,
|
||||
90031FB22C616EE900408370 /* SimpleChatbotTests */,
|
||||
90031FBC2C616EE900408370 /* SimpleChatbotUITests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
90031FA12C616EE700408370 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
90031FAE2C616EE800408370 /* Preview Assets.xcassets in Resources */,
|
||||
90031FAB2C616EE800408370 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
90031FB12C616EE900408370 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
90031FBB2C616EE900408370 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
90031F9F2C616EE700408370 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
90ABB99D2C73C2D1000D9CC7 /* CallContainerModel.swift in Sources */,
|
||||
90ABB9A62C74EA8A000D9CC7 /* SettingsPreference.swift in Sources */,
|
||||
90383A962D9C35BD00D0DDA3 /* LiveMessage.swift in Sources */,
|
||||
90ABB99A2C73A6A9000D9CC7 /* MockCallContainerModel.swift in Sources */,
|
||||
90031FA92C616EE700408370 /* PreJoinView.swift in Sources */,
|
||||
90383A982D9D85E700D0DDA3 /* CameraButtonView.swift in Sources */,
|
||||
90383A932D9C35B300D0DDA3 /* ChatView.swift in Sources */,
|
||||
90ABB9982C738356000D9CC7 /* CustomColors.swift in Sources */,
|
||||
90ABB98E2C735ED6000D9CC7 /* MeetingView.swift in Sources */,
|
||||
90ABB9A32C74E1CE000D9CC7 /* SettingsView.swift in Sources */,
|
||||
90ABB9932C73820D000D9CC7 /* MicrophoneView.swift in Sources */,
|
||||
90ABB9A82C74EAB1000D9CC7 /* SettingsManager.swift in Sources */,
|
||||
90031FDC2C6D5DD700408370 /* ToastModifier.swift in Sources */,
|
||||
90031FA72C616EE700408370 /* SimpleChatbotApp.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
90031FAF2C616EE900408370 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
90031FB82C616EE900408370 /* SimpleChatbotTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
90031FB92C616EE900408370 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
90031FC42C616EE900408370 /* SimpleChatbotUITestsLaunchTests.swift in Sources */,
|
||||
90031FC22C616EE900408370 /* SimpleChatbotUITests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
90031FB52C616EE900408370 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 90031FA22C616EE700408370 /* SimpleChatbot */;
|
||||
targetProxy = 90031FB42C616EE900408370 /* PBXContainerItemProxy */;
|
||||
};
|
||||
90031FBF2C616EE900408370 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 90031FA22C616EE700408370 /* SimpleChatbot */;
|
||||
targetProxy = 90031FBE2C616EE900408370 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
90031FC52C616EE900408370 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.5;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
90031FC62C616EE900408370 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.5;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
90031FC82C616EE900408370 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"SimpleChatbot/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = EEBGKV9N3N;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = SimpleChatbot/Info.plist;
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = co.daily.SimpleChatbot;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
90031FC92C616EE900408370 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"SimpleChatbot/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = EEBGKV9N3N;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = SimpleChatbot/Info.plist;
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = co.daily.SimpleChatbot;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
90031FCB2C616EE900408370 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = EEBGKV9N3N;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.5;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = co.daily.SimpleChatbotTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SimpleChatbot.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/SimpleChatbot";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
90031FCC2C616EE900408370 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = EEBGKV9N3N;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.5;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = co.daily.SimpleChatbotTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SimpleChatbot.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/SimpleChatbot";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
90031FCE2C616EE900408370 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = EEBGKV9N3N;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = co.daily.SimpleChatbotUITests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_TARGET_NAME = SimpleChatbot;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
90031FCF2C616EE900408370 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = EEBGKV9N3N;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = co.daily.SimpleChatbotUITests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_TARGET_NAME = SimpleChatbot;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
90031F9E2C616EE700408370 /* Build configuration list for PBXProject "SimpleChatbot" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
90031FC52C616EE900408370 /* Debug */,
|
||||
90031FC62C616EE900408370 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
90031FC72C616EE900408370 /* Build configuration list for PBXNativeTarget "SimpleChatbot" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
90031FC82C616EE900408370 /* Debug */,
|
||||
90031FC92C616EE900408370 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
90031FCA2C616EE900408370 /* Build configuration list for PBXNativeTarget "SimpleChatbotTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
90031FCB2C616EE900408370 /* Debug */,
|
||||
90031FCC2C616EE900408370 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
90031FCD2C616EE900408370 /* Build configuration list for PBXNativeTarget "SimpleChatbotUITests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
90031FCE2C616EE900408370 /* Debug */,
|
||||
90031FCF2C616EE900408370 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
90383A992DA4620800D0DDA3 /* XCRemoteSwiftPackageReference "pipecat-client-ios-small-webrtc" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/pipecat-ai/pipecat-client-ios-small-webrtc";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.0.1;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
90383A902D9C357F00D0DDA3 /* PipecatClientIOSSmallWebrtc */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = PipecatClientIOSSmallWebrtc;
|
||||
};
|
||||
90383A9A2DA4620800D0DDA3 /* PipecatClientIOSSmallWebrtc */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 90383A992DA4620800D0DDA3 /* XCRemoteSwiftPackageReference "pipecat-client-ios-small-webrtc" */;
|
||||
productName = PipecatClientIOSSmallWebrtc;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 90031F9B2C616EE700408370 /* Project object */;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"originHash" : "77cb3fee4071811f880e69dbcd5a8ba01711a73372960391d6366c4c3a0d36eb",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "pipecat-client-ios",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pipecat-ai/pipecat-client-ios.git",
|
||||
"state" : {
|
||||
"revision" : "992641fb5f7d1a794ecfc33babb5fe36e2a8ffdd",
|
||||
"version" : "0.3.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "pipecat-client-ios-small-webrtc",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pipecat-ai/pipecat-client-ios-small-webrtc",
|
||||
"state" : {
|
||||
"revision" : "a6e4516b1fcbed772ca97a9616dddc9329097958",
|
||||
"version" : "0.0.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "webrtc",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/stasel/WebRTC",
|
||||
"state" : {
|
||||
"revision" : "5b2eb61cace7d62726b29a38b768b07d6bc55c45",
|
||||
"version" : "134.0.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1540"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "90031FA22C616EE700408370"
|
||||
BuildableName = "SimpleChatbot.app"
|
||||
BlueprintName = "SimpleChatbot"
|
||||
ReferencedContainer = "container:SimpleChatbot.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "90031FA22C616EE700408370"
|
||||
BuildableName = "SimpleChatbot.app"
|
||||
BlueprintName = "SimpleChatbot"
|
||||
ReferencedContainer = "container:SimpleChatbot.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "90031FA22C616EE700408370"
|
||||
BuildableName = "SimpleChatbot.app"
|
||||
BlueprintName = "SimpleChatbot"
|
||||
ReferencedContainer = "container:SimpleChatbot.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "appstore.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 29 KiB |
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Square Black.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
<svg width="450" height="450" viewBox="0 0 450 450" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="450" height="450" fill="white"/>
|
||||
<path d="M104.772 129.77C109.448 128.01 114.725 129.331 118.02 133.086L160.936 182H289.064L331.98 133.086C335.275 129.331 340.552 128.01 345.228 129.77C349.904 131.531 353 136.004 353 141V249H391V273H329V172.873L303.52 201.915C301.242 204.511 297.955 206 294.5 206H155.5C152.045 206 148.758 204.511 146.48 201.915L121 172.873V273H59V249H97V141C97 136.004 100.096 131.531 104.772 129.77Z" fill="black"/>
|
||||
<path d="M329 297H391V321H329V297Z" fill="black"/>
|
||||
<path d="M59 297H121V321H59V297Z" fill="black"/>
|
||||
<path d="M187 257C187 265.837 179.837 273 171 273C162.163 273 155 265.837 155 257C155 248.164 162.163 241 171 241C179.837 241 187 248.164 187 257Z" fill="black"/>
|
||||
<path d="M295 257C295 265.837 287.837 273 279 273C270.163 273 263 265.837 263 257C263 248.164 270.163 241 279 241C287.837 241 295 248.164 295 257Z" fill="black"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 982 B |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "vision.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 9.33333V6.66667C4 5.95942 4.28095 5.28115 4.78105 4.78105C5.28115 4.28095 5.95942 4 6.66667 4H9.33333" stroke="#15803D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M22.6667 4H25.3334C26.0407 4 26.7189 4.28095 27.219 4.78105C27.7191 5.28115 28.0001 5.95942 28.0001 6.66667V9.33333" stroke="#15803D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M28.0001 22.6667V25.3333C28.0001 26.0406 27.7191 26.7188 27.219 27.2189C26.7189 27.719 26.0407 28 25.3334 28H22.6667" stroke="#15803D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.33333 28H6.66667C5.95942 28 5.28115 27.719 4.78105 27.2189C4.28095 26.7188 4 26.0406 4 25.3333V22.6667" stroke="#15803D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M16.0001 18.2857C17.2624 18.2857 18.2858 17.2624 18.2858 16C18.2858 14.7376 17.2624 13.7143 16.0001 13.7143C14.7377 13.7143 13.7144 14.7376 13.7144 16C13.7144 17.2624 14.7377 18.2857 16.0001 18.2857Z" stroke="#15803D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M25.2588 16.44C25.3584 16.1551 25.3584 15.8449 25.2588 15.56C24.5081 13.7206 23.2265 12.1464 21.5775 11.0383C19.9285 9.93028 17.9868 9.3385 16.0001 9.3385C14.0134 9.3385 12.0717 9.93028 10.4227 11.0383C8.7737 12.1464 7.49212 13.7206 6.74144 15.56C6.64185 15.8449 6.64185 16.1551 6.74144 16.44C7.49212 18.2794 8.7737 19.8536 10.4227 20.9616C12.0717 22.0697 14.0134 22.6615 16.0001 22.6615C17.9868 22.6615 19.9285 22.0697 21.5775 20.9616C23.2265 19.8536 24.5081 18.2794 25.2588 16.44Z" stroke="#15803D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>voip</string>
|
||||
</array>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Camera is necessary for transmitting video in a call</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Microphone is necessary for transmitting audio in a call</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct SimpleChatbotApp: App {
|
||||
|
||||
@StateObject var callContainerModel = CallContainerModel()
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
if (!callContainerModel.isInCall) {
|
||||
PreJoinView().environmentObject(callContainerModel)
|
||||
} else {
|
||||
MeetingView().environmentObject(callContainerModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,284 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
import PipecatClientIOSSmallWebrtc
|
||||
import PipecatClientIOS
|
||||
|
||||
class CallContainerModel: ObservableObject {
|
||||
|
||||
@Published var voiceClientStatus: String = TransportState.disconnected.description
|
||||
@Published var isInCall: Bool = false
|
||||
@Published var isBotReady: Bool = false
|
||||
|
||||
@Published var isMicEnabled: Bool = false
|
||||
@Published var isCamEnabled: Bool = false
|
||||
@Published var localCamId: MediaTrackId? = nil
|
||||
@Published var botCamId: MediaTrackId? = nil
|
||||
|
||||
@Published var toastMessage: String? = nil
|
||||
@Published var showToast: Bool = false
|
||||
|
||||
@Published var messages: [LiveMessage] = []
|
||||
@Published var liveBotMessage: LiveMessage?
|
||||
@Published var liveUserMessage: LiveMessage?
|
||||
|
||||
var rtviClientIOS: RTVIClient?
|
||||
|
||||
@Published var selectedMic: MediaDeviceId? = nil {
|
||||
didSet {
|
||||
guard let selectedMic else { return } // don't store nil
|
||||
var settings = SettingsManager.getSettings()
|
||||
settings.selectedMic = selectedMic.id
|
||||
SettingsManager.updateSettings(settings: settings)
|
||||
}
|
||||
}
|
||||
@Published var availableMics: [MediaDeviceInfo] = []
|
||||
|
||||
init() {
|
||||
// Changing the log level
|
||||
PipecatClientIOS.setLogLevel(.warn)
|
||||
PipecatClientIOSSmallWebrtc.setLogLevel(.info)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func connect(backendURL: String) {
|
||||
self.resetLiveMessages()
|
||||
|
||||
let baseUrl = backendURL.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if(baseUrl.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty){
|
||||
self.showError(message: "Need to fill the backendURL")
|
||||
return
|
||||
}
|
||||
|
||||
let currentSettings = SettingsManager.getSettings()
|
||||
let rtviClientOptions = RTVIClientOptions.init(
|
||||
enableMic: currentSettings.enableMic,
|
||||
enableCam: currentSettings.enableCam,
|
||||
params: RTVIClientParams(
|
||||
config: [
|
||||
.init(
|
||||
service: SmallWebRTCTransport.SERVICE_NAME,
|
||||
options: [
|
||||
.init(name: "server_url", value: .string(baseUrl))
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
self.rtviClientIOS = RTVIClient.init(
|
||||
transport: SmallWebRTCTransport.init(options: rtviClientOptions),
|
||||
options: rtviClientOptions
|
||||
)
|
||||
self.rtviClientIOS?.delegate = self
|
||||
|
||||
// Registering the llm helper, we will need this to handle the function calling
|
||||
let llmHelper = try? self.rtviClientIOS?.registerHelper(service: "llm", helper: LLMHelper.self)
|
||||
llmHelper?.delegate = self
|
||||
|
||||
self.rtviClientIOS?.start() { result in
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
self.showError(message: error.localizedDescription)
|
||||
self.rtviClientIOS = nil
|
||||
case .success():
|
||||
// Apply initial mic preference
|
||||
if let selectedMic = SettingsManager.getSettings().selectedMic {
|
||||
self.selectMic(MediaDeviceId(id: selectedMic))
|
||||
}
|
||||
// Populate available devices list
|
||||
self.availableMics = self.rtviClientIOS?.getAllMics() ?? []
|
||||
}
|
||||
}
|
||||
self.saveCredentials(backendURL: backendURL)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func disconnect() {
|
||||
self.rtviClientIOS?.disconnect(completion: nil)
|
||||
self.rtviClientIOS?.release()
|
||||
self.rtviClientIOS = nil
|
||||
}
|
||||
|
||||
func showError(message: String) {
|
||||
self.toastMessage = message
|
||||
self.showToast = true
|
||||
// Hide the toast after 5 seconds
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
|
||||
self.showToast = false
|
||||
self.toastMessage = nil
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func toggleMicInput() {
|
||||
self.rtviClientIOS?.enableMic(enable: !self.isMicEnabled) { result in
|
||||
switch result {
|
||||
case .success():
|
||||
self.isMicEnabled = self.rtviClientIOS?.isMicEnabled ?? false
|
||||
case .failure(let error):
|
||||
self.showError(message: error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func toggleCamInput() {
|
||||
self.rtviClientIOS?.enableCam(enable: !self.isCamEnabled) { result in
|
||||
switch result {
|
||||
case .success():
|
||||
self.isCamEnabled = self.rtviClientIOS?.isCamEnabled ?? false
|
||||
case .failure(let error):
|
||||
self.showError(message: error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func saveCredentials(backendURL: String) {
|
||||
var currentSettings = SettingsManager.getSettings()
|
||||
currentSettings.backendURL = backendURL
|
||||
// Saving the settings
|
||||
SettingsManager.updateSettings(settings: currentSettings)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func selectMic(_ mic: MediaDeviceId) {
|
||||
self.selectedMic = mic
|
||||
self.rtviClientIOS?.updateMic(micId: mic, completion: nil)
|
||||
}
|
||||
|
||||
private func createLiveMessage(content:String = "", type:MessageType) {
|
||||
// Creating a new one
|
||||
DispatchQueue.main.async {
|
||||
let liveMessage = LiveMessage(content: content, type: type, updatedAt: Date())
|
||||
self.messages.append(liveMessage)
|
||||
if type == .bot {
|
||||
self.liveBotMessage = liveMessage
|
||||
} else if type == .user {
|
||||
self.liveUserMessage = liveMessage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func appendTextToLiveMessage(fromBot: Bool, content:String) {
|
||||
DispatchQueue.main.async {
|
||||
// Updating the last message with the new content
|
||||
if fromBot {
|
||||
self.liveBotMessage?.content += content
|
||||
} else {
|
||||
self.liveUserMessage?.content += content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func resetLiveMessages() {
|
||||
DispatchQueue.main.async {
|
||||
self.messages = []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CallContainerModel:RTVIClientDelegate, LLMHelperDelegate {
|
||||
|
||||
private func handleEvent(eventName: String, eventValue: Any? = nil) {
|
||||
if let value = eventValue {
|
||||
print("Pipecat Demo, received event: \(eventName), value:\(value)")
|
||||
} else {
|
||||
print("Pipecat Demo, received event: \(eventName)")
|
||||
}
|
||||
}
|
||||
|
||||
func onTransportStateChanged(state: TransportState) {
|
||||
Task { @MainActor in
|
||||
self.handleEvent(eventName: "onTransportStateChanged", eventValue: state)
|
||||
self.voiceClientStatus = state.description
|
||||
self.isInCall = ( state == .connecting || state == .connected || state == .ready || state == .authenticating )
|
||||
self.createLiveMessage(content: state.description, type: .system)
|
||||
}
|
||||
}
|
||||
|
||||
func onBotReady(botReadyData: BotReadyData) {
|
||||
Task { @MainActor in
|
||||
self.handleEvent(eventName: "onBotReady")
|
||||
self.isBotReady = true
|
||||
}
|
||||
}
|
||||
|
||||
func onConnected() {
|
||||
Task { @MainActor in
|
||||
self.handleEvent(eventName: "onConnected")
|
||||
self.isMicEnabled = self.rtviClientIOS?.isMicEnabled ?? false
|
||||
self.isCamEnabled = self.rtviClientIOS?.isCamEnabled ?? false
|
||||
}
|
||||
}
|
||||
|
||||
func onDisconnected() {
|
||||
Task { @MainActor in
|
||||
self.handleEvent(eventName: "onDisconnected")
|
||||
self.isBotReady = false
|
||||
}
|
||||
}
|
||||
|
||||
func onError(message: String) {
|
||||
Task { @MainActor in
|
||||
self.handleEvent(eventName: "onError", eventValue: message)
|
||||
self.showError(message: message)
|
||||
}
|
||||
}
|
||||
|
||||
func onAvailableMicsUpdated(mics: [MediaDeviceInfo]) {
|
||||
Task { @MainActor in
|
||||
self.availableMics = mics
|
||||
}
|
||||
}
|
||||
|
||||
func onMicUpdated(mic: MediaDeviceInfo?) {
|
||||
Task { @MainActor in
|
||||
self.selectedMic = mic?.id
|
||||
}
|
||||
}
|
||||
|
||||
func onBotTranscript(data: String) {
|
||||
self.handleEvent(eventName: "onBotTranscript", eventValue: data)
|
||||
}
|
||||
|
||||
func onTracksUpdated(tracks: Tracks) {
|
||||
self.handleEvent(eventName: "onTracksUpdated", eventValue: tracks)
|
||||
Task { @MainActor in
|
||||
self.localCamId = tracks.local.video
|
||||
self.botCamId = tracks.bot?.video ?? nil
|
||||
}
|
||||
}
|
||||
|
||||
func onUserStartedSpeaking() {
|
||||
self.createLiveMessage(content: "User started speaking", type: .system)
|
||||
self.handleEvent(eventName: "onUserStartedSpeaking")
|
||||
self.createLiveMessage(type: .user)
|
||||
}
|
||||
|
||||
func onUserStoppedSpeaking() {
|
||||
self.createLiveMessage(content: "User stopped speaking", type: .system)
|
||||
self.handleEvent(eventName: "onUserStoppedSpeaking")
|
||||
}
|
||||
|
||||
func onBotStartedSpeaking() {
|
||||
self.createLiveMessage(content: "Bot started speaking", type: .system)
|
||||
self.handleEvent(eventName: "onBotStartedSpeaking")
|
||||
self.createLiveMessage(type: .bot)
|
||||
}
|
||||
|
||||
func onBotStoppedSpeaking() {
|
||||
self.createLiveMessage(content: "Bot stopped speaking", type: .system)
|
||||
self.handleEvent(eventName: "onBotStoppedSpeaking")
|
||||
}
|
||||
|
||||
func onUserTranscript(data: Transcript) {
|
||||
if data.final ?? false {
|
||||
self.handleEvent(eventName: "onUserTranscript", eventValue: data.text)
|
||||
self.appendTextToLiveMessage(fromBot: false, content: data.text)
|
||||
}
|
||||
}
|
||||
|
||||
func onBotTTSText(data: BotTTSText) {
|
||||
self.appendTextToLiveMessage(fromBot: true, content: data.text)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import SwiftUI
|
||||
import PipecatClientIOS
|
||||
|
||||
class MockCallContainerModel: CallContainerModel {
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
let liveMessageFromSystem = LiveMessage(
|
||||
content: "System message",
|
||||
type: .system,
|
||||
updatedAt: Date()
|
||||
)
|
||||
let liveMessageFromUser = LiveMessage(
|
||||
content: "Message from User",
|
||||
type: .user,
|
||||
updatedAt: Date()
|
||||
)
|
||||
let liveMessageFromBot = LiveMessage(
|
||||
content: "Message from bot",
|
||||
type: .bot,
|
||||
updatedAt: Date()
|
||||
)
|
||||
self.messages = [ liveMessageFromSystem, liveMessageFromUser, liveMessageFromBot ]
|
||||
}
|
||||
|
||||
override func connect(backendURL: String) {
|
||||
print("connect")
|
||||
}
|
||||
|
||||
override func disconnect() {
|
||||
print("disconnect")
|
||||
}
|
||||
|
||||
override func showError(message: String) {
|
||||
self.toastMessage = message
|
||||
self.showToast = true
|
||||
// Hide the toast after 5 seconds
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
|
||||
self.showToast = false
|
||||
self.toastMessage = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
enum MessageType {
|
||||
case bot, user, system
|
||||
}
|
||||
|
||||
class LiveMessage: ObservableObject, Identifiable, Equatable {
|
||||
@Published var content: String
|
||||
let type: MessageType
|
||||
let updatedAt: Date
|
||||
|
||||
init(content: String, type: MessageType, updatedAt: Date) {
|
||||
self.content = content
|
||||
self.type = type
|
||||
self.updatedAt = updatedAt
|
||||
}
|
||||
|
||||
static func == (lhs: LiveMessage, rhs: LiveMessage) -> Bool {
|
||||
lhs.updatedAt == rhs.updatedAt
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
import SwiftUI
|
||||
import PipecatClientIOSSmallWebrtc
|
||||
|
||||
struct MeetingView: View {
|
||||
|
||||
@State private var showingSettings = false
|
||||
@EnvironmentObject private var model: CallContainerModel
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
ZStack {
|
||||
SmallWebRTCVideoViewSwiftUI(videoTrack: self.model.botCamId, videoScaleMode: .fill)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
|
||||
VStack {
|
||||
ChatView()
|
||||
.frame(maxHeight: .infinity)
|
||||
|
||||
HStack {
|
||||
MicrophoneView(audioLevel: 0, isMuted: !self.model.isMicEnabled)
|
||||
.frame(width: 100, height: 100)
|
||||
.onTapGesture {
|
||||
self.model.toggleMicInput()
|
||||
}
|
||||
CameraButtonView(trackId: self.model.localCamId, isMuted: !self.model.isCamEnabled)
|
||||
.frame(width: 120, height: 120)
|
||||
.onTapGesture {
|
||||
self.model.toggleCamInput()
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
Button(action: {
|
||||
self.showingSettings = true
|
||||
}) {
|
||||
HStack {
|
||||
Image(systemName: "gearshape")
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
Text("Settings")
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.sheet(isPresented: $showingSettings) {
|
||||
SettingsView(showingSettings: $showingSettings).environmentObject(self.model)
|
||||
}
|
||||
}
|
||||
.foregroundColor(.black)
|
||||
.background(Color.white)
|
||||
.border(Color.buttonsBorder, width: 1)
|
||||
.cornerRadius(12)
|
||||
.padding([.horizontal])
|
||||
|
||||
Button(action: {
|
||||
self.model.disconnect()
|
||||
}) {
|
||||
HStack {
|
||||
Image(systemName: "rectangle.portrait.and.arrow.right")
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
Text("End")
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
}
|
||||
.foregroundColor(.white)
|
||||
.background(Color.black)
|
||||
.cornerRadius(12)
|
||||
.padding([.bottom, .horizontal])
|
||||
}
|
||||
.background(Color.backgroundApp)
|
||||
.toast(message: model.toastMessage, isShowing: model.showToast)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
let mockModel = MockCallContainerModel()
|
||||
let result = MeetingView().environmentObject(mockModel as CallContainerModel)
|
||||
return result
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct PreJoinView: View {
|
||||
|
||||
@State var backendURL: String
|
||||
|
||||
@EnvironmentObject private var model: CallContainerModel
|
||||
|
||||
init() {
|
||||
let currentSettings = SettingsManager.getSettings()
|
||||
self.backendURL = currentSettings.backendURL
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 20) {
|
||||
Image("pipecat")
|
||||
.resizable()
|
||||
.frame(width: 80, height: 80)
|
||||
Text("Pipecat Client iOS.")
|
||||
.font(.headline)
|
||||
TextField("Server URL", text: $backendURL)
|
||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding([.bottom, .horizontal])
|
||||
Button("Connect") {
|
||||
Task {
|
||||
await self.model.connect(backendURL: self.backendURL)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(Color.black)
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(8)
|
||||
}
|
||||
.padding()
|
||||
.frame(maxHeight: .infinity)
|
||||
.background(Color.backgroundApp)
|
||||
.toast(message: model.toastMessage, isShowing: model.showToast)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
PreJoinView().environmentObject(MockCallContainerModel() as CallContainerModel)
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import SwiftUI
|
||||
import PipecatClientIOS
|
||||
import PipecatClientIOSSmallWebrtc
|
||||
|
||||
struct CameraButtonView: View {
|
||||
var trackId: MediaTrackId?
|
||||
var isMuted: Bool
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
let width = geometry.size.width
|
||||
let circleSize = width * 0.9
|
||||
let innerCircleSize = width * 0.82
|
||||
|
||||
ZStack {
|
||||
Circle()
|
||||
.stroke(Color.gray, lineWidth: 1)
|
||||
.frame(width: circleSize)
|
||||
|
||||
if (!isMuted){
|
||||
SmallWebRTCVideoViewSwiftUI(videoTrack: trackId, videoScaleMode: .fill)
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.clipShape(Circle())
|
||||
} else {
|
||||
Circle()
|
||||
.fill(Color.disabledVision)
|
||||
.frame(width: innerCircleSize)
|
||||
Image("vision")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: width * 0.3)
|
||||
.foregroundColor(.green)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity) // Ensures the ZStack is centered
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
CameraButtonView(trackId: nil, isMuted: true)
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ChatView: View {
|
||||
@EnvironmentObject private var model: CallContainerModel
|
||||
@State private var timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
ScrollViewReader { scrollViewProxy in
|
||||
ScrollView {
|
||||
VStack(spacing: 10) {
|
||||
ForEach(self.model.messages) { message in
|
||||
MessageView(message: message)
|
||||
.frame(maxWidth: .infinity, alignment: messageAlignment(for: message.type))
|
||||
.padding(.horizontal)
|
||||
.id(message.id)
|
||||
}
|
||||
}
|
||||
.onChange(of: self.model.messages) { _, _ in
|
||||
scrollToLastMessage(scrollViewProxy)
|
||||
}
|
||||
}
|
||||
.onReceive(timer) { _ in
|
||||
scrollToLastMessage(scrollViewProxy)
|
||||
}
|
||||
.onAppear {
|
||||
scrollToLastMessage(scrollViewProxy)
|
||||
}
|
||||
}
|
||||
}
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
}
|
||||
|
||||
private func messageAlignment(for type: MessageType) -> Alignment {
|
||||
switch type {
|
||||
case .bot: return .leading
|
||||
case .user: return .trailing
|
||||
case .system: return .center
|
||||
}
|
||||
}
|
||||
|
||||
private func scrollToLastMessage(_ scrollViewProxy: ScrollViewProxy) {
|
||||
if let lastMessageId = self.model.messages.last?.id {
|
||||
withAnimation {
|
||||
scrollViewProxy.scrollTo(lastMessageId, anchor: .bottom)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MessageView: View {
|
||||
@ObservedObject var message: LiveMessage
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
if message.type == .bot {
|
||||
Image(systemName: "gearshape")
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
}
|
||||
|
||||
Text(message.content)
|
||||
.padding(message.type == .system ? 5 : 10)
|
||||
.foregroundColor(.white)
|
||||
.background(messageBackgroundColor(for: message.type))
|
||||
.cornerRadius(15)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 15)
|
||||
.stroke(Color.gray.opacity(0.5), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
.padding(messagePadding(for: message.type))
|
||||
}
|
||||
|
||||
private func messageBackgroundColor(for type: MessageType) -> Color {
|
||||
switch type {
|
||||
case .bot: return .black
|
||||
case .user: return .gray
|
||||
case .system: return .blue.opacity(0.6)
|
||||
}
|
||||
}
|
||||
|
||||
private func messagePadding(for type: MessageType) -> EdgeInsets {
|
||||
switch type {
|
||||
case .bot: return EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 40)
|
||||
case .user: return EdgeInsets(top: 0, leading: 40, bottom: 0, trailing: 0)
|
||||
case .system: return EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
let mockModel = MockCallContainerModel()
|
||||
let result = ChatView().environmentObject(mockModel as CallContainerModel)
|
||||
return result
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct MicrophoneView: View {
|
||||
var audioLevel: Float // Current audio level
|
||||
var isMuted: Bool // Muted state
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
let width = geometry.size.width
|
||||
let circleSize = width * 0.9
|
||||
let innerCircleSize = width * 0.82
|
||||
let audioCircleSize = CGFloat(audioLevel) * (width * 0.95)
|
||||
|
||||
ZStack {
|
||||
Circle()
|
||||
.stroke(Color.gray, lineWidth: 1)
|
||||
.frame(width: circleSize)
|
||||
|
||||
Circle()
|
||||
.fill(isMuted ? Color.disabledMic : Color.backgroundCircle)
|
||||
.frame(width: innerCircleSize)
|
||||
|
||||
if !isMuted {
|
||||
Circle()
|
||||
.fill(Color.micVolume)
|
||||
.opacity(0.5)
|
||||
.frame(width: audioCircleSize)
|
||||
.animation(.easeInOut(duration: 0.2), value: audioLevel)
|
||||
}
|
||||
|
||||
Image(systemName: isMuted ? "mic.slash.fill" : "mic.fill")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: width * 0.2)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity) // Ensures the ZStack is centered
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
MicrophoneView(audioLevel: 1, isMuted: false)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ToastModifier: ViewModifier {
|
||||
var message: String?
|
||||
var isShowing: Bool
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
ZStack {
|
||||
content
|
||||
if isShowing, let message = message {
|
||||
VStack {
|
||||
Text(message)
|
||||
.padding()
|
||||
.background(Color.black.opacity(0.7))
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(8)
|
||||
.transition(.slide)
|
||||
.padding(.top, 50)
|
||||
Spacer()
|
||||
}
|
||||
.animation(.easeInOut(duration: 0.5), value: isShowing)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
func toast(message: String?, isShowing: Bool) -> some View {
|
||||
self.modifier(ToastModifier(message: message, isShowing: isShowing))
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
public extension Color {
|
||||
|
||||
static let backgroundCircle = Color(hex: "#374151")
|
||||
static let backgroundCircleNotConnected = Color(hex: "#D1D5DB")
|
||||
static let backgroundApp = Color(hex: "#F9FAFB")
|
||||
static let buttonsBorder = Color(hex: "#E5E7EB")
|
||||
static let micVolume = Color(hex: "#86EFAC")
|
||||
static let disabledMic = Color(hex: "#ee6b6e")
|
||||
static let disabledVision = Color(hex: "#BBF7D0")
|
||||
|
||||
init(hex: String) {
|
||||
let scanner = Scanner(string: hex)
|
||||
_ = scanner.scanString("#")
|
||||
|
||||
var rgb: UInt64 = 0
|
||||
scanner.scanHexInt64(&rgb)
|
||||
|
||||
let red = Double((rgb >> 16) & 0xFF) / 255.0
|
||||
let green = Double((rgb >> 8) & 0xFF) / 255.0
|
||||
let blue = Double(rgb & 0xFF) / 255.0
|
||||
|
||||
self.init(red: red, green: green, blue: blue)
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
class SettingsManager {
|
||||
private static let preferencesKey = "settingsPreference"
|
||||
|
||||
static func getSettings() -> SettingsPreference {
|
||||
if let data = UserDefaults.standard.data(forKey: preferencesKey),
|
||||
let settings = try? JSONDecoder().decode(SettingsPreference.self, from: data) {
|
||||
return settings
|
||||
} else {
|
||||
// default values in case we don't have any settings
|
||||
return SettingsPreference(enableMic: true, enableCam: true, backendURL: "http://YOUR_IP:7860")
|
||||
}
|
||||
}
|
||||
|
||||
static func updateSettings(settings: SettingsPreference) {
|
||||
if let data = try? JSONEncoder().encode(settings) {
|
||||
UserDefaults.standard.set(data, forKey: preferencesKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
struct SettingsPreference: Codable {
|
||||
var selectedMic: String?
|
||||
var enableMic: Bool
|
||||
var enableCam: Bool
|
||||
var backendURL: String
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsView: View {
|
||||
|
||||
@EnvironmentObject private var model: CallContainerModel
|
||||
|
||||
@Binding var showingSettings: Bool
|
||||
|
||||
@State private var isMicEnabled: Bool = true
|
||||
@State private var isCamEnabled: Bool = true
|
||||
@State private var backendURL: String = ""
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Form {
|
||||
Section {
|
||||
List(model.availableMics, id: \.self.id.id) { mic in
|
||||
Button(action: {
|
||||
model.selectMic(mic.id)
|
||||
}) {
|
||||
HStack {
|
||||
Text(mic.name)
|
||||
Spacer()
|
||||
if mic.id == model.selectedMic {
|
||||
Image(systemName: "checkmark")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Audio Settings")
|
||||
Text("(No selection = system default)")
|
||||
}
|
||||
}
|
||||
Section(header: Text("Start options")) {
|
||||
Toggle("Enable Microphone", isOn: $isMicEnabled)
|
||||
Toggle("Enable Cam", isOn: $isCamEnabled)
|
||||
}
|
||||
Section(header: Text("Server")) {
|
||||
TextField("Backend URL", text: $backendURL)
|
||||
.keyboardType(.URL)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Settings")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("Close") {
|
||||
self.saveSettings()
|
||||
self.showingSettings = false
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
self.loadSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func saveSettings() {
|
||||
let newSettings = SettingsPreference(
|
||||
selectedMic: model.selectedMic?.id,
|
||||
enableMic: isMicEnabled,
|
||||
enableCam: isCamEnabled,
|
||||
backendURL: backendURL
|
||||
)
|
||||
SettingsManager.updateSettings(settings: newSettings)
|
||||
}
|
||||
|
||||
private func loadSettings() {
|
||||
let savedSettings = SettingsManager.getSettings()
|
||||
self.isMicEnabled = savedSettings.enableMic
|
||||
self.isCamEnabled = savedSettings.enableCam
|
||||
self.backendURL = savedSettings.backendURL
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
let mockModel = MockCallContainerModel()
|
||||
let result = SettingsView(showingSettings: .constant(true)).environmentObject(mockModel as CallContainerModel)
|
||||
return result
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import XCTest
|
||||
@testable import SimpleChatbot
|
||||
|
||||
final class SimpleChatbotTests: XCTestCase {
|
||||
|
||||
override func setUpWithError() throws {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
func testExample() throws {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||
// Any test you write for XCTest can be annotated as throws and async.
|
||||
// Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
|
||||
// Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
|
||||
}
|
||||
|
||||
func testPerformanceExample() throws {
|
||||
// This is an example of a performance test case.
|
||||
self.measure {
|
||||
// Put the code you want to measure the time of here.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import XCTest
|
||||
|
||||
final class SimpleChatbotUITests: XCTestCase {
|
||||
|
||||
override func setUpWithError() throws {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
|
||||
// In UI tests it is usually best to stop immediately when a failure occurs.
|
||||
continueAfterFailure = false
|
||||
|
||||
// In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
func testExample() throws {
|
||||
// UI tests must launch the application that they test.
|
||||
let app = XCUIApplication()
|
||||
app.launch()
|
||||
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||
}
|
||||
|
||||
func testLaunchPerformance() throws {
|
||||
if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
|
||||
// This measures how long it takes to launch your application.
|
||||
measure(metrics: [XCTApplicationLaunchMetric()]) {
|
||||
XCUIApplication().launch()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import XCTest
|
||||
|
||||
final class SimpleChatbotUITestsLaunchTests: XCTestCase {
|
||||
|
||||
override class var runsForEachTargetApplicationUIConfiguration: Bool {
|
||||
true
|
||||
}
|
||||
|
||||
override func setUpWithError() throws {
|
||||
continueAfterFailure = false
|
||||
}
|
||||
|
||||
func testLaunch() throws {
|
||||
let app = XCUIApplication()
|
||||
app.launch()
|
||||
|
||||
// Insert steps here to perform after app launch but before taking a screenshot,
|
||||
// such as logging into a test account or navigating somewhere in the app
|
||||
|
||||
let attachment = XCTAttachment(screenshot: app.screenshot())
|
||||
attachment.name = "Launch Screen"
|
||||
attachment.lifetime = .keepAlways
|
||||
add(attachment)
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,16 @@
|
||||
import { type PropsWithChildren } from "react";
|
||||
import { RTVIClient } from "@pipecat-ai/client-js";
|
||||
import { DailyTransport } from "@pipecat-ai/daily-transport";
|
||||
import { RTVIClientProvider } from "@pipecat-ai/client-react";
|
||||
import { type PropsWithChildren } from 'react';
|
||||
import { RTVIClient } from '@pipecat-ai/client-js';
|
||||
import { DailyTransport } from '@pipecat-ai/daily-transport';
|
||||
import { RTVIClientProvider } from '@pipecat-ai/client-react';
|
||||
|
||||
const transport = new DailyTransport({
|
||||
dailyFactoryOptions: {
|
||||
inputSettings: {
|
||||
video: {
|
||||
processor: {
|
||||
type: "background-blur",
|
||||
config: {
|
||||
strength: 0.8,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const transport = new DailyTransport();
|
||||
|
||||
const client = new RTVIClient({
|
||||
transport,
|
||||
params: {
|
||||
baseUrl: "http://localhost:7860",
|
||||
baseUrl: 'http://localhost:7860',
|
||||
endpoints: {
|
||||
connect: "/connect",
|
||||
connect: '/connect',
|
||||
},
|
||||
},
|
||||
enableMic: true,
|
||||
|
||||
@@ -158,7 +158,7 @@ class CartesiaTTSService(AudioContextWordTTSService):
|
||||
voice_config["__experimental_controls"]["emotion"] = self._settings["emotion"]
|
||||
|
||||
msg = {
|
||||
"transcript": text,
|
||||
"transcript": text or " ", # Text must contain at least one character
|
||||
"continue": continue_transcript,
|
||||
"context_id": self._context_id,
|
||||
"model_id": self.model_name,
|
||||
@@ -287,7 +287,7 @@ class CartesiaTTSService(AudioContextWordTTSService):
|
||||
self._context_id = str(uuid.uuid4())
|
||||
await self.create_audio_context(self._context_id)
|
||||
|
||||
msg = self._build_msg(text=text)
|
||||
msg = self._build_msg(text=text or " ") # Text must contain at least one character
|
||||
|
||||
try:
|
||||
await self._get_websocket().send(msg)
|
||||
|
||||
@@ -105,7 +105,7 @@ class OpenAITTSService(TTSService):
|
||||
extra_body["instructions"] = self._instructions
|
||||
|
||||
async with self._client.audio.speech.with_streaming_response.create(
|
||||
input=text,
|
||||
input=text or " ", # Text must contain at least one character
|
||||
model=self.model_name,
|
||||
voice=VALID_VOICES[self._voice_id],
|
||||
response_format="pcm",
|
||||
|
||||
@@ -269,8 +269,7 @@ class TTSService(AIService):
|
||||
filter.reset_interruption()
|
||||
text = filter.filter(text)
|
||||
|
||||
if text:
|
||||
await self.process_generator(self.run_tts(text))
|
||||
await self.process_generator(self.run_tts(text))
|
||||
|
||||
await self.stop_processing_metrics()
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||
from pipecat.transports.base_transport import TransportParams
|
||||
from pipecat.utils.time import nanoseconds_to_seconds
|
||||
|
||||
BOT_VAD_STOP_SECS = 0.35
|
||||
BOT_VAD_STOP_SECS = 0.3
|
||||
|
||||
|
||||
class BaseOutputTransport(FrameProcessor):
|
||||
|
||||
@@ -169,6 +169,8 @@ class DailyCallbacks(BaseModel):
|
||||
on_error: Called when an error occurs.
|
||||
on_app_message: Called when receiving an app message.
|
||||
on_call_state_updated: Called when call state changes.
|
||||
on_client_connected: Called when a client (participant) connects.
|
||||
on_client_disconnected: Called when a client (participant) disconnects.
|
||||
on_dialin_connected: Called when dial-in is connected.
|
||||
on_dialin_ready: Called when dial-in is ready.
|
||||
on_dialin_stopped: Called when dial-in is stopped.
|
||||
@@ -193,6 +195,8 @@ class DailyCallbacks(BaseModel):
|
||||
on_error: Callable[[str], Awaitable[None]]
|
||||
on_app_message: Callable[[Any, str], Awaitable[None]]
|
||||
on_call_state_updated: Callable[[str], Awaitable[None]]
|
||||
on_client_connected: Callable[[Mapping[str, Any]], Awaitable[None]]
|
||||
on_client_disconnected: Callable[[Mapping[str, Any]], Awaitable[None]]
|
||||
on_dialin_connected: Callable[[Any], Awaitable[None]]
|
||||
on_dialin_ready: Callable[[str], Awaitable[None]]
|
||||
on_dialin_stopped: Callable[[Any], Awaitable[None]]
|
||||
@@ -1070,6 +1074,8 @@ class DailyTransport(BaseTransport):
|
||||
on_error=self._on_error,
|
||||
on_app_message=self._on_app_message,
|
||||
on_call_state_updated=self._on_call_state_updated,
|
||||
on_client_connected=self._on_client_connected,
|
||||
on_client_disconnected=self._on_client_disconnected,
|
||||
on_dialin_connected=self._on_dialin_connected,
|
||||
on_dialin_ready=self._on_dialin_ready,
|
||||
on_dialin_stopped=self._on_dialin_stopped,
|
||||
@@ -1103,6 +1109,8 @@ class DailyTransport(BaseTransport):
|
||||
self._register_event_handler("on_error")
|
||||
self._register_event_handler("on_app_message")
|
||||
self._register_event_handler("on_call_state_updated")
|
||||
self._register_event_handler("on_client_connected")
|
||||
self._register_event_handler("on_client_disconnected")
|
||||
self._register_event_handler("on_dialin_connected")
|
||||
self._register_event_handler("on_dialin_ready")
|
||||
self._register_event_handler("on_dialin_stopped")
|
||||
@@ -1246,6 +1254,12 @@ class DailyTransport(BaseTransport):
|
||||
async def _on_call_state_updated(self, state: str):
|
||||
await self._call_event_handler("on_call_state_updated", state)
|
||||
|
||||
async def _on_client_connected(self, participant: Any):
|
||||
await self._call_event_handler("on_client_connected", participant)
|
||||
|
||||
async def _on_client_disconnected(self, participant: Any):
|
||||
await self._call_event_handler("on_client_disconnected", participant)
|
||||
|
||||
async def _handle_dialin_ready(self, sip_endpoint: str):
|
||||
if not self._params.dialin_settings:
|
||||
return
|
||||
@@ -1321,11 +1335,15 @@ class DailyTransport(BaseTransport):
|
||||
await self._call_event_handler("on_first_participant_joined", participant)
|
||||
|
||||
await self._call_event_handler("on_participant_joined", participant)
|
||||
# Also call on_client_connected for compatibility with other transports
|
||||
await self._call_event_handler("on_client_connected", participant)
|
||||
|
||||
async def _on_participant_left(self, participant, reason):
|
||||
id = participant["id"]
|
||||
logger.info(f"Participant left {id}")
|
||||
await self._call_event_handler("on_participant_left", participant, reason)
|
||||
# Also call on_client_disconnected for compatibility with other transports
|
||||
await self._call_event_handler("on_client_disconnected", participant)
|
||||
|
||||
async def _on_participant_updated(self, participant):
|
||||
await self._call_event_handler("on_participant_updated", participant)
|
||||
|
||||
Reference in New Issue
Block a user