frontend support browser header (#2761)

This commit is contained in:
Shuchang Zheng
2025-06-21 08:50:19 +08:00
committed by GitHub
parent 592ed941ce
commit 1bf270df55
20 changed files with 495 additions and 0 deletions

View File

@@ -11,6 +11,7 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { KeyValueInput } from "@/components/KeyValueInput";
import { toast } from "@/components/ui/use-toast";
import { useApiCredential } from "@/hooks/useApiCredential";
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
@@ -36,6 +37,7 @@ type Props = {
proxyLocation: ProxyLocation;
webhookCallbackUrl: string;
maxScreenshotScrollingTimes: number | null;
extraHttpHeaders: Record<string, string> | null;
};
};
@@ -76,6 +78,7 @@ type RunWorkflowRequestBody = {
webhook_callback_url?: string | null;
browser_session_id: string | null;
max_screenshot_scrolling_times?: number | null;
extra_http_headers?: Record<string, string> | null;
};
function getRunWorkflowRequestBody(
@@ -87,6 +90,7 @@ function getRunWorkflowRequestBody(
proxyLocation,
browserSessionId,
maxScreenshotScrollingTimes,
extraHttpHeaders,
...parameters
} = values;
@@ -111,6 +115,15 @@ function getRunWorkflowRequestBody(
body.webhook_callback_url = webhookCallbackUrl;
}
if (extraHttpHeaders) {
try {
body.extra_http_headers = JSON.parse(extraHttpHeaders);
} catch (e) {
console.error("Invalid extra Header JSON");
body.extra_http_headers = null;
}
}
return body;
}
@@ -119,6 +132,7 @@ type RunWorkflowFormType = Record<string, unknown> & {
proxyLocation: ProxyLocation;
browserSessionId: string | null;
maxScreenshotScrollingTimes: number | null;
extraHttpHeaders: string | null;
};
function RunWorkflowForm({
@@ -141,6 +155,9 @@ function RunWorkflowForm({
proxyLocation: initialSettings.proxyLocation,
browserSessionId: browserSessionIdDefault,
maxScreenshotScrollingTimes: initialSettings.maxScreenshotScrollingTimes,
extraHttpHeaders: initialSettings.extraHttpHeaders
? JSON.stringify(initialSettings.extraHttpHeaders)
: null,
},
});
const apiCredential = useApiCredential();
@@ -192,6 +209,7 @@ function RunWorkflowForm({
proxyLocation,
browserSessionId,
maxScreenshotScrollingTimes,
extraHttpHeaders,
...parameters
} = values;
@@ -205,6 +223,7 @@ function RunWorkflowForm({
proxyLocation,
browserSessionId,
maxScreenshotScrollingTimes,
extraHttpHeaders,
});
}
@@ -408,6 +427,40 @@ function RunWorkflowForm({
);
}}
/>
<FormField
key="extraHttpHeaders"
control={form.control}
name="extraHttpHeaders"
render={({ field }) => {
return (
<FormItem>
<div className="flex gap-16">
<FormLabel>
<div className="w-72">
<div className="flex items-center gap-2 text-lg">
Extra HTTP Headers
</div>
<h2 className="text-sm text-slate-400">
Specify some self defined HTTP requests headers in
Dict format
</h2>
</div>
</FormLabel>
<div className="w-full space-y-2">
<FormControl>
<KeyValueInput
value={field.value ?? ""}
onChange={(val) => field.onChange(val)}
addButtonText="Add Header"
/>
</FormControl>
<FormMessage />
</div>
</div>
</FormItem>
);
}}
/>
<FormField
key="maxScreenshotScrollingTimes"
control={form.control}

View File

@@ -37,6 +37,10 @@ function WorkflowRunParameters() {
? (location.state.webhookCallbackUrl as string)
: null;
const extraHttpHeaders = location.state
? (location.state.extraHttpHeaders as Record<string, string>)
: null;
const initialValues = location.state?.data
? location.state.data
: workflowParameters?.reduce(
@@ -115,6 +119,8 @@ function WorkflowRunParameters() {
maxScreenshotScrollingTimes ??
workflow.max_screenshot_scrolling_times ??
null,
extraHttpHeaders:
extraHttpHeaders ?? workflow.extra_http_headers ?? null,
}}
/>
</div>

View File

@@ -282,6 +282,39 @@ function FlowRenderer({
return;
}
const client = await getClient(credentialGetter);
const extraHttpHeaders: Record<string, string> = {};
if (data.settings.extraHttpHeaders) {
try {
const parsedHeaders = JSON.parse(data.settings.extraHttpHeaders);
if (
parsedHeaders &&
typeof parsedHeaders === "object" &&
!Array.isArray(parsedHeaders)
) {
for (const [key, value] of Object.entries(parsedHeaders)) {
if (key && typeof key === "string") {
if (key in extraHttpHeaders) {
toast({
title: "Error",
description: `Duplicate key '${key}' in extra http headers`,
variant: "destructive",
});
continue;
}
extraHttpHeaders[key] = String(value);
}
}
}
} catch (error) {
toast({
title: "Error",
description: "Invalid JSON format in extra http headers",
variant: "destructive",
});
return;
}
}
const requestBody: WorkflowCreateYAMLRequest = {
title: data.title,
description: workflow.description,
@@ -292,6 +325,7 @@ function FlowRenderer({
max_screenshot_scrolling_times:
data.settings.maxScreenshotScrollingTimes,
totp_verification_url: workflow.totp_verification_url,
extra_http_headers: extraHttpHeaders,
workflow_definition: {
parameters: data.parameters,
blocks: data.blocks,

View File

@@ -61,6 +61,9 @@ function WorkflowEditor() {
webhookCallbackUrl: workflow.webhook_callback_url,
model: workflow.model,
maxScreenshotScrollingTimes: workflow.max_screenshot_scrolling_times,
extraHttpHeaders: workflow.extra_http_headers
? JSON.stringify(workflow.extra_http_headers)
: null,
};
const elements = getElements(

View File

@@ -21,6 +21,7 @@ import { ModelsResponse } from "@/api/types";
import { ModelSelector } from "@/components/ModelSelector";
import { WorkflowModel } from "@/routes/workflows/types/workflowTypes";
import { MAX_SCREENSHOT_SCROLLING_TIMES_DEFAULT } from "../Taskv2Node/types";
import { KeyValueInput } from "@/components/KeyValueInput";
function StartNode({ id, data }: NodeProps<StartNode>) {
const credentialGetter = useCredentialGetter();
@@ -55,6 +56,7 @@ function StartNode({ id, data }: NodeProps<StartNode>) {
maxScreenshotScrollingTimes: data.withWorkflowSettings
? data.maxScreenshotScrollingTimes
: null,
extraHttpHeaders: data.withWorkflowSettings ? data.extraHttpHeaders : null,
});
function handleChange(key: string, value: unknown) {
@@ -134,6 +136,19 @@ function StartNode({ id, data }: NodeProps<StartNode>) {
/>
</div>
</div>
<div className="space-y-2">
<div className="flex items-center gap-2">
<Label>Extra HTTP Headers</Label>
<HelpTooltip content="Specify some self defined HTTP requests headers in Dict format" />
</div>
<KeyValueInput
value={inputs.extraHttpHeaders ?? null}
onChange={(val) =>
handleChange("extraHttpHeaders", val)
}
addButtonText="Add Header"
/>
</div>
<div className="space-y-2">
<div className="flex items-center gap-2">
<Label>Max Scrolling Screenshots</Label>

View File

@@ -10,6 +10,7 @@ export type WorkflowStartNodeData = {
persistBrowserSession: boolean;
model: WorkflowModel | null;
maxScreenshotScrollingTimes: number | null;
extraHttpHeaders: string | null;
editable: boolean;
};

View File

@@ -664,6 +664,7 @@ function getElements(
webhookCallbackUrl: settings.webhookCallbackUrl ?? "",
model: settings.model,
maxScreenshotScrollingTimes: settings.maxScreenshotScrollingTimes,
extraHttpHeaders: settings.extraHttpHeaders,
editable,
}),
);
@@ -1325,6 +1326,7 @@ function getWorkflowSettings(nodes: Array<AppNode>): WorkflowSettings {
webhookCallbackUrl: null,
model: null,
maxScreenshotScrollingTimes: null,
extraHttpHeaders: null,
};
const startNodes = nodes.filter(isStartNode);
const startNodeWithWorkflowSettings = startNodes.find(
@@ -1341,6 +1343,7 @@ function getWorkflowSettings(nodes: Array<AppNode>): WorkflowSettings {
webhookCallbackUrl: data.webhookCallbackUrl,
model: data.model,
maxScreenshotScrollingTimes: data.maxScreenshotScrollingTimes,
extraHttpHeaders: data.extraHttpHeaders,
};
}
return defaultSettings;
@@ -1997,6 +2000,7 @@ function convert(workflow: WorkflowApiResponse): WorkflowCreateYAMLRequest {
model: workflow.model,
totp_verification_url: workflow.totp_verification_url,
max_screenshot_scrolling_times: workflow.max_screenshot_scrolling_times,
extra_http_headers: workflow.extra_http_headers,
workflow_definition: {
parameters: convertParametersToParameterYAML(userParameters),
blocks: convertBlocksToBlockYAML(workflow.workflow_definition.blocks),

View File

@@ -466,6 +466,7 @@ export type WorkflowApiResponse = {
workflow_definition: WorkflowDefinition;
proxy_location: ProxyLocation | null;
webhook_callback_url: string | null;
extra_http_headers: Record<string, string> | null;
persist_browser_session: boolean;
model: WorkflowModel | null;
totp_verification_url: string | null;
@@ -482,6 +483,7 @@ export type WorkflowSettings = {
persistBrowserSession: boolean;
model: WorkflowModel | null;
maxScreenshotScrollingTimes: number | null;
extraHttpHeaders: string | null;
};
export type WorkflowModel = JsonObjectExtendable<{ model_name: string }>;

View File

@@ -13,6 +13,7 @@ export type WorkflowCreateYAMLRequest = {
workflow_definition: WorkflowDefinitionYAML;
is_saved_task?: boolean;
max_screenshot_scrolling_times?: number | null;
extra_http_headers?: Record<string, string> | null;
};
export type WorkflowDefinitionYAML = {

View File

@@ -11,6 +11,7 @@ import { Input } from "@/components/ui/input";
import { ProxySelector } from "@/components/ProxySelector";
import { SendEmailBlockParameters } from "./blockInfo/SendEmailBlockInfo";
import { ProxyLocation } from "@/api/types";
import { KeyValueInput } from "@/components/KeyValueInput";
function WorkflowPostRunParameters() {
const { data: workflowRunTimeline, isLoading: workflowRunTimelineIsLoading } =
@@ -54,6 +55,10 @@ function WorkflowPostRunParameters() {
? workflowRun.task_v2?.proxy_location
: workflowRun.proxy_location;
const extraHttpHeaders = isTaskV2
? workflowRun.task_v2?.extra_http_headers
: workflowRun.extra_http_headers;
return (
<div className="space-y-5">
{activeBlock && isTaskVariantBlock(activeBlock) ? (
@@ -147,6 +152,20 @@ function WorkflowPostRunParameters() {
}}
/>
</div>
<div className="flex gap-16">
<div className="w-80">
<h1 className="text-lg">Extra HTTP Headers</h1>
</div>
<div className="w-full">
<KeyValueInput
value={
extraHttpHeaders ? JSON.stringify(extraHttpHeaders) : null
}
readOnly={true}
onChange={() => {}}
/>
</div>
</div>
</div>
</div>
{workflowRun.task_v2 ? (