fix(frontend/builder): make Google Drive file inputs chainable (#12274)

Resolves: OPEN-3018

Google Drive picker fields on INPUT blocks were missing connection
handles, making them non-chainable in the new builder.

### Changes 🏗️

- **Render `TitleFieldTemplate` with `InputNodeHandle`** — uses
`getHandleId()` with `fieldPathId.$id` (which correctly resolves to e.g.
`agpt_%_spreadsheet`), fixing the previous `_@_` handle error caused by
using `idSchema.$id` (undefined for custom RJSF FieldProps)
- **Override `showHandles: !!nodeId`** in uiOptions — the INPUT block's
`generate-ui-schema.ts` sets `showHandles: false`, but Google Drive
fields need handles to be chainable
- **Hide picker content when handle is connected** — uses
`useEdgeStore.isInputConnected()` to detect wired connections and
conditionally hides the picker/placeholder UI

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
- [x] Add a Google Drive file input block to a graph in the new builder
  - [x] Verify the connection handle appears on the input
  - [x] Connect another block's output to the Google Drive input handle
- [x] Verify the picker UI hides when connected and reappears when
disconnected
- [x] Verify the Google Drive picker still works normally on non-INPUT
block nodes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes input-handle ID generation and conditional rendering for
Google Drive fields in the builder; regressions could break edge
connections or hide the picker unexpectedly on some nodes.
> 
> **Overview**
> Google Drive picker fields now render a proper RJSF
`TitleFieldTemplate` (and thus input handles) using a computed
`handleId` derived from `fieldPathId.$id`, and force `showHandles` on
when a `nodeId` is present.
> 
> The picker/placeholder UI is now conditionally hidden when
`useEdgeStore.isInputConnected()` reports the input handle is connected,
preventing duplicate input UI when the value comes from an upstream
node.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
1f1df53a38. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: abhi1992002 <abhimanyu1992002@gmail.com>
Co-authored-by: Abhimanyu Yadav <122007096+Abhi1992002@users.noreply.github.com>
This commit is contained in:
Nicholas Tindle
2026-03-04 22:28:01 -06:00
committed by GitHub
parent 592830ce9b
commit 3722d05b9b
2 changed files with 48 additions and 10 deletions

View File

@@ -1,32 +1,60 @@
import { BlockUIType } from "@/app/(platform)/build/components/types";
import { GoogleDrivePickerInput } from "@/components/contextual/GoogleDrivePicker/GoogleDrivePickerInput";
import { GoogleDrivePickerConfig } from "@/lib/autogpt-server-api";
import { FieldProps, getUiOptions } from "@rjsf/utils";
import { FieldProps, getTemplate, getUiOptions, titleId } from "@rjsf/utils";
import { cleanUpHandleId, getHandleId, updateUiOption } from "../../helpers";
import { useEdgeStore } from "@/app/(platform)/build/stores/edgeStore";
export const GoogleDrivePickerField = (props: FieldProps) => {
const { schema, uiSchema, onChange, fieldPathId, formData, registry } = props;
const uiOptions = getUiOptions(uiSchema);
const config: GoogleDrivePickerConfig = schema.google_drive_picker_config;
const { nodeId } = registry.formContext;
const uiType = registry.formContext?.uiType;
const TitleFieldTemplate = getTemplate("TitleFieldTemplate", registry);
const handleId = getHandleId({ uiOptions, id: fieldPathId.$id, schema });
const updatedUiSchema = updateUiOption(uiSchema, {
handleId,
showHandles: !!nodeId,
});
const { isInputConnected } = useEdgeStore();
const isConnected = isInputConnected(nodeId, cleanUpHandleId(handleId));
if (uiType === BlockUIType.INPUT) {
return (
<div className="rounded-3xl border border-gray-200 p-2 pl-4 text-xs text-gray-500 hover:cursor-not-allowed">
Select files when you run the graph
<div className="flex flex-col gap-2">
{!isConnected && (
<div className="rounded-3xl border border-gray-200 p-2 pl-4 text-xs text-gray-500 hover:cursor-not-allowed">
Select files when you run the graph
</div>
)}
</div>
);
}
return (
<div>
<GoogleDrivePickerInput
config={config}
value={formData}
onChange={(value) => onChange(value, fieldPathId.path)}
className={uiOptions.className}
showRemoveButton={true}
<div className="flex flex-col gap-2">
<TitleFieldTemplate
id={titleId(fieldPathId.$id)}
title={schema.title || ""}
required={false}
schema={schema}
uiSchema={updatedUiSchema}
registry={registry}
/>
{!isConnected && (
<GoogleDrivePickerInput
config={config}
value={formData}
onChange={(value) => onChange(value, fieldPathId.path)}
className={uiOptions.className}
showRemoveButton={true}
/>
)}
</div>
);
};

View File

@@ -44,9 +44,19 @@ export function generateUiSchemaForCustomFields(
const customFieldId = findCustomFieldId(propSchema);
if (customFieldId) {
const hasAnyOfOrOneOf =
propSchema.anyOf || propSchema.oneOf ? true : false;
uiSchema[key] = {
...(uiSchema[key] as object),
"ui:field": customFieldId,
...(hasAnyOfOrOneOf && {
"ui:options": {
...((uiSchema[key] as Record<string, unknown>)?.[
"ui:options"
] as object),
fieldReplacesAnyOrOneOf: true,
},
}),
};
// Skip further processing for custom fields
continue;