mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-22 08:38:13 +02:00
fix: fix pointer events on phone call dialog (#70)
* fix: fix pointer events on phone call dialog * feat: add max recording limit * chore: revert docker compose local changes * chore: revert langfuse changes * fix: kill descendents before killing main process
This commit is contained in:
parent
beb0091c01
commit
713c35df64
8 changed files with 175 additions and 85 deletions
|
|
@ -33,7 +33,8 @@ class AudioConfig:
|
|||
transport_out_sample_rate: int
|
||||
vad_sample_rate: int = 16000 # VAD typically resamples internally
|
||||
pipeline_sample_rate: Optional[int] = None # If None, uses transport rates
|
||||
buffer_size_seconds: float = 1.0 # This is how frequenly we will call merge_auido
|
||||
buffer_size_seconds: float = 5.0 # This is how frequenly we will call merge_auido
|
||||
max_recording_duration_seconds: float = 300.0 # 5 minutes max recording duration
|
||||
|
||||
def __post_init__(self):
|
||||
# Validate VAD sample rate
|
||||
|
|
@ -75,6 +76,12 @@ class AudioConfig:
|
|||
"""Calculate buffer size in samples based on pipeline sample rate."""
|
||||
return int(self.pipeline_sample_rate * self.buffer_size_seconds)
|
||||
|
||||
@property
|
||||
def max_recording_bytes(self) -> int:
|
||||
"""Calculate max recording size in bytes based on pipeline sample rate and duration."""
|
||||
# 2 bytes per sample (16-bit PCM)
|
||||
return int(self.pipeline_sample_rate * 2 * self.max_recording_duration_seconds)
|
||||
|
||||
|
||||
def create_audio_config(transport_type: str) -> AudioConfig:
|
||||
"""Create audio configuration based on transport type.
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ def create_pipeline_components(audio_config: AudioConfig, engine: "PipecatEngine
|
|||
audio_buffer = AudioBuffer(
|
||||
sample_rate=audio_config.pipeline_sample_rate,
|
||||
buffer_size=audio_config.buffer_size_bytes,
|
||||
max_recording_bytes=audio_config.max_recording_bytes,
|
||||
)
|
||||
|
||||
# Create synchronizer for merged audio (outside pipeline)
|
||||
|
|
|
|||
2
pipecat
2
pipecat
|
|
@ -1 +1 @@
|
|||
Subproject commit 5e95754902332ee4a7ff7ebcee3cca7e70fce825
|
||||
Subproject commit 30c6d1edb93c144a52adc6a3aa1aa618b1ee85fc
|
||||
|
|
@ -76,6 +76,45 @@ fi
|
|||
###############################################################################
|
||||
|
||||
mkdir -p "$RUN_DIR"
|
||||
|
||||
# Function to get all descendant PIDs of a process (children, grandchildren, etc.)
|
||||
get_descendants() {
|
||||
local parent_pid=$1
|
||||
local descendants=""
|
||||
local children
|
||||
|
||||
# Get direct children
|
||||
children=$(pgrep -P "$parent_pid" 2>/dev/null || true)
|
||||
|
||||
for child in $children; do
|
||||
# Recursively get descendants of each child
|
||||
descendants="$descendants $child $(get_descendants "$child")"
|
||||
done
|
||||
|
||||
echo "$descendants"
|
||||
}
|
||||
|
||||
# Function to kill a process and all its descendants
|
||||
kill_process_tree() {
|
||||
local pid=$1
|
||||
local signal=$2
|
||||
local descendants
|
||||
|
||||
descendants=$(get_descendants "$pid")
|
||||
|
||||
# Kill children first (bottom-up), then parent
|
||||
for desc_pid in $descendants; do
|
||||
if kill -0 "$desc_pid" 2>/dev/null; then
|
||||
kill "$signal" "$desc_pid" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
# Kill the parent
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
kill "$signal" "$pid" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
for name in "${SERVICE_NAMES[@]}"; do
|
||||
pidfile="$RUN_DIR/$name.pid"
|
||||
|
||||
|
|
@ -83,15 +122,28 @@ for name in "${SERVICE_NAMES[@]}"; do
|
|||
oldpid=$(<"$pidfile")
|
||||
|
||||
if kill -0 "$oldpid" 2>/dev/null; then
|
||||
echo "Stopping $name (PID $oldpid and its process group)…"
|
||||
echo "Stopping $name (PID $oldpid and all descendants)…"
|
||||
|
||||
# Kill the entire process group (negative PID)
|
||||
kill -TERM -"$oldpid" 2>/dev/null || kill -TERM "$oldpid" 2>/dev/null || true
|
||||
# Kill the entire process tree (parent + all descendants)
|
||||
kill_process_tree "$oldpid" "-TERM"
|
||||
sleep 4
|
||||
|
||||
# Check if parent or any descendants are still alive
|
||||
still_alive=false
|
||||
if kill -0 "$oldpid" 2>/dev/null; then
|
||||
still_alive=true
|
||||
else
|
||||
for desc_pid in $(get_descendants "$oldpid"); do
|
||||
if kill -0 "$desc_pid" 2>/dev/null; then
|
||||
still_alive=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if $still_alive; then
|
||||
echo "⚠️ $name did not exit cleanly, forcing stop..."
|
||||
kill -KILL -"$oldpid" 2>/dev/null || kill -KILL "$oldpid" 2>/dev/null || true
|
||||
kill_process_tree "$oldpid" "-KILL"
|
||||
sleep 1
|
||||
fi
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import 'react-international-phone/style.css';
|
||||
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { PhoneInput } from 'react-international-phone';
|
||||
|
|
@ -44,14 +45,15 @@ export const PhoneCallDialog = ({
|
|||
const [callError, setCallError] = useState<string | null>(null);
|
||||
const [callSuccessMsg, setCallSuccessMsg] = useState<string | null>(null);
|
||||
const [phoneChanged, setPhoneChanged] = useState(false);
|
||||
const [configureDialogOpen, setConfigureDialogOpen] = useState(false);
|
||||
const [needsConfiguration, setNeedsConfiguration] = useState(false);
|
||||
const [checkingConfig, setCheckingConfig] = useState(false);
|
||||
const [needsConfiguration, setNeedsConfiguration] = useState<boolean | null>(null);
|
||||
|
||||
// Check telephony configuration when dialog opens
|
||||
useEffect(() => {
|
||||
const checkConfig = async () => {
|
||||
if (!open) return;
|
||||
|
||||
setCheckingConfig(true);
|
||||
try {
|
||||
const accessToken = await getAccessToken();
|
||||
const configResponse = await getTelephonyConfigurationApiV1OrganizationsTelephonyConfigGet({
|
||||
|
|
@ -60,18 +62,19 @@ export const PhoneCallDialog = ({
|
|||
|
||||
if (configResponse.error || (!configResponse.data?.twilio && !configResponse.data?.vonage && !configResponse.data?.vobiz)) {
|
||||
setNeedsConfiguration(true);
|
||||
setConfigureDialogOpen(true);
|
||||
onOpenChange(false);
|
||||
} else {
|
||||
setNeedsConfiguration(false);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to check telephony config:", err);
|
||||
setNeedsConfiguration(false);
|
||||
} finally {
|
||||
setCheckingConfig(false);
|
||||
}
|
||||
};
|
||||
|
||||
checkConfig();
|
||||
}, [open, getAccessToken, onOpenChange]);
|
||||
}, [open, getAccessToken]);
|
||||
|
||||
// Reset state when dialog closes
|
||||
useEffect(() => {
|
||||
|
|
@ -79,6 +82,7 @@ export const PhoneCallDialog = ({
|
|||
setCallError(null);
|
||||
setCallSuccessMsg(null);
|
||||
setCallLoading(false);
|
||||
setNeedsConfiguration(null);
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
|
|
@ -101,7 +105,7 @@ export const PhoneCallDialog = ({
|
|||
};
|
||||
|
||||
const handleConfigureContinue = () => {
|
||||
setConfigureDialogOpen(false);
|
||||
onOpenChange(false);
|
||||
router.push('/telephony-configurations');
|
||||
};
|
||||
|
||||
|
|
@ -146,75 +150,96 @@ export const PhoneCallDialog = ({
|
|||
}
|
||||
};
|
||||
|
||||
return (
|
||||
// Render loading state
|
||||
const renderLoading = () => (
|
||||
<>
|
||||
{/* Phone Call Dialog */}
|
||||
<Dialog open={open && !needsConfiguration} onOpenChange={onOpenChange}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Phone Call</DialogTitle>
|
||||
<DialogDescription>
|
||||
Enter the phone number to call. The number will be saved automatically.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<PhoneInput
|
||||
defaultCountry="in"
|
||||
value={phoneNumber}
|
||||
onChange={handlePhoneInputChange}
|
||||
/>
|
||||
<DialogFooter className="flex-col sm:flex-row gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
onOpenChange(false);
|
||||
router.push('/telephony-configurations');
|
||||
}}
|
||||
>
|
||||
Configure Telephony
|
||||
</Button>
|
||||
<div className="flex gap-2 flex-1 justify-end">
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">Cancel</Button>
|
||||
</DialogClose>
|
||||
{!callSuccessMsg ? (
|
||||
<Button
|
||||
onClick={handleStartCall}
|
||||
disabled={callLoading || !phoneNumber}
|
||||
>
|
||||
{callLoading ? "Calling..." : "Start Call"}
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={() => onOpenChange(false)}>
|
||||
Close
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</DialogFooter>
|
||||
{callError && <div className="text-red-500 text-sm mt-2">{callError}</div>}
|
||||
{callSuccessMsg && <div className="text-green-600 text-sm mt-2">{callSuccessMsg}</div>}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Configure Telephony Dialog */}
|
||||
<Dialog open={configureDialogOpen} onOpenChange={setConfigureDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Configure Telephony</DialogTitle>
|
||||
<DialogDescription>
|
||||
You need to configure your telephony settings before making phone calls.
|
||||
You will be redirected to the telephony configuration page.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button variant="ghost" onClick={() => setConfigureDialogOpen(false)}>
|
||||
Do it Later
|
||||
</Button>
|
||||
<Button onClick={handleConfigureContinue}>
|
||||
Continue
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Phone Call</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
// Render configuration needed state
|
||||
const renderConfigurationNeeded = () => (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Configure Telephony</DialogTitle>
|
||||
<DialogDescription>
|
||||
You need to configure your telephony settings before making phone calls.
|
||||
You will be redirected to the telephony configuration page.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button variant="ghost" onClick={() => onOpenChange(false)}>
|
||||
Do it Later
|
||||
</Button>
|
||||
<Button onClick={handleConfigureContinue}>
|
||||
Continue
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</>
|
||||
);
|
||||
|
||||
// Render phone call form
|
||||
const renderPhoneCallForm = () => (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Phone Call</DialogTitle>
|
||||
<DialogDescription>
|
||||
Enter the phone number to call. The number will be saved automatically.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<PhoneInput
|
||||
defaultCountry="in"
|
||||
value={phoneNumber}
|
||||
onChange={handlePhoneInputChange}
|
||||
/>
|
||||
<DialogFooter className="flex-col sm:flex-row gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
onOpenChange(false);
|
||||
router.push('/telephony-configurations');
|
||||
}}
|
||||
>
|
||||
Configure Telephony
|
||||
</Button>
|
||||
<div className="flex gap-2 flex-1 justify-end">
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">Cancel</Button>
|
||||
</DialogClose>
|
||||
{!callSuccessMsg ? (
|
||||
<Button
|
||||
onClick={handleStartCall}
|
||||
disabled={callLoading || !phoneNumber}
|
||||
>
|
||||
{callLoading ? "Calling..." : "Start Call"}
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={() => onOpenChange(false)}>
|
||||
Close
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</DialogFooter>
|
||||
{callError && <div className="text-red-500 text-sm mt-2">{callError}</div>}
|
||||
{callSuccessMsg && <div className="text-green-600 text-sm mt-2">{callSuccessMsg}</div>}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent>
|
||||
{checkingConfig || needsConfiguration === null
|
||||
? renderLoading()
|
||||
: needsConfiguration
|
||||
? renderConfigurationNeeded()
|
||||
: renderPhoneCallForm()
|
||||
}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -125,7 +125,12 @@ export const WorkflowEditorHeader = ({
|
|||
Web Call
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={onPhoneCallClick}
|
||||
onClick={() => {
|
||||
// Delay opening dialog to next event cycle to allow DropdownMenu
|
||||
// to clean up first, preventing pointer-events: none stuck on body
|
||||
// See: https://github.com/radix-ui/primitives/issues/1241
|
||||
setTimeout(onPhoneCallClick, 0);
|
||||
}}
|
||||
className="text-white hover:bg-[#2a2a2a] cursor-pointer"
|
||||
>
|
||||
<Phone className="w-4 h-4 mr-2" />
|
||||
|
|
|
|||
|
|
@ -121,7 +121,6 @@ export default function WorkflowRunPage() {
|
|||
<Link href={`/workflow/${params.workflowId}`}>
|
||||
<Button
|
||||
ref={customizeButtonRef}
|
||||
variant="outline"
|
||||
className="gap-2"
|
||||
onClick={() => {
|
||||
if (!hasSeenTooltip('customize_workflow')) {
|
||||
|
|
@ -132,7 +131,7 @@ export default function WorkflowRunPage() {
|
|||
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
Customize Workflow
|
||||
Back to Agent
|
||||
</Button>
|
||||
</Link>
|
||||
</CardHeader>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import Link from "next/link";
|
|||
import { usePathname, useRouter } from "next/navigation";
|
||||
import React from "react";
|
||||
|
||||
import ThemeToggle from "@/components/ThemeSwitcher";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Sidebar,
|
||||
|
|
@ -405,7 +406,7 @@ export function AppSidebar() {
|
|||
)}
|
||||
|
||||
{/* Theme Toggle - at the very bottom */}
|
||||
{/* <div className={cn(
|
||||
<div className={cn(
|
||||
"mt-2 pt-2 border-t",
|
||||
state === "collapsed" ? "flex justify-center" : ""
|
||||
)}>
|
||||
|
|
@ -431,7 +432,7 @@ export function AppSidebar() {
|
|||
className="hover:bg-accent hover:text-accent-foreground"
|
||||
/>
|
||||
)}
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</SidebarFooter>
|
||||
<SidebarRail />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue