feat: add template variable rendering for transfer call destination

This commit is contained in:
Abhishek Kumar 2026-07-02 13:35:25 +05:30
parent 65d46bc313
commit 9966940624
8 changed files with 279 additions and 142 deletions

View file

@ -238,7 +238,7 @@ export default function TelephonyConfigurationsPage() {
<div className="grid gap-3">
{items.map((item) => (
<Card key={item.id}>
<CardContent className="flex items-center gap-4 py-4">
<CardContent className="flex flex-col gap-4 py-4 sm:flex-row sm:items-center">
<Link
href={`/telephony-configurations/${item.id}`}
className="flex flex-1 items-center gap-4 min-w-0"
@ -276,7 +276,7 @@ export default function TelephonyConfigurationsPage() {
</button>
</div>
</Link>
<div className="flex items-center gap-1">
<div className="flex w-full flex-wrap items-center justify-end gap-1 sm:w-auto sm:flex-nowrap">
{!item.is_default_outbound && (
<Button
variant="ghost"
@ -303,13 +303,15 @@ export default function TelephonyConfigurationsPage() {
>
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
<Link
href={`/telephony-configurations/${item.id}`}
className="text-muted-foreground"
aria-label="View phone numbers"
>
<ChevronRight className="h-5 w-5" />
</Link>
<Button variant="outline" size="sm" asChild>
<Link
href={`/telephony-configurations/${item.id}`}
aria-label={`Manage phone numbers for ${item.name}`}
>
Manage Phone Numbers
<ChevronRight className="h-4 w-4" />
</Link>
</Button>
</div>
</CardContent>
</Card>

View file

@ -1,7 +1,5 @@
"use client";
import {useState } from "react";
import type { RecordingResponseSchema } from "@/client/types.gen";
import { RecordingSelect, StaticTextWarning } from "@/components/flow/TextOrAudioInput";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
@ -47,40 +45,6 @@ export function TransferCallToolConfig({
timeout,
onTimeoutChange,
}: TransferCallToolConfigProps) {
const [sipMode, setSipMode] = useState(() => /^(PJSIP|SIP)\//i.test(destination));
// Validation patterns
const isValidPhoneNumber = (phone: string): boolean => {
const e164Pattern = /^\+[1-9]\d{1,14}$/;
return e164Pattern.test(phone);
};
const isValidSipEndpoint = (endpoint: string): boolean => {
const sipPattern = /^(PJSIP|SIP)\/[\w\-\.@]+$/i;
return sipPattern.test(endpoint);
};
const getValidationError = (): string | null => {
if (!destination) return null;
if (sipMode) {
return isValidSipEndpoint(destination)
? null
: "Please enter a valid SIP endpoint (e.g., PJSIP/1234 or SIP/extension@domain.com)";
} else {
return isValidPhoneNumber(destination)
? null
: "Please enter a valid phone number in E.164 format (e.g., +1234567890)";
}
};
const destinationError = getValidationError();
const handleSipModeToggle = () => {
setSipMode(!sipMode);
onDestinationChange(""); // Clear destination when switching modes
};
return (
<Card>
<CardHeader>
@ -117,30 +81,23 @@ export function TransferCallToolConfig({
<div className="grid gap-2 pt-4 border-t">
<Label>Transfer Destination</Label>
<Label className="text-xs text-muted-foreground">
{sipMode
? "SIP endpoint to transfer the call to (e.g., PJSIP/1234 or SIP/extension@domain.com)"
: "Phone number to transfer the call to (E.164 format with country code)"
}
</Label>
<div className="text-xs text-muted-foreground space-y-1">
<p>Enter one of these destination formats:</p>
<ul className="list-disc pl-4 space-y-1">
<li>SIP endpoint, e.g. PJSIP/1234</li>
<li>E.164 phone number, e.g. +1234567890</li>
<li>
Template variable, e.g. {"{{initial_context.transfer_destination}}"}
</li>
</ul>
</div>
<Input
value={destination}
onChange={(e) => onDestinationChange(e.target.value)}
placeholder={sipMode ? "PJSIP/1234 or SIP/extension@domain.com" : "+1234567890"}
className={destinationError ? "border-red-500 focus:border-red-500" : ""}
placeholder={
"+1234567890, PJSIP/1234, or {{initial_context.transfer_destination}}"
}
/>
{destinationError && (
<Label className="text-xs text-red-500">
{destinationError}
</Label>
)}
<button
type="button"
className="text-xs text-muted-foreground hover:text-foreground underline w-fit"
onClick={handleSipModeToggle}
>
{sipMode ? "Use phone number instead" : "Use SIP endpoint instead"}
</button>
</div>
<div className="grid gap-4 pt-4 border-t">

View file

@ -290,18 +290,14 @@ export default function ToolDetailPage() {
const handleSave = async () => {
if (!tool) return;
const normalizedTransferDestination = transferDestination.trim();
// Validation based on tool type
if (tool.category === "calculator") {
// No validation needed for built-in tools
} else if (tool.category === "transfer_call") {
// Validate destination for Transfer Call tools (supports both E.164 and SIP endpoints)
const e164Pattern = /^\+[1-9]\d{1,14}$/;
const sipPattern = /^(PJSIP|SIP)\/[\w\-\.@]+$/i;
const isValidE164 = e164Pattern.test(transferDestination);
const isValidSip = sipPattern.test(transferDestination);
if (!transferDestination || (!isValidE164 && !isValidSip)) {
setError("Please enter a valid phone number (E.164 format) or SIP endpoint (e.g., PJSIP/1234)");
if (!normalizedTransferDestination) {
setError("Please enter a transfer destination");
return;
}
} else if (tool.category === "mcp") {
@ -382,7 +378,7 @@ export default function ToolDetailPage() {
schema_version: 1,
type: "transfer_call",
config: {
destination: transferDestination,
destination: normalizedTransferDestination,
messageType: transferMessageType,
customMessage: transferMessageType === "custom" ? customMessage : undefined,
audioRecordingId: transferMessageType === "audio" ? transferAudioRecordingId || undefined : undefined,