Merge branch 'form_io' of github.com:logspace-ai/langflow into form_io

This commit is contained in:
Lucas Oliveira 2023-07-06 15:13:05 -03:00
commit 5d417fc11d
9 changed files with 318 additions and 82 deletions

View file

@ -71,30 +71,68 @@ def validate_prompt(template: str):
except Exception as exc:
raise ValueError(str(exc)) from exc
# if len(input_variables) > 1:
# # If there's more than one input variable
return input_variables
def check_input_variables(input_variables: list):
invalid_chars = []
fixed_variables = []
wrong_variables = set()
empty_variables = []
for variable in input_variables:
new_var = variable
# if variable is empty, then we should add that to the wrong variables
if not variable:
empty_variables.append(variable)
continue
# if variable starts with a number we should add that to the invalid chars
# and wrong variables
if variable[0].isdigit():
invalid_chars.append(variable[0])
new_var = new_var.replace(variable[0], "")
wrong_variables.add(variable)
for char in INVALID_CHARACTERS:
if char in variable:
invalid_chars.append(char)
new_var = new_var.replace(char, "")
wrong_variables.add(variable)
fixed_variables.append(new_var)
if new_var != variable:
input_variables.remove(variable)
input_variables.append(new_var)
# if new_var != variable and new_var not in input_variables:
# input_variables.remove(variable)
# input_variables.append(new_var)
# If any of the input_variables is not in the fixed_variables, then it means that
# there are invalid characters in the input_variables
if any(var not in fixed_variables for var in input_variables):
raise ValueError(
f"Invalid input variables: {input_variables}. Please, use something like {fixed_variables} instead."
)
if any(var not in fixed_variables for var in input_variables):
error_message = build_error_message(
input_variables,
invalid_chars,
wrong_variables,
fixed_variables,
empty_variables,
)
raise ValueError(error_message)
return input_variables
def build_error_message(
input_variables, invalid_chars, wrong_variables, fixed_variables, empty_variables
):
input_variables_str = ", ".join([f"'{var}'" for var in input_variables])
error_string = f"Invalid input variables: {input_variables_str}."
if wrong_variables and invalid_chars:
", ".join([f"'{var}'" for var in wrong_variables])
invalid_chars_str = ", ".join([f"'{char}'" for char in invalid_chars])
error_string += (
f" Please, remove the invalid characters: {invalid_chars_str}"
" from the variables: {wrong_variables_str}."
)
elif empty_variables:
error_string += f" There are {len(empty_variables)} empty variable{'s' if len(empty_variables) > 1 else ''}."
elif len(set(fixed_variables)) != len(fixed_variables):
error_string += " There are duplicate variables."
return error_string

View file

@ -37,7 +37,7 @@
"base64-js": "^1.5.1",
"class-variance-authority": "^0.6.0",
"clsx": "^1.2.1",
"dompurify": "^3.0.3",
"dompurify": "^3.0.4",
"esbuild": "^0.17.18",
"lodash": "^4.17.21",
"lucide-react": "^0.233.0",
@ -5142,9 +5142,9 @@
}
},
"node_modules/dompurify": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.3.tgz",
"integrity": "sha512-axQ9zieHLnAnHh0sfAamKYiqXMJAVwu+LM/alQ7WDagoWessyWvMSFyW65CqF3owufNu8HBcE4cM2Vflu7YWcQ=="
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.4.tgz",
"integrity": "sha512-ae0mA+Qiqp6C29pqZX3fQgK+F91+F7wobM/v8DRzDqJdZJELXiFUx4PP4pK/mzUS0xkiSEx3Ncd9gr69jg3YsQ=="
},
"node_modules/electron-to-chromium": {
"version": "1.4.440",

View file

@ -32,7 +32,7 @@
"base64-js": "^1.5.1",
"class-variance-authority": "^0.6.0",
"clsx": "^1.2.1",
"dompurify": "^3.0.3",
"dompurify": "^3.0.4",
"esbuild": "^0.17.18",
"lodash": "^4.17.21",
"lucide-react": "^0.233.0",

View file

@ -1,4 +1,4 @@
import { ShadTooltipProps } from "../../types/components";
import { RadialProgressType, ShadToolTipType } from "../../types/components";
import {
Tooltip,
TooltipContent,
@ -6,29 +6,22 @@ import {
TooltipTrigger,
} from "../ui/tooltip";
const ShadTooltip = ({
delayDuration = 500,
side,
export default function ShadTooltip({
content,
side,
asChild = true,
children,
style,
}: ShadTooltipProps) => {
delayDuration,
}: ShadToolTipType) {
return (
<TooltipProvider>
<Tooltip delayDuration={delayDuration}>
<TooltipTrigger asChild>{children}</TooltipTrigger>
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
<TooltipContent
className={style}
side={side}
avoidCollisions={false}
sticky="always"
>
<TooltipContent side={side} avoidCollisions={false} sticky="always">
{content}
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
};
export default ShadTooltip;
}

View file

@ -523,3 +523,10 @@ export const NOUNS: string[] = [
*
*/
export const USER_PROJECTS_HEADER = "My Collection";
/**
* CSS for highlight HTML
* @constant
*
*/
export const HIGHLIGH_CSS =
"block pl-3 pr-14 py-2 w-full h-full text-sm outline-0 border-0 break-all";

View file

@ -102,7 +102,7 @@ export default function CodeAreaModal({
return (
<Dialog open={true} onOpenChange={setModalOpen}>
<DialogTrigger></DialogTrigger>
<DialogContent className="h-[500px] lg:max-w-[700px]">
<DialogContent className="min-w-[80vw]">
<DialogHeader>
<DialogTitle className="flex items-center">
<span className="pr-2">Edit Code</span>
@ -115,7 +115,7 @@ export default function CodeAreaModal({
<DialogDescription>{CODE_PROMPT_DIALOG_SUBTITLE}</DialogDescription>
</DialogHeader>
<div className="mt-2 flex h-full w-full">
<div className="mt-2 flex h-[60vh] w-full">
<AceEditor
value={code}
mode="python"
@ -129,7 +129,7 @@ export default function CodeAreaModal({
onChange={(value) => {
setCode(value);
}}
className="h-[300px] w-full rounded-lg border-[1px] border-ring custom-scroll "
className="h-full w-full rounded-lg border-[1px] border-gray-300 custom-scroll dark:border-gray-600"
/>
</div>

View file

@ -1,4 +1,4 @@
import { useContext, useRef, useState } from "react";
import { useContext, useRef, useState, useEffect } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
import { darkContext } from "../../contexts/darkContext";
import { postValidatePrompt } from "../../controllers/API";
@ -14,9 +14,31 @@ import {
} from "../../components/ui/dialog";
import { Button } from "../../components/ui/button";
import { Textarea } from "../../components/ui/textarea";
import { PROMPT_DIALOG_SUBTITLE, TEXT_DIALOG_SUBTITLE } from "../../constants";
import {
HIGHLIGH_CSS,
PROMPT_DIALOG_SUBTITLE,
TEXT_DIALOG_SUBTITLE,
} from "../../constants";
import { FileText } from "lucide-react";
import { APIClassType } from "../../types/api";
import {
INVALID_CHARACTERS,
TypeModal,
classNames,
getRandomKeyByssmm,
regexHighlight,
varHighlightHTML,
} from "../../utils";
import { Badge } from "../../components/ui/badge";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "../../components/ui/tooltip";
import ShadTooltip from "../../components/ShadTooltipComponent";
import { set } from "lodash";
import DOMPurify from "dompurify";
export default function GenericModal({
field_name = "",
@ -41,7 +63,10 @@ export default function GenericModal({
const [myModalTitle] = useState(modalTitle);
const [myModalType] = useState(type);
const [open, setOpen] = useState(true);
const [myValue, setMyValue] = useState(value);
const [inputValue, setInputValue] = useState(value);
const [isEdit, setIsEdit] = useState(true);
const [wordsHighlightInvalid, setWordsHighlightInvalid] = useState([]);
const [wordsHighlight, setWordsHighlight] = useState([]);
const { dark } = useContext(darkContext);
const { setErrorData, setSuccessData } = useContext(alertContext);
const { closePopUp, setCloseEdit } = useContext(PopUpContext);
@ -54,10 +79,108 @@ export default function GenericModal({
}
}
function checkVariables(valueToCheck) {
const regex = /\{([^{}]+)\}/g;
const matches = [];
let match;
while ((match = regex.exec(valueToCheck))) {
matches.push(`{${match[1]}}`);
}
let invalid_chars = [];
let fixed_variables = [];
let input_variables = matches;
for (let variable of input_variables) {
let new_var = variable;
for (let char of INVALID_CHARACTERS) {
if (variable.includes(char)) {
invalid_chars.push(new_var);
}
}
fixed_variables.push(new_var);
if (new_var !== variable) {
const index = input_variables.indexOf(variable);
if (index !== -1) {
input_variables.splice(index, 1, new_var);
}
}
}
const filteredWordsHighlight = matches.filter(
(word) => !invalid_chars.includes(word)
);
setWordsHighlightInvalid(invalid_chars);
setWordsHighlight(filteredWordsHighlight);
}
useEffect(() => {
if (type == TypeModal.PROMPT && inputValue && inputValue != "") {
checkVariables(inputValue);
}
}, []);
const coloredContent = (inputValue || "")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(regexHighlight, varHighlightHTML({ name: "$1" }))
.replace(/\n/g, "<br />");
const TextAreaContentView = () => {
return (
<div
className={HIGHLIGH_CSS}
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(coloredContent) }}
suppressContentEditableWarning={true}
onClick={() => {
setIsEdit(true);
}}
/>
);
};
function validatePrompt(closeModal: boolean) {
postValidatePrompt(field_name, inputValue, nodeClass)
.then((apiReturn) => {
if (apiReturn.data) {
setNodeClass(apiReturn.data.frontend_node);
setModalOpen(closeModal);
let inputVariables = apiReturn.data.input_variables;
if (inputVariables.length === 0) {
setIsEdit(true);
setErrorData({
title:
"The template you are attempting to use does not contain any variables for data entry.",
});
} else {
setIsEdit(false);
setSuccessData({
title: "Prompt is ready",
});
setModalOpen(closeModal);
setValue(inputValue);
}
} else {
setIsEdit(true);
setErrorData({
title: "Something went wrong, please try again",
});
}
})
.catch((error) => {
setIsEdit(true);
return setErrorData({
title: "There is something wrong with this prompt, please review it",
list: [error.response.data.detail],
});
});
}
return (
<Dialog open={true} onOpenChange={setModalOpen}>
<DialogTrigger></DialogTrigger>
<DialogContent className="lg:max-w-[700px]">
<DialogContent className="min-w-[80vw]">
<DialogHeader>
<DialogTitle className="flex items-center">
<span className="pr-2">{myModalTitle}</span>
@ -83,61 +206,89 @@ export default function GenericModal({
</DialogDescription>
</DialogHeader>
<div className="mt-2 flex h-full w-full">
<Textarea
ref={ref}
className=" form-primary h-[300px] w-full "
value={myValue}
onChange={(e) => {
setMyValue(e.target.value);
}}
placeholder="Type message here."
/>
{type == TypeModal.PROMPT &&
inputValue &&
inputValue != "" &&
wordsHighlight.length > 0 && (
<>
<div>
<span className="">Variables: </span>
{wordsHighlight.map((word, index) => (
<ShadTooltip
key={getRandomKeyByssmm() + index}
content={word.replace(/[{}]/g, "")}
asChild={false}
delayDuration={1500}
>
<Badge
key={index}
size="lg"
className="m-1 max-w-[40vw] cursor-default truncate p-2.5 text-sm"
>
<div className="relative bottom-[1px]">
<span>
{word.replace(/[{}]/g, "").length > 59
? word.replace(/[{}]/g, "").slice(0, 56) + "..."
: word.replace(/[{}]/g, "")}
</span>
</div>
</Badge>
</ShadTooltip>
))}
</div>
</>
)}
<div
className={classNames(
!isEdit ? "rounded-lg border" : "",
"flex h-[60vh] w-full"
)}
>
{type == TypeModal.PROMPT && isEdit ? (
<Textarea
ref={ref}
className="form-input h-full w-full rounded-lg border-gray-300 focus-visible:ring-1 dark:border-gray-700 dark:bg-gray-900 dark:text-white"
value={inputValue}
onBlur={() => {
blur();
validatePrompt(true);
}}
autoFocus
onChange={(e) => {
setInputValue(e.target.value);
checkVariables(e.target.value);
}}
placeholder="Type message here."
/>
) : type == TypeModal.PROMPT && !isEdit ? (
<TextAreaContentView />
) : type != TypeModal.PROMPT ? (
<Textarea
ref={ref}
className="form-input h-full w-full rounded-lg border-gray-300 focus-visible:ring-1 dark:border-gray-700 dark:bg-gray-900 dark:text-white"
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value);
}}
placeholder="Type message here."
/>
) : (
<></>
)}
</div>
<DialogFooter>
<Button
className="mt-3"
onClick={() => {
setValue(myValue);
setValue(inputValue);
switch (myModalType) {
case 1:
setModalOpen(false);
break;
case 2:
console.log("postValidatePrompt");
postValidatePrompt(field_name, myValue, nodeClass)
.then((apiReturn) => {
if (apiReturn.data) {
setNodeClass(apiReturn.data.frontend_node);
setModalOpen(false);
let inputVariables = apiReturn.data.input_variables;
if (inputVariables.length === 0) {
setErrorData({
title:
"The template you are attempting to use does not contain any variables for data entry.",
});
} else {
setSuccessData({
title: "Prompt is ready",
});
setModalOpen(false);
setValue(myValue);
}
} else {
setErrorData({
title: "Something went wrong, please try again",
});
}
})
.catch((error) => {
return setErrorData({
title:
"There is something wrong with this prompt, please review it",
list: [error.response.data.detail],
});
});
validatePrompt(false);
break;
default:

View file

@ -141,3 +141,22 @@ export type ShadTooltipProps = {
children: ReactNode;
style?: string;
};
export type ShadToolTipType = {
content?: string;
side?: "top" | "right" | "bottom" | "left";
asChild?: boolean;
children?: ReactElement;
delayDuration?: number;
};
export type TextHighlightType = {
value?: string;
side?: "top" | "right" | "bottom" | "left";
asChild?: boolean;
children?: ReactElement;
delayDuration?: number;
};
export interface IVarHighlightType {
name: string;
}

View file

@ -49,6 +49,7 @@ import {
import { SupabaseIcon } from "./icons/supabase";
import { MongoDBIcon } from "./icons/MongoDB";
import { VertexAIIcon } from "./icons/VertexAI";
import { IVarHighlightType } from "./types/components";
export function classNames(...classes: Array<string>) {
return classes.filter(Boolean).join(" ");
@ -1006,3 +1007,30 @@ export function getRandomKeyByssmm(): string {
const milliseconds = String(now.getMilliseconds()).padStart(3, "0");
return seconds + milliseconds + Math.abs(Math.floor(Math.random() * 10001));
}
export const INVALID_CHARACTERS = [
" ",
",",
".",
":",
";",
"!",
"?",
"/",
"\\",
"(",
")",
"[",
"]",
];
export const regexHighlight = /\{([^}]+)\}/g;
export const varHighlightHTML = ({ name }: IVarHighlightType) => {
const html = `<div class="inline-flex items-center justify-center rounded-md font-medium text-primary bg-muted pb-1">
<span class='opacity-60 pl-1'>{</span>
<span>${name}</span>
<span class='opacity-60 pr-1'>}</span>
</div>`;
return html;
};