feat: add message before tool calls (#185)

This commit is contained in:
Abhishek 2026-03-09 17:28:13 +05:30 committed by GitHub
parent 8b5a36e55c
commit ec58356276
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 126 additions and 19 deletions

View file

@ -1,5 +1,7 @@
"use client";
import { AlertCircle } from "lucide-react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@ -133,7 +135,11 @@ export function EndCallToolConfig({
</label>
</div>
{messageType === "custom" && (
<div className="pl-8">
<div className="pl-8 space-y-2">
<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)}

View file

@ -1,5 +1,7 @@
"use client";
import { AlertCircle } from "lucide-react";
import {
CredentialSelector,
type HttpMethod,
@ -33,6 +35,8 @@ export interface HttpApiToolConfigProps {
onParametersChange: (parameters: ToolParameter[]) => void;
timeoutMs: number;
onTimeoutMsChange: (timeout: number) => void;
customMessage: string;
onCustomMessageChange: (message: string) => void;
}
export function HttpApiToolConfig({
@ -52,6 +56,8 @@ export function HttpApiToolConfig({
onParametersChange,
timeoutMs,
onTimeoutMsChange,
customMessage,
onCustomMessageChange,
}: HttpApiToolConfigProps) {
return (
<Card>
@ -126,6 +132,23 @@ export function HttpApiToolConfig({
showValidation
/>
</div>
<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., &quot;Let me look that up for you&quot;)
</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}
/>
</div>
</TabsContent>
<TabsContent value="auth" className="space-y-4 mt-4">

View file

@ -2,6 +2,8 @@
import {useState } from "react";
import { AlertCircle } from "lucide-react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@ -167,7 +169,11 @@ export function TransferCallToolConfig({
</label>
</div>
{messageType === "custom" && (
<div className="pl-8">
<div className="pl-8 space-y-2">
<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)}

View file

@ -41,6 +41,7 @@ interface HttpApiConfigWithParams {
credential_uuid?: string;
parameters?: ToolParameter[];
timeout_ms?: number;
customMessage?: string;
}
export default function ToolDetailPage() {
@ -59,6 +60,9 @@ export default function ToolDetailPage() {
const [name, setName] = useState("");
const [description, setDescription] = useState("");
// Shared form state
const [customMessage, setCustomMessage] = useState("");
// HTTP API form state
const [httpMethod, setHttpMethod] = useState<HttpMethod>("POST");
const [url, setUrl] = useState("");
@ -69,7 +73,6 @@ export default function ToolDetailPage() {
// End Call form state
const [endCallMessageType, setEndCallMessageType] = useState<EndCallMessageType>("none");
const [endCallCustomMessage, setEndCallCustomMessage] = useState("");
const [endCallReason, setEndCallReason] = useState(false);
const [endCallReasonDescription, setEndCallReasonDescription] = useState("");
@ -83,7 +86,6 @@ export default function ToolDetailPage() {
// Transfer Call form state
const [transferDestination, setTransferDestination] = useState("");
const [transferMessageType, setTransferMessageType] = useState<EndCallMessageType>("none");
const [transferCustomMessage, setTransferCustomMessage] = useState("");
const [transferTimeout, setTransferTimeout] = useState(30);
// Redirect if not authenticated
@ -129,12 +131,12 @@ export default function ToolDetailPage() {
const config = tool.definition?.config as EndCallConfig | undefined;
if (config) {
setEndCallMessageType(config.messageType || "none");
setEndCallCustomMessage(config.customMessage || "");
setCustomMessage(config.customMessage || "");
setEndCallReason(config.endCallReason ?? false);
setEndCallReasonDescription(config.endCallReasonDescription || "");
} else {
setEndCallMessageType("none");
setEndCallCustomMessage("");
setCustomMessage("");
setEndCallReason(false);
setEndCallReasonDescription("");
}
@ -144,12 +146,12 @@ export default function ToolDetailPage() {
if (config) {
setTransferDestination(config.destination || "");
setTransferMessageType(config.messageType || "none");
setTransferCustomMessage(config.customMessage || "");
setCustomMessage(config.customMessage || "");
setTransferTimeout(config.timeout ?? 30);
} else {
setTransferDestination("");
setTransferMessageType("none");
setTransferCustomMessage("");
setCustomMessage("");
setTransferTimeout(30);
}
} else {
@ -160,6 +162,7 @@ export default function ToolDetailPage() {
setUrl(config.url || "");
setCredentialUuid(config.credential_uuid || "");
setTimeoutMs(config.timeout_ms || 5000);
setCustomMessage(config.customMessage || "");
// Convert headers object to array
if (config.headers) {
@ -243,7 +246,7 @@ export default function ToolDetailPage() {
type: "end_call",
config: {
messageType: endCallMessageType,
customMessage: endCallMessageType === "custom" ? endCallCustomMessage : undefined,
customMessage: endCallMessageType === "custom" ? customMessage : undefined,
endCallReason,
endCallReasonDescription: endCallReason ? endCallReasonDescription || undefined : undefined,
},
@ -260,7 +263,7 @@ export default function ToolDetailPage() {
config: {
destination: transferDestination,
messageType: transferMessageType,
customMessage: transferMessageType === "custom" ? transferCustomMessage : undefined,
customMessage: transferMessageType === "custom" ? customMessage : undefined,
timeout: transferTimeout,
},
},
@ -291,6 +294,7 @@ export default function ToolDetailPage() {
parameters:
validParameters.length > 0 ? validParameters : undefined,
timeout_ms: timeoutMs,
customMessage: customMessage || undefined,
},
},
};
@ -462,8 +466,8 @@ const data = await response.json();`;
onDescriptionChange={setDescription}
messageType={endCallMessageType}
onMessageTypeChange={setEndCallMessageType}
customMessage={endCallCustomMessage}
onCustomMessageChange={setEndCallCustomMessage}
customMessage={customMessage}
onCustomMessageChange={setCustomMessage}
endCallReason={endCallReason}
onEndCallReasonChange={handleEndCallReasonChange}
endCallReasonDescription={endCallReasonDescription}
@ -479,8 +483,8 @@ const data = await response.json();`;
onDestinationChange={setTransferDestination}
messageType={transferMessageType}
onMessageTypeChange={setTransferMessageType}
customMessage={transferCustomMessage}
onCustomMessageChange={setTransferCustomMessage}
customMessage={customMessage}
onCustomMessageChange={setCustomMessage}
timeout={transferTimeout}
onTimeoutChange={setTransferTimeout}
/>
@ -502,6 +506,8 @@ const data = await response.json();`;
onParametersChange={setParameters}
timeoutMs={timeoutMs}
onTimeoutMsChange={setTimeoutMs}
customMessage={customMessage}
onCustomMessageChange={setCustomMessage}
/>
)}

View file

@ -25,17 +25,19 @@ interface EdgeDetailsDialogProps {
const EdgeDetailsDialog = ({ open, onOpenChange, data, onSave }: EdgeDetailsDialogProps) => {
const [condition, setCondition] = useState(data?.condition ?? '');
const [label, setLabel] = useState(data?.label ?? '');
const [transitionSpeech, setTransitionSpeech] = useState(data?.transition_speech ?? '');
// Update form state when data changes (e.g., from undo/redo)
useEffect(() => {
if (open) {
setCondition(data?.condition ?? '');
setLabel(data?.label ?? '');
setTransitionSpeech(data?.transition_speech ?? '');
}
}, [data, open]);
const handleSave = () => {
onSave({ condition: condition, label: label });
onSave({ condition: condition, label: label, transition_speech: transitionSpeech || undefined });
onOpenChange(false);
};
@ -77,6 +79,22 @@ const EdgeDetailsDialog = ({ open, onOpenChange, data, onSave }: EdgeDetailsDial
onChange={(e) => setCondition(e.target.value)}
/>
</div>
<div className="grid gap-2">
<Label>Transition Speech</Label>
<Label className="text-xs text-muted-foreground">
Optional text the assistant will speak right before transitioning to the node.
This text will not be attached in Conversation Context. Use this as simple filler to reduce latency.
</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={transitionSpeech}
placeholder="e.g. Let me transfer you to our billing department..."
onChange={(e) => setTransitionSpeech(e.target.value)}
/>
</div>
</div>
<DialogFooter>
<div className="flex items-center gap-2">

View file

@ -73,6 +73,7 @@ export type FlowNode = {
export type FlowEdgeData = {
condition: string;
label: string;
transition_speech?: string;
invalid?: boolean;
validationMessage?: string | null;
}