{Object.entries(dialogTemplate).map(([fieldKey, fieldValue]) => (
-
+
{getCustomParameterTitle({
title:
(fieldValue as { display_name: string })?.display_name ??
"",
nodeId,
isFlexView: false,
+ required:
+ (fieldValue as { required: boolean })?.required ?? false,
})}
= ({
name={fieldKey}
nodeId={nodeId}
templateData={fieldValue as Partial}
- templateValue={fieldValues[fieldKey] || ""}
+ templateValue={(fieldValue as { value: string })?.value ?? ""}
editNode={false}
handleNodeClass={() => {}}
nodeClass={dialogNodeData}
- disabled={false}
- placeholder=""
+ disabled={
+ (fieldValue as { disabled: boolean })?.disabled ?? false
+ }
+ placeholder={
+ (fieldValue as { placeholder: string })?.placeholder ?? ""
+ }
isToolMode={false}
/>
diff --git a/src/frontend/src/components/core/dropdownComponent/index.tsx b/src/frontend/src/components/core/dropdownComponent/index.tsx
index 2332f7d16..0ad0623ef 100644
--- a/src/frontend/src/components/core/dropdownComponent/index.tsx
+++ b/src/frontend/src/components/core/dropdownComponent/index.tsx
@@ -3,7 +3,10 @@ import { usePostTemplateValue } from "@/controllers/API/queries/nodes/use-post-t
import NodeDialog from "@/CustomNodes/GenericNode/components/NodeDialogComponent";
import { mutateTemplate } from "@/CustomNodes/helpers/mutate-template";
import useAlertStore from "@/stores/alertStore";
-import { getStatusColor } from "@/utils/stringManipulation";
+import {
+ convertStringToHTML,
+ getStatusColor,
+} from "@/utils/stringManipulation";
import { PopoverAnchor } from "@radix-ui/react-popover";
import Fuse from "fuse.js";
import { cloneDeep } from "lodash";
@@ -66,7 +69,8 @@ export default function Dropdown({
const fuse = new Fuse(validOptions, { keys: ["name", "value"] });
const PopoverContentDropdown =
children || editNode ? PopoverContent : PopoverContentWithoutPortal;
- const { nodeClass, nodeId, handleNodeClass, tooltip } = baseInputProps;
+ const { nodeClass, nodeId, handleNodeClass, tooltip, helperText } =
+ baseInputProps;
// API and store hooks
const postTemplateValue = usePostTemplateValue({
@@ -79,7 +83,7 @@ export default function Dropdown({
// Utility functions
const filterMetadataKeys = (
metadata: Record
= {},
- excludeKeys: string[] = ["api_endpoint", "icon", "status"],
+ excludeKeys: string[] = ["api_endpoint", "icon", "status", "org_id"],
) => {
return Object.fromEntries(
Object.entries(metadata).filter(([key]) => !excludeKeys.includes(key)),
@@ -161,7 +165,7 @@ export default function Dropdown({
);
const renderTriggerButton = () => (
-
+
)}
- {value && filteredOptions.includes(value) ? value : placeholderName}{" "}
+ {value && filteredOptions.includes(value)
+ ? value
+ : placeholderName}{" "}
-
+ {helperText && (
+
+ {convertStringToHTML(helperText)}
+
+ )}
+
);
const renderSearchInput = () => (
@@ -302,7 +313,7 @@ export default function Dropdown({
data-testid={`${option}-${index}-option`}
>
- {optionsMetaData && optionsMetaData.length > 0 && (
+ {optionsMetaData?.[index]?.icon && (
): JSX.Element {
const min = -Infinity;
// Clear component state
@@ -59,6 +60,9 @@ export default function IntComponent({
);
};
+ const DISABLED_INPUT_CLASS =
+ "cursor-default bg-secondary border-border border rounded-md py-2 px-3 text-sm text-input placeholder:text-input";
+
const handleNumberChange = (newValue) => {
handleOnNewValue({ value: Number(newValue) });
};
@@ -87,26 +91,35 @@ export default function IntComponent({
min={getMinValue()}
max={getMaxValue()}
onChange={handleNumberChange}
+ isDisabled={disabled || readonly}
value={value ?? ""}
>
handleKeyDown(event, value, "")}
onInput={handleInputChange}
- disabled={disabled}
+ disabled={disabled || readonly}
placeholder={editNode ? "Integer number" : "Type an integer number"}
data-testid={id}
ref={inputRef}
/>
-
+
-
+
= {
handleOnNewValue: handleOnNewValueType;
disabled: boolean;
nodeClass?: APIClassType;
+ helperText?: string;
handleNodeClass?: (value: any, code?: string, type?: string) => void;
readonly?: boolean;
placeholder?: string;
diff --git a/src/frontend/src/customization/components/custom-parameter.tsx b/src/frontend/src/customization/components/custom-parameter.tsx
index 521f532ab..eeff957c4 100644
--- a/src/frontend/src/customization/components/custom-parameter.tsx
+++ b/src/frontend/src/customization/components/custom-parameter.tsx
@@ -53,10 +53,12 @@ export function getCustomParameterTitle({
title,
nodeId,
isFlexView,
+ required,
}: {
title: string;
nodeId: string;
isFlexView: boolean;
+ required: boolean;
}) {
return (
@@ -66,6 +68,7 @@ export function getCustomParameterTitle({
>
{title}
+ {required && *}
);
}
diff --git a/src/frontend/src/utils/stringManipulation.ts b/src/frontend/src/utils/stringManipulation.ts
index 3ed01a6c1..115fee42f 100644
--- a/src/frontend/src/utils/stringManipulation.ts
+++ b/src/frontend/src/utils/stringManipulation.ts
@@ -1,3 +1,5 @@
+import DOMPurify from "dompurify";
+import React from "react";
import { FieldParserType } from "../types/api";
function toSnakeCase(str: string): string {
@@ -134,3 +136,13 @@ export const getStatusColor = (status: string): string => {
return "";
};
+
+export const convertStringToHTML = (htmlString: string): JSX.Element => {
+ return React.createElement("span", {
+ dangerouslySetInnerHTML: { __html: sanitizeHTML(htmlString) },
+ });
+};
+
+export const sanitizeHTML = (htmlString: string): string => {
+ return DOMPurify.sanitize(htmlString);
+};
diff --git a/src/frontend/tests/core/features/playground.spec.ts b/src/frontend/tests/core/features/playground.spec.ts
index 58cf2c526..db723bd3d 100644
--- a/src/frontend/tests/core/features/playground.spec.ts
+++ b/src/frontend/tests/core/features/playground.spec.ts
@@ -28,14 +28,11 @@ test(
await page
.getByTestId("outputsChat Output")
- .dragTo(page.locator('//*[@id="react-flow-id"]'));
- await page.mouse.up();
- await page.mouse.down();
+ .hover()
+ .then(async () => {
+ await page.getByTestId("add-component-button-chat-output").click();
+ });
- await adjustScreenView(page);
-
- await page.getByTestId("zoom_out").click();
- await page.getByTestId("zoom_out").click();
await page.getByTestId("zoom_out").click();
await page.getByTestId("zoom_out").click();
@@ -47,13 +44,9 @@ test(
await page
.getByTestId("inputsChat Input")
- .dragTo(page.locator('//*[@id="react-flow-id"]'));
- await page.mouse.up();
- await page.mouse.down();
-
- await page.waitForSelector('[data-testid="fit_view"]', {
- timeout: 100000,
- });
+ .dragTo(page.locator('//*[@id="react-flow-id"]'), {
+ targetPosition: { x: 100, y: 100 },
+ });
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("text output");
@@ -63,92 +56,21 @@ test(
await page
.getByTestId("outputsText Output")
- .dragTo(page.locator('//*[@id="react-flow-id"]'));
- await page.mouse.up();
- await page.mouse.down();
+ .dragTo(page.locator('//*[@id="react-flow-id"]'), {
+ targetPosition: { x: 300, y: 300 },
+ });
await adjustScreenView(page);
- await page.getByTestId("zoom_out").click();
- await page.getByTestId("zoom_out").click();
- await page.getByTestId("zoom_out").click();
- await page.getByTestId("zoom_out").click();
+ await page
+ .getByTestId("handle-chatinput-noshownode-message-source")
+ .click();
- const elementsChatInput = await page
- .locator('[data-testid="handle-chatinput-noshownode-message-source"]')
- .all();
+ await page.getByTestId("handle-textoutput-shownode-text-left").click();
- let visibleElementHandle;
+ await page.getByTestId("handle-textoutput-shownode-message-right").click();
+ await page.getByTestId("handle-chatoutput-noshownode-text-target").click();
- for (const element of elementsChatInput) {
- if (await element.isVisible()) {
- visibleElementHandle = element;
- break;
- }
- }
-
- // Click and hold on the first element
- await visibleElementHandle.hover();
- await page.mouse.down();
-
- // Move to the second element
-
- const elementsTextOutput = await page
- .getByTestId("handle-textoutput-shownode-text-left")
- .all();
-
- for (const element of elementsTextOutput) {
- if (await element.isVisible()) {
- visibleElementHandle = element;
- break;
- }
- }
-
- await visibleElementHandle.hover();
-
- // Release the mouse
- await page.mouse.up();
-
- await page.getByTestId("fit_view").click();
- await page.getByTestId("fit_view").click();
- await page.getByTestId("fit_view").click();
- await page.getByTestId("fit_view").click();
-
- //
-
- const elementsTextOutputRight = await page
- .locator('[data-testid="handle-textoutput-shownode-message-right"]')
- .all();
-
- for (const element of elementsTextOutputRight) {
- if (await element.isVisible()) {
- visibleElementHandle = element;
- break;
- }
- }
-
- // Click and hold on the first element
- await visibleElementHandle.hover();
- await page.mouse.down();
-
- //
- const elementsChatOutput = await page
- .getByTestId("handle-chatoutput-noshownode-text-target")
- .all();
-
- for (const element of elementsChatOutput) {
- if (await element.isVisible()) {
- visibleElementHandle = element;
- break;
- }
- }
-
- await visibleElementHandle.hover();
-
- // Release the mouse
- await page.mouse.up();
-
- await page.getByTestId("fit_view").click();
await page.getByText("Playground", { exact: true }).last().click();
await page.waitForSelector('[data-testid="input-chat-playground"]', {
timeout: 100000,
diff --git a/src/frontend/tests/core/regression/generalBugs-prompt.spec.ts b/src/frontend/tests/core/regression/generalBugs-prompt.spec.ts
index 5d20b440a..f3dfdc704 100644
--- a/src/frontend/tests/core/regression/generalBugs-prompt.spec.ts
+++ b/src/frontend/tests/core/regression/generalBugs-prompt.spec.ts
@@ -42,7 +42,7 @@ test(
await page.getByText("Edit Prompt", { exact: true }).click();
- await page.getByTestId("edit-prompt-sanitized").click();
+ await page.getByTestId("edit-prompt-sanitized").last().click();
await page
.getByTestId("modal-promptarea_prompt_template")
@@ -52,18 +52,19 @@ test(
let promptSanitizedText = await page
.getByTestId("edit-prompt-sanitized")
+ .last()
.textContent();
expect(promptSanitizedText).toBe("THIS IS A TEST");
- await page.getByTestId("edit-prompt-sanitized").click();
+ await page.getByTestId("edit-prompt-sanitized").last().click();
await page.keyboard.press(`ControlOrMeta+a`);
await page.keyboard.press("Backspace");
await page.getByText("Edit Prompt", { exact: true }).click();
- await page.getByTestId("edit-prompt-sanitized").click();
+ await page.getByTestId("edit-prompt-sanitized").last().click();
await page
.getByTestId("modal-promptarea_prompt_template")
@@ -73,6 +74,7 @@ test(
promptSanitizedText = await page
.getByTestId("edit-prompt-sanitized")
+ .last()
.textContent();
expect(promptSanitizedText).toBe("THIS IS A TEST 2");
diff --git a/src/frontend/tests/core/unit/chatInputOutput.spec.ts b/src/frontend/tests/core/unit/chatInputOutput.spec.ts
index 5f7d1fe02..f4d6ff4ad 100644
--- a/src/frontend/tests/core/unit/chatInputOutput.spec.ts
+++ b/src/frontend/tests/core/unit/chatInputOutput.spec.ts
@@ -20,9 +20,10 @@ test("chat_io_teste", { tag: ["@release", "@workspace"] }, async ({ page }) => {
await page
.getByTestId("outputsChat Output")
- .dragTo(page.locator('//*[@id="react-flow-id"]'));
- await page.mouse.up();
- await page.mouse.down();
+ .hover()
+ .then(async () => {
+ await page.getByTestId("add-component-button-chat-output").click();
+ });
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("chat input");
@@ -32,59 +33,19 @@ test("chat_io_teste", { tag: ["@release", "@workspace"] }, async ({ page }) => {
await page
.getByTestId("inputsChat Input")
- .dragTo(page.locator('//*[@id="react-flow-id"]'));
- await page.mouse.up();
- await page.mouse.down();
+ .dragTo(page.locator('//*[@id="react-flow-id"]'), {
+ targetPosition: { x: 100, y: 100 },
+ });
await page.waitForSelector('[data-testid="fit_view"]', {
timeout: 100000,
});
await page.getByTestId("fit_view").click();
- await page.getByTestId("zoom_out").click();
- await page.getByTestId("zoom_out").click();
- await page.getByTestId("zoom_out").click();
- await page.getByTestId("zoom_out").click();
- await page.getByTestId("zoom_out").click();
- await page.getByTestId("zoom_out").click();
- await page.getByTestId("zoom_out").click();
- const elementsChatInput = await page
- .locator('[data-testid="handle-chatinput-noshownode-message-source"]')
- .all();
+ await page.getByTestId("handle-chatinput-noshownode-message-source").click();
+ await page.getByTestId("handle-chatoutput-noshownode-text-target").click();
- let visibleElementHandle;
-
- for (const element of elementsChatInput) {
- if (await element.isVisible()) {
- visibleElementHandle = element;
- break;
- }
- }
-
- // Click and hold on the first element
- await visibleElementHandle.hover();
- await page.mouse.down();
-
- // Move to the second element
-
- const elementsChatOutput = await page
- .getByTestId("handle-chatoutput-noshownode-text-target")
- .all();
-
- for (const element of elementsChatOutput) {
- if (await element.isVisible()) {
- visibleElementHandle = element;
- break;
- }
- }
-
- await visibleElementHandle.hover();
-
- // Release the mouse
- await page.mouse.up();
-
- await page.getByTestId("fit_view").click();
await page.getByText("Playground", { exact: true }).last().click();
await page.waitForSelector('[data-testid="input-chat-playground"]', {
timeout: 100000,
diff --git a/src/frontend/tests/extended/integrations/chatInputOutputUser-shard-1.spec.ts b/src/frontend/tests/extended/integrations/chatInputOutputUser-shard-1.spec.ts
index ca239b121..f0715cfd4 100644
--- a/src/frontend/tests/extended/integrations/chatInputOutputUser-shard-1.spec.ts
+++ b/src/frontend/tests/extended/integrations/chatInputOutputUser-shard-1.spec.ts
@@ -95,17 +95,13 @@ test(
.getByTestId("inputlist_str_urls_0")
.fill("https://www.example.com");
- // Connect text output to first chat output
- const urlTextOutput = await page
- .getByTestId("handle-url-shownode-text-right")
- .nth(0);
- await urlTextOutput.hover();
- await page.mouse.down();
- const firstChatInput = await page
+ await page.getByTestId("handle-url-shownode-text-right").nth(0).click();
+ await page.waitForTimeout(600);
+
+ await page
.getByTestId("handle-chatoutput-noshownode-text-target")
- .nth(0);
- await firstChatInput.hover();
- await page.mouse.up();
+ .nth(0)
+ .click();
// Run flow and test text output inspection
await page.getByTestId("button_run_url").first().click();
@@ -120,16 +116,16 @@ test(
await page.keyboard.press("Escape");
// Connect dataframe output to second chat output
- const urlDataframeOutput = await page
+ await page
.getByTestId("handle-url-shownode-dataframe-right")
- .nth(0);
- await urlDataframeOutput.hover();
- await page.mouse.down();
- const secondChatInput = await page
+ .nth(0)
+ .click();
+ await page.waitForTimeout(600);
+ await page
.getByTestId("handle-chatoutput-noshownode-text-target")
- .nth(1);
- await secondChatInput.hover();
- await page.mouse.up();
+ .nth(1)
+ .click();
+ await page.waitForTimeout(600);
// Run and verify text output is still shown
await page.getByTestId("button_run_url").first().click();
@@ -143,6 +139,7 @@ test(
},
);
await page.keyboard.press("Escape");
+ await page.waitForTimeout(600);
// Remove text connection
const textEdge = await page.locator(".react-flow__edge").first();
@@ -162,7 +159,7 @@ test(
},
);
await page.keyboard.press("Escape");
-
+ await page.waitForTimeout(600);
// Remove all connections
const dataEdge = await page.locator(".react-flow__edge").first();
await dataEdge.click();