mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-28 08:49:42 +02:00
feat: add test mode for API trigger
This commit is contained in:
parent
f041e6030d
commit
4171ad7a54
13 changed files with 279 additions and 124 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -2110,30 +2110,6 @@ export type MpsCreditsResponse = {
|
|||
total_quota: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* MigrationSpec
|
||||
*
|
||||
* Declared migration step (JSON-serializable view).
|
||||
*
|
||||
* The migrate callable is registered out-of-band via `register_migration()`
|
||||
* and never serialized — LLM and frontend consumers only see version
|
||||
* metadata and warn on mismatch.
|
||||
*/
|
||||
export type MigrationSpec = {
|
||||
/**
|
||||
* From Version
|
||||
*/
|
||||
from_version: string;
|
||||
/**
|
||||
* To Version
|
||||
*/
|
||||
to_version: string;
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
description: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* NodeCategory
|
||||
*
|
||||
|
|
@ -2206,10 +2182,6 @@ export type NodeSpec = {
|
|||
* Examples
|
||||
*/
|
||||
examples?: Array<NodeExample>;
|
||||
/**
|
||||
* Migrations
|
||||
*/
|
||||
migrations?: Array<MigrationSpec>;
|
||||
graph_constraints?: GraphConstraints | null;
|
||||
};
|
||||
|
||||
|
|
@ -8875,6 +8847,46 @@ export type InitiateCallApiV1PublicAgentUuidPostResponses = {
|
|||
|
||||
export type InitiateCallApiV1PublicAgentUuidPostResponse = InitiateCallApiV1PublicAgentUuidPostResponses[keyof InitiateCallApiV1PublicAgentUuidPostResponses];
|
||||
|
||||
export type InitiateCallTestApiV1PublicAgentTestUuidPostData = {
|
||||
body: TriggerCallRequest;
|
||||
headers: {
|
||||
/**
|
||||
* X-Api-Key
|
||||
*/
|
||||
'X-API-Key': string;
|
||||
};
|
||||
path: {
|
||||
/**
|
||||
* Uuid
|
||||
*/
|
||||
uuid: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/api/v1/public/agent/test/{uuid}';
|
||||
};
|
||||
|
||||
export type InitiateCallTestApiV1PublicAgentTestUuidPostErrors = {
|
||||
/**
|
||||
* Not found
|
||||
*/
|
||||
404: unknown;
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type InitiateCallTestApiV1PublicAgentTestUuidPostError = InitiateCallTestApiV1PublicAgentTestUuidPostErrors[keyof InitiateCallTestApiV1PublicAgentTestUuidPostErrors];
|
||||
|
||||
export type InitiateCallTestApiV1PublicAgentTestUuidPostResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
200: TriggerCallResponse;
|
||||
};
|
||||
|
||||
export type InitiateCallTestApiV1PublicAgentTestUuidPostResponse = InitiateCallTestApiV1PublicAgentTestUuidPostResponses[keyof InitiateCallTestApiV1PublicAgentTestUuidPostResponses];
|
||||
|
||||
export type DownloadWorkflowArtifactApiV1PublicDownloadWorkflowTokenArtifactTypeGetData = {
|
||||
body?: never;
|
||||
path: {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ import { NodeEditForm, useNodeSpecs } from "@/components/flow/renderer";
|
|||
import { ToolBadges } from "@/components/flow/ToolBadges";
|
||||
import { FlowNodeData } from "@/components/flow/types";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { NODE_DOCUMENTATION_URLS } from "@/constants/documentation";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { NodeContent } from "./common/NodeContent";
|
||||
import { NodeEditDialog } from "./common/NodeEditDialog";
|
||||
|
|
@ -80,12 +82,22 @@ function seedValues(
|
|||
return out;
|
||||
}
|
||||
|
||||
function buildTriggerEndpoint(triggerPath: string | undefined): string {
|
||||
if (!triggerPath) return "";
|
||||
interface TriggerEndpoints {
|
||||
production: string;
|
||||
test: string;
|
||||
}
|
||||
|
||||
function buildTriggerEndpoints(
|
||||
triggerPath: string | undefined,
|
||||
): TriggerEndpoints {
|
||||
if (!triggerPath) return { production: "", test: "" };
|
||||
const backendUrl =
|
||||
process.env.NEXT_PUBLIC_BACKEND_URL ||
|
||||
(typeof window !== "undefined" ? window.location.origin : "");
|
||||
return `${backendUrl}/api/v1/public/agent/${triggerPath}`;
|
||||
return {
|
||||
production: `${backendUrl}/api/v1/public/agent/${triggerPath}`,
|
||||
test: `${backendUrl}/api/v1/public/agent/test/${triggerPath}`,
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Canvas preview dispatch ──────────────────────────────────────────────
|
||||
|
|
@ -106,7 +118,7 @@ function CanvasPreview({
|
|||
onStaleDocuments: (uuids: string[]) => void;
|
||||
}) {
|
||||
if (spec.name === "trigger") {
|
||||
const endpoint = buildTriggerEndpoint(data.trigger_path);
|
||||
const endpoint = buildTriggerEndpoints(data.trigger_path).production;
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs text-muted-foreground">API Endpoint:</p>
|
||||
|
|
@ -229,21 +241,105 @@ function StatusDot({ enabled }: { enabled: boolean }) {
|
|||
);
|
||||
}
|
||||
|
||||
// ─── Trigger curl example helper (rendered inside the dialog form) ────────
|
||||
// ─── Trigger webhook URLs (test + production) — rendered inside the dialog ─
|
||||
|
||||
function TriggerCurlExample({ endpoint }: { endpoint: string }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const curl = `curl -X POST "${endpoint}" \\
|
||||
function buildCurl(endpoint: string): string {
|
||||
return `curl -X POST "${endpoint}" \\
|
||||
-H "X-API-Key: YOUR_API_KEY" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{"phone_number": "+1234567890", "initial_context": {}}'`;
|
||||
}
|
||||
|
||||
function ClickToCopy({
|
||||
value,
|
||||
children,
|
||||
className,
|
||||
title,
|
||||
}: {
|
||||
value: string;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
title?: string;
|
||||
}) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const onCopy = async () => {
|
||||
if (!value) return;
|
||||
await navigator.clipboard.writeText(value);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCopy}
|
||||
title={title ?? "Click to copy"}
|
||||
className={cn(
|
||||
"group relative text-left transition-colors hover:bg-accent/60 cursor-pointer disabled:cursor-default",
|
||||
className,
|
||||
)}
|
||||
disabled={!value}
|
||||
>
|
||||
{children}
|
||||
<span
|
||||
aria-hidden={!copied}
|
||||
className={cn(
|
||||
"pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 rounded bg-foreground/90 px-1.5 py-0.5 text-[10px] font-medium text-background shadow transition-opacity",
|
||||
copied ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
>
|
||||
Copied!
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function UrlPanel({
|
||||
endpoint,
|
||||
helperText,
|
||||
}: {
|
||||
endpoint: string;
|
||||
helperText: string;
|
||||
}) {
|
||||
const curl = endpoint ? buildCurl(endpoint) : "";
|
||||
return (
|
||||
<div className="grid gap-2 pt-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs font-mono bg-muted px-1.5 py-0.5 rounded shrink-0">
|
||||
POST
|
||||
</span>
|
||||
<ClickToCopy
|
||||
value={endpoint}
|
||||
title="Click to copy URL"
|
||||
className="flex-1 bg-muted rounded px-2 py-1"
|
||||
>
|
||||
<code className="text-xs break-all">
|
||||
{endpoint || "Generating..."}
|
||||
</code>
|
||||
</ClickToCopy>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">{helperText}</p>
|
||||
<p className="text-sm font-medium pt-2">Example Request</p>
|
||||
<ClickToCopy
|
||||
value={curl}
|
||||
title="Click to copy curl"
|
||||
className="block w-full bg-muted rounded"
|
||||
>
|
||||
<pre className="text-xs px-3 py-2 overflow-x-auto whitespace-pre-wrap">
|
||||
{curl || "Generating..."}
|
||||
</pre>
|
||||
</ClickToCopy>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TriggerWebhookUrls({ endpoints }: { endpoints: TriggerEndpoints }) {
|
||||
return (
|
||||
<div className="grid gap-2">
|
||||
<p className="text-sm font-medium">API Endpoint</p>
|
||||
<p className="text-sm font-medium">Webhook URLs</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Use this endpoint to trigger calls via API. Requires an API key in
|
||||
the X-API-Key header.{" "}
|
||||
Test mode runs the latest draft so you can verify changes before
|
||||
publishing. Production runs the published agent. Both require an
|
||||
API key in the X-API-Key header.{" "}
|
||||
<Link
|
||||
href="/api-keys"
|
||||
target="_blank"
|
||||
|
|
@ -252,27 +348,24 @@ function TriggerCurlExample({ endpoint }: { endpoint: string }) {
|
|||
Get your API key
|
||||
</Link>
|
||||
</p>
|
||||
<code className="text-xs break-all bg-muted px-2 py-1 rounded">
|
||||
{endpoint || "Generating..."}
|
||||
</code>
|
||||
<p className="text-sm font-medium pt-2">Example Request</p>
|
||||
<div className="relative">
|
||||
<pre className="text-xs bg-muted px-3 py-2 rounded overflow-x-auto whitespace-pre-wrap">
|
||||
{curl}
|
||||
</pre>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="absolute top-2 right-2"
|
||||
onClick={async () => {
|
||||
await navigator.clipboard.writeText(curl);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
}}
|
||||
>
|
||||
{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
<Tabs defaultValue="test" className="w-full">
|
||||
<TabsList>
|
||||
<TabsTrigger value="test">Test URL</TabsTrigger>
|
||||
<TabsTrigger value="production">Production URL</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="test">
|
||||
<UrlPanel
|
||||
endpoint={endpoints.test}
|
||||
helperText="Runs the latest draft, falling back to the published agent when no draft exists."
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="production">
|
||||
<UrlPanel
|
||||
endpoint={endpoints.production}
|
||||
helperText="Runs the published agent."
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -318,7 +411,7 @@ export const GenericNode = memo(({ data, selected, id, type }: GenericNodeProps)
|
|||
// ── Trigger auto-UUID + canvas copy state ──────────────────────────
|
||||
const [triggerCopied, setTriggerCopied] = useState(false);
|
||||
const handleCopyTrigger = useCallback(async () => {
|
||||
const endpoint = buildTriggerEndpoint(data.trigger_path);
|
||||
const endpoint = buildTriggerEndpoints(data.trigger_path).production;
|
||||
if (!endpoint) return;
|
||||
await navigator.clipboard.writeText(endpoint);
|
||||
setTriggerCopied(true);
|
||||
|
|
@ -472,8 +565,8 @@ export const GenericNode = memo(({ data, selected, id, type }: GenericNodeProps)
|
|||
}}
|
||||
/>
|
||||
{type === "trigger" && (
|
||||
<TriggerCurlExample
|
||||
endpoint={buildTriggerEndpoint(data.trigger_path)}
|
||||
<TriggerWebhookUrls
|
||||
endpoints={buildTriggerEndpoints(data.trigger_path)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue