http block support multipart (#4259)

This commit is contained in:
LawyZheng
2025-12-11 01:03:11 +08:00
committed by GitHub
parent 0207d13855
commit 86ec31f556
12 changed files with 279 additions and 15 deletions

View File

@@ -58,6 +58,8 @@ const headersTooltip =
"HTTP headers to include with the request as JSON object.";
const bodyTooltip =
"Request body as JSON object. Only used for POST, PUT, PATCH methods.";
const filesTooltip =
'Files to upload as multipart/form-data. Dictionary mapping field names to file paths/URLs. Supports HTTP/HTTPS URLs, S3 URIs (s3://), or limited local file access. Example: {"file": "https://example.com/file.pdf"} or {"document": "s3://bucket/path/file.pdf"}';
const timeoutTooltip = "Request timeout in seconds.";
const followRedirectsTooltip =
"Whether to automatically follow HTTP redirects.";
@@ -287,12 +289,34 @@ function HttpRequestNode({ id, data }: NodeProps<HttpRequestNodeType>) {
</div>
)}
{/* Files Section */}
{showBodyEditor && (
<div className="space-y-2">
<div className="flex gap-2">
<Label className="text-xs text-slate-300">Files</Label>
<HelpTooltip content={filesTooltip} />
</div>
<CodeEditor
className="w-full"
language="json"
value={data.files}
onChange={(value) => {
update({ files: value || "{}" });
}}
readOnly={!editable}
minHeight="80px"
maxHeight="160px"
/>
</div>
)}
{/* Request Preview */}
<RequestPreview
method={data.method}
url={data.url}
headers={data.headers}
body={data.body}
files={data.files}
/>
</div>

View File

@@ -182,11 +182,13 @@ export function RequestPreview({
url,
headers,
body,
files,
}: {
method: string;
url: string;
headers: string;
body: string;
files?: string;
}) {
const [expanded, setExpanded] = useState(false);
@@ -194,6 +196,8 @@ export function RequestPreview({
if (!hasContent) return null;
const hasFiles = files && files.trim() && files !== "{}";
return (
<div className="rounded-md border bg-slate-50 p-3 dark:bg-slate-900/50">
<div className="flex items-center justify-between">
@@ -202,6 +206,11 @@ export function RequestPreview({
<span className="font-mono text-sm text-slate-600 dark:text-slate-400">
{url || "No URL specified"}
</span>
{hasFiles && (
<Badge variant="outline" className="text-xs">
Files
</Badge>
)}
</div>
<Button
variant="ghost"
@@ -232,6 +241,17 @@ export function RequestPreview({
</pre>
</div>
)}
{/* Files (only for POST, PUT, PATCH) */}
{["POST", "PUT", "PATCH"].includes(method.toUpperCase()) &&
hasFiles && (
<div>
<div className="mb-1 text-xs font-medium">Files:</div>
<pre className="overflow-x-auto rounded bg-slate-100 p-2 text-xs text-slate-600 dark:bg-slate-800 dark:text-slate-400">
{files || "{}"}
</pre>
</div>
)}
</div>
)}
</div>

View File

@@ -7,6 +7,7 @@ export type HttpRequestNodeData = NodeBaseData & {
url: string;
headers: string; // JSON string representation of headers
body: string; // JSON string representation of body
files: string; // JSON string representation of files (dict mapping field names to file paths/URLs)
timeout: number;
followRedirects: boolean;
parameterKeys: Array<string>;
@@ -22,6 +23,7 @@ export const httpRequestNodeDefaultData: HttpRequestNodeData = {
url: "",
headers: "{}",
body: "{}",
files: "{}",
timeout: 30,
followRedirects: true,
parameterKeys: [],

View File

@@ -760,6 +760,7 @@ function convertToNode(
url: block.url ?? "",
headers: JSON.stringify(block.headers || {}, null, 2),
body: JSON.stringify(block.body || {}, null, 2),
files: JSON.stringify(block.files || {}, null, 2),
timeout: block.timeout,
followRedirects: block.follow_redirects,
parameterKeys: block.parameters.map((p) => p.key),
@@ -2193,6 +2194,17 @@ function getWorkflowBlock(
string
> | null,
body: JSONParseSafe(node.data.body) as Record<string, unknown> | null,
files: (() => {
const parsed = JSONParseSafe(node.data.files) as Record<
string,
string
> | null;
// Convert empty object to null to match backend's "if not self.files" check
if (parsed && Object.keys(parsed).length === 0) {
return null;
}
return parsed;
})(),
timeout: node.data.timeout,
follow_redirects: node.data.followRedirects,
parameter_keys: node.data.parameterKeys,

View File

@@ -546,6 +546,7 @@ export type HttpRequestBlock = WorkflowBlockBase & {
url: string | null;
headers: Record<string, string> | null;
body: Record<string, unknown> | null;
files: Record<string, string> | null; // Dictionary mapping field names to file paths/URLs
timeout: number;
follow_redirects: boolean;
parameters: Array<WorkflowParameter>;

View File

@@ -396,6 +396,7 @@ export type HttpRequestBlockYAML = BlockYAMLBase & {
url: string | null;
headers: Record<string, string> | null;
body: Record<string, unknown> | null;
files?: Record<string, string> | null; // Dictionary mapping field names to file paths/URLs
timeout: number;
follow_redirects: boolean;
parameter_keys?: Array<string> | null;