mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-07 07:55:16 +02:00
add transfer call tool editor
This commit is contained in:
parent
f77a2afca6
commit
97a44f00ff
6 changed files with 229 additions and 29 deletions
|
|
@ -0,0 +1,63 @@
|
|||
"""add transfer call category
|
||||
|
||||
Revision ID: f5db3dfa1f62
|
||||
Revises: 34c8537dfde5
|
||||
Create Date: 2026-02-06 09:24:44.887105
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from alembic_postgresql_enum import TableReference
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "f5db3dfa1f62"
|
||||
down_revision: Union[str, None] = "34c8537dfde5"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_index(
|
||||
"idx_queued_runs_campaign_state_optimized",
|
||||
"queued_runs",
|
||||
["campaign_id", "state"],
|
||||
unique=False,
|
||||
postgresql_where=sa.text("state = 'queued'"),
|
||||
)
|
||||
op.sync_enum_values(
|
||||
enum_schema="public",
|
||||
enum_name="tool_category",
|
||||
new_values=["http_api", "end_call", "transfer_call", "native", "integration"],
|
||||
affected_columns=[
|
||||
TableReference(
|
||||
table_schema="public", table_name="tools", column_name="category"
|
||||
)
|
||||
],
|
||||
enum_values_to_rename=[],
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.sync_enum_values(
|
||||
enum_schema="public",
|
||||
enum_name="tool_category",
|
||||
new_values=["http_api", "end_call", "native", "integration"],
|
||||
affected_columns=[
|
||||
TableReference(
|
||||
table_schema="public", table_name="tools", column_name="category"
|
||||
)
|
||||
],
|
||||
enum_values_to_rename=[],
|
||||
)
|
||||
op.drop_index(
|
||||
"idx_queued_runs_campaign_state_optimized",
|
||||
table_name="queued_runs",
|
||||
postgresql_where=sa.text("state = 'queued'"),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
|
@ -75,8 +75,8 @@ class EndCallToolDefinition(BaseModel):
|
|||
class TransferCallConfig(BaseModel):
|
||||
"""Configuration for Transfer Call tools."""
|
||||
|
||||
transfer_number: str = Field(description="Number to transfer the call to")
|
||||
transfer_message: Optional[str] = Field(
|
||||
transferNumber: str = Field(description="Number to transfer the call to")
|
||||
transferMessage: Optional[str] = Field(
|
||||
default=None, description="Message to play before transferring the call"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -279,7 +279,9 @@ class CustomToolManager:
|
|||
|
||||
# Wait for the audio to play or until stopped
|
||||
try:
|
||||
await asyncio.wait_for(stop_event.wait(), timeout=duration_secs)
|
||||
await asyncio.wait_for(
|
||||
stop_event.wait(), timeout=duration_secs + 1.5
|
||||
)
|
||||
break # Stop event was set
|
||||
except asyncio.TimeoutError:
|
||||
pass # Continue looping
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
"use client";
|
||||
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
|
||||
export interface TransferCallToolConfigProps {
|
||||
name: string;
|
||||
onNameChange: (name: string) => void;
|
||||
description: string;
|
||||
onDescriptionChange: (description: string) => void;
|
||||
transferNumber: string;
|
||||
onTransferNumberChange: (number: string) => void;
|
||||
transferMessage: string;
|
||||
onTransferMessageChange: (message: string) => void;
|
||||
}
|
||||
|
||||
export function TransferCallToolConfig({
|
||||
name,
|
||||
onNameChange,
|
||||
description,
|
||||
onDescriptionChange,
|
||||
transferNumber,
|
||||
onTransferNumberChange,
|
||||
transferMessage,
|
||||
onTransferMessageChange,
|
||||
}: TransferCallToolConfigProps) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Transfer Call Configuration</CardTitle>
|
||||
<CardDescription>
|
||||
Configure call transfer behavior
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="grid gap-2">
|
||||
<Label>Tool Name</Label>
|
||||
<Label className="text-xs text-muted-foreground">
|
||||
A descriptive name for this tool
|
||||
</Label>
|
||||
<Input
|
||||
value={name}
|
||||
onChange={(e) => onNameChange(e.target.value)}
|
||||
placeholder="e.g., Transfer Call"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label>Description</Label>
|
||||
<Label className="text-xs text-muted-foreground">
|
||||
Helps the LLM understand when to use this tool
|
||||
</Label>
|
||||
<Textarea
|
||||
value={description}
|
||||
onChange={(e) => onDescriptionChange(e.target.value)}
|
||||
placeholder="When should the AI transfer the call?"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 pt-4 border-t">
|
||||
<div className="grid gap-2">
|
||||
<Label>Transfer Number</Label>
|
||||
<Label className="text-xs text-muted-foreground">
|
||||
The phone number to transfer the call to
|
||||
</Label>
|
||||
<Input
|
||||
value={transferNumber}
|
||||
onChange={(e) => onTransferNumberChange(e.target.value)}
|
||||
placeholder="e.g., +14155551234"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label>Transfer Message</Label>
|
||||
<Label className="text-xs text-muted-foreground">
|
||||
Optional message to play before transferring
|
||||
</Label>
|
||||
<Textarea
|
||||
value={transferMessage}
|
||||
onChange={(e) => onTransferMessageChange(e.target.value)}
|
||||
placeholder="e.g., Please hold while I transfer your call."
|
||||
rows={2}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
export { EndCallToolConfig, type EndCallToolConfigProps } from "./EndCallToolConfig";
|
||||
export { HttpApiToolConfig, type HttpApiToolConfigProps } from "./HttpApiToolConfig";
|
||||
export { TransferCallToolConfig, type TransferCallToolConfigProps } from "./TransferCallToolConfig";
|
||||
|
|
|
|||
|
|
@ -28,8 +28,9 @@ import {
|
|||
getToolTypeLabel,
|
||||
renderToolIcon,
|
||||
type ToolCategory,
|
||||
type TransferCallConfig,
|
||||
} from "../config";
|
||||
import { EndCallToolConfig, HttpApiToolConfig } from "./components";
|
||||
import { EndCallToolConfig, HttpApiToolConfig, TransferCallToolConfig } from "./components";
|
||||
|
||||
// Extended HttpApiConfig with parameters (until client types are regenerated)
|
||||
interface HttpApiConfigWithParams {
|
||||
|
|
@ -69,6 +70,10 @@ export default function ToolDetailPage() {
|
|||
const [endCallMessageType, setEndCallMessageType] = useState<EndCallMessageType>("none");
|
||||
const [endCallCustomMessage, setEndCallCustomMessage] = useState("");
|
||||
|
||||
// Transfer Call form state
|
||||
const [transferNumber, setTransferNumber] = useState("");
|
||||
const [transferMessage, setTransferMessage] = useState("");
|
||||
|
||||
// Redirect if not authenticated
|
||||
useEffect(() => {
|
||||
if (!loading && !user) {
|
||||
|
|
@ -117,6 +122,16 @@ export default function ToolDetailPage() {
|
|||
setEndCallMessageType("none");
|
||||
setEndCallCustomMessage("");
|
||||
}
|
||||
} else if (tool.category === "transfer_call") {
|
||||
// Populate transfer call specific fields
|
||||
const config = tool.definition?.config as TransferCallConfig | undefined;
|
||||
if (config) {
|
||||
setTransferNumber(config.transferNumber || "");
|
||||
setTransferMessage(config.transferMessage || "");
|
||||
} else {
|
||||
setTransferNumber("");
|
||||
setTransferMessage("");
|
||||
}
|
||||
} else {
|
||||
// Populate HTTP API specific fields
|
||||
const config = tool.definition?.config as HttpApiConfigWithParams | undefined;
|
||||
|
|
@ -163,7 +178,7 @@ export default function ToolDetailPage() {
|
|||
if (!tool) return;
|
||||
|
||||
// Validation based on tool type
|
||||
if (tool.category !== "end_call") {
|
||||
if (tool.category === "http_api") {
|
||||
// Validate URL for HTTP API tools
|
||||
const urlValidation = validateUrl(url);
|
||||
if (!urlValidation.valid) {
|
||||
|
|
@ -201,6 +216,20 @@ export default function ToolDetailPage() {
|
|||
},
|
||||
},
|
||||
};
|
||||
} else if (tool.category === "transfer_call") {
|
||||
// Build transfer call request body
|
||||
requestBody = {
|
||||
name,
|
||||
description: description || undefined,
|
||||
definition: {
|
||||
schema_version: 1,
|
||||
type: "transfer_call",
|
||||
config: {
|
||||
transferNumber,
|
||||
transferMessage: transferMessage || undefined,
|
||||
},
|
||||
},
|
||||
};
|
||||
} else {
|
||||
// Build HTTP API request body
|
||||
const headersObject: Record<string, string> = {};
|
||||
|
|
@ -331,6 +360,7 @@ const data = await response.json();`;
|
|||
}
|
||||
|
||||
const isEndCallTool = tool.category === "end_call";
|
||||
const isTransferCallTool = tool.category === "transfer_call";
|
||||
const categoryConfig = getCategoryConfig(tool.category as ToolCategory);
|
||||
|
||||
return (
|
||||
|
|
@ -365,30 +395,6 @@ const data = await response.json();`;
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{!isEndCallTool && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowCodeDialog(true)}
|
||||
>
|
||||
<Code className="w-4 h-4 mr-2" />
|
||||
View Code
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={handleSave} disabled={isSaving}>
|
||||
{isSaving ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
Saving...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
Save
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
|
|
@ -414,6 +420,17 @@ const data = await response.json();`;
|
|||
customMessage={endCallCustomMessage}
|
||||
onCustomMessageChange={setEndCallCustomMessage}
|
||||
/>
|
||||
) : isTransferCallTool ? (
|
||||
<TransferCallToolConfig
|
||||
name={name}
|
||||
onNameChange={setName}
|
||||
description={description}
|
||||
onDescriptionChange={setDescription}
|
||||
transferNumber={transferNumber}
|
||||
onTransferNumberChange={setTransferNumber}
|
||||
transferMessage={transferMessage}
|
||||
onTransferMessageChange={setTransferMessage}
|
||||
/>
|
||||
) : (
|
||||
<HttpApiToolConfig
|
||||
name={name}
|
||||
|
|
@ -434,6 +451,31 @@ const data = await response.json();`;
|
|||
onTimeoutMsChange={setTimeoutMs}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-end gap-2 mt-6">
|
||||
{!isEndCallTool && !isTransferCallTool && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowCodeDialog(true)}
|
||||
>
|
||||
<Code className="w-4 h-4 mr-2" />
|
||||
View Code
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={handleSave} disabled={isSaving}>
|
||||
{isSaving ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
Saving...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
Save
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue