Merge dev into types_refactor

This commit is contained in:
Igor Carvalho 2023-08-02 14:30:10 -03:00
commit a4319eaa43
149 changed files with 9170 additions and 2704 deletions

View file

@ -13,7 +13,7 @@ import IntComponent from "../../../../components/intComponent";
import PromptAreaComponent from "../../../../components/promptComponent";
import TextAreaComponent from "../../../../components/textAreaComponent";
import ToggleShadComponent from "../../../../components/toggleShadComponent";
import { MAX_LENGTH_TO_SCROLL_TOOLTIP } from "../../../../constants/constants";
import { TOOLTIP_EMPTY } from "../../../../constants/constants";
import { TabsContext } from "../../../../contexts/tabsContext";
import { typesContext } from "../../../../contexts/typesContext";
import { ParameterComponentType } from "../../../../types/components";
@ -23,11 +23,7 @@ import {
nodeIconsLucide,
nodeNames,
} from "../../../../utils/styleUtils";
import {
classNames,
getRandomKeyByssmm,
groupByFamily,
} from "../../../../utils/utils";
import { classNames, groupByFamily } from "../../../../utils/utils";
export default function ParameterComponent({
left,
@ -45,11 +41,12 @@ export default function ParameterComponent({
}: ParameterComponentType): JSX.Element {
const ref = useRef<HTMLDivElement>(null);
const refHtml = useRef(null);
const refNumberComponents = useRef(0);
const infoHtml = useRef(null);
const updateNodeInternals = useUpdateNodeInternals();
const [position, setPosition] = useState(0);
const { setTabsState, tabId, save } = useContext(TabsContext);
const { setTabsState, tabId, save, flows } = useContext(TabsContext);
const flow = flows.find((f) => f.id === tabId).data?.nodes ?? null;
// Update component position
useEffect(() => {
@ -80,9 +77,11 @@ export default function ParameterComponent({
[tabId]: {
...prev[tabId],
isPending: true,
formKeysData: prev[tabId].formKeysData,
},
};
});
renderTooltips();
};
useEffect(() => {
@ -98,57 +97,65 @@ export default function ParameterComponent({
);
}, [info]);
useEffect(() => {
const groupedObj = groupByFamily(myData, tooltipTitle, left, data.type);
function renderTooltips() {
let groupedObj = groupByFamily(myData, tooltipTitle, left, flow);
refNumberComponents.current = groupedObj[0]?.type?.length;
if (groupedObj && groupedObj.length > 0) {
refHtml.current = groupedObj.map((item, i) => {
const Icon: any =
nodeIconsLucide[item.family] ?? nodeIconsLucide["unknown"];
refHtml.current = groupedObj.map((item, i) => {
const Icon: any = nodeIconsLucide[item.family];
return (
<span
key={getRandomKeyByssmm() + item.family + i}
className={classNames(
i > 0 ? "mt-2 flex items-center" : "flex items-center"
)}
>
<div
className="h-5 w-5"
style={{
color: nodeColors[item.family],
}}
return (
<span
key={i}
className={classNames(
i > 0 ? "mt-2 flex items-center" : "flex items-center"
)}
>
<Icon
<div
className="h-5 w-5"
strokeWidth={1.5}
style={{
color: nodeColors[item.family] ?? nodeColors.unknown,
color: nodeColors[item.family],
}}
/>
</div>
<span className="ps-2 text-xs text-foreground">
{nodeNames[item.family] ?? ""}{" "}
<span className="text-xs">
{" "}
{item.type === "" ? "" : " - "}
{item.type.split(", ").length > 2
? item.type.split(", ").map((el, i) => (
<React.Fragment key={el + i}>
<span>
{i === item.type.split(", ").length - 1
? el
: (el += `, `)}
</span>
</React.Fragment>
))
: item.type}
>
<Icon
className="h-5 w-5"
strokeWidth={1.5}
style={{
color: nodeColors[item.family] ?? nodeColors.unknown,
}}
/>
</div>
<span className="ps-2 text-xs text-foreground">
{nodeNames[item.family] ?? "Other"}
<span className="text-xs">
{" "}
{item.type === "" ? "" : " - "}
{item.type.split(", ").length > 2
? item.type.split(", ").map((el, i) => (
<React.Fragment key={el + i}>
<span>
{i === item.type.split(", ").length - 1
? el
: (el += `, `)}
</span>
</React.Fragment>
))
: item.type}
</span>
</span>
</span>
</span>
);
});
}, [tooltipTitle]);
);
});
} else {
refHtml.current = <span>{TOOLTIP_EMPTY}</span>;
}
}
useEffect(() => {
renderTooltips();
}, [tooltipTitle, flow]);
return (
<div
ref={ref}
@ -190,11 +197,7 @@ export default function ParameterComponent({
<></>
) : (
<ShadTooltip
styleClasses={
refNumberComponents.current > MAX_LENGTH_TO_SCROLL_TOOLTIP
? "tooltip-fixed-width custom-scroll overflow-y-scroll nowheel"
: "tooltip-fixed-width"
}
styleClasses={"tooltip-fixed-width custom-scroll nowheel"}
delayDuration={0}
content={refHtml.current}
side={left ? "left" : "right"}
@ -252,7 +255,7 @@ export default function ParameterComponent({
<div className="mt-2 w-full">
<ToggleShadComponent
disabled={disabled}
enabled={data.node?.template[name].value}
enabled={data.node.template[name].value ?? false}
setEnabled={(t) => {
handleOnNewValue(t);
}}
@ -280,6 +283,7 @@ export default function ParameterComponent({
) : left === true && type === "code" ? (
<div className="mt-2 w-full">
<CodeAreaComponent
dynamic={data.node.template[name].dynamic ?? false}
setNodeClass={(nodeClass) => {
data.node = nodeClass;
}}

View file

@ -1,11 +1,10 @@
import { cloneDeep } from "lodash";
import { useContext, useEffect, useRef, useState } from "react";
import { useContext, useEffect, useState } from "react";
import { NodeToolbar, useUpdateNodeInternals } from "reactflow";
import ShadTooltip from "../../components/ShadTooltipComponent";
import Tooltip from "../../components/TooltipComponent";
import IconComponent from "../../components/genericIconComponent";
import { useSSE } from "../../contexts/SSEContext";
import { alertContext } from "../../contexts/alertContext";
import { TabsContext } from "../../contexts/tabsContext";
import { typesContext } from "../../contexts/typesContext";
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
@ -23,15 +22,9 @@ export default function GenericNode({
selected: boolean;
}): JSX.Element {
const [data, setData] = useState(olddata);
const { setErrorData } = useContext(alertContext);
const { updateFlow, flows, tabId } = useContext(TabsContext);
const updateNodeInternals = useUpdateNodeInternals();
const showError = useRef(true);
const { types, deleteNode, reactFlowInstance } = useContext(typesContext);
// any to avoid type conflict
// PROBLEM HERE OTAVIO
const Icon: any =
nodeIconsLucide[data.type] || nodeIconsLucide[types[data.type]];
const name = nodeIconsLucide[data.type] ? data.type : types[data.type];
const [validationStatus, setValidationStatus] = useState(null);
// State for outline color
@ -68,18 +61,6 @@ export default function GenericNode({
}
}, [sseData, data.id]);
if (!Icon) {
if (showError.current) {
setErrorData({
title: data.type
? `The ${data.type} node could not be rendered, please review your json file`
: "There was a node that can't be rendered, please review your json file",
});
showError.current = false;
}
deleteNode(data.id);
return;
}
return (
<>
<NodeToolbar>
@ -96,6 +77,11 @@ export default function GenericNode({
"generic-node-div"
)}
>
{data.node.beta && (
<div className="beta-badge-wrapper">
<div className="beta-badge-content">BETA</div>
</div>
)}
<div className="generic-node-div-title">
<div className="generic-node-title-arrangement">
<IconComponent
@ -128,7 +114,7 @@ export default function GenericNode({
</span>
) : (
<div className="max-h-96 overflow-auto">
{validationStatus.params
{typeof validationStatus.params === "string"
? validationStatus.params
.split("\n")
.map((line: string, index: number) => (
@ -181,6 +167,14 @@ export default function GenericNode({
{data.node.template[t].show &&
!data.node.template[t].advanced ? (
<ParameterComponent
key={
(data.node.template[t].input_types?.join(";") ??
data.node.template[t].type) +
"|" +
t +
"|" +
data.id
}
data={data}
setData={setData}
color={
@ -228,6 +222,7 @@ export default function GenericNode({
{" "}
</div>
<ParameterComponent
key={[data.type, data.id, ...data.node.base_classes].join("|")}
data={data}
setData={setData}
color={nodeColors[types[data.type]] ?? nodeColors.unknown}

View file

@ -1,11 +1,14 @@
import React, { ChangeEvent, useState } from "react";
import React, { ChangeEvent, useEffect, useRef, useState } from "react";
import { Input } from "../../components/ui/input";
import { Label } from "../../components/ui/label";
import { Textarea } from "../../components/ui/textarea";
import { InputProps } from "../../types/components";
import { readFlowsFromDatabase } from "../../controllers/API";
export const EditFlowSettings: React.FC<InputProps> = ({
name,
invalidName,
setInvalidName,
description,
maxLength = 50,
flows,
@ -15,6 +18,14 @@ export const EditFlowSettings: React.FC<InputProps> = ({
updateFlow,
}: InputProps): JSX.Element => {
const [isMaxLength, setIsMaxLength] = useState(false);
const nameLists = useRef([]);
useEffect(() => {
readFlowsFromDatabase().then((flows) => {
flows.forEach((flow) => {
nameLists.current.push(flow.name);
});
});
}, []);
const handleNameChange = (event: ChangeEvent<HTMLInputElement>) => {
const { value } = event.target;
@ -23,7 +34,11 @@ export const EditFlowSettings: React.FC<InputProps> = ({
} else {
setIsMaxLength(false);
}
if (!nameLists.current.includes(value)) {
setInvalidName(false);
} else {
setInvalidName(true);
}
setName(value);
};
@ -45,6 +60,9 @@ export const EditFlowSettings: React.FC<InputProps> = ({
{isMaxLength && (
<span className="edit-flow-span">Character limit reached</span>
)}
{invalidName && (
<span className="edit-flow-span">Name already in use</span>
)}
</div>
<Input
className="nopan nodrag noundo nocopy mt-2 font-normal"

View file

@ -12,9 +12,8 @@ import { NodeType } from "../../types/flow";
export default function Chat({ flow }: ChatType): JSX.Element {
const [open, setOpen] = useState(false);
const [isBuilt, setIsBuilt] = useState(false);
const [canOpen, setCanOpen] = useState(false);
const { tabsState } = useContext(TabsContext);
const { tabsState, isBuilt, setIsBuilt } = useContext(TabsContext);
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {

View file

@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import CodeAreaModal from "../../modals/codeAreaModal";
import { TextAreaComponentType } from "../../types/components";
import { CodeAreaComponentType } from "../../types/components";
import IconComponent from "../genericIconComponent";
@ -10,8 +10,9 @@ export default function CodeAreaComponent({
disabled,
editNode = false,
nodeClass,
dynamic,
setNodeClass,
}: TextAreaComponentType): JSX.Element {
}: CodeAreaComponentType) {
const [myValue, setMyValue] = useState(
typeof value == "string" ? value : JSON.stringify(value)
);
@ -29,6 +30,7 @@ export default function CodeAreaComponent({
return (
<div className={disabled ? "pointer-events-none w-full " : " w-full"}>
<CodeAreaModal
dynamic={dynamic}
value={myValue}
nodeClass={nodeClass}
setNodeClass={setNodeClass}
@ -43,7 +45,7 @@ export default function CodeAreaComponent({
editNode
? "input-edit-node input-dialog"
: (disabled ? " input-disable input-ring " : "") +
" input-primary text-muted-foreground "
" primary-input text-muted-foreground "
}
>
{myValue !== "" ? myValue : "Type something..."}

View file

@ -132,7 +132,7 @@ export default function CodeTabsComponent({
</TabsTrigger>
))}
</TabsList>
{Number(activeTab) < 3 && (
{Number(activeTab) < 4 && (
<div className="float-right mx-1 flex gap-2">
<button
className="flex items-center gap-1.5 rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"
@ -161,15 +161,23 @@ export default function CodeTabsComponent({
className="api-modal-tabs-content"
key={index} // Remember to add a unique key prop
>
{index < 3 ? (
<SyntaxHighlighter
className="mt-0 h-full w-full overflow-auto custom-scroll"
language={tab.mode}
style={oneDark}
>
{tab.code}
</SyntaxHighlighter>
) : index === 3 ? (
{index < 4 ? (
<>
{tab.description && (
<div
className="mb-2 w-full text-left text-sm"
dangerouslySetInnerHTML={{ __html: tab.description }}
></div>
)}
<SyntaxHighlighter
className="mt-0 h-full w-full overflow-auto custom-scroll"
language={tab.mode}
style={oneDark}
>
{tab.code}
</SyntaxHighlighter>
</>
) : index === 4 ? (
<>
<div className="api-modal-according-display">
<div
@ -411,8 +419,12 @@ export default function CodeTabsComponent({
.suffixes
}
onFileChange={(
k: any
) => {}}
value: any
) => {
t.data.node.template[
n
].file_path = value;
}}
></InputFileComponent>
</div>
</ShadTooltip>

View file

@ -66,12 +66,12 @@ export default function InputFileComponent({
const { file_path } = data;
console.log("File name:", file_path);
// sets the value that goes to the backend
onFileChange(file_path);
// Update the state and callback with the name of the file
// sets the value to the user
setMyValue(file.name);
onChange(file.name);
// sets the value that goes to the backend
onFileChange(file_path);
setLoading(false);
})
.catch(() => {
@ -102,8 +102,8 @@ export default function InputFileComponent({
editNode
? "input-edit-node input-dialog text-muted-foreground"
: disabled
? "input-disable input-dialog input-primary"
: "input-dialog input-primary text-muted-foreground"
? "input-disable input-dialog primary-input"
: "input-dialog primary-input text-muted-foreground"
}
>
{myValue !== "" ? myValue : "No file"}

View file

@ -51,7 +51,7 @@ export default function PromptAreaComponent({
editNode
? "input-edit-node input-dialog"
: (disabled ? " input-disable text-ring " : "") +
" input-primary text-muted-foreground "
" primary-input text-muted-foreground "
}
>
{value !== "" ? value : "Type your prompt here..."}

View file

@ -9,7 +9,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
return (
<input
type={type}
className={cn("nopan nodrag noundo nocopy input-primary", className)}
className={cn("nopan nodrag noundo nocopy primary-input", className)}
ref={ref}
{...props}
/>

View file

@ -19,7 +19,7 @@ const TooltipContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
"overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
"overflow-y-auto rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
className
)}
{...props}

View file

@ -143,6 +143,12 @@ export const TEXT_DIALOG_SUBTITLE = "Edit your text.";
export const IMPORT_DIALOG_SUBTITLE =
"Upload a JSON file or select from the available community examples.";
/**
* The text that shows when a tooltip is empty
* @constant
*/
export const TOOLTIP_EMPTY = "No compatible components found.";
/**
* The base text for subtitle of code dialog
* @constant
@ -491,3 +497,14 @@ export const NOUNS: string[] = [
*
*/
export const USER_PROJECTS_HEADER = "My Collection";
/**
* URLs excluded from error retries.
* @constant
*
*/
export const URL_EXCLUDED_FROM_ERROR_RETRIES = [
"/api/v1/validate/code",
"/api/v1/custom_component",
"/api/v1/validate/prompt",
];

View file

@ -20,7 +20,11 @@ import {
import { APIClassType, APITemplateType } from "../types/api";
import { FlowType, NodeType } from "../types/flow";
import { TabsContextType, TabsState } from "../types/tabs";
import { updateIds, updateTemplate } from "../utils/reactflowUtils";
import {
addVersionToDuplicates,
updateIds,
updateTemplate,
} from "../utils/reactflowUtils";
import { getRandomDescription, getRandomName } from "../utils/utils";
import { alertContext } from "./alertContext";
import { typesContext } from "./typesContext";
@ -40,6 +44,8 @@ const TabsContextInitialValue: TabsContextType = {
downloadFlows: () => {},
uploadFlows: () => {},
uploadFlow: () => {},
isBuilt: false,
setIsBuilt: (state: boolean) => {},
hardReset: () => {},
saveFlow: async (flow: FlowType) => {},
lastCopiedSelection: null,
@ -448,6 +454,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
processFlowEdges(newFlow);
processFlowNodes(newFlow);
const flowName = addVersionToDuplicates(newFlow, flows);
newFlow.name = flowName;
try {
const { id } = await saveFlowToDatabase(newFlow);
// Change the id to the new id.
@ -583,10 +593,14 @@ export function TabsProvider({ children }: { children: ReactNode }) {
}
}
const [isBuilt, setIsBuilt] = useState(false);
return (
<TabsContext.Provider
value={{
saveFlow,
isBuilt,
setIsBuilt,
lastCopiedSelection,
setLastCopiedSelection,
hardReset,

View file

@ -1,5 +1,6 @@
import axios, { AxiosError, AxiosInstance } from "axios";
import { useContext, useEffect, useRef } from "react";
import { URL_EXCLUDED_FROM_ERROR_RETRIES } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
// Create a new Axios instance
@ -15,6 +16,9 @@ function ApiInterceptor() {
const interceptor = api.interceptors.response.use(
(response) => response,
async (error: AxiosError) => {
if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) {
return Promise.reject(error);
}
let retryCount = 0;
while (retryCount < 4) {
@ -31,7 +35,7 @@ function ApiInterceptor() {
"Refresh the page",
"Use a new flow tab",
"Check if the backend is up",
"Endpoint: " + error.config.url,
"Endpoint: " + error.config?.url,
],
});
return Promise.reject(error);

View file

@ -339,3 +339,10 @@ export async function uploadFile(
formData.append("file", file);
return await api.post(`/api/v1/upload/${id}`, formData);
}
export async function postCustomComponent(
code: string,
apiClass: APIClassType
): Promise<AxiosResponse<APIClassType>> {
return await api.post(`/api/v1/custom_component`, { code });
}

View file

@ -0,0 +1,22 @@
import { Infinity } from "lucide-react";
import { forwardRef } from "react";
const GradientSparkles = forwardRef<SVGSVGElement, React.PropsWithChildren<{}>>(
(props, ref) => {
return (
<>
<svg width="0" height="0" style={{ position: "absolute" }}>
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
<stop className="gradient-start" offset="0%" />
<stop className="gradient-end" offset="100%" />
</linearGradient>
</defs>
</svg>
<Infinity stroke="url(#grad1)" ref={ref} {...props} />
</>
);
}
);
export default GradientSparkles;

View file

@ -5,9 +5,12 @@ import ContextWrapper from "./contexts";
import reportWebVitals from "./reportWebVitals";
import { ApiInterceptor } from "./controllers/API/api";
// @ts-ignore
import "./style/index.css";
import "./style/classes.css";
// @ts-ignore
import "./style/applies.css";
// @ts-ignore
import "./style/classes.css";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement

View file

@ -21,6 +21,7 @@ import {
getCurlCode,
getPythonApiCode,
getPythonCode,
getWidgetCode,
} from "../../utils/utils";
import BaseModal from "../baseModal";
@ -29,9 +30,11 @@ const ApiModal = forwardRef(
{
flow,
children,
disable,
}: {
flow: FlowType;
children: ReactNode;
disable: boolean;
},
ref
) => {
@ -43,6 +46,7 @@ const ApiModal = forwardRef(
const pythonApiCode = getPythonApiCode(flow, tweak.current, tabsState);
const curl_code = getCurlCode(flow, tweak.current, tabsState);
const pythonCode = getPythonCode(flow, tweak.current, tabsState);
const widgetCode = getWidgetCode(flow, tabsState);
const tweaksCode = buildTweaks(flow);
const [tabs, setTabs] = useState([
{
@ -67,6 +71,15 @@ const ApiModal = forwardRef(
language: "py",
code: pythonCode,
},
{
name: "Chat Widget HTML",
description:
"Insert this code anywhere in your &lt;body&gt; tag. To use with react and other libs, check our <a class='link-color' href='https://langflow.org/guidelines/widget'>documentation</a>.",
mode: "html",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: widgetCode,
},
]);
function startState() {
@ -111,6 +124,15 @@ const ApiModal = forwardRef(
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
code: pythonCode,
},
{
name: "Chat Widget HTML",
description:
"Insert this code anywhere in your &lt;body&gt; tag. To use with react and other libs, check our <a class='link-color' href='https://langflow.org/guidelines/widget'>documentation</a>.",
mode: "html",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: widgetCode,
},
{
name: "Tweaks",
mode: "python",
@ -143,6 +165,15 @@ const ApiModal = forwardRef(
language: "py",
code: pythonCode,
},
{
name: "Chat Widget HTML",
description:
"Insert this code anywhere in your &lt;body&gt; tag. To use with react and other libs, check our <a class='link-color' href='https://langflow.org/guidelines/widget'>documentation</a>.",
mode: "html",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: widgetCode,
},
]);
}
}, [flow["data"]["nodes"], open]);
@ -210,13 +241,15 @@ const ApiModal = forwardRef(
tweak.current.push(newTweak);
}
const pythonApiCode = getPythonApiCode(flow, tweak.current);
const curl_code = getCurlCode(flow, tweak.current);
const pythonCode = getPythonCode(flow, tweak.current);
const pythonApiCode = getPythonApiCode(flow, tweak.current, tabsState);
const curl_code = getCurlCode(flow, tweak.current, tabsState);
const pythonCode = getPythonCode(flow, tweak.current, tabsState);
const widgetCode = getWidgetCode(flow, tabsState);
tabs[0].code = curl_code;
tabs[1].code = pythonApiCode;
tabs[2].code = pythonCode;
tabs[3].code = widgetCode;
setTweak(tweak.current);
}
@ -253,7 +286,7 @@ const ApiModal = forwardRef(
}
return (
<BaseModal open={open} setOpen={setOpen}>
<BaseModal open={open} setOpen={setOpen} disable={disable}>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Header description={EXPORT_CODE_DIALOG}>
<span className="pr-2">Code</span>

View file

@ -241,7 +241,7 @@ const EditNodeModal = forwardRef(
}
suffixes={myData.node.template[n].suffixes}
onFileChange={(t: string) => {
handleOnNewValue(t, n);
data.node.template[n].file_path = t;
}}
></InputFileComponent>
</div>
@ -264,6 +264,13 @@ const EditNodeModal = forwardRef(
) : myData.node?.template[n].type === "code" ? (
<div className="mx-auto">
<CodeAreaComponent
dynamic={
data.node.template[n].dynamic ?? false
}
setNodeClass={(nodeClass) => {
data.node = nodeClass;
}}
nodeClass={data.node}
disabled={disabled}
editNode={true}
value={myData.node.template[n].value ?? ""}

View file

@ -0,0 +1,180 @@
import { useState } from "react";
import CodeAreaComponent from "../../../../components/codeAreaComponent";
import Dropdown from "../../../../components/dropdownComponent";
import FloatComponent from "../../../../components/floatComponent";
import InputComponent from "../../../../components/inputComponent";
import InputFileComponent from "../../../../components/inputFileComponent";
import InputListComponent from "../../../../components/inputListComponent";
import IntComponent from "../../../../components/intComponent";
import PromptAreaComponent from "../../../../components/promptComponent";
import TextAreaComponent from "../../../../components/textAreaComponent";
import ToggleComponent from "../../../../components/toggleComponent";
import { classNames } from "../../../../utils/utils";
export default function ModalField({
data,
title,
required,
id,
name,
type,
index,
}) {
const [enabled, setEnabled] = useState(
data.node.template[name]?.value ?? false
);
const display =
type === "str" ||
type === "int" ||
type === "prompt" ||
type === "bool" ||
type === "float" ||
type === "file" ||
type === "code";
return (
<div
className={classNames(
"flex w-full flex-row items-center justify-between",
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 && (
<div>
<span className="mx-2">{title}</span>
<span className="text-status-red">{required ? " *" : ""}</span>
</div>
)}
{type === "str" && !data.node.template[name].options ? (
<div className="w-1/2">
{data.node.template[name].list ? (
<InputListComponent
disabled={false}
value={
!data.node.template[name].value ||
data.node.template[name].value === ""
? [""]
: data.node.template[name].value
}
onChange={(t: string[]) => {
data.node.template[name].value = t;
}}
/>
) : data.node.template[name].multiline ? (
<TextAreaComponent
disabled={false}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
}}
/>
) : (
<InputComponent
disabled={false}
password={data.node.template[name].password ?? false}
value={data.node.template[name].value ?? ""}
onChange={(t) => {
data.node.template[name].value = t;
}}
/>
)}
</div>
) : type === "bool" ? (
<div className="ml-auto">
{" "}
<ToggleComponent
disabled={false}
enabled={enabled}
setEnabled={(t) => {
data.node.template[name].value = t;
setEnabled(t);
}}
size="small"
/>
</div>
) : type === "float" ? (
<div className="w-1/2">
<FloatComponent
disabled={false}
value={data.node.template[name].value ?? ""}
onChange={(t) => {
data.node.template[name].value = t;
}}
/>
</div>
) : type === "str" && data.node.template[name].options ? (
<div className="w-1/2">
<Dropdown
options={data.node.template[name].options}
onSelect={(newValue) => (data.node.template[name].value = newValue)}
value={data.node.template[name].value ?? "Choose an option"}
></Dropdown>
</div>
) : type === "int" ? (
<div className="w-1/2">
<IntComponent
disabled={false}
value={data.node.template[name].value ?? ""}
onChange={(t) => {
data.node.template[name].value = t;
}}
/>
</div>
) : type === "file" ? (
<div className="w-1/2">
<InputFileComponent
disabled={false}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
}}
fileTypes={data.node.template[name].fileTypes}
suffixes={data.node.template[name].suffixes}
onFileChange={(t: string) => {
data.node.template[name].file_path = t;
}}
></InputFileComponent>
</div>
) : type === "prompt" ? (
<div className="w-1/2">
<PromptAreaComponent
field_name={name}
disabled={false}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
}}
/>
</div>
) : type === "code" ? (
<div className="w-1/2">
<CodeAreaComponent
dynamic={data.node.template[name].dynamic ?? false}
setNodeClass={(nodeClass) => {
data.node = nodeClass;
}}
nodeClass={data.node}
disabled={false}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
}}
/>
</div>
) : (
<div className="hidden"></div>
)}
</div>
);
}

View file

@ -47,11 +47,13 @@ interface BaseModalProps {
];
open?: boolean;
setOpen?: (open: boolean) => void;
disable?: boolean;
size?: "smaller" | "small" | "medium" | "large" | "large-h-full";
}
function BaseModal({
open,
setOpen,
disable = false,
children,
size = "large",
}: BaseModalProps) {
@ -74,7 +76,7 @@ function BaseModal({
switch (size) {
case "smaller":
minWidth = "min-w-[40vw]";
height = "h-[25vh]";
height = "h-[27vh]";
break;
case "small":
minWidth = "min-w-[40vw]";
@ -100,7 +102,10 @@ function BaseModal({
//UPDATE COLORS AND STYLE CLASSSES
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger className="w-full" hidden={triggerChild ? false : true}>
<DialogTrigger
className={"w-full " + (disable ? "button-disable" : "")}
hidden={triggerChild ? false : true}
>
{triggerChild}
</DialogTrigger>
<DialogContent className={minWidth}>
@ -108,8 +113,9 @@ function BaseModal({
<div className={`mt-2 flex flex-col ${height} w-full `}>
{ContentChild}
</div>
<div className="flex flex-row-reverse">{ContentFooter}</div>
{ContentFooter && (
<div className="flex flex-row-reverse">{ContentFooter}</div>
)}
</DialogContent>
</Dialog>
);

View file

@ -3,15 +3,18 @@ import "ace-builds/src-noconflict/ext-language_tools";
import "ace-builds/src-noconflict/mode-python";
import "ace-builds/src-noconflict/theme-github";
import "ace-builds/src-noconflict/theme-twilight";
import { useContext, useState } from "react";
// import "ace-builds/webpack-resolver";
import { ReactNode, useContext, useEffect, useState } from "react";
import AceEditor from "react-ace";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { CODE_PROMPT_DIALOG_SUBTITLE } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { darkContext } from "../../contexts/darkContext";
import { postValidateCode } from "../../controllers/API";
import { codeAreaModalPropsType } from "../../types/components";
import { typesContext } from "../../contexts/typesContext";
import { postCustomComponent, postValidateCode } from "../../controllers/API";
import { APIClassType } from "../../types/api";
import BaseModal from "../baseModal";
export default function CodeAreaModal({
@ -20,12 +23,27 @@ export default function CodeAreaModal({
nodeClass,
setNodeClass,
children,
dynamic,
}: codeAreaModalPropsType): JSX.Element {
const [code, setCode] = useState(value);
const { dark } = useContext(darkContext);
const { reactFlowInstance } = useContext(typesContext);
const [height, setHeight] = useState(null);
const { setErrorData, setSuccessData } = useContext(alertContext);
const [error, setError] = useState<{
detail: { error: string; traceback: string };
}>(null);
function handleClick() {
useEffect(() => {
// if nodeClass.template has more fields other than code and dynamic is true
// do not run handleClick
if (dynamic && Object.keys(nodeClass.template).length > 2) {
return;
}
processCode();
}, []);
function processNonDynamicField() {
postValidateCode(code)
.then((apiReturn) => {
if (apiReturn.data) {
@ -35,8 +53,9 @@ export default function CodeAreaModal({
setSuccessData({
title: "Code is ready to run",
});
setValue(code);
setOpen(false);
setValue(code);
// setValue(code);
} else {
if (funcErrors.length !== 0) {
setErrorData({
@ -64,8 +83,56 @@ export default function CodeAreaModal({
});
}
function processDynamicField() {
postCustomComponent(code, nodeClass)
.then((apiReturn) => {
const { data } = apiReturn;
if (data) {
setNodeClass(data);
setValue(code);
setError({ detail: { error: undefined, traceback: undefined } });
setOpen(false);
}
})
.catch((err) => {
setError(err.response.data);
});
}
function processCode() {
if (!dynamic) {
processNonDynamicField();
} else {
processDynamicField();
}
}
function handleClick() {
processCode();
}
useEffect(() => {
// Function to be executed after the state changes
const delayedFunction = setTimeout(() => {
if (error?.detail.error !== undefined) {
//trigger to update the height, does not really apply any height
setHeight("90%");
}
//600 to happen after the transition of 500ms
}, 600);
// Cleanup function to clear the timeout if the component unmounts or the state changes again
return () => {
clearTimeout(delayedFunction);
};
}, [error, setHeight]);
const [open, setOpen] = useState(false);
useEffect(() => {
setCode(value);
}, [value, open]);
return (
<BaseModal open={open} setOpen={setOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
@ -83,6 +150,7 @@ export default function CodeAreaModal({
<AceEditor
value={code}
mode="python"
height={height ?? "100%"}
highlightActiveLine={true}
showPrintMargin={false}
fontSize={14}
@ -93,9 +161,26 @@ export default function CodeAreaModal({
onChange={(value) => {
setCode(value);
}}
className="h-full w-full rounded-lg border-[1px] border-border custom-scroll"
className="h-full w-full rounded-lg border-[1px] border-gray-300 custom-scroll dark:border-gray-600"
/>
</div>
<div
className={
"w-full transition-all delay-500 " +
(error?.detail.error !== undefined ? "h-2/6" : "h-0")
}
>
<div className="mt-1 h-full w-full overflow-y-auto overflow-x-clip text-left custom-scroll">
<h1 className="text-lg text-destructive">
{error?.detail?.error}
</h1>
<div className="ml-2 w-full break-all text-sm text-status-red">
<pre className="w-full whitespace-pre-wrap break-all">
{error?.detail?.traceback}
</pre>
</div>
</div>
</div>
<div className="flex h-fit w-full justify-end">
<Button className="mt-3" onClick={handleClick} type="submit">
Check & Save

View file

@ -21,6 +21,8 @@ export default function FlowSettingsModal({
const [description, setDescription] = useState(
flows.find((f) => f.id === tabId).description
);
const [invalidName, setInvalidName] = useState(false);
function handleClick(): void {
let savedFlow = flows.find((f) => f.id === tabId);
savedFlow.name = name;
@ -37,6 +39,8 @@ export default function FlowSettingsModal({
</BaseModal.Header>
<BaseModal.Content>
<EditFlowSettings
invalidName={invalidName}
setInvalidName={setInvalidName}
name={name}
description={description}
flows={flows}
@ -48,7 +52,7 @@ export default function FlowSettingsModal({
</BaseModal.Content>
<BaseModal.Footer>
<Button onClick={handleClick} type="submit">
<Button disabled={invalidName} onClick={handleClick} type="submit">
Save
</Button>
</BaseModal.Footer>

View file

@ -112,7 +112,6 @@ export default function FormModal({
return newChat;
});
};
//add proper type signature for function
function updateLastMessage({
@ -374,11 +373,7 @@ export default function FormModal({
if (lockChat) setLockChat(false);
}
function setModalOpen(x: boolean): void {
setOpen(x);
}
function handleOnCheckedChange(checked: boolean, i: string): void {
function handleOnCheckedChange(checked: boolean, i: string) {
if (checked === true) {
setChatKey(i);
setChatValue(tabsState[flow.id].formKeysData.input_keys[i]);
@ -497,22 +492,12 @@ export default function FormModal({
<Badge variant="gray" size="md">
{i}
</Badge>
<div
className="-mb-1"
onClick={(event) => {
event.stopPropagation();
}}
>
<div className="-mb-1">
<ToggleShadComponent
enabled={chatKey === i}
setEnabled={(value) =>
handleOnCheckedChange(value, i)
}
setEnabled={() => {}}
size="small"
disabled={tabsState[
id.current
].formKeysData.handle_keys?.some((t) => t === i)}
disabled={true}
/>
</div>
</div>
@ -521,28 +506,9 @@ export default function FormModal({
keyValue={i}
>
<div className="file-component-tab-column">
{tabsState[id.current].formKeysData.handle_keys?.some(
(t) => t === i
) && (
<div className="font-normal text-muted-foreground ">
Source: Component
</div>
)}
<Textarea
className="custom-scroll"
value={tabsState[id.current].formKeysData.input_keys[i]}
onChange={(e) => {
setTabsState((old) => {
let newTabsState = _.cloneDeep(old);
newTabsState[id.current].formKeysData.input_keys[
i
] = e.target.value;
return newTabsState;
});
}}
disabled={chatKey === i}
placeholder="Enter text..."
></Textarea>
<div className="font-normal text-muted-foreground ">
Source: Memory
</div>
</div>
</AccordionComponent>
</div>

View file

@ -1,4 +1,4 @@
import { useContext, useState } from "react";
import { useContext, useEffect, useState } from "react";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
import IconComponent from "../../../../components/genericIconComponent";
import { Input } from "../../../../components/ui/input";
@ -18,8 +18,8 @@ import { classNames } from "../../../../utils/utils";
import DisclosureComponent from "../DisclosureComponent";
export default function ExtraSidebar(): JSX.Element {
const { data } = useContext(typesContext);
const { flows, tabId, uploadFlow, tabsState, saveFlow } =
const { data, templates } = useContext(typesContext);
const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt } =
useContext(TabsContext);
const { setSuccessData, setErrorData } = useContext(alertContext);
const [dataFilter, setFilterData] = useState(data);
@ -57,56 +57,83 @@ export default function ExtraSidebar(): JSX.Element {
});
}
const flow = flows.find((f) => f.id === tabId);
useEffect(() => {
// show components with error on load
let errors = [];
Object.keys(templates).forEach((component) => {
if (templates[component].error) {
errors.push(component);
}
});
if (errors.length > 0)
setErrorData({ title: " Components with errors: ", list: errors });
}, []);
return (
<div className="side-bar-arrangement">
<div className="side-bar-buttons-arrangement">
<ShadTooltip content="Import" side="top">
<button
className="extra-side-bar-buttons"
onClick={(): void => {
uploadFlow();
}}
>
<IconComponent name="FileUp" className="side-bar-button-size " />
</button>
</ShadTooltip>
<ShadTooltip content="Export" side="top">
<div className="side-bar-button">
<ShadTooltip content="Import" side="top">
<button
className="extra-side-bar-buttons"
onClick={() => {
uploadFlow();
}}
>
<IconComponent name="FileUp" className="side-bar-button-size " />
</button>
</ShadTooltip>
</div>
<div className="side-bar-button">
<ExportModal>
<div className={classNames("extra-side-bar-buttons")}>
<IconComponent name="FileDown" className="side-bar-button-size" />
</div>
</ExportModal>
</ShadTooltip>
<ShadTooltip content="Code" side="top">
{flow && flow.data && (
<ApiModal flow={flow}>
<ShadTooltip content="Export" side="top">
<div className={classNames("extra-side-bar-buttons")}>
<IconComponent name="Code2" className="side-bar-button-size" />
<IconComponent
name="FileDown"
className="side-bar-button-size"
/>
</div>
</ApiModal>
)}
</ShadTooltip>
</ExportModal>
</div>
<ShadTooltip content={"Code"} side="top">
<div className="side-bar-button">
{flow && flow.data && (
<ApiModal flow={flow} disable={!isBuilt}>
<div className={classNames("extra-side-bar-buttons")}>
<IconComponent
name="Code2"
className={
"side-bar-button-size" +
(isBuilt ? " " : " extra-side-bar-save-disable")
}
/>
</div>
</ApiModal>
)}
</div>
</ShadTooltip>
<ShadTooltip content="Save" side="top">
<button
className="extra-side-bar-buttons"
onClick={(event) => {
saveFlow(flow);
setSuccessData({ title: "Changes saved successfully" });
}}
disabled={!isPending}
>
<IconComponent
name="Save"
<div className="side-bar-button">
<ShadTooltip content="Save" side="top">
<button
className={
"side-bar-button-size" +
(isPending ? " " : " extra-side-bar-save-disable")
"extra-side-bar-buttons " + (isPending ? "" : "button-disable")
}
/>
</button>
</ShadTooltip>
onClick={(event) => {
saveFlow(flow);
setSuccessData({ title: "Changes saved successfully" });
}}
>
<IconComponent
name="Save"
className={
"side-bar-button-size" +
(isPending ? " " : " extra-side-bar-save-disable")
}
/>
</button>
</ShadTooltip>
</div>
</div>
<Separator />
<div className="side-bar-search-div-placement">
@ -138,7 +165,7 @@ export default function ExtraSidebar(): JSX.Element {
Object.keys(dataFilter[d]).length > 0 ? (
<DisclosureComponent
openDisc={search.length == 0 ? false : true}
key={nodeNames[d]}
key={i}
button={{
title: nodeNames[d] ?? nodeNames.unknown,
Icon: nodeIconsLucide[d] ?? nodeIconsLucide.unknown,
@ -151,13 +178,16 @@ export default function ExtraSidebar(): JSX.Element {
<ShadTooltip
content={data[d][t].display_name}
side="right"
key={data[d][t].display_name}
key={k}
>
<div key={k} data-tooltip-id={t}>
<div
draggable
draggable={!data[d][t].error}
className={
"side-bar-components-border bg-background"
"side-bar-components-border bg-background" +
(data[d][t].error
? " cursor-not-allowed select-none"
: "")
}
style={{
borderLeftColor:

File diff suppressed because it is too large Load diff

View file

@ -1,30 +1,38 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
pre {
font-family: inherit;
font-family: inherit;
}
.react-flow__pane {
cursor: default;
cursor: default;
}
.AccordionContent {
overflow: hidden;
overflow: hidden;
}
.AccordionContent[data-state='open'] {
animation: slideDown 300ms ease-out;
.AccordionContent[data-state="open"] {
animation: slideDown 300ms ease-out;
}
.AccordionContent[data-state="closed"] {
animation: slideUp 300ms ease-out;
}
.gradient-end {
animation: gradient-motion-end 3s infinite forwards;
}
.gradient-start {
animation: gradient-motion-start 4s infinite forwards;
}
.AccordionContent[data-state='closed'] {
animation: slideUp 300ms ease-out;
}

View file

@ -2,125 +2,127 @@
@tailwind components;
@tailwind utilities;
/* TODO: Confirm that all colors here are found in tailwind config */
@layer base {
:root {
--background: 0 0% 100%; /* hsl(0 0% 100%) */
--foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
--muted: 210 40% 98%; /* hsl(210 40% 98%) */
--muted-foreground: 215.4 16.3% 46.9%; /* hsl(215 16% 46%) */
--popover: 0 0% 100%; /* hsl(0 0% 100%) */
--popover-foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
--card: 0 0% 100%; /* hsl(0 0% 100%) */
--card-foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
--border: 214.3 21.8% 91.4%; /* hsl(214 32% 91%) */
--input: 214.3 21.8% 91.4%; /* hsl(214 32% 91%) */
--primary: 222.2 27% 11.2%; /* hsl(222 27% 18%) */
--primary-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
--secondary: 210 40% 96.1%; /* hsl(210 40% 96%) */
--secondary-foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
--accent: 210 30% 96.1%; /* hsl(210 30% 96%) */
--accent-foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
--destructive: 0 100% 50%; /* hsl(0 100% 50%) */
--destructive-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
--radius: 0.5rem;
--ring: 215 20.2% 65.1%; /* hsl(215 20% 65%) */
--round-btn-shadow: #00000063;
--error-background: #fef2f2;
--error-foreground: #991b1b;
--success-background: #f0fdf4;
--success-foreground: #14532d;
--background: 0 0% 100%; /* hsl(0 0% 100%) */
--foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
--muted: 210 40% 98%; /* hsl(210 40% 98%) */
--muted-foreground: 215.4 16.3% 46.9%; /* hsl(215 16% 46%) */
--popover: 0 0% 100%; /* hsl(0 0% 100%) */
--popover-foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
--card: 0 0% 100%; /* hsl(0 0% 100%) */
--card-foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
--border: 214.3 21.8% 91.4%; /* hsl(214 32% 91%) */
--input: 214.3 21.8% 91.4%; /* hsl(214 32% 91%) */
--primary: 222.2 27% 11.2%; /* hsl(222 27% 18%) */
--primary-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
--secondary: 210 40% 96.1%; /* hsl(210 40% 96%) */
--secondary-foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
--accent: 210 30% 96.1%; /* hsl(210 30% 96%) */
--accent-foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
--destructive: 0 100% 50%; /* hsl(0 100% 50%) */
--destructive-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
--radius: 0.5rem;
--ring: 215 20.2% 65.1%; /* hsl(215 20% 65%) */
--round-btn-shadow: #00000063;
--info-background: #f0f4fd;
--info-foreground: #141653;
--error-background: #fef2f2;
--error-foreground: #991b1b;
--high-indigo: #4338ca;
--medium-indigo: #6366f1;
--low-indigo: #e0e7ff;
--success-background: #f0fdf4;
--success-foreground: #14532d;
--chat-bot-icon: #afe6ef;
--chat-user-icon: #aface9;
/* Colors that are shared in dark and light mode */
--blur-shared: #151923de;
--build-trigger: #dc735b;
--chat-trigger: #5c8be1;
--chat-trigger-disabled: #b4c3da;
--status-red: #ef4444;
--status-yellow: #eab308;
--chat-send: #059669;
--status-green: #4ade80;
--status-blue:#2563eb;
--connection: #555;
--info-background: #f0f4fd;
--info-foreground: #141653;
--high-indigo: #4338ca;
--medium-indigo: #6366f1;
--low-indigo: #e0e7ff;
--beta-background: rgb(219 234 254);
--beta-foreground: rgb(37 99 235);
--chat-bot-icon: #afe6ef;
--chat-user-icon: #aface9;
/* Colors that are shared in dark and light mode */
--blur-shared: #151923de;
--build-trigger: #dc735b;
--chat-trigger: #5c8be1;
--chat-trigger-disabled: #b4c3da;
--status-red: #ef4444;
--status-yellow: #eab308;
--chat-send: #059669;
--status-green: #4ade80;
--status-blue: #2563eb;
--connection: #555;
}
.dark {
--background: 224 35% 7.5%; /* hsl(224 40% 10%) */
--foreground: 213 31% 80%; /* hsl(213 31% 91%) */
--muted: 223 27% 11%; /* hsl(223 27% 11%) */
--muted-foreground: 215.4 16.3% 56.9%; /* hsl(215 16% 56%) */
--popover: 224 71% 4%; /* hsl(224 71% 4%) */
--popover-foreground: 215 20.2% 65.1%; /* hsl(215 20% 65%) */
--card: 224 25% 15.5%; /* hsl(224 71% 4%) */
--card-foreground: 213 31% 80%; /* hsl(213 31% 91%) */
--border: 216 24% 17%; /* hsl(216 34% 17%) */
--input: 216 24% 17%; /* hsl(216 34% 17%) */
--primary: 210 20% 80%; /* hsl(210 20% 80%) */
--primary-foreground: 222.2 27.4% 1.2%; /* hsl(222 47% 1%) */
--secondary: 222.2 37.4% 7.2%; /* hsl(222 47% 11%) */
--secondary-foreground: 210 40% 80%; /* hsl(210 40% 80%) */
--accent: 216 24% 20%; /* hsl(216 34% 17%) */
--accent-foreground: 210 30% 98%; /* hsl(210 40% 98%) */
--destructive: 0 63% 31%; /* hsl(0 63% 31%) */
--destructive-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
--ring: 216 24% 30%; /* hsl(216 24% 30%) */
--radius: 0.5rem;
--round-btn-shadow: #00000063;
--success-background: #022c22;
--success-foreground: #ecfdf5;
--error-foreground: #fef2f2;
--error-background: #450a0a;
--info-foreground: #eff6ff;
--info-background: #172554;
--high-indigo: #4338ca;
--medium-indigo: #6366f1;
--low-indigo: #e0e7ff;
/* Colors that are shared in dark and light mode */
--blur-shared: #151923d2;
--build-trigger: #dc735b;
--chat-trigger: #5c8be1;
--chat-trigger-disabled: #2d3b54;
--status-red: #ef4444;
--status-yellow: #eab308;
--chat-send: #059669;
--status-green: #4ade80;
--status-blue: #2563eb;
--connection: #555;
--beta-background: rgb(37 99 235);
--beta-foreground: rgb(219 234 254);
--chat-bot-icon: #235d70;
--chat-user-icon: #4f3d6e;
}
}
.dark {
--background: 224 35% 7.5%; /* hsl(224 40% 10%) */
--foreground: 213 31% 80%; /* hsl(213 31% 91%) */
--muted: 223 27% 11%; /* hsl(223 27% 11%) */
--muted-foreground: 215.4 16.3% 56.9%; /* hsl(215 16% 56%) */
--popover: 224 71% 4%; /* hsl(224 71% 4%) */
--popover-foreground: 215 20.2% 65.1%; /* hsl(215 20% 65%) */
--card: 224 25% 15.5%; /* hsl(224 71% 4%) */
--card-foreground: 213 31% 80%; /* hsl(213 31% 91%) */
--border: 216 24% 17%; /* hsl(216 34% 17%) */
--input: 216 24% 17%; /* hsl(216 34% 17%) */
--primary: 210 20% 80%; /* hsl(210 20% 80%) */
--primary-foreground: 222.2 27.4% 1.2%; /* hsl(222 47% 1%) */
--secondary: 222.2 37.4% 7.2%; /* hsl(222 47% 11%) */
--secondary-foreground: 210 40% 80%; /* hsl(210 40% 80%) */
--accent: 216 24% 20%; /* hsl(216 34% 17%) */
--accent-foreground: 210 30% 98%; /* hsl(210 40% 98%) */
--destructive: 0 63% 31%; /* hsl(0 63% 31%) */
--destructive-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
--ring: 216 24% 30%; /* hsl(216 24% 30%) */
--radius: 0.5rem;
--round-btn-shadow: #00000063;
--success-background: #022c22;
--success-foreground: #ecfdf5;
--error-foreground: #fef2f2;
--error-background: #450a0a;
--info-foreground: #eff6ff;
--info-background: #172554;
--high-indigo: #4338ca;
--medium-indigo: #6366f1;
--low-indigo: #e0e7ff;
/* Colors that are shared in dark and light mode */
--blur-shared: #151923d2;
--build-trigger: #dc735b;
--chat-trigger: #5c8be1;
--chat-trigger-disabled: #2d3b54;
--status-red: #ef4444;
--status-yellow: #eab308;
--chat-send: #059669;
--status-green: #4ade80;
--status-blue: #2563eb;
--connection: #555;
--chat-bot-icon: #235d70;
--chat-user-icon: #4f3d6e;
}}

View file

@ -14,8 +14,10 @@ export type APIClassType = {
display_name: string;
input_types?: Array<string>;
output_types?: Array<string>;
beta?: boolean;
documentation: string;
[key: string]: Array<string> | string | APITemplateType | undefined;
error?: string;
[key: string]: Array<string> | string | APITemplateType | boolean | undefined;
};
export type TemplateVariableType = {

View file

@ -65,8 +65,9 @@ export type CodeAreaComponentType = {
onChange: (value: string[] | string) => void;
value: string;
editNode?: boolean;
nodeClass: APIClassType;
setNodeClass: (value: APIClassType) => void;
nodeClass?: APIClassType;
setNodeClass?: (value: APIClassType) => void;
dynamic?: boolean;
};
export type FileComponentType = {
@ -176,9 +177,11 @@ export type InputProps = {
maxLength?: number;
flows: Array<{ id: string; name: string; description: string }>;
tabId: string;
invalidName: boolean;
setName: (name: string) => void;
setDescription: (description: string) => void;
updateFlow: (flow: { id: string; name: string }) => void;
setInvalidName: (invalidName: boolean) => void;
};
export type TooltipProps = {
@ -321,6 +324,7 @@ export type codeAreaModalPropsType = {
nodeClass: APIClassType | undefined;
setNodeClass: (Class: APIClassType) => void | undefined;
children: ReactNode;
dynamic?: boolean;
};
export type chatMessagePropsType = {

View file

@ -18,6 +18,8 @@ export type TabsContextType = {
) => void;
downloadFlows: () => void;
uploadFlows: () => void;
isBuilt: boolean;
setIsBuilt: (state: boolean) => void;
uploadFlow: (newFlow?: boolean, file?: File) => void;
hardReset: () => void;
getNodeId: (nodeType: string) => string;

View file

@ -219,3 +219,16 @@ export function validateNodes(reactFlowInstance: ReactFlowInstance) {
.getNodes()
.flatMap((n: NodeType) => validateNode(n, reactFlowInstance));
}
export function addVersionToDuplicates(flow: FlowType, flows: FlowType[]) {
const existingNames = flows.map((item) => item.name);
let newName = flow.name;
let count = 1;
while (existingNames.includes(newName)) {
newName = `${flow.name} (${count})`;
count++;
}
return newName;
}

View file

@ -14,6 +14,7 @@ import {
Cpu,
Download,
DownloadCloud,
Edit,
Eraser,
ExternalLink,
File,
@ -74,6 +75,7 @@ import { EvernoteIcon } from "../icons/Evernote";
import { FBIcon } from "../icons/FacebookMessenger";
import { GitBookIcon } from "../icons/GitBook";
import { GoogleIcon } from "../icons/Google";
import GradientSparkles from "../icons/GradientSparkles";
import { HuggingFaceIcon } from "../icons/HuggingFace";
import { IFixIcon } from "../icons/IFixIt";
import { MetaIcon } from "../icons/Meta";
@ -147,6 +149,7 @@ export const nodeColors: { [char: string]: string } = {
str: "#049524",
retrievers: "#e6b25a",
unknown: "#9CA3AF",
custom_components: "#ab11ab",
};
export const nodeNames: { [char: string]: string } = {
@ -167,7 +170,8 @@ export const nodeNames: { [char: string]: string } = {
retrievers: "Retrievers",
utilities: "Utilities",
output_parsers: "Output Parsers",
unknown: "Unknown",
custom_components: "Custom",
unknown: "Other",
};
export const nodeIconsLucide: iconsType = {
@ -225,6 +229,8 @@ export const nodeIconsLucide: iconsType = {
unknown: HelpCircle,
WikipediaQueryRun: SvgWikipedia,
WolframAlphaQueryRun: SvgWolfram,
custom_components: GradientSparkles,
custom: Edit,
Trash2,
X,
XCircle,

View file

@ -1,13 +1,8 @@
import clsx, { ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
import { ADJECTIVES, DESCRIPTIONS, NOUNS } from "../flow_constants";
import {
IVarHighlightType,
groupDataType,
groupedObjType,
tweakType,
} from "../types/components";
import { FlowType } from "../types/flow";
import { IVarHighlightType, groupDataType, groupedObjType, tweakType } from "../types/components";
import { FlowType, NodeType } from "../types/flow";
import { TabsState } from "../types/tabs";
import { buildTweaks } from "./reactflowUtils";
@ -93,124 +88,88 @@ export function checkUpperWords(str: string): string {
export const isWrappedWithClass = (event: any, className: string | undefined) =>
event.target.closest(`.${className}`);
export function groupByFamily(
data: groupDataType,
baseClasses: string,
left: boolean,
type: string
): groupedObjType[] {
let parentOutput: string;
let arrOfParent: string[] = [];
let arrOfType: { family: string; type: string; component: string }[] = [];
let arrOfLength: { length: number; type: string }[] = [];
let lastType = "";
Object.keys(data).forEach((d) => {
Object.keys(data[d]).forEach((n) => {
try {
if (
data[d][n].base_classes.some((r) =>
baseClasses.split("\n").includes(r)
)
) {
arrOfParent.push(d);
}
if (n === type) {
parentOutput = d;
}
export function groupByFamily(data: groupDataType, baseClasses: string, left: boolean, flow?: NodeType[]): groupedObjType[] {
const baseClassesSet = new Set(baseClasses.split("\n"));
let arrOfPossibleInputs = [];
let arrOfPossibleOutputs = [];
let checkedNodes = new Map();
const excludeTypes = new Set([
"str",
"bool",
"float",
"code",
"prompt",
"file",
"int",
]);
if (d !== lastType) {
arrOfLength.push({
length: Object.keys(data[d]).length,
type: d,
});
const checkBaseClass = (t: any) =>
t.type &&
t.show &&
((!excludeTypes.has(t.type) && baseClassesSet.has(t.type)) ||
(t.input_types && t.input_types.some((x) => baseClassesSet.has(x))));
lastType = d;
}
} catch (e) {
console.log(e);
}
});
});
if (flow) {
for (const node of flow) {
const nodeData = node.data;
const foundNode = checkedNodes.get(nodeData.type);
checkedNodes.set(nodeData.type, {
hasBaseClassInTemplate:
foundNode?.hasBaseClassInTemplate ||
Object.values(nodeData.node.template).some(checkBaseClass),
hasBaseClassInBaseClasses:
foundNode?.hasBaseClassInBaseClasses ||
nodeData.node.base_classes.some((t) => baseClassesSet.has(t)),
});
}
}
Object.keys(data).map((d) => {
Object.keys(data[d]).map((n) => {
try {
baseClasses.split("\n").forEach((tol) => {
data[d][n].base_classes.forEach((data) => {
if (tol === data) {
arrOfType.push({
family: d,
type: data,
component: n,
});
}
});
});
} catch (e) {
console.log(e);
}
});
});
for (const [d, nodes] of Object.entries(data)) {
let tempInputs = [],
tempOutputs = [];
if (left === false) {
let groupedBy = arrOfType.filter((object, index, self) => {
const foundIndex = self.findIndex(
(o) => o.family === object.family && o.type === object.type
);
return foundIndex === index;
});
return groupedBy.reduce((result, item) => {
const existingGroup = result.find(
(group) => group.family === item.family
);
if (existingGroup) {
existingGroup.type += `, ${item.type}`;
} else {
result.push({
family: item.family,
type: item.type,
component: item.component,
});
for (const [n, node] of Object.entries(nodes)) {
let foundNode = checkedNodes.get(n);
if (!foundNode) {
foundNode = {
hasBaseClassInTemplate: Object.values(node.template).some(
checkBaseClass
),
hasBaseClassInBaseClasses: node.base_classes.some((t) =>
baseClassesSet.has(t)
),
};
checkedNodes.set(n, foundNode);
}
if (left === false) {
let resFil = result.filter((group) => group.family === parentOutput);
result = resFil;
}
return result;
}, []);
} else {
const groupedArray = [];
const groupedData = {};
arrOfType.forEach((item) => {
const { family, type, component } = item;
const key = `${family}-${type}`;
if (!groupedData[key]) {
groupedData[key] = { family, type, component: [component] };
} else {
groupedData[key].component.push(component);
}
});
for (const key in groupedData) {
groupedArray.push(groupedData[key]);
if (foundNode.hasBaseClassInTemplate) tempInputs.push(n);
if (foundNode.hasBaseClassInBaseClasses) tempOutputs.push(n);
}
groupedArray.forEach((object, index, self) => {
const findObj = arrOfLength.find((x) => x.type === object.family);
if (object.component.length === findObj?.length) {
self[index]["type"] = "";
} else {
self[index]["type"] = object.component.join(", ");
}
});
return groupedArray;
const totalNodes = Object.keys(nodes).length;
if (tempInputs.length)
arrOfPossibleInputs.push({
category: d,
nodes: tempInputs,
full: tempInputs.length === totalNodes,
});
if (tempOutputs.length)
arrOfPossibleOutputs.push({
category: d,
nodes: tempOutputs,
full: tempOutputs.length === totalNodes,
});
}
return left
? arrOfPossibleOutputs.map((t) => ({
family: t.category,
type: t.full ? "" : t.nodes.join(", "),
}))
: arrOfPossibleInputs.map((t) => ({
family: t.category,
type: t.full ? "" : t.nodes.join(", "),
}));
}
export function buildInputs(tabsState: TabsState, id: string): string {
@ -374,7 +333,7 @@ export function getCurlCode(
/**
* Function to get the python code for the API
* @param {string} flowName - The name of the flow
* @param {string} flow - The current flow
* @returns {string} - The python code
*/
export function getPythonCode(
@ -396,3 +355,33 @@ flow = load_flow_from_json("${flowName}.json", tweaks=TWEAKS)
inputs = ${inputs}
flow(inputs)`;
}
/**
* Function to get the widget code for the API
* @param {string} flow - The current flow.
* @returns {string} - The widget code
*/
export function getWidgetCode(flow: FlowType, tabsState?: TabsState): string {
const flowId = flow.id;
const flowName = flow.name;
const inputs = buildInputs(tabsState, flow.id);
return `<script src="https://cdn.jsdelivr.net/gh/logspace-ai/langflow-embedded-chat@main/dist/build/static/js/bundle.min.js"></script>
<!-- chat_inputs: Stringified JSON with all the input keys and its values. The value of the key that is defined
as chat_input_field will be overwritten by the chat message.
chat_input_field: Input key that you want the chat to send the user message with. -->
<langflow-chat
window_title="${flowName}"
flow_id="${flowId}"
${
tabsState[flow.id] && tabsState[flow.id].formKeysData
? `chat_inputs='${inputs}'
chat_input_field="${
Object.keys(tabsState[flow.id].formKeysData.input_keys)[0]
}"
`
: ""
}host_url="http://localhost:7860"
></langflow-chat>`;
}

View file

@ -71,6 +71,8 @@ module.exports = {
"status-yellow": "var(--status-yellow)",
"success-background": "var(--success-background)",
"success-foreground": "var(--success-foreground)",
"beta-background": "var(--beta-background)",
"beta-foreground": "var(--beta-foreground)",
"chat-bot-icon": "var(--chat-bot-icon)",
"chat-user-icon": "var(--chat-user-icon)",