mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-16 08:25:18 +02:00
feat: allow uploading recording as part of node transition
This commit is contained in:
parent
bb5f56bfb7
commit
65c76ca7ff
36 changed files with 2255 additions and 201 deletions
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
import { AlertCircle } from "lucide-react";
|
||||
|
||||
import type { RecordingResponseSchema } from "@/client/types.gen";
|
||||
import { RecordingSelect } from "@/components/flow/TextOrAudioInput";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -20,6 +22,9 @@ export interface EndCallToolConfigProps {
|
|||
onMessageTypeChange: (messageType: EndCallMessageType) => void;
|
||||
customMessage: string;
|
||||
onCustomMessageChange: (message: string) => void;
|
||||
audioRecordingId: string;
|
||||
onAudioRecordingIdChange: (id: string) => void;
|
||||
recordings?: RecordingResponseSchema[];
|
||||
endCallReason: boolean;
|
||||
onEndCallReasonChange: (enabled: boolean) => void;
|
||||
endCallReasonDescription: string;
|
||||
|
|
@ -35,6 +40,9 @@ export function EndCallToolConfig({
|
|||
onMessageTypeChange,
|
||||
customMessage,
|
||||
onCustomMessageChange,
|
||||
audioRecordingId,
|
||||
onAudioRecordingIdChange,
|
||||
recordings = [],
|
||||
endCallReason,
|
||||
onEndCallReasonChange,
|
||||
endCallReasonDescription,
|
||||
|
|
@ -148,6 +156,24 @@ export function EndCallToolConfig({
|
|||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-start space-x-3 p-3 border rounded-lg hover:bg-muted/50">
|
||||
<RadioGroupItem value="audio" id="audio" className="mt-1" />
|
||||
<label htmlFor="audio" className="flex-1 space-y-2 cursor-pointer">
|
||||
<span className="font-medium">Pre-recorded Audio</span>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Play a pre-recorded audio file before disconnecting
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
{messageType === "audio" && (
|
||||
<div className="pl-8">
|
||||
<RecordingSelect
|
||||
value={audioRecordingId}
|
||||
onChange={onAudioRecordingIdChange}
|
||||
recordings={recordings}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
import { AlertCircle } from "lucide-react";
|
||||
|
||||
import type { RecordingResponseSchema } from "@/client/types.gen";
|
||||
import { TextOrAudioInput } from "@/components/flow/TextOrAudioInput";
|
||||
import {
|
||||
CredentialSelector,
|
||||
type HttpMethod,
|
||||
|
|
@ -37,6 +39,11 @@ export interface HttpApiToolConfigProps {
|
|||
onTimeoutMsChange: (timeout: number) => void;
|
||||
customMessage: string;
|
||||
onCustomMessageChange: (message: string) => void;
|
||||
customMessageType: 'text' | 'audio';
|
||||
onCustomMessageTypeChange: (type: 'text' | 'audio') => void;
|
||||
customMessageRecordingId: string;
|
||||
onCustomMessageRecordingIdChange: (id: string) => void;
|
||||
recordings?: RecordingResponseSchema[];
|
||||
}
|
||||
|
||||
export function HttpApiToolConfig({
|
||||
|
|
@ -58,6 +65,11 @@ export function HttpApiToolConfig({
|
|||
onTimeoutMsChange,
|
||||
customMessage,
|
||||
onCustomMessageChange,
|
||||
customMessageType,
|
||||
onCustomMessageTypeChange,
|
||||
customMessageRecordingId,
|
||||
onCustomMessageRecordingIdChange,
|
||||
recordings = [],
|
||||
}: HttpApiToolConfigProps) {
|
||||
return (
|
||||
<Card>
|
||||
|
|
@ -136,18 +148,28 @@ export function HttpApiToolConfig({
|
|||
<div className="grid gap-2 pt-4 border-t">
|
||||
<Label>Custom Message</Label>
|
||||
<Label className="text-xs text-muted-foreground">
|
||||
Optional message the AI will speak before executing this tool (e.g., "Let me look that up for you")
|
||||
Optional message the AI will speak or play before executing this tool.
|
||||
</Label>
|
||||
<div className="flex items-start gap-2 rounded-md bg-amber-50 p-2 text-xs text-amber-700 border border-amber-200">
|
||||
<AlertCircle className="h-3.5 w-3.5 mt-0.5 shrink-0" />
|
||||
<span>This text is spoken as-is. For multilingual workflows, choose your phrasing carefully.</span>
|
||||
</div>
|
||||
<Textarea
|
||||
value={customMessage}
|
||||
onChange={(e) => onCustomMessageChange(e.target.value)}
|
||||
placeholder="e.g., Let me check that for you, one moment please."
|
||||
rows={2}
|
||||
/>
|
||||
<TextOrAudioInput
|
||||
type={customMessageType}
|
||||
onTypeChange={onCustomMessageTypeChange}
|
||||
recordingId={customMessageRecordingId}
|
||||
onRecordingIdChange={onCustomMessageRecordingIdChange}
|
||||
recordings={recordings}
|
||||
>
|
||||
<>
|
||||
<div className="flex items-start gap-2 rounded-md bg-amber-50 p-2 text-xs text-amber-700 border border-amber-200">
|
||||
<AlertCircle className="h-3.5 w-3.5 mt-0.5 shrink-0" />
|
||||
<span>This text is spoken as-is. For multilingual workflows, choose your phrasing carefully.</span>
|
||||
</div>
|
||||
<Textarea
|
||||
value={customMessage}
|
||||
onChange={(e) => onCustomMessageChange(e.target.value)}
|
||||
placeholder="e.g., Let me check that for you, one moment please."
|
||||
rows={2}
|
||||
/>
|
||||
</>
|
||||
</TextOrAudioInput>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
import { AlertCircle } from "lucide-react";
|
||||
import {useState } from "react";
|
||||
|
||||
import type { RecordingResponseSchema } from "@/client/types.gen";
|
||||
import { RecordingSelect } from "@/components/flow/TextOrAudioInput";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -22,6 +24,9 @@ export interface TransferCallToolConfigProps {
|
|||
onMessageTypeChange: (messageType: EndCallMessageType) => void;
|
||||
customMessage: string;
|
||||
onCustomMessageChange: (message: string) => void;
|
||||
audioRecordingId: string;
|
||||
onAudioRecordingIdChange: (id: string) => void;
|
||||
recordings?: RecordingResponseSchema[];
|
||||
timeout?: number; // Make optional to match API type
|
||||
onTimeoutChange: (timeout: number) => void;
|
||||
}
|
||||
|
|
@ -37,6 +42,9 @@ export function TransferCallToolConfig({
|
|||
onMessageTypeChange,
|
||||
customMessage,
|
||||
onCustomMessageChange,
|
||||
audioRecordingId,
|
||||
onAudioRecordingIdChange,
|
||||
recordings = [],
|
||||
timeout,
|
||||
onTimeoutChange,
|
||||
}: TransferCallToolConfigProps) {
|
||||
|
|
@ -181,6 +189,24 @@ export function TransferCallToolConfig({
|
|||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-start space-x-3 p-3 border rounded-lg hover:bg-muted/50">
|
||||
<RadioGroupItem value="audio" id="audio" className="mt-1" />
|
||||
<label htmlFor="audio" className="flex-1 space-y-2 cursor-pointer">
|
||||
<span className="font-medium">Pre-recorded Audio</span>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Play a pre-recorded audio file before transferring
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
{messageType === "audio" && (
|
||||
<div className="pl-8">
|
||||
<RecordingSelect
|
||||
value={audioRecordingId}
|
||||
onChange={onAudioRecordingIdChange}
|
||||
recordings={recordings}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ export default function ToolDetailPage() {
|
|||
const [endCallMessageType, setEndCallMessageType] = useState<EndCallMessageType>("none");
|
||||
const [endCallReason, setEndCallReason] = useState(false);
|
||||
const [endCallReasonDescription, setEndCallReasonDescription] = useState("");
|
||||
const [audioRecordingId, setAudioRecordingId] = useState("");
|
||||
|
||||
const handleEndCallReasonChange = (enabled: boolean) => {
|
||||
setEndCallReason(enabled);
|
||||
|
|
@ -87,6 +88,11 @@ export default function ToolDetailPage() {
|
|||
const [transferDestination, setTransferDestination] = useState("");
|
||||
const [transferMessageType, setTransferMessageType] = useState<EndCallMessageType>("none");
|
||||
const [transferTimeout, setTransferTimeout] = useState(30);
|
||||
const [transferAudioRecordingId, setTransferAudioRecordingId] = useState("");
|
||||
|
||||
// HTTP API form state - custom message type
|
||||
const [customMessageType, setCustomMessageType] = useState<'text' | 'audio'>('text');
|
||||
const [customMessageRecordingId, setCustomMessageRecordingId] = useState("");
|
||||
|
||||
// Redirect if not authenticated
|
||||
useEffect(() => {
|
||||
|
|
@ -132,11 +138,14 @@ export default function ToolDetailPage() {
|
|||
if (config) {
|
||||
setEndCallMessageType(config.messageType || "none");
|
||||
setCustomMessage(config.customMessage || "");
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
setAudioRecordingId((config as any).audioRecordingId || "");
|
||||
setEndCallReason(config.endCallReason ?? false);
|
||||
setEndCallReasonDescription(config.endCallReasonDescription || "");
|
||||
} else {
|
||||
setEndCallMessageType("none");
|
||||
setCustomMessage("");
|
||||
setAudioRecordingId("");
|
||||
setEndCallReason(false);
|
||||
setEndCallReasonDescription("");
|
||||
}
|
||||
|
|
@ -147,11 +156,14 @@ export default function ToolDetailPage() {
|
|||
setTransferDestination(config.destination || "");
|
||||
setTransferMessageType(config.messageType || "none");
|
||||
setCustomMessage(config.customMessage || "");
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
setTransferAudioRecordingId((config as any).audioRecordingId || "");
|
||||
setTransferTimeout(config.timeout ?? 30);
|
||||
} else {
|
||||
setTransferDestination("");
|
||||
setTransferMessageType("none");
|
||||
setCustomMessage("");
|
||||
setTransferAudioRecordingId("");
|
||||
setTransferTimeout(30);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -163,6 +175,10 @@ export default function ToolDetailPage() {
|
|||
setCredentialUuid(config.credential_uuid || "");
|
||||
setTimeoutMs(config.timeout_ms || 5000);
|
||||
setCustomMessage(config.customMessage || "");
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
setCustomMessageType((config as any).customMessageType || "text");
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
setCustomMessageRecordingId((config as any).customMessageRecordingId || "");
|
||||
|
||||
// Convert headers object to array
|
||||
if (config.headers) {
|
||||
|
|
@ -259,6 +275,7 @@ export default function ToolDetailPage() {
|
|||
config: {
|
||||
messageType: endCallMessageType,
|
||||
customMessage: endCallMessageType === "custom" ? customMessage : undefined,
|
||||
audioRecordingId: endCallMessageType === "audio" ? audioRecordingId || undefined : undefined,
|
||||
endCallReason,
|
||||
endCallReasonDescription: endCallReason ? endCallReasonDescription || undefined : undefined,
|
||||
},
|
||||
|
|
@ -276,6 +293,7 @@ export default function ToolDetailPage() {
|
|||
destination: transferDestination,
|
||||
messageType: transferMessageType,
|
||||
customMessage: transferMessageType === "custom" ? customMessage : undefined,
|
||||
audioRecordingId: transferMessageType === "audio" ? transferAudioRecordingId || undefined : undefined,
|
||||
timeout: transferTimeout,
|
||||
},
|
||||
},
|
||||
|
|
@ -306,7 +324,9 @@ export default function ToolDetailPage() {
|
|||
parameters:
|
||||
validParameters.length > 0 ? validParameters : undefined,
|
||||
timeout_ms: timeoutMs,
|
||||
customMessage: customMessage || undefined,
|
||||
customMessage: customMessageType === 'text' ? (customMessage || undefined) : undefined,
|
||||
customMessageType,
|
||||
customMessageRecordingId: customMessageType === 'audio' ? (customMessageRecordingId || undefined) : undefined,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -490,6 +510,8 @@ const data = await response.json();`;
|
|||
onMessageTypeChange={setEndCallMessageType}
|
||||
customMessage={customMessage}
|
||||
onCustomMessageChange={setCustomMessage}
|
||||
audioRecordingId={audioRecordingId}
|
||||
onAudioRecordingIdChange={setAudioRecordingId}
|
||||
endCallReason={endCallReason}
|
||||
onEndCallReasonChange={handleEndCallReasonChange}
|
||||
endCallReasonDescription={endCallReasonDescription}
|
||||
|
|
@ -507,6 +529,8 @@ const data = await response.json();`;
|
|||
onMessageTypeChange={setTransferMessageType}
|
||||
customMessage={customMessage}
|
||||
onCustomMessageChange={setCustomMessage}
|
||||
audioRecordingId={transferAudioRecordingId}
|
||||
onAudioRecordingIdChange={setTransferAudioRecordingId}
|
||||
timeout={transferTimeout}
|
||||
onTimeoutChange={setTransferTimeout}
|
||||
/>
|
||||
|
|
@ -530,6 +554,10 @@ const data = await response.json();`;
|
|||
onTimeoutMsChange={setTimeoutMs}
|
||||
customMessage={customMessage}
|
||||
onCustomMessageChange={setCustomMessage}
|
||||
customMessageType={customMessageType}
|
||||
onCustomMessageTypeChange={setCustomMessageType}
|
||||
customMessageRecordingId={customMessageRecordingId}
|
||||
onCustomMessageRecordingIdChange={setCustomMessageRecordingId}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import type {
|
|||
|
||||
export type ToolCategory = "http_api" | "end_call" | "transfer_call" | "calculator" | "native" | "integration";
|
||||
|
||||
export type EndCallMessageType = "none" | "custom";
|
||||
export type EndCallMessageType = "none" | "custom" | "audio";
|
||||
|
||||
export interface ToolCategoryConfig {
|
||||
value: ToolCategory;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue