Merge branch 'dev' into feat/slack-directory-loader

This commit is contained in:
Gabriel Luiz Freitas Almeida 2023-05-29 18:36:32 -03:00 committed by GitHub
commit 8db30348d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 494 additions and 404 deletions

View file

@ -5,6 +5,10 @@ all: help
init:
@echo 'Installing pre-commit hooks'
git config core.hooksPath .githooks
@echo 'Installing backend dependencies'
make install_backend
@echo 'Installing frontend dependencies'
make install_frontend
coverage:
poetry run pytest --cov \

377
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -22,22 +22,22 @@ langflow = "langflow.__main__:main"
[tool.poetry.dependencies]
python = ">=3.9,<3.12"
fastapi = "^0.92.0"
fastapi = "^0.95.0"
uvicorn = "^0.20.0"
beautifulsoup4 = "^4.11.2"
google-search-results = "^2.4.1"
google-api-python-client = "^2.79.0"
typer = "^0.7.0"
gunicorn = "^20.1.0"
langchain = "^0.0.176"
openai = "^0.27.2"
langchain = "^0.0.184"
openai = "^0.27.7"
types-pyyaml = "^6.0.12.8"
dill = "^0.3.6"
pandas = "^1.5.3"
chromadb = "^0.3.21"
huggingface-hub = "^0.13.3"
rich = "^13.3.3"
llama-cpp-python = "0.1.50"
llama-cpp-python = "^0.1.50"
networkx = "^3.1"
unstructured = "^0.5.11"
pypdf = "^3.7.1"
@ -49,12 +49,15 @@ psycopg2-binary = "^2.9.6"
pyarrow = "^11.0.0"
tiktoken = "^0.3.3"
wikipedia = "^1.4.0"
langchain-serve = { version = "^0.0.33", optional = true }
langchain-serve = { version = "^0.0.38", optional = true }
qdrant-client = "^1.2.0"
websockets = "^11.0.3"
weaviate-client = "^3.19.2"
jina = "3.15.2"
sentence-transformers = "^2.2.2"
ctransformers = "^0.2.2"
cohere = "^4.6.0"
[tool.poetry.group.dev.dependencies]
black = "^23.1.0"

View file

@ -120,7 +120,7 @@ def save_binary_file(content: str, file_name: str, accepted_types: list[str]) ->
# Get the destination folder
cache_path = Path(tempfile.gettempdir()) / PREFIX
if content is None:
if not content:
raise ValueError("Please, reload the file in the loader.")
data = content.split(",")[1]
decoded_bytes = base64.b64decode(data)

View file

@ -43,16 +43,19 @@ documentloaders:
- GitbookLoader
- ReadTheDocsLoader
- SlackDirectoryLoader
- NotionDirectoryLoader
embeddings:
- OpenAIEmbeddings
- HuggingFaceEmbeddings
- CohereEmbeddings
llms:
- OpenAI
# - AzureOpenAI
- ChatOpenAI
- HuggingFaceHub
- LlamaCpp
- CTransformers
- Cohere
memories:
- ConversationBufferMemory
- ConversationSummaryMemory
@ -63,6 +66,9 @@ prompts:
- ZeroShotPrompt
textsplitters:
- CharacterTextSplitter
- RecursiveCharacterTextSplitter
- LatexTextSplitter
- PythonCodeTextSplitter
toolkits:
- OpenAPIToolkit
- JsonToolkit
@ -114,7 +120,7 @@ vectorstores:
- Qdrant
- Weaviate
wrappers:
- RequestsWrapper # Wait more tests
- RequestsWrapper
# - ChatPromptTemplate
# - SystemMessagePromptTemplate
# - HumanMessagePromptTemplate

View file

@ -53,30 +53,33 @@ class LangChainTypeCreator(BaseModel, ABC):
signature = self.get_signature(name)
if signature is None:
logger.error(f"Node {name} not loaded")
return None
if isinstance(signature, FrontendNode):
return signature
fields = [
TemplateField(
name=key,
field_type=value["type"],
required=value.get("required", False),
placeholder=value.get("placeholder", ""),
is_list=value.get("list", False),
show=value.get("show", True),
multiline=value.get("multiline", False),
value=value.get("value", None),
suffixes=value.get("suffixes", []),
file_types=value.get("fileTypes", []),
content=value.get("content", None),
if not isinstance(signature, FrontendNode):
fields = [
TemplateField(
name=key,
field_type=value["type"],
required=value.get("required", False),
placeholder=value.get("placeholder", ""),
is_list=value.get("list", False),
show=value.get("show", True),
multiline=value.get("multiline", False),
value=value.get("value", None),
suffixes=value.get("suffixes", []),
file_types=value.get("fileTypes", []),
content=value.get("content", None),
)
for key, value in signature["template"].items()
if key != "_type"
]
template = Template(type_name=name, fields=fields)
signature = self.frontend_node_class(
template=template,
description=signature.get("description", ""),
base_classes=signature["base_classes"],
name=name,
)
for key, value in signature["template"].items()
if key != "_type"
]
template = Template(type_name=name, fields=fields)
return self.frontend_node_class(
template=template,
description=signature.get("description", ""),
base_classes=signature["base_classes"],
name=name,
)
signature.add_extra_fields()
return signature

View file

@ -121,7 +121,7 @@ class DocumentLoaderCreator(LangChainTypeCreator):
"value": "",
"display_name": "Web Page",
}
elif name in {"ReadTheDocsLoader"}:
elif name in {"ReadTheDocsLoader", "NotionDirectoryLoader"}:
signature["template"]["path"] = {
"type": "str",
"required": True,

View file

@ -44,7 +44,7 @@ def try_setting_streaming_options(langchain_object, websocket):
langchain_object.llm_chain, "llm"
):
llm = langchain_object.llm_chain.llm
if isinstance(llm, BaseLanguageModel):
llm.streaming = bool(hasattr(llm, "streaming"))
if isinstance(llm, BaseLanguageModel) and hasattr(llm, "streaming"):
llm.streaming = True
return langchain_object

View file

@ -24,6 +24,9 @@ class FrontendNode(BaseModel):
}
}
def add_extra_fields(self) -> None:
pass
@staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
"""Formats a given field based on its attributes and value."""

View file

@ -22,6 +22,22 @@ class EmbeddingFrontendNode(FrontendNode):
field.display_name = "Jina API URL"
field.password = False
@staticmethod
def format_openai_fields(field: TemplateField):
if "openai" in field.name:
field.show = True
field.advanced = True
split_name = field.name.split("_")
title_name = " ".join([s.capitalize() for s in split_name])
field.display_name = title_name.replace("Openai", "OpenAI").replace(
"Api", "API"
)
if "api_key" in field.name:
field.password = True
field.show = True
field.advanced = False
@staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
FrontendNode.format_field(field, name)
@ -30,9 +46,6 @@ class EmbeddingFrontendNode(FrontendNode):
if field.name == "headers":
field.show = False
if "openai" in field.name:
field.show = True
field.advanced = "api_key" not in field.name
# Format Jina fields
EmbeddingFrontendNode.format_jina_fields(field)
EmbeddingFrontendNode.format_openai_fields(field)

View file

@ -43,7 +43,7 @@ class LLMFrontendNode(FrontendNode):
field.field_type = "code"
field.advanced = True
field.show = True
elif field.name in ["model_name", "temperature"]:
elif field.name in ["model_name", "temperature", "model_file", "model_type"]:
field.advanced = False
field.show = True

View file

@ -5,6 +5,21 @@ from langflow.template.frontend_node.base import FrontendNode
class VectorStoreFrontendNode(FrontendNode):
def add_extra_fields(self) -> None:
if self.template.type_name == "Weaviate":
extra_field = TemplateField(
name="weaviate_url",
field_type="str",
required=True,
placeholder="http://localhost:8080",
show=True,
advanced=False,
multiline=False,
value="http://localhost:8080",
)
self.template.add_field(extra_field)
@staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
FrontendNode.format_field(field, name)

View file

@ -23,3 +23,6 @@ class Template(BaseModel):
result = {field.name: field.to_dict() for field in self.fields}
result["_type"] = self.type_name # type: ignore
return result
def add_field(self, field: TemplateField) -> None:
self.fields.append(field)

View file

@ -119,6 +119,7 @@ export default function ParameterComponent({
) : (
<InputComponent
disabled={disabled}
disableCopyPaste={true}
password={data.node.template[name].password ?? false}
value={data.node.template[name].value ?? ""}
onChange={(t) => {

View file

@ -1,13 +1,23 @@
import {
BugAntIcon,
CheckCircleIcon,
Cog6ToothIcon,
EllipsisHorizontalCircleIcon,
ExclamationCircleIcon,
InformationCircleIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import { classNames, nodeColors, nodeIcons, toNormalCase } from "../../utils";
import {
CheckCircleIcon,
EllipsisHorizontalCircleIcon,
ExclamationCircleIcon,
} from "@heroicons/react/24/solid";
import {
classNames,
nodeColors,
nodeIcons,
toNormalCase,
toTitleCase,
} from "../../utils";
import ParameterComponent from "./components/parameterComponent";
import { typesContext } from "../../contexts/typesContext";
import { useContext, useState, useEffect, useRef, Fragment } from "react";
@ -101,7 +111,9 @@ export default function GenericNode({
color: nodeColors[types[data.type]] ?? nodeColors.unknown,
}}
/>
<div className="ml-2 truncate">{data.type}</div>
<Tooltip title={data.type} placement="top">
<div className="ml-2 truncate">{data.type}</div>
</Tooltip>
<div>
<Tooltip
title={
@ -170,7 +182,7 @@ export default function GenericNode({
)
? ""
: "hidden",
"w-6 h-6 dark:text-gray-300 hover:animate-spin"
"w-6 h-6 dark:text-gray-300 hover:animate-spin-once"
)}
></Cog6ToothIcon>
</button>
@ -225,8 +237,8 @@ export default function GenericNode({
data.node.template[t].display_name
? data.node.template[t].display_name
: data.node.template[t].name
? toNormalCase(data.node.template[t].name)
: toNormalCase(t)
? toTitleCase(data.node.template[t].name)
: toTitleCase(t)
}
name={t}
tooltipTitle={

View file

@ -23,14 +23,12 @@ export default function ChatTrigger({ open, setOpen }) {
>
<div className="absolute bottom-4 right-3">
<div
// style={{ backgroundColor: nodeColors["chat"] }}
className="border flex justify-center align-center py-1 px-3 w-12 h-12 rounded-full bg-gradient-to-r from-blue-500 via-blue-600 to-blue-700 dark:border-gray-600"
className="border flex justify-center align-center py-1 px-3 w-12 h-12 rounded-full bg-gradient-to-r from-blue-500 via-blue-600 to-blue-700 dark:border-gray-600 cursor-pointer"
onClick={() => {
setOpen(true);
}}
>
<button
onClick={() => {
setOpen(true);
}}
>
<button>
<div className="flex gap-3 items-center">
<ChatBubbleBottomCenterTextIcon
className="h-6 w-6 mt-1"

View file

@ -6,11 +6,13 @@ import { TabsContext } from "../../contexts/tabsContext";
export default function InputComponent({
value,
onChange,
disableCopyPaste = false,
disabled,
password,
}: InputComponentType) {
const [myValue, setMyValue] = useState(value ?? "");
const [pwdVisible, setPwdVisible] = useState(false);
const { setDisableCopyPaste } = useContext(TabsContext);
useEffect(() => {
if (disabled) {
setMyValue("");
@ -27,12 +29,18 @@ export default function InputComponent({
>
<input
value={myValue}
onFocus={() => {
if (disableCopyPaste) setDisableCopyPaste(true);
}}
onBlur={() => {
if (disableCopyPaste) setDisableCopyPaste(false);
}}
className={classNames(
"block w-full pr-12 form-input dark:bg-gray-900 dark:border-gray-600 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm",
disabled ? " bg-gray-200 dark:bg-gray-700" : "",
password && !pwdVisible && myValue !== "" ? "password" : ""
)}
placeholder="Type a text"
placeholder="Type something..."
onChange={(e) => {
setMyValue(e.target.value);
onChange(e.target.value);

View file

@ -33,7 +33,7 @@ export default function InputListComponent({
"block w-full form-input rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" +
(disabled ? " bg-gray-200" : "")
}
placeholder="Type a text"
placeholder="Type something..."
onChange={(e) => {
setInputList((old) => {
let newInputList = _.cloneDeep(old);

View file

@ -5,6 +5,7 @@ import CodeAreaModal from "../../modals/codeAreaModal";
import TextAreaModal from "../../modals/textAreaModal";
import { TextAreaComponentType } from "../../types/components";
import GenericModal from "../../modals/genericModal";
import { TypeModal } from "../../utils";
export default function PromptAreaComponent({
value,
@ -30,6 +31,7 @@ export default function PromptAreaComponent({
onClick={() => {
openPopUp(
<GenericModal
type={TypeModal.PROMPT}
value={myValue}
buttonText="Check & Save"
modalTitle="Edit Prompt"
@ -51,6 +53,7 @@ export default function PromptAreaComponent({
onClick={() => {
openPopUp(
<GenericModal
type={TypeModal.PROMPT}
value={myValue}
buttonText="Check & Save"
modalTitle="Edit Prompt"

View file

@ -3,6 +3,7 @@ import { useContext, useEffect, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
import { TextAreaComponentType } from "../../types/components";
import GenericModal from "../../modals/genericModal";
import { TypeModal } from "../../utils";
export default function TextAreaComponent({
value,
@ -24,6 +25,7 @@ export default function TextAreaComponent({
onClick={() => {
openPopUp(
<GenericModal
type={TypeModal.TEXT}
buttonText="Finishing Editing"
modalTitle="Edit Text"
value={myValue}
@ -45,6 +47,7 @@ export default function TextAreaComponent({
onClick={() => {
openPopUp(
<GenericModal
type={TypeModal.TEXT}
buttonText="Finishing Editing"
modalTitle="Edit Text"
value={myValue}

View file

@ -63,7 +63,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
console.log(node.data.node.template[key].type);
if (node.data.node.template[key].type === "file") {
console.log(node.data.node.template[key]);
node.data.node.template[key].content = "";
// ! Commenting this out for now, as it is causing issues with the file upload
// node.data.node.template[key].content = "";
}
});
});
@ -86,6 +87,19 @@ export function TabsProvider({ children }: { children: ReactNode }) {
cookieObject.flows.forEach((flow) => {
flow.data.nodes.forEach((node) => {
if (Object.keys(templates[node.data.type]["template"]).length > 0) {
node.data.node.base_classes =
templates[node.data.type]["base_classes"];
flow.data.edges.forEach((edge) => {
if (edge.source === node.id) {
edge.sourceHandle = edge.sourceHandle
.split("|")
.slice(0, 2)
.concat(templates[node.data.type]["base_classes"])
.join("|");
}
});
node.data.node.description =
templates[node.data.type]["description"];
node.data.node.template = updateTemplate(
templates[node.data.type][
"template"
@ -275,6 +289,18 @@ export function TabsProvider({ children }: { children: ReactNode }) {
if (data) {
data.nodes.forEach((node) => {
if (Object.keys(templates[node.data.type]["template"]).length > 0) {
node.data.node.base_classes =
templates[node.data.type]["base_classes"];
flow.data.edges.forEach((edge) => {
if (edge.source === node.id) {
edge.sourceHandle = edge.sourceHandle
.split("|")
.slice(0, 2)
.concat(templates[node.data.type]["base_classes"])
.join("|");
}
});
node.data.node.description = templates[node.data.type]["description"];
node.data.node.template = updateTemplate(
templates[node.data.type]["template"] as unknown as APITemplateType,
node.data.node.template as APITemplateType

View file

@ -12,7 +12,15 @@ import PromptAreaComponent from "../../../../components/promptComponent";
import CodeAreaComponent from "../../../../components/codeAreaComponent";
import { classNames } from "../../../../utils";
export default function ModalField({ data, title, required, id, name, type }) {
export default function ModalField({
data,
title,
required,
id,
name,
type,
index,
}) {
const { save } = useContext(TabsContext);
const [enabled, setEnabled] = useState(
data.node.template[name]?.value ?? false
@ -30,7 +38,17 @@ export default function ModalField({ data, title, required, id, name, type }) {
<div
className={classNames(
"flex flex-row w-full items-center justify-between",
display ? "" : "hidden"
display ? "" : "hidden",
Object.keys(data.node.template).filter(
(t) =>
t.charAt(0) !== "_" &&
data.node.template[t].advanced &&
data.node.template[t].show
).length -
1 ===
index
? "pb-4"
: ""
)}
>
{display && (

View file

@ -3,7 +3,14 @@ import { XMarkIcon } from "@heroicons/react/24/outline";
import { Fragment, useContext, useRef, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
import { NodeDataType } from "../../types/flow";
import { nodeColors, nodeIcons, toNormalCase } from "../../utils";
import {
classNames,
limitScrollFieldsModal,
nodeColors,
nodeIcons,
toNormalCase,
toTitleCase,
} from "../../utils";
import { typesContext } from "../../contexts/typesContext";
import ModalField from "./components/ModalField";
@ -84,8 +91,20 @@ export default function NodeModal({ data }: { data: NodeDataType }) {
</div>
</div>
<div className="h-full w-full bg-gray-200 dark:bg-gray-900 p-4 gap-4 flex flex-row justify-center items-center">
<div className="flex h-full w-full">
<div className="overflow-hidden px-4 sm:p-4 w-full rounded-lg bg-white dark:bg-gray-800 shadow">
<div className="flex w-full h-[445px]">
<div
className={classNames(
"px-4 sm:p-4 w-full rounded-lg bg-white dark:bg-gray-800 shadow",
Object.keys(data.node.template).filter(
(t) =>
t.charAt(0) !== "_" &&
data.node.template[t].advanced &&
data.node.template[t].show
).length > limitScrollFieldsModal
? "overflow-scroll overflow-x-hidden custom-scroll"
: "overflow-hidden"
)}
>
<div className="flex flex-col h-full gap-5">
{Object.keys(data.node.template)
.filter(
@ -103,8 +122,8 @@ export default function NodeModal({ data }: { data: NodeDataType }) {
data.node.template[t].display_name
? data.node.template[t].display_name
: data.node.template[t].name
? toNormalCase(data.node.template[t].name)
: toNormalCase(t)
? toTitleCase(data.node.template[t].name)
: toTitleCase(t)
}
required={data.node.template[t].required}
id={
@ -116,6 +135,7 @@ export default function NodeModal({ data }: { data: NodeDataType }) {
}
name={t}
type={data.node.template[t].type}
index={idx}
/>
);
})}

View file

@ -5,19 +5,23 @@ import { PopUpContext } from "../../contexts/popUpContext";
import { darkContext } from "../../contexts/darkContext";
import { checkPrompt } from "../../controllers/API";
import { alertContext } from "../../contexts/alertContext";
import { TypeModal } from "../../utils";
export default function PromptAreaModal({
value,
setValue,
buttonText,
modalTitle,
type,
}: {
setValue: (value: string) => void;
value: string;
buttonText: string;
modalTitle: string;
type: number;
}) {
const [myButtonText, setmyButtonText] = useState(buttonText);
const [myModalTitle, setMyModalTitle] = useState(modalTitle);
const [myButtonText] = useState(buttonText);
const [myModalTitle] = useState(modalTitle);
const [myModalType] = useState(type);
const [open, setOpen] = useState(true);
const [myValue, setMyValue] = useState(value);
const { dark } = useContext(darkContext);
@ -114,36 +118,47 @@ export default function PromptAreaModal({
type="button"
className="inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm"
onClick={() => {
checkPrompt(myValue)
.then((apiReturn) => {
if (apiReturn.data) {
let inputVariables =
apiReturn.data.input_variables;
if (inputVariables.length === 0) {
setErrorData({
switch (myModalType) {
case 1:
setModalOpen(false);
break;
case 2:
checkPrompt(myValue)
.then((apiReturn) => {
if (apiReturn.data) {
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:
"The template you are attempting to use does not contain any variables for data entry.",
"There is something wrong with this prompt, please review it",
list: [error.response.data.detail],
});
} 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],
});
});
break;
default:
break;
}
}}
>
{myButtonText}

View file

@ -4,6 +4,7 @@ import { nodeColors, nodeIcons, nodeNames } from "../../../../utils";
import { useContext, useEffect, useState } from "react";
import { typesContext } from "../../../../contexts/typesContext";
import { APIClassType, APIObjectType } from "../../../../types/api";
import Tooltip from "../../../../components/TooltipComponent";
export default function ExtraSidebar() {
const { data } = useContext(typesContext);
@ -33,28 +34,30 @@ export default function ExtraSidebar() {
{Object.keys(data[d])
.sort()
.map((t: string, k) => (
<div key={k}>
<div
draggable
className={" cursor-grab border-l-8 rounded-l-md"}
style={{
borderLeftColor: nodeColors[d] ?? nodeColors.unknown,
}}
onDragStart={(event) =>
onDragStart(event, {
type: t,
node: data[d][t],
})
}
>
<div className="flex w-full justify-between text-sm px-3 py-1 items-center border-dashed border-gray-400 dark:border-gray-600 border-l-0 rounded-md rounded-l-none border">
<span className="text-black dark:text-white w-36 pr-1 truncate text-xs">
{t}
</span>
<Bars2Icon className="w-4 h-6 text-gray-400 dark:text-gray-600" />
<Tooltip title={t.length > 21 ? t : ""} placement="right">
<div key={k}>
<div
draggable
className={" cursor-grab border-l-8 rounded-l-md"}
style={{
borderLeftColor: nodeColors[d] ?? nodeColors.unknown,
}}
onDragStart={(event) =>
onDragStart(event, {
type: t,
node: data[d][t],
})
}
>
<div className="flex w-full justify-between text-sm px-3 py-1 items-center border-dashed border-gray-400 dark:border-gray-600 border-l-0 rounded-md rounded-l-none border">
<span className="text-black dark:text-white w-36 pr-1 truncate text-xs">
{t}
</span>
<Bars2Icon className="w-4 h-6 text-gray-400 dark:text-gray-600" />
</div>
</div>
</div>
</div>
</Tooltip>
))}
{Object.keys(data[d]).length === 0 && (
<div className="text-gray-400 text-center">Coming soon</div>

View file

@ -317,10 +317,8 @@ export default function FlowPage({ flow }: { flow: FlowType }) {
onPaneClick={() => {
setDisableCopyPaste(false);
}}
onNodeClick={() => {
setDisableCopyPaste(true);
}}
onPaneMouseLeave={() => {
console.log("saiu o mouse");
setDisableCopyPaste(true);
}}
onNodesChange={onNodesChange}

View file

@ -10,6 +10,7 @@ export type InputComponentType = {
disabled?: boolean;
onChange: (value: string) => void;
password: boolean;
disableCopyPaste?: boolean;
};
export type ToggleComponentType = {
enabled: boolean;

View file

@ -20,11 +20,19 @@ import { Connection, Edge, Node, ReactFlowInstance } from "reactflow";
import { FlowType } from "./types/flow";
import { APITemplateType, TemplateVariableType } from "./types/api";
import _ from "lodash";
import { v4 as uuidv4 } from "uuid";
export function classNames(...classes: Array<string>) {
return classes.filter(Boolean).join(" ");
}
export const limitScrollFieldsModal = 7;
export enum TypeModal {
TEXT = 1,
PROMPT = 2,
}
export const textColors = {
white: "text-white",
red: "text-red-700",
@ -492,3 +500,64 @@ export const programmingLanguages: languageMap = {
css: ".css",
// add more file extensions here, make sure the key is same as language prop in CodeBlock.tsx component
};
export function toTitleCase(str: string) {
let result = str
.split("_")
.map((word, index) => {
if (index === 0) {
return checkUpperWords(
word[0].toUpperCase() + word.slice(1).toLowerCase()
);
}
return checkUpperWords(word.toLowerCase());
})
.join(" ");
return result
.split("-")
.map((word, index) => {
if (index === 0) {
return checkUpperWords(
word[0].toUpperCase() + word.slice(1).toLowerCase()
);
}
return checkUpperWords(word.toLowerCase());
})
.join(" ");
}
export const upperCaseWords: string[] = ["llm", "uri"];
export function checkUpperWords(str: string) {
const words = str.split(" ").map((word) => {
return upperCaseWords.includes(word.toLowerCase())
? word.toUpperCase()
: word[0].toUpperCase() + word.slice(1).toLowerCase();
});
return words.join(" ");
}
export function updateIds(newFLow: FlowType, baseFlow: FlowType) {
newFLow.data.nodes.forEach((node) => {
while (baseFlow.data.nodes.some((n) => n.id === node.id)) {
const newId = uuidv4();
newFLow.data.edges.forEach((edge) => {
if (edge.source === node.id) {
edge.source = newId;
}
if (edge.target === node.id) {
edge.target = newId;
}
const index = edge.id.split("|").findIndex((e) => e === node.id);
if (index != -1) {
let tempList = edge.id.split("|");
tempList[index] = newId;
edge.id = tempList.concat(newId).join("|");
}
node.id = newId;
});
}
});
return newFLow;
}

View file

@ -17,6 +17,7 @@ module.exports = {
animation: {
"pulse-green": "pulseGreen 1s linear",
'spin-once': 'spin 1s linear 0.7'
},
keyframes: {
pulseGreen: {

View file

@ -0,0 +1,59 @@
from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.embeddings import EmbeddingFrontendNode
def test_format_jina_fields():
field = TemplateField(name="jina")
EmbeddingFrontendNode.format_jina_fields(field)
assert field.show is True
assert field.advanced is False
field = TemplateField(name="auth")
EmbeddingFrontendNode.format_jina_fields(field)
assert field.password is True
assert field.show is True
assert field.advanced is False
field = TemplateField(name="jina_api_url")
EmbeddingFrontendNode.format_jina_fields(field)
assert field.show is True
assert field.advanced is True
assert field.display_name == "Jina API URL"
assert field.password is False
def test_format_openai_fields():
field = TemplateField(name="openai")
EmbeddingFrontendNode.format_openai_fields(field)
assert field.show is True
assert field.advanced is True
assert field.display_name == "OpenAI"
field = TemplateField(name="openai_api_key")
EmbeddingFrontendNode.format_openai_fields(field)
assert field.password is True
assert field.show is True
assert field.advanced is False
def test_format_field():
field = TemplateField(name="headers")
EmbeddingFrontendNode.format_field(field)
assert field.show is False
field = TemplateField(name="jina")
EmbeddingFrontendNode.format_field(field)
assert field.advanced is False
assert field.show is True
field = TemplateField(name="openai")
EmbeddingFrontendNode.format_field(field)
assert field.advanced is True
assert field.show is True
assert field.display_name == "OpenAI"
field = TemplateField(name="test_field", required=True)
EmbeddingFrontendNode.format_field(field)
assert field.advanced is False
assert field.show is True
assert field.required is True