mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-16 08:25:18 +02:00
feat: add pre call fetch configuration (#222)
* feat: add pre call fetch configuration * docs: add NEW tags for pages about new features --------- Co-authored-by: Sabiha Khan <sabihak89@gmail.com>
This commit is contained in:
parent
c4c4b591db
commit
ec2f322486
27 changed files with 646 additions and 66 deletions
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { FileText } from "lucide-react";
|
||||
import { ExternalLink, FileText } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useMemo } from "react";
|
||||
|
||||
|
|
@ -8,6 +8,7 @@ import type { DocumentResponseSchema } from "@/client/types.gen";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { KNOWLEDGE_BASE_DOC_URL } from "@/constants/documentation";
|
||||
|
||||
interface DocumentSelectorProps {
|
||||
value: string[];
|
||||
|
|
@ -57,7 +58,10 @@ export const DocumentSelector = ({
|
|||
<>
|
||||
<Label>{label}</Label>
|
||||
{description && (
|
||||
<Label className="text-xs text-muted-foreground">{description}</Label>
|
||||
<Label className="text-xs text-muted-foreground">
|
||||
{description}{" "}
|
||||
<a href={KNOWLEDGE_BASE_DOC_URL} target="_blank" rel="noopener noreferrer" className="underline">Learn more</a>
|
||||
</Label>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
|
@ -66,11 +70,12 @@ export const DocumentSelector = ({
|
|||
No documents available. Upload documents to the knowledge base first.
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<Link href="/files">
|
||||
<Button variant="outline" size="sm">
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link href="/files" target="_blank">
|
||||
<ExternalLink className="h-4 w-4 mr-2" />
|
||||
Upload Documents
|
||||
</Button>
|
||||
</Link>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -83,7 +88,10 @@ export const DocumentSelector = ({
|
|||
<>
|
||||
<Label>{label}</Label>
|
||||
{description && (
|
||||
<Label className="text-xs text-muted-foreground">{description}</Label>
|
||||
<Label className="text-xs text-muted-foreground">
|
||||
{description}{" "}
|
||||
<a href={KNOWLEDGE_BASE_DOC_URL} target="_blank" rel="noopener noreferrer" className="underline">Learn more</a>
|
||||
</Label>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
|
@ -123,15 +131,23 @@ export const DocumentSelector = ({
|
|||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="p-2 bg-muted/30">
|
||||
<Link
|
||||
href="/files"
|
||||
target="_blank"
|
||||
className="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
Manage Documents
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs text-muted-foreground pt-1">
|
||||
<span>
|
||||
|
||||
{value.length > 0 && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{value.length} {value.length === 1 ? "document" : "documents"} selected
|
||||
</span>
|
||||
<Link href="/files" className="hover:underline">
|
||||
Manage Documents
|
||||
</Link>
|
||||
</div>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import type { ToolResponse } from "@/client/types.gen";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { TOOLS_INTRODUCTION_DOC_URL } from "@/constants/documentation";
|
||||
|
||||
interface ToolSelectorProps {
|
||||
value: string[];
|
||||
|
|
@ -46,7 +47,8 @@ export function ToolSelector({
|
|||
<Label>{label}</Label>
|
||||
{description && (
|
||||
<Label className="text-xs text-muted-foreground">
|
||||
{description}
|
||||
{description}{" "}
|
||||
<a href={TOOLS_INTRODUCTION_DOC_URL} target="_blank" rel="noopener noreferrer" className="underline">Learn more</a>
|
||||
</Label>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { NodeProps, NodeToolbar, Position } from "@xyflow/react";
|
||||
import { Edit, FileText, Play, PlusIcon, Trash2Icon, Wrench } from "lucide-react";
|
||||
import { ChevronRight, Edit, FileText, Play, PlusIcon, Settings, Trash2Icon, Wrench } from "lucide-react";
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { useWorkflow } from "@/app/workflow/[workflowId]/contexts/WorkflowContext";
|
||||
|
|
@ -11,12 +11,14 @@ import { MentionTextarea } from "@/components/flow/MentionTextarea";
|
|||
import { ToolBadges } from "@/components/flow/ToolBadges";
|
||||
import { ToolSelector } from "@/components/flow/ToolSelector";
|
||||
import { ExtractionVariable, FlowNodeData } from "@/components/flow/types";
|
||||
import { CredentialSelector, UrlInput, validateUrl } from "@/components/http";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { CONTEXT_VARIABLES_DOC_URL, NODE_DOCUMENTATION_URLS } from "@/constants/documentation";
|
||||
import { CONTEXT_VARIABLES_DOC_URL, NODE_DOCUMENTATION_URLS, PRE_CALL_DATA_FETCH_DOC_URL } from "@/constants/documentation";
|
||||
|
||||
import { NodeContent } from "./common/NodeContent";
|
||||
import { NodeEditDialog } from "./common/NodeEditDialog";
|
||||
|
|
@ -48,6 +50,12 @@ interface StartCallEditFormProps {
|
|||
setToolUuids: (value: string[]) => void;
|
||||
documentUuids: string[];
|
||||
setDocumentUuids: (value: string[]) => void;
|
||||
preCallFetchEnabled: boolean;
|
||||
setPreCallFetchEnabled: (value: boolean) => void;
|
||||
preCallFetchUrl: string;
|
||||
setPreCallFetchUrl: (value: string) => void;
|
||||
preCallFetchCredentialUuid: string;
|
||||
setPreCallFetchCredentialUuid: (value: string) => void;
|
||||
tools: ToolResponse[];
|
||||
documents: DocumentResponseSchema[];
|
||||
recordings: RecordingResponseSchema[];
|
||||
|
|
@ -77,6 +85,9 @@ export const StartCall = memo(({ data, selected, id }: StartCallNodeProps) => {
|
|||
const [variables, setVariables] = useState<ExtractionVariable[]>(data.extraction_variables ?? []);
|
||||
const [toolUuids, setToolUuids] = useState<string[]>(data.tool_uuids ?? []);
|
||||
const [documentUuids, setDocumentUuids] = useState<string[]>(data.document_uuids ?? []);
|
||||
const [preCallFetchEnabled, setPreCallFetchEnabled] = useState(data.pre_call_fetch_enabled ?? false);
|
||||
const [preCallFetchUrl, setPreCallFetchUrl] = useState(data.pre_call_fetch_url ?? "");
|
||||
const [preCallFetchCredentialUuid, setPreCallFetchCredentialUuid] = useState(data.pre_call_fetch_credential_uuid ?? "");
|
||||
|
||||
// Compute if form has unsaved changes (only check prompt, name, greeting)
|
||||
const isDirty = useMemo(() => {
|
||||
|
|
@ -88,6 +99,14 @@ export const StartCall = memo(({ data, selected, id }: StartCallNodeProps) => {
|
|||
}, [greeting, prompt, name, data]);
|
||||
|
||||
const handleSave = async () => {
|
||||
// Validate pre-call fetch URL if enabled
|
||||
if (preCallFetchEnabled && preCallFetchUrl) {
|
||||
const urlValidation = validateUrl(preCallFetchUrl);
|
||||
if (!urlValidation.valid) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
handleSaveNodeData({
|
||||
...data,
|
||||
greeting: greeting || undefined,
|
||||
|
|
@ -102,6 +121,9 @@ export const StartCall = memo(({ data, selected, id }: StartCallNodeProps) => {
|
|||
extraction_variables: variables,
|
||||
tool_uuids: toolUuids.length > 0 ? toolUuids : undefined,
|
||||
document_uuids: documentUuids.length > 0 ? documentUuids : undefined,
|
||||
pre_call_fetch_enabled: preCallFetchEnabled,
|
||||
pre_call_fetch_url: preCallFetchEnabled ? preCallFetchUrl || undefined : undefined,
|
||||
pre_call_fetch_credential_uuid: preCallFetchEnabled && preCallFetchCredentialUuid ? preCallFetchCredentialUuid : undefined,
|
||||
});
|
||||
setOpen(false);
|
||||
await saveWorkflow();
|
||||
|
|
@ -122,6 +144,9 @@ export const StartCall = memo(({ data, selected, id }: StartCallNodeProps) => {
|
|||
setVariables(data.extraction_variables ?? []);
|
||||
setToolUuids(data.tool_uuids ?? []);
|
||||
setDocumentUuids(data.document_uuids ?? []);
|
||||
setPreCallFetchEnabled(data.pre_call_fetch_enabled ?? false);
|
||||
setPreCallFetchUrl(data.pre_call_fetch_url ?? "");
|
||||
setPreCallFetchCredentialUuid(data.pre_call_fetch_credential_uuid ?? "");
|
||||
}
|
||||
setOpen(newOpen);
|
||||
};
|
||||
|
|
@ -141,6 +166,9 @@ export const StartCall = memo(({ data, selected, id }: StartCallNodeProps) => {
|
|||
setVariables(data.extraction_variables ?? []);
|
||||
setToolUuids(data.tool_uuids ?? []);
|
||||
setDocumentUuids(data.document_uuids ?? []);
|
||||
setPreCallFetchEnabled(data.pre_call_fetch_enabled ?? false);
|
||||
setPreCallFetchUrl(data.pre_call_fetch_url ?? "");
|
||||
setPreCallFetchCredentialUuid(data.pre_call_fetch_credential_uuid ?? "");
|
||||
}
|
||||
}, [data, open]);
|
||||
|
||||
|
|
@ -243,6 +271,12 @@ export const StartCall = memo(({ data, selected, id }: StartCallNodeProps) => {
|
|||
setToolUuids={setToolUuids}
|
||||
documentUuids={documentUuids}
|
||||
setDocumentUuids={setDocumentUuids}
|
||||
preCallFetchEnabled={preCallFetchEnabled}
|
||||
setPreCallFetchEnabled={setPreCallFetchEnabled}
|
||||
preCallFetchUrl={preCallFetchUrl}
|
||||
setPreCallFetchUrl={setPreCallFetchUrl}
|
||||
preCallFetchCredentialUuid={preCallFetchCredentialUuid}
|
||||
setPreCallFetchCredentialUuid={setPreCallFetchCredentialUuid}
|
||||
tools={tools ?? []}
|
||||
documents={documents ?? []}
|
||||
recordings={recordings ?? []}
|
||||
|
|
@ -278,6 +312,12 @@ const StartCallEditForm = ({
|
|||
setToolUuids,
|
||||
documentUuids,
|
||||
setDocumentUuids,
|
||||
preCallFetchEnabled,
|
||||
setPreCallFetchEnabled,
|
||||
preCallFetchUrl,
|
||||
setPreCallFetchUrl,
|
||||
preCallFetchCredentialUuid,
|
||||
setPreCallFetchCredentialUuid,
|
||||
tools,
|
||||
documents,
|
||||
recordings,
|
||||
|
|
@ -475,6 +515,57 @@ const StartCallEditForm = ({
|
|||
description="Select documents from the knowledge base that the agent can reference during this conversation step."
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Advanced Settings */}
|
||||
<div className="pt-4 border-t mt-4">
|
||||
<Collapsible>
|
||||
<CollapsibleTrigger className="flex items-center gap-2 w-full text-sm font-medium hover:text-foreground text-muted-foreground">
|
||||
<Settings className="h-4 w-4" />
|
||||
<span>Advanced Settings</span>
|
||||
<ChevronRight className="h-4 w-4 ml-auto transition-transform [[data-state=open]>svg&]:rotate-90" />
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="mt-4 space-y-4">
|
||||
{/* Pre-Call Data Fetch */}
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch
|
||||
id="pre-call-fetch"
|
||||
checked={preCallFetchEnabled}
|
||||
onCheckedChange={setPreCallFetchEnabled}
|
||||
/>
|
||||
<Label htmlFor="pre-call-fetch">Pre-Call Data Fetch</Label>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Fetch data from an external API before the call starts. A standardized POST request with caller/called numbers will be sent. The JSON response fields will be merged into the call context and available as template variables in your prompts.{" "}
|
||||
<a href={PRE_CALL_DATA_FETCH_DOC_URL} target="_blank" rel="noopener noreferrer" className="underline">Learn more</a>
|
||||
</p>
|
||||
|
||||
{preCallFetchEnabled && (
|
||||
<div className="border rounded-md p-4 space-y-4 bg-muted/20">
|
||||
<div className="grid gap-2">
|
||||
<Label>Endpoint URL</Label>
|
||||
<Label className="text-xs text-muted-foreground">
|
||||
The URL to send the pre-call data fetch request to.
|
||||
</Label>
|
||||
<UrlInput
|
||||
value={preCallFetchUrl}
|
||||
onChange={setPreCallFetchUrl}
|
||||
placeholder="https://api.example.com/customer-lookup"
|
||||
showValidation
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label>Authentication</Label>
|
||||
<CredentialSelector
|
||||
value={preCallFetchCredentialUuid}
|
||||
onChange={setPreCallFetchCredentialUuid}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ export type FlowNodeData = {
|
|||
detect_voicemail?: boolean;
|
||||
delayed_start?: boolean;
|
||||
delayed_start_duration?: number;
|
||||
// Pre-call data fetch (StartCall only)
|
||||
pre_call_fetch_enabled?: boolean;
|
||||
pre_call_fetch_url?: string;
|
||||
pre_call_fetch_credential_uuid?: string;
|
||||
// Trigger node specific
|
||||
trigger_path?: string;
|
||||
// Webhook node specific
|
||||
|
|
|
|||
|
|
@ -12,6 +12,12 @@ export const NODE_DOCUMENTATION_URLS: Record<string, string> = {
|
|||
|
||||
export const CONTEXT_VARIABLES_DOC_URL = `${DOCS_BASE}/core-concepts/context-and-variables`;
|
||||
|
||||
export const TOOLS_INTRODUCTION_DOC_URL = `${DOCS_BASE}/voice-agent/tools/introduction`;
|
||||
|
||||
export const KNOWLEDGE_BASE_DOC_URL = `${DOCS_BASE}/voice-agent/knowledge-base`;
|
||||
|
||||
export const PRE_CALL_DATA_FETCH_DOC_URL = `${DOCS_BASE}/voice-agent/pre-call-data-fetch`;
|
||||
|
||||
export const TOOL_DOCUMENTATION_URLS: Record<string, string> = {
|
||||
http_api: `${DOCS_BASE}/voice-agent/tools/http-api`,
|
||||
end_call: `${DOCS_BASE}/voice-agent/tools/end-call`,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue