Set border radius (#84)

Co-authored-by: Freddy Boulton <freddyboulton@hf-freddy.local>
This commit is contained in:
Freddy Boulton
2025-02-26 11:53:25 -05:00
committed by GitHub
parent c6769fe33f
commit da872627b9
13 changed files with 51 additions and 17 deletions

3
.gitignore vendored
View File

@@ -17,4 +17,5 @@ demo/scratch
.vscode .vscode
.DS_Store .DS_Store
test/ test/
.venv* .venv*
.env

View File

@@ -42,6 +42,8 @@ class UIArgs(TypedDict):
"""Color of the icon button. Default is var(--color-accent) of the demo theme.""" """Color of the icon button. Default is var(--color-accent) of the demo theme."""
pulse_color: NotRequired[str] pulse_color: NotRequired[str]
"""Color of the pulse animation. Default is var(--color-accent) of the demo theme.""" """Color of the pulse animation. Default is var(--color-accent) of the demo theme."""
icon_radius: NotRequired[int]
"""Border radius of the icon button expressed as a percentage of the button size. Default is 50%."""
class Stream(WebRTCConnectionMixin): class Stream(WebRTCConnectionMixin):
@@ -322,6 +324,7 @@ class Stream(WebRTCConnectionMixin):
icon=ui_args.get("icon"), icon=ui_args.get("icon"),
icon_button_color=ui_args.get("icon_button_color"), icon_button_color=ui_args.get("icon_button_color"),
pulse_color=ui_args.get("pulse_color"), pulse_color=ui_args.get("pulse_color"),
icon_radius=ui_args.get("icon_radius"),
) )
for component in additional_output_components: for component in additional_output_components:
if component not in same_components: if component not in same_components:
@@ -358,6 +361,10 @@ class Stream(WebRTCConnectionMixin):
rtc_configuration=self.rtc_configuration, rtc_configuration=self.rtc_configuration,
mode="send-receive", mode="send-receive",
modality="audio", modality="audio",
icon=ui_args.get("icon"),
icon_button_color=ui_args.get("icon_button_color"),
pulse_color=ui_args.get("pulse_color"),
icon_radius=ui_args.get("icon_radius"),
) )
for component in additional_input_components: for component in additional_input_components:
if component not in same_components: if component not in same_components:
@@ -400,6 +407,7 @@ class Stream(WebRTCConnectionMixin):
icon=ui_args.get("icon"), icon=ui_args.get("icon"),
icon_button_color=ui_args.get("icon_button_color"), icon_button_color=ui_args.get("icon_button_color"),
pulse_color=ui_args.get("pulse_color"), pulse_color=ui_args.get("pulse_color"),
icon_radius=ui_args.get("icon_radius"),
) )
for component in additional_input_components: for component in additional_input_components:
if component not in same_components: if component not in same_components:
@@ -443,6 +451,7 @@ class Stream(WebRTCConnectionMixin):
icon=ui_args.get("icon"), icon=ui_args.get("icon"),
icon_button_color=ui_args.get("icon_button_color"), icon_button_color=ui_args.get("icon_button_color"),
pulse_color=ui_args.get("pulse_color"), pulse_color=ui_args.get("pulse_color"),
icon_radius=ui_args.get("icon_radius"),
) )
for component in additional_input_components: for component in additional_input_components:
if component not in same_components: if component not in same_components:
@@ -552,6 +561,7 @@ class Stream(WebRTCConnectionMixin):
port: int = 8000, port: int = 8000,
**kwargs, **kwargs,
): ):
import atexit
import secrets import secrets
import threading import threading
import time import time
@@ -563,7 +573,6 @@ class Stream(WebRTCConnectionMixin):
from gradio.networking import setup_tunnel from gradio.networking import setup_tunnel
from gradio.tunneling import CURRENT_TUNNELS from gradio.tunneling import CURRENT_TUNNELS
from huggingface_hub import get_token from huggingface_hub import get_token
import atexit
app = FastAPI() app = FastAPI()

View File

@@ -1,14 +1,15 @@
import asyncio import asyncio
import fractions import fractions
import functools
import inspect
import io import io
import json import json
import logging import logging
import tempfile import tempfile
import traceback
from contextvars import ContextVar from contextvars import ContextVar
from typing import Any, Callable, Literal, Protocol, TypedDict, cast from typing import Any, Callable, Literal, Protocol, TypedDict, cast
import functools
import traceback
import inspect
import av import av
import numpy as np import numpy as np
from numpy.typing import NDArray from numpy.typing import NDArray

View File

@@ -87,6 +87,7 @@ class WebRTC(Component, WebRTCConnectionMixin):
icon: str | None = None, icon: str | None = None,
icon_button_color: str | None = None, icon_button_color: str | None = None,
pulse_color: str | None = None, pulse_color: str | None = None,
icon_radius: int | None = None,
button_labels: dict | None = None, button_labels: dict | None = None,
): ):
""" """
@@ -119,6 +120,7 @@ class WebRTC(Component, WebRTCConnectionMixin):
icon_button_color: Color of the icon button. Default is var(--color-accent) of the demo theme. icon_button_color: Color of the icon button. Default is var(--color-accent) of the demo theme.
pulse_color: Color of the pulse animation. Default is var(--color-accent) of the demo theme. pulse_color: Color of the pulse animation. Default is var(--color-accent) of the demo theme.
button_labels: Text to display on the audio or video start, stop, waiting buttons. Dict with keys "start", "stop", "waiting" mapping to the text to display on the buttons. button_labels: Text to display on the audio or video start, stop, waiting buttons. Dict with keys "start", "stop", "waiting" mapping to the text to display on the buttons.
icon_radius: Border radius of the icon button expressed as a percentage of the button size. Default is 50%
""" """
self.time_limit = time_limit self.time_limit = time_limit
self.height = height self.height = height
@@ -129,6 +131,7 @@ class WebRTC(Component, WebRTCConnectionMixin):
self.mode = mode self.mode = mode
self.modality = modality self.modality = modality
self.icon_button_color = icon_button_color self.icon_button_color = icon_button_color
self.icon_radius = icon_radius
self.pulse_color = pulse_color self.pulse_color = pulse_color
self.rtp_params = rtp_params or {} self.rtp_params = rtp_params or {}
self.button_labels = { self.button_labels = {

View File

@@ -149,14 +149,14 @@ class WebRTCConnectionMixin:
if isinstance(self.event_handler, StreamHandlerBase): if isinstance(self.event_handler, StreamHandlerBase):
handler = self.event_handler.copy() handler = self.event_handler.copy()
handler.emit = webrtc_error_handler(handler.emit) handler.emit = webrtc_error_handler(handler.emit) # type: ignore
handler.receive = webrtc_error_handler(handler.receive) handler.receive = webrtc_error_handler(handler.receive) # type: ignore
handler.start_up = webrtc_error_handler(handler.start_up) handler.start_up = webrtc_error_handler(handler.start_up) # type: ignore
handler.shutdown = webrtc_error_handler(handler.shutdown) handler.shutdown = webrtc_error_handler(handler.shutdown) # type: ignore
if hasattr(handler, "video_receive"): if hasattr(handler, "video_receive"):
handler.video_receive = webrtc_error_handler(handler.video_receive) handler.video_receive = webrtc_error_handler(handler.video_receive) # type: ignore
if hasattr(handler, "video_emit"): if hasattr(handler, "video_emit"):
handler.video_emit = webrtc_error_handler(handler.video_emit) handler.video_emit = webrtc_error_handler(handler.video_emit) # type: ignore
else: else:
handler = webrtc_error_handler(cast(Callable, self.event_handler)) handler = webrtc_error_handler(cast(Callable, self.event_handler))

View File

@@ -5,16 +5,16 @@ import time
from io import BytesIO from io import BytesIO
import gradio as gr import gradio as gr
from gradio.utils import get_space
import numpy as np import numpy as np
from google import genai
from dotenv import load_dotenv from dotenv import load_dotenv
from fastrtc import ( from fastrtc import (
AsyncAudioVideoStreamHandler, AsyncAudioVideoStreamHandler,
Stream, Stream,
get_twilio_turn_credentials,
WebRTC, WebRTC,
get_twilio_turn_credentials,
) )
from google import genai
from gradio.utils import get_space
from PIL import Image from PIL import Image
load_dotenv() load_dotenv()

View File

@@ -38,6 +38,7 @@
export let icon: string | undefined = undefined; export let icon: string | undefined = undefined;
export let icon_button_color: string = "var(--color-accent)"; export let icon_button_color: string = "var(--color-accent)";
export let pulse_color: string = "var(--color-accent)"; export let pulse_color: string = "var(--color-accent)";
export let icon_radius: number = 50;
const on_change_cb = (msg: "change" | "tick" | any) => { const on_change_cb = (msg: "change" | "tick" | any) => {
if ( if (
@@ -124,6 +125,7 @@
{icon} {icon}
{icon_button_color} {icon_button_color}
{pulse_color} {pulse_color}
{icon_radius}
i18n={gradio.i18n} i18n={gradio.i18n}
on:tick={() => gradio.dispatch("tick")} on:tick={() => gradio.dispatch("tick")}
on:error={({ detail }) => gradio.dispatch("error", detail)} on:error={({ detail }) => gradio.dispatch("error", detail)}
@@ -178,6 +180,7 @@
{icon} {icon}
{reject_cb} {reject_cb}
{icon_button_color} {icon_button_color}
{icon_radius}
{pulse_color} {pulse_color}
{button_labels} {button_labels}
on:tick={() => gradio.dispatch("tick")} on:tick={() => gradio.dispatch("tick")}

View File

@@ -11,6 +11,7 @@
export let icon_button_color: string = "var(--color-accent)"; export let icon_button_color: string = "var(--color-accent)";
export let pulse_color: string = "var(--color-accent)"; export let pulse_color: string = "var(--color-accent)";
export let pending: boolean = false; export let pending: boolean = false;
export let icon_radius: number = 50;
let audioContext: AudioContext; let audioContext: AudioContext;
let analyser: AnalyserNode; let analyser: AnalyserNode;
@@ -34,6 +35,7 @@
}); });
function setupAudioContext() { function setupAudioContext() {
// @ts-ignore
audioContext = new (window.AudioContext || window.webkitAudioContext)(); audioContext = new (window.AudioContext || window.webkitAudioContext)();
analyser = audioContext.createAnalyser(); analyser = audioContext.createAnalyser();
const source = audioContext.createMediaStreamSource( const source = audioContext.createMediaStreamSource(
@@ -58,6 +60,7 @@
); );
for (let i = 0; i < bars.length; i++) { for (let i = 0; i < bars.length; i++) {
const barHeight = (dataArray[i] / 255) * 2; const barHeight = (dataArray[i] / 255) * 2;
// @ts-ignore
bars[i].style.transform = `scaleY(${Math.max(0.1, barHeight)})`; bars[i].style.transform = `scaleY(${Math.max(0.1, barHeight)})`;
} }
@@ -78,6 +81,7 @@
{pulse_color} {pulse_color}
{icon} {icon}
{icon_button_color} {icon_button_color}
{icon_radius}
{audio_source_callback} {audio_source_callback}
/> />
</div> </div>

View File

@@ -33,6 +33,7 @@
export let icon: string | undefined = undefined; export let icon: string | undefined = undefined;
export let icon_button_color: string = "var(--color-accent)"; export let icon_button_color: string = "var(--color-accent)";
export let pulse_color: string = "var(--color-accent)"; export let pulse_color: string = "var(--color-accent)";
export let icon_radius: number = 50;
export let button_labels: { start: string; stop: string; waiting: string }; export let button_labels: { start: string; stop: string; waiting: string };
let pending = false; let pending = false;
@@ -291,6 +292,7 @@
{icon_button_color} {icon_button_color}
{pulse_color} {pulse_color}
{pending} {pending}
{icon_radius}
/> />
<StreamingBar time_limit={_time_limit} /> <StreamingBar time_limit={_time_limit} />
<div class="button-wrap" class:pulse={stopword_recognized}> <div class="button-wrap" class:pulse={stopword_recognized}>

View File

@@ -63,6 +63,7 @@
{icon_button_color} {icon_button_color}
{pulse_color} {pulse_color}
{button_labels} {button_labels}
{icon_radius}
on:error on:error
on:start_recording on:start_recording
on:stop_recording on:stop_recording

View File

@@ -7,6 +7,7 @@
export let icon: string | ComponentType = undefined; export let icon: string | ComponentType = undefined;
export let icon_button_color: string = "var(--color-accent)"; export let icon_button_color: string = "var(--color-accent)";
export let pulse_color: string = "var(--color-accent)"; export let pulse_color: string = "var(--color-accent)";
export let icon_radius: number = 50;
let audioContext: AudioContext; let audioContext: AudioContext;
let analyser: AnalyserNode; let analyser: AnalyserNode;
@@ -27,6 +28,7 @@
}); });
function setupAudioContext() { function setupAudioContext() {
// @ts-ignore
audioContext = new (window.AudioContext || window.webkitAudioContext)(); audioContext = new (window.AudioContext || window.webkitAudioContext)();
analyser = audioContext.createAnalyser(); analyser = audioContext.createAnalyser();
const source = audioContext.createMediaStreamSource( const source = audioContext.createMediaStreamSource(
@@ -77,7 +79,12 @@
style:background={icon_button_color} style:background={icon_button_color}
> >
{#if typeof icon === "string"} {#if typeof icon === "string"}
<img src={icon} alt="Audio visualization icon" class="icon-image" /> <img
src={icon}
alt="Audio visualization icon"
class="icon-image"
style:border-radius={`${icon_radius}%`}
/>
{:else if icon === undefined} {:else if icon === undefined}
<div></div> <div></div>
{:else} {:else}
@@ -123,7 +130,6 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: contain; object-fit: contain;
border-radius: 50%;
} }
.pulse-ring { .pulse-ring {

View File

@@ -18,6 +18,7 @@
export let icon: string | undefined = undefined; export let icon: string | undefined = undefined;
export let icon_button_color: string = "var(--color-accent)"; export let icon_button_color: string = "var(--color-accent)";
export let pulse_color: string = "var(--color-accent)"; export let pulse_color: string = "var(--color-accent)";
export let icon_radius: number = 50;
export let server: { export let server: {
offer: (body: any) => Promise<any>; offer: (body: any) => Promise<any>;
@@ -65,6 +66,7 @@
}); });
let stream = null; let stream = null;
const timeoutId = setTimeout(() => { const timeoutId = setTimeout(() => {
// @ts-ignore
on_change_cb({ type: "connection_timeout" }); on_change_cb({ type: "connection_timeout" });
}, 5000); }, 5000);
@@ -120,6 +122,7 @@
{icon} {icon}
{icon_button_color} {icon_button_color}
{pulse_color} {pulse_color}
{icon_radius}
/> />
</div> </div>
{/if} {/if}

View File

@@ -8,7 +8,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "fastrtc" name = "fastrtc"
version = "0.0.5.post2" version = "0.0.6"
description = "The realtime communication library for Python" description = "The realtime communication library for Python"
readme = "README.md" readme = "README.md"
license = "apache-2.0" license = "apache-2.0"
@@ -83,3 +83,4 @@ packages = ["/backend/fastrtc"]
[tool.ruff] [tool.ruff]
target-version = "py310" target-version = "py310"
extend-exclude = ["demo/phonic_chat"]