Skip to content

Commit

Permalink
feat: Generic Callback Dialog Input for Custom Component (#6236)
Browse files Browse the repository at this point in the history
* force dialog

* Reimplement backend dialog

* Update astradb.py

* Clean up dropdown options

* Remove unused import

* [autofix.ci] apply automated fixes

* Update astradb.py

* Ruff fixes

* Update Vector Store RAG.json

* [autofix.ci] apply automated fixes

* fix: Conditionally render custom option dialog in dropdown

* ✨ (NodeDialogComponent/index.tsx): Add support for passing 'name' prop to NodeDialog component to improve customization and flexibility
📝 (NodeDialogComponent/index.tsx): Update comments and remove unused import to improve code readability and maintainability
🔧 (dropdownComponent/index.tsx): Pass 'name' prop to Dropdown component to enhance customization and flexibility

* ✨ Refactor NodeDialog component to improve state management and payload handling

* Update astradb.py

* [autofix.ci] apply automated fixes

* ✨ Enhance NodeDialog and Dropdown components with improved payload handling and type safety

* Add DB creation functionality

* First version of create

* Update astradb.py

* Fix ruff errors

* Update Vector Store RAG.json

* [autofix.ci] apply automated fixes

* Update astradb.py

* [autofix.ci] apply automated fixes

* Update astradb.py

* [autofix.ci] apply automated fixes

* Update astradb.py

* Update astradb.py

* Update astradb.py

* Update Vector Store RAG.json

* [autofix.ci] apply automated fixes

* Update astradb.py

* [autofix.ci] apply automated fixes

* feat: Enhance dropdown and node dialog with loading states and improved UX

* refactor: Improve error handling in NodeDialog component

* refactor: Update default excluded keys in dropdown metadata filter

* [autofix.ci] apply automated fixes

* refactor: Update Vector Store RAG starter project JSON with formatting and connection ID corrections

* Hide fields that aren't relevant yet

* [autofix.ci] apply automated fixes

* Update Vector Store RAG.json

* [autofix.ci] apply automated fixes

* Update astradb.py

* feat: Improve dropdown component with loading states and enhanced UX

* Update astradb.py

* [autofix.ci] apply automated fixes

* Update astradb.py

* Simon feedback

* [autofix.ci] apply automated fixes

* feat: Enhance dropdown and UI components with status indicators and loading states

* refactor: Update dropdown metadata filtering to exclude 'icon' key

* fix: Conditionally render dropdown icon when available

* fix: Improve dropdown icon rendering with null checks

* chore: Remove debug console log in dropdown component

* Add support for icons in the dropdowns

* Update astradb.py

* Update Vector Store RAG.json

* [autofix.ci] apply automated fixes

* feat: Enhance dropdown status display and color handling

* feat: Add auto-close functionality to node dialog and expand status color handling

* feat: Add real-time template refresh for node dialog fields

* refactor: Improve node dialog component state management and naming

* Async for create collection

* [autofix.ci] apply automated fixes

* Dynamic provider list generation

* Update astradb.py

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* Update astradb.py

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Eric Hare <[email protected]>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: cristhianzl <[email protected]>
  • Loading branch information
4 people authored Feb 15, 2025
1 parent b8d2e63 commit c902fb9
Show file tree
Hide file tree
Showing 11 changed files with 1,310 additions and 534 deletions.
456 changes: 315 additions & 141 deletions src/backend/base/langflow/components/vectorstores/astradb.py

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -8,105 +8,184 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { usePostTemplateValue } from "@/controllers/API/queries/nodes/use-post-template-value";
import { getCustomParameterTitle } from "@/customization/components/custom-parameter";
import useHandleOnNewValue from "@/CustomNodes/hooks/use-handle-new-value";
import { mutateTemplate } from "@/CustomNodes/helpers/mutate-template";
import useAlertStore from "@/stores/alertStore";
import useFlowStore from "@/stores/flowStore";
import { InputFieldType } from "@/types/api";
import { cloneDeep } from "lodash";
import { APIClassType, InputFieldType } from "@/types/api";
import { useState } from "react";

export const NodeDialog = ({
open,
onClose,
dialogInputs,
nodeId,
}: {
interface NodeDialogProps {
open: boolean;
onClose: () => void;
dialogInputs: any;
nodeId: string;
name: string;
nodeClass: APIClassType;
}

interface ValueObject {
value: string;
}

export const NodeDialog: React.FC<NodeDialogProps> = ({
open,
onClose,
dialogInputs,
nodeId,
name,
nodeClass,
}) => {
const [isLoading, setIsLoading] = useState(false);
const [fieldValues, setFieldValues] = useState<Record<string, string>>({});

const nodes = useFlowStore((state) => state.nodes);
const setNode = useFlowStore((state) => state.setNode);
const setErrorData = useAlertStore((state) => state.setErrorData);

const handleNewValue = (value: string, key: string) => {
let rawValue = value;
const postTemplateValue = usePostTemplateValue({
parameterId: name,
nodeId: nodeId,
node: nodeClass,
});

if (typeof value === "object" && value) {
rawValue = (value as { value: string }).value;
}
const { fields, functionality: submitButtonText } = dialogInputs || {};
const dialogNodeData = fields?.data?.node;
const dialogTemplate = dialogNodeData?.template || {};

const setNodeClass = (newNode: APIClassType) => {
const targetNode = nodes.find((node) => node.id === nodeId);
if (!targetNode) return;

const template = cloneDeep(dialogInputs?.fields?.data?.node?.template);
template[key].value = value;
targetNode.data.node = newNode;
setNode(nodeId, targetNode);
};

const handleErrorData = (newState: {
title: string;
list?: Array<string>;
}) => {
setErrorData(newState);
setIsLoading(false);
};

const updateFieldValue = (value: string | ValueObject, fieldKey: string) => {
const newValue = typeof value === "object" ? value.value : value;
const targetNode = nodes.find((node) => node.id === nodeId);
if (!targetNode || !name) return;

const newNode = cloneDeep(nodes.find((node) => node.id === nodeId));
if (newNode) {
const template = newNode.data.node.template;
const databaseFields = template.database_name.dialog_inputs.fields;
const nodeTemplate = databaseFields.data.node.template;
targetNode.data.node.template[name].dialog_inputs.fields.data.node.template[
fieldKey
].value = newValue;
setNode(nodeId, targetNode);
setFieldValues((prev) => ({ ...prev, [fieldKey]: newValue }));

if (dialogTemplate[fieldKey].real_time_refresh) {
mutateTemplate(
{ [fieldKey]: newValue },
nodeClass,
setNodeClass,
postTemplateValue,
handleErrorData,
name,
);
}
};

nodeTemplate[key].value = rawValue;
const handleCloseDialog = () => {
setFieldValues({});
const targetNode = nodes.find((node) => node.id === nodeId);
if (targetNode && name) {
const nodeTemplate = targetNode.data.node.template;
Object.keys(dialogTemplate).forEach((key) => {
nodeTemplate[name].dialog_inputs.fields.data.node.template[key].value =
"";
});
setNode(nodeId, targetNode);
}
setNode(nodeId, newNode!);
setIsLoading(false);
onClose();
};

const handleSubmitDialog = async () => {
setIsLoading(true);

await mutateTemplate(
fieldValues,
nodeClass,
setNodeClass,
postTemplateValue,
handleErrorData,
name,
handleCloseDialog,
nodeClass.tool_mode,
);

setTimeout(() => {
handleCloseDialog();
}, 5000);
};

// Render
return (
<Dialog open={open} onOpenChange={onClose}>
<Dialog open={open} onOpenChange={handleCloseDialog}>
<DialogContent className="max-w-[700px] gap-2 px-1 py-6">
<DialogHeader className="px-5 pb-3">
<DialogTitle>
<div className="flex items-center">
<span className="pb-2">
{dialogInputs.fields?.data?.node?.display_name}
</span>
<span className="pb-2">{dialogNodeData?.display_name}</span>
</div>
</DialogTitle>
<DialogDescription>
<div className="flex items-center gap-2">
{dialogInputs.fields?.data?.node?.description}
{dialogNodeData?.description}
</div>
</DialogDescription>
</DialogHeader>

<div className="flex flex-col gap-5 overflow-y-auto px-5">
{Object.entries(dialogInputs?.fields?.data?.node?.template ?? {}).map(
([key, value]) => (
<div key={key}>
<div>
{getCustomParameterTitle({
title:
dialogInputs?.fields?.data?.node?.template[key]
.display_name ?? "",
nodeId,
isFlexView: false,
})}
</div>
<ParameterRenderComponent
handleOnNewValue={(value: string) =>
handleNewValue(value, key)
}
name={key}
nodeId={nodeId}
templateData={value as Partial<InputFieldType>}
templateValue={
dialogInputs?.fields?.data?.node?.template[key].value
}
editNode={false}
handleNodeClass={() => {}}
nodeClass={dialogInputs.fields?.data?.node}
disabled={false}
placeholder=""
isToolMode={false}
/>
{Object.entries(dialogTemplate).map(([fieldKey, fieldValue]) => (
<div key={fieldKey}>
<div>
{getCustomParameterTitle({
title:
(fieldValue as { display_name: string })?.display_name ??
"",
nodeId,
isFlexView: false,
})}
</div>
),
)}
<ParameterRenderComponent
handleOnNewValue={(value: string) =>
updateFieldValue(value, fieldKey)
}
name={fieldKey}
nodeId={nodeId}
templateData={fieldValue as Partial<InputFieldType>}
templateValue={fieldValues[fieldKey] || ""}
editNode={false}
handleNodeClass={() => {}}
nodeClass={dialogNodeData}
disabled={false}
placeholder=""
isToolMode={false}
/>
</div>
))}
</div>

<DialogFooter className="px-5 pt-3">
<Button variant="outline" onClick={onClose}>
<Button variant="secondary" onClick={handleCloseDialog}>
Cancel
</Button>
<Button>{dialogInputs.functionality}</Button>
<Button
variant="default"
onClick={handleSubmitDialog}
loading={isLoading}
>
{submitButtonText}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
Expand Down
21 changes: 21 additions & 0 deletions src/frontend/src/components/common/fetchIconComponent/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import ForwardedIconComponent from "../genericIconComponent";

const FetchIconComponent = ({
source,
name,
}: {
source: string;
name: string;
}) => {
return (
<div>
{source ? (
<img src={source} alt={name} />
) : (
<ForwardedIconComponent name="Unknown" />
)}
</div>
);
};

export default FetchIconComponent;
23 changes: 23 additions & 0 deletions src/frontend/src/components/common/loadingTextComponent/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useEffect, useState } from "react";

const LoadingTextComponent = ({ text }: { text: string }) => {
const [dots, setDots] = useState(".");

useEffect(() => {
const interval = setInterval(() => {
setDots((prevDots) => (prevDots === "..." ? "" : `${prevDots}.`));
}, 300);

return () => {
clearInterval(interval);
};
}, []);

if (!text) {
return null;
}

return <span>{`${text}${dots}`}</span>;
};

export default LoadingTextComponent;
Loading

0 comments on commit c902fb9

Please sign in to comment.