mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-22 08:38:13 +02:00
feat: add Tuner Integration to Dograh (#311)
* Add tuner integration * bump pipecat version * chore: update pipecat submodule to match upstream and use tuner-pipecat-sdk 0.2.0 Update pipecat submodule from 0.0.109.dev23 to 13e98d0d9 (the exact commit upstream dograh-hq/dograh uses after v1.30.1). This installs pipecat-ai as 1.1.0.post277 via setuptools_scm, satisfying tuner-pipecat-sdk 0.2.0's pipecat-ai>=1.0.0 requirement. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * wire tuner * feat: refactor integrations into self contained packages * chore: simplify ensure_public_access_token * fix: remove NodeSpec and make DTOs the source of truth * feat: send relevant signal to mcp using to_mcp_dict * fix: fix tests * cleanup: remove nango integrations * feat: add agents.md for integrations --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Abhishek Kumar <abhishek@a6k.me>
This commit is contained in:
parent
afa78fe859
commit
5f28c1b2a9
93 changed files with 3388 additions and 3414 deletions
|
|
@ -31,7 +31,8 @@ type NodeStyleVariant =
|
|||
| "global"
|
||||
| "trigger"
|
||||
| "webhook"
|
||||
| "qa";
|
||||
| "qa"
|
||||
| "integration";
|
||||
|
||||
const STYLE_VARIANT_BY_SPEC: Record<string, NodeStyleVariant> = {
|
||||
startCall: "start",
|
||||
|
|
@ -100,6 +101,70 @@ function buildTriggerEndpoints(
|
|||
};
|
||||
}
|
||||
|
||||
function resolveIntegrationEnabled(
|
||||
spec: NodeSpec,
|
||||
data: FlowNodeData,
|
||||
): boolean {
|
||||
for (const prop of spec.properties) {
|
||||
if (!prop.name.endsWith("enabled")) continue;
|
||||
const value = data[prop.name];
|
||||
if (typeof value === "boolean") return value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function resolveIntegrationSummary(
|
||||
spec: NodeSpec,
|
||||
data: FlowNodeData,
|
||||
): string {
|
||||
for (const prop of spec.properties) {
|
||||
if (
|
||||
prop.name === "name" ||
|
||||
prop.name.endsWith("enabled") ||
|
||||
/api[_-]?key|token|secret/i.test(prop.name)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = data[prop.name];
|
||||
if (typeof value === "string" && value.trim().length > 0) {
|
||||
return value.length > 30 ? `${value.slice(0, 30)}...` : value;
|
||||
}
|
||||
if (typeof value === "number") {
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
return "Not configured";
|
||||
}
|
||||
|
||||
function getBadgeForSpec(
|
||||
spec: NodeSpec | undefined,
|
||||
variant: NodeStyleVariant,
|
||||
): { label: string; className: string } {
|
||||
if (!spec) {
|
||||
return { label: "Node", className: "bg-zinc-500 text-white" };
|
||||
}
|
||||
|
||||
switch (variant) {
|
||||
case "start":
|
||||
return { label: "Start Node", className: "bg-emerald-500 text-white" };
|
||||
case "agent":
|
||||
return { label: "Agent Node", className: "bg-blue-500 text-white" };
|
||||
case "end":
|
||||
return { label: "End Node", className: "bg-rose-500 text-white" };
|
||||
case "global":
|
||||
return { label: "Global Node", className: "bg-amber-500 text-white" };
|
||||
case "trigger":
|
||||
return { label: "API Trigger", className: "bg-purple-500 text-white" };
|
||||
case "webhook":
|
||||
return { label: "Webhook", className: "bg-indigo-500 text-white" };
|
||||
case "qa":
|
||||
return { label: "QA Analysis", className: "bg-teal-500 text-white" };
|
||||
case "integration":
|
||||
return { label: spec.display_name, className: "bg-cyan-600 text-white" };
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Canvas preview dispatch ──────────────────────────────────────────────
|
||||
|
||||
function CanvasPreview({
|
||||
|
|
@ -188,6 +253,21 @@ function CanvasPreview({
|
|||
);
|
||||
}
|
||||
|
||||
if (spec.category === "integration") {
|
||||
const enabled = resolveIntegrationEnabled(spec, data);
|
||||
const destination = resolveIntegrationSummary(spec, data);
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs font-mono bg-muted px-1.5 py-0.5 rounded">
|
||||
{destination}
|
||||
</span>
|
||||
</div>
|
||||
<StatusDot enabled={enabled} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Default: prompt preview + tool/document badges (when spec declares them).
|
||||
const hasToolRefs = spec.properties.some((p) => p.type === "tool_refs");
|
||||
const hasDocRefs = spec.properties.some((p) => p.type === "document_refs");
|
||||
|
|
@ -501,10 +581,20 @@ export const GenericNode = memo(({ data, selected, id, type }: GenericNodeProps)
|
|||
}, [data, open]);
|
||||
|
||||
// ── Render ──────────────────────────────────────────────────────────
|
||||
const styleVariant = STYLE_VARIANT_BY_SPEC[type];
|
||||
const handles = HANDLES_BY_SPEC[type] ?? { source: true, target: true };
|
||||
const styleVariant =
|
||||
STYLE_VARIANT_BY_SPEC[type] ??
|
||||
(spec?.category === "integration" ? "integration" : "agent");
|
||||
const handles =
|
||||
HANDLES_BY_SPEC[type] ??
|
||||
(spec?.category === "integration"
|
||||
? { source: false, target: false }
|
||||
: { source: true, target: true });
|
||||
const badge = getBadgeForSpec(spec, styleVariant);
|
||||
const Icon = spec ? resolveIcon(spec.icon) : Circle;
|
||||
const docUrl = DOC_URL_BY_SPEC[type];
|
||||
const contentLabel = spec?.properties.some((p) => p.name === "prompt")
|
||||
? "Prompt"
|
||||
: "Details";
|
||||
|
||||
// Edit dialog title: "Edit {display_name}". Webhook keeps the original
|
||||
// "Edit Webhook" wording — display_name is "Webhook" so it works out.
|
||||
|
|
@ -520,7 +610,9 @@ export const GenericNode = memo(({ data, selected, id, type }: GenericNodeProps)
|
|||
hovered_through_edge={data.hovered_through_edge}
|
||||
title={data.name || fallbackTitle}
|
||||
icon={<Icon />}
|
||||
nodeType={styleVariant}
|
||||
badgeLabel={badge.label}
|
||||
badgeClassName={badge.className}
|
||||
contentLabel={contentLabel}
|
||||
hasSourceHandle={handles.source}
|
||||
hasTargetHandle={handles.target}
|
||||
onDoubleClick={() => setOpen(true)}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ interface NodeContentProps {
|
|||
hovered_through_edge?: boolean;
|
||||
title: string;
|
||||
icon: ReactNode;
|
||||
nodeType?: 'start' | 'agent' | 'end' | 'global' | 'trigger' | 'webhook' | 'qa';
|
||||
badgeLabel?: string;
|
||||
badgeClassName?: string;
|
||||
contentLabel?: string;
|
||||
hasSourceHandle?: boolean;
|
||||
hasTargetHandle?: boolean;
|
||||
children?: ReactNode;
|
||||
|
|
@ -22,26 +24,7 @@ interface NodeContentProps {
|
|||
}
|
||||
|
||||
// Get badge styling based on node type
|
||||
const getNodeTypeBadge = (nodeType?: string) => {
|
||||
switch (nodeType) {
|
||||
case 'start':
|
||||
return { label: 'Start Node', className: 'bg-emerald-500 text-white' };
|
||||
case 'agent':
|
||||
return { label: 'Agent Node', className: 'bg-blue-500 text-white' };
|
||||
case 'end':
|
||||
return { label: 'End Node', className: 'bg-rose-500 text-white' };
|
||||
case 'global':
|
||||
return { label: 'Global Node', className: 'bg-amber-500 text-white' };
|
||||
case 'trigger':
|
||||
return { label: 'API Trigger', className: 'bg-purple-500 text-white' };
|
||||
case 'webhook':
|
||||
return { label: 'Webhook', className: 'bg-indigo-500 text-white' };
|
||||
case 'qa':
|
||||
return { label: 'QA Analysis', className: 'bg-teal-500 text-white' };
|
||||
default:
|
||||
return { label: 'Node', className: 'bg-zinc-500 text-white' };
|
||||
}
|
||||
};
|
||||
const DEFAULT_BADGE = { label: 'Node', className: 'bg-zinc-500 text-white' };
|
||||
|
||||
export const NodeContent = ({
|
||||
selected,
|
||||
|
|
@ -50,7 +33,9 @@ export const NodeContent = ({
|
|||
hovered_through_edge,
|
||||
title,
|
||||
icon,
|
||||
nodeType,
|
||||
badgeLabel,
|
||||
badgeClassName,
|
||||
contentLabel = "Prompt",
|
||||
hasSourceHandle = false,
|
||||
hasTargetHandle = false,
|
||||
children,
|
||||
|
|
@ -58,7 +43,10 @@ export const NodeContent = ({
|
|||
onDoubleClick,
|
||||
nodeId,
|
||||
}: NodeContentProps) => {
|
||||
const badge = getNodeTypeBadge(nodeType);
|
||||
const badge = {
|
||||
label: badgeLabel ?? DEFAULT_BADGE.label,
|
||||
className: badgeClassName ?? DEFAULT_BADGE.className,
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseNode
|
||||
|
|
@ -98,7 +86,9 @@ export const NodeContent = ({
|
|||
|
||||
{/* Content area with prompt label */}
|
||||
<div className="p-4">
|
||||
<div className="text-xs text-muted-foreground mb-1.5 font-medium">Prompt:</div>
|
||||
<div className="text-xs text-muted-foreground mb-1.5 font-medium">
|
||||
{contentLabel}:
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,14 +5,13 @@ export enum NodeType {
|
|||
GLOBAL_NODE = 'globalNode',
|
||||
TRIGGER = 'trigger',
|
||||
WEBHOOK = 'webhook',
|
||||
QA = 'qa'
|
||||
QA = 'qa',
|
||||
}
|
||||
|
||||
export type FlowNodeData = {
|
||||
prompt: string;
|
||||
prompt?: string;
|
||||
name: string;
|
||||
is_start?: boolean;
|
||||
is_static?: boolean;
|
||||
is_end?: boolean;
|
||||
invalid?: boolean;
|
||||
validationMessage?: string | null;
|
||||
|
|
@ -26,8 +25,6 @@ export type FlowNodeData = {
|
|||
greeting?: string;
|
||||
greeting_type?: 'text' | 'audio';
|
||||
greeting_recording_id?: string;
|
||||
wait_for_user_greeting?: boolean;
|
||||
detect_voicemail?: boolean;
|
||||
delayed_start?: boolean;
|
||||
delayed_start_duration?: number;
|
||||
// Pre-call data fetch (StartCall only)
|
||||
|
|
@ -61,6 +58,7 @@ export type FlowNodeData = {
|
|||
mcp_tool_filters?: Record<string, string[]>;
|
||||
// Documents - array of knowledge base document UUIDs that can be referenced by this node
|
||||
document_uuids?: string[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export type FlowNode = {
|
||||
|
|
@ -135,4 +133,3 @@ export interface Credential {
|
|||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,10 +29,7 @@ const BLANK_WORKFLOW_DEFINITION = {
|
|||
allow_interrupt: false,
|
||||
invalid: false,
|
||||
validationMessage: null,
|
||||
is_static: false,
|
||||
add_global_prompt: false,
|
||||
wait_for_user_response: false,
|
||||
detect_voicemail: true,
|
||||
delayed_start: false,
|
||||
is_start: true,
|
||||
selected_through_edge: false,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue