Merge branch 'cz/mergeAll' into two_edges

This commit is contained in:
ogabrielluiz 2024-06-10 15:57:46 -03:00
commit 921250cbe1
63 changed files with 710 additions and 737 deletions

View file

@ -684,7 +684,6 @@
"max": 1,
"step": 0.1
},
<<<<<<< HEAD
"data": {
"type": "Prompt",
"node": {
@ -823,8 +822,7 @@
"x": 2969.0261961391298,
"y": 442.1613649809069
},
"dragging": false
=======
"dragging": false,
"load_from_db": false,
"title_case": false
},
@ -907,7 +905,6 @@
]
},
"_type": "CustomComponent"
>>>>>>> origin/dev
},
"description": "Generate embeddings using OpenAI models.",
"base_classes": [

View file

@ -219,7 +219,7 @@ async def build_and_cache_graph_from_db(flow_id: str, session: Session, chat_ser
if vertex is None:
raise ValueError(f"Vertex {vertex_id} not found")
if not vertex._raw_params.get("session_id"):
vertex.update_raw_params({"session_id": flow_id})
vertex.update_raw_params({"session_id": flow_id}, overwrite=True)
await chat_service.set_cache(flow_id, graph)
return graph

View file

@ -0,0 +1,24 @@
from langflow.schema import Record
def chroma_collection_to_records(collection_dict: dict):
"""
Converts a collection of chroma vectors into a list of records.
Args:
collection_dict (dict): A dictionary containing the collection of chroma vectors.
Returns:
list: A list of records, where each record represents a document in the collection.
"""
records = []
for i, doc in enumerate(collection_dict["documents"]):
record_dict = {
"id": collection_dict["ids"][i],
"text": doc,
}
if "metadatas" in collection_dict:
for key, value in collection_dict["metadatas"][i].items():
record_dict[key] = value
records.append(Record(**record_dict))
return records

View file

@ -15,4 +15,5 @@ class RecordsOutput(Component):
]
def record_response(self) -> Record:
self.status = self.input_value
return self.input_value

View file

@ -0,0 +1,68 @@
# from langflow.field_typing import Data
from langchain.chains.query_constructor.base import AttributeInfo
from langchain.retrievers.self_query.base import SelfQueryRetriever
from langchain_core.vectorstores import VectorStore
from langflow.custom import CustomComponent
from langflow.field_typing import BaseLanguageModel, Text
from langflow.schema import Record
from langflow.schema.message import Message
class SelfQueryRetrieverComponent(CustomComponent):
display_name: str = "Self Query Retriever"
description: str = "Retriever that uses a vector store and an LLM to generate the vector store queries."
icon = "LangChain"
def build_config(self):
return {
"query": {
"display_name": "Query",
"input_types": ["Message", "Text"],
"info": "Query to be passed as input.",
},
"vectorstore": {
"display_name": "Vector Store",
"info": "Vector Store to be passed as input.",
},
"attribute_infos": {
"display_name": "Metadata Field Info",
"info": "Metadata Field Info to be passed as input.",
},
"document_content_description": {
"display_name": "Document Content Description",
"info": "Document Content Description to be passed as input.",
},
"llm": {
"display_name": "LLM",
"info": "LLM to be passed as input.",
},
}
def build(
self,
query: Message,
vectorstore: VectorStore,
attribute_infos: list[Record],
document_content_description: Text,
llm: BaseLanguageModel,
) -> Record:
metadata_field_infos = [AttributeInfo(**record.data) for record in attribute_infos]
self_query_retriever = SelfQueryRetriever.from_llm(
llm=llm,
vectorstore=vectorstore,
document_contents=document_content_description,
metadata_field_info=metadata_field_infos,
enable_limit=True,
)
if isinstance(query, Message):
input_text = query.text
elif isinstance(query, str):
input_text = query
else:
raise ValueError(f"Query type {type(query)} not supported.")
documents = self_query_retriever.invoke(input=input_text)
records = [Record.from_document(document) for document in documents]
self.status = records
return records

View file

@ -1,3 +1,4 @@
from copy import deepcopy
from typing import List, Optional, Union
import chromadb
@ -7,6 +8,7 @@ from langchain_core.embeddings import Embeddings
from langchain_core.retrievers import BaseRetriever
from langchain_core.vectorstores import VectorStore
from langflow.base.vectorstores.utils import chroma_collection_to_records
from langflow.custom import CustomComponent
from langflow.schema import Record
@ -48,6 +50,11 @@ class ChromaComponent(CustomComponent):
"display_name": "Server SSL Enabled",
"advanced": True,
},
"allow_duplicates": {
"display_name": "Allow Duplicates",
"advanced": True,
"info": "If false, will not add documents that are already in the Vector Store.",
},
}
def build(
@ -61,6 +68,7 @@ class ChromaComponent(CustomComponent):
chroma_server_host: Optional[str] = None,
chroma_server_http_port: Optional[int] = None,
chroma_server_grpc_port: Optional[int] = None,
allow_duplicates: bool = False,
) -> Union[VectorStore, BaseRetriever]:
"""
Builds the Vector Store or BaseRetriever object.
@ -75,6 +83,7 @@ class ChromaComponent(CustomComponent):
- chroma_server_host (Optional[str]): The host for the Chroma server.
- chroma_server_http_port (Optional[int]): The HTTP port for the Chroma server.
- chroma_server_grpc_port (Optional[int]): The gRPC port for the Chroma server.
- allow_duplicates (bool): Whether to allow duplicates in the Vector Store.
Returns:
- Union[VectorStore, BaseRetriever]: The Vector Store or BaseRetriever object.
@ -93,32 +102,34 @@ class ChromaComponent(CustomComponent):
)
client = chromadb.HttpClient(settings=chroma_settings)
# If documents, then we need to create a Chroma instance using .from_documents
# Check index_directory and expand it if it is a relative path
if index_directory is not None:
index_directory = self.resolve_path(index_directory)
chroma = Chroma(
persist_directory=index_directory,
client=client,
embedding_function=embedding,
collection_name=collection_name,
)
if allow_duplicates:
stored_records = []
else:
stored_records = chroma_collection_to_records(chroma.get())
_stored_documents_without_id = []
for record in deepcopy(stored_records):
del record.id
_stored_documents_without_id.append(record)
documents = []
for _input in inputs or []:
if isinstance(_input, Record):
documents.append(_input.to_lc_document())
if _input not in _stored_documents_without_id:
documents.append(_input.to_lc_document())
else:
documents.append(_input)
if documents is not None and embedding is not None:
if len(documents) == 0:
raise ValueError("If documents are provided, there must be at least one document.")
chroma = Chroma.from_documents(
documents=documents, # type: ignore
persist_directory=index_directory,
collection_name=collection_name,
embedding=embedding,
client=client,
)
else:
chroma = Chroma(
persist_directory=index_directory,
client=client,
embedding_function=embedding,
)
raise ValueError("Inputs must be a Record objects.")
if documents and embedding is not None:
chroma.add_documents(documents)
self.status = stored_records
return chroma

View file

@ -123,8 +123,11 @@ class CustomComponent(BaseComponent):
@staticmethod
def resolve_path(path: str) -> str:
"""Resolves the path to an absolute path."""
if not path:
return path
path_object = Path(path)
if path_object.parts[0] == "~":
if path_object.parts and path_object.parts[0] == "~":
path_object = path_object.expanduser()
elif path_object.is_relative_to("."):
path_object = path_object.resolve()

View file

@ -25,6 +25,7 @@ class Prompt(Record):
prompt_template = PromptTemplate.from_template(self.template)
variables_with_str_values = dict_values_to_string(self.variables)
formatted_prompt = prompt_template.format(**variables_with_str_values)
self.text = formatted_prompt
return formatted_prompt
@classmethod

View file

@ -1,12 +1,12 @@
import copy
import json
from typing import cast, Optional
from typing import Optional, cast
from langchain_core.documents import Document
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage
from langchain_core.prompt_values import ImagePromptValue
from langchain_core.prompts.image import ImagePromptTemplate
from pydantic import BaseModel, model_serializer, model_validator
from langchain_core.prompt_values import ImagePromptValue
class Record(BaseModel):
@ -200,3 +200,6 @@ class Record(BaseModel):
def __contains__(self, key):
return key in self.data
def __eq__(self, other):
return isinstance(other, Record) and self.data == other.data

View file

@ -1 +1 @@
build/*
build/*

View file

@ -82,7 +82,7 @@
"start": "vite",
"build": "vite build",
"serve": "vite preview",
"format": "npx prettier --write \"{docs,src}/**/*.{js,jsx,ts,tsx,json,md}\" --ignore-path .prettierignore",
"format": "npx prettier --write \"{tests,src}/**/*.{js,jsx,ts,tsx,json,md}\" --ignore-path .prettierignore",
"type-check": "tsc --noEmit --pretty --project tsconfig.json && vite"
},
"simple-git-hooks": {
@ -138,4 +138,4 @@
"ua-parser-js": "^1.0.37",
"vite": "^4.5.2"
}
}
}

View file

@ -28,7 +28,6 @@ export default function SwitchOutputView(nodeId): JSX.Element {
if (resultMessage.raw) {
resultMessage = resultMessage.raw;
}
console.log("resultType", results);
return (
<>
<Case condition={!resultType || resultType === "unknown"}>

View file

@ -43,6 +43,7 @@ import useHandleNodeClass from "../../../hooks/use-handle-node-class";
import useHandleRefreshButtonPress from "../../../hooks/use-handle-refresh-buttons";
import HandleTooltips from "../HandleTooltipComponent";
import OutputComponent from "../OutputComponent";
import OutputModal from "../outputModal";
import { TEXT_FIELD_TYPES } from "./constants";
export default function ParameterComponent({
@ -64,6 +65,7 @@ export default function ParameterComponent({
outputProxy,
}: ParameterComponentType): JSX.Element {
const infoHtml = useRef<HTMLDivElement & ReactNode>(null);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const setNode = useFlowStore((state) => state.setNode);
@ -73,6 +75,17 @@ export default function ParameterComponent({
const updateNodeInternals = useUpdateNodeInternals();
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
const [openOutputModal, setOpenOutputModal] = useState(false);
const flowPool = useFlowStore((state) => state.flowPool);
const displayOutputPreview = !!flowPool[data.id];
const unknownOutput = !!(
flowPool[data.id] &&
flowPool[data.id][flowPool[data.id].length - 1]?.data?.logs[0]?.type ===
"unknown"
);
const { handleOnNewValue: handleOnNewValueHook } = useHandleOnNewValue(
data,
name,
@ -80,7 +93,7 @@ export default function ParameterComponent({
handleUpdateValues,
debouncedHandleUpdateValues,
setNode,
setIsLoading,
setIsLoading
);
const { handleNodeClass: handleNodeClassHook } = useHandleNodeClass(
@ -88,7 +101,7 @@ export default function ParameterComponent({
name,
takeSnapshot,
setNode,
updateNodeInternals,
updateNodeInternals
);
const { handleRefreshButtonPress: handleRefreshButtonPressHook } =
@ -97,7 +110,7 @@ export default function ParameterComponent({
let disabled =
edges.some(
(edge) =>
edge.targetHandle === scapedJSONStringfy(proxy ? { ...id, proxy } : id),
edge.targetHandle === scapedJSONStringfy(proxy ? { ...id, proxy } : id)
) ?? false;
const handleRefreshButtonPress = async (name, data) => {
@ -108,7 +121,7 @@ export default function ParameterComponent({
const handleOnNewValue = async (
newValue: string | string[] | boolean | Object[],
skipSnapshot: boolean | undefined = false,
skipSnapshot: boolean | undefined = false
): Promise<void> => {
handleOnNewValueHook(newValue, skipSnapshot);
};
@ -195,14 +208,14 @@ export default function ParameterComponent({
className={classNames(
left ? "my-12 -ml-0.5 " : " my-12 -mr-0.5 ",
"h-3 w-3 rounded-full border-2 bg-background",
!showNode ? "mt-0" : "",
!showNode ? "mt-0" : ""
)}
style={{
borderColor: color ?? nodeColors.unknown,
}}
onClick={() => {
setFilterEdge(
groupByFamily(myData, tooltipTitle!, left, nodes!),
groupByFamily(myData, tooltipTitle!, left, nodes!)
);
}}
></Handle>
@ -238,7 +251,40 @@ export default function ParameterComponent({
{renderTitle()}
</ShadTooltip>
) : (
renderTitle()
<div className="flex gap-2">
<span className={!left && data.node?.frozen ? " text-ice" : ""}>
{renderTitle()}
</span>
{!left && (
<ShadTooltip
content={
displayOutputPreview
? unknownOutput
? "Output can't be displayed"
: "Inspect Output"
: "Please build the component first"
}
>
<Button
variant="none"
size="none"
disabled={!displayOutputPreview || unknownOutput}
onClick={() => setOpenOutputModal(true)}
data-testid={`output-inspection-${title.toLowerCase()}`}
>
<IconComponent
className={classNames(
"h-5 w-5 rounded-md",
displayOutputPreview && !unknownOutput
? " hover:bg-secondary-foreground/5 hover:text-medium-indigo"
: " cursor-not-allowed text-muted-foreground"
)}
name={"ScanEye"}
/>
</Button>
</ShadTooltip>
)}
</div>
)}
<span className={(required ? "ml-2 " : "") + "text-status-red"}>
{required ? "*" : ""}
@ -287,12 +333,12 @@ export default function ParameterComponent({
}
className={classNames(
left ? "-ml-0.5" : "-mr-0.5",
"h-3 w-3 rounded-full border-2 bg-background",
"h-3 w-3 rounded-full border-2 bg-background"
)}
style={{ borderColor: color ?? nodeColors.unknown }}
onClick={() => {
setFilterEdge(
groupByFamily(myData, tooltipTitle!, left, nodes!),
groupByFamily(myData, tooltipTitle!, left, nodes!)
);
}}
/>
@ -387,7 +433,7 @@ export default function ParameterComponent({
});
}}
name={name}
data={data.node?.template[name]}
data={data.node?.template[name]!}
/>
</div>
{data.node?.template[name]?.refresh_button && (
@ -574,6 +620,13 @@ export default function ParameterComponent({
/>
</div>
</Case>
{openOutputModal && (
<OutputModal
open={openOutputModal}
nodeId={data.id}
setOpen={setOpenOutputModal}
/>
)}
</>
</div>
);

View file

@ -1,47 +1,52 @@
import emojiRegex from "emoji-regex";
import { cloneDeep } from "lodash";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { NodeToolbar, useUpdateNodeInternals } from "reactflow";
import IconComponent from "../../components/genericIconComponent";
import InputComponent from "../../components/inputComponent";
import ShadTooltip from "../../components/shadTooltipComponent";
import { Button } from "../../components/ui/button";
import Checkmark from "../../components/ui/checkmark";
import Loading from "../../components/ui/loading";
import { Textarea } from "../../components/ui/textarea";
import Xmark from "../../components/ui/xmark";
import {
RUN_TIMESTAMP_PREFIX,
STATUS_BUILD,
STATUS_BUILDING,
TOOLTIP_OUTDATED_NODE,
} from "../../constants/constants";
import { BuildStatus } from "../../constants/enums";
import { postCustomComponent } from "../../controllers/API";
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
import useAlertStore from "../../stores/alertStore";
import { useDarkStore } from "../../stores/darkStore";
import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { useShortcutsStore } from "../../stores/shortcuts";
import { useTypesStore } from "../../stores/typesStore";
import { APIClassType } from "../../types/api";
import { validationStatusType } from "../../types/components";
import { VertexBuildTypeAPI } from "../../types/api";
import { NodeDataType } from "../../types/flow";
import { handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils";
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
import { classNames, cn } from "../../utils/utils";
import { countHandlesFn } from "../helpers/count-handles";
import { getSpecificClassFromBuildStatus } from "../helpers/get-class-from-build-status";
import useCheckCodeValidity from "../hooks/use-check-code-validity";
import useIconNodeRender from "../hooks/use-icon-render";
import useIconStatus from "../hooks/use-icons-status";
import useUpdateNodeCode from "../hooks/use-update-node-code";
import useUpdateValidationStatus from "../hooks/use-update-validation-status";
import useValidationStatusString from "../hooks/use-validation-status-string";
import getFieldTitle from "../utils/get-field-title";
import sortFields from "../utils/sort-fields";
import ParameterComponent from "./components/parameterComponent";
export default function GenericNode({
data,
xPos,
yPos,
selected,
}: {
data: NodeDataType;
selected: boolean;
xPos: number;
yPos: number;
xPos?: number;
yPos?: number;
}): JSX.Element {
const types = useTypesStore((state) => state.types);
const templates = useTypesStore((state) => state.templates);
@ -51,192 +56,47 @@ export default function GenericNode({
const setNode = useFlowStore((state) => state.setNode);
const updateNodeInternals = useUpdateNodeInternals();
const setErrorData = useAlertStore((state) => state.setErrorData);
const name = nodeIconsLucide[data.type] ? data.type : types[data.type];
const isDark = useDarkStore((state) => state.dark);
const buildStatus = useFlowStore(
(state) => state.flowBuildStatus[data.id]?.status
);
const lastRunTime = useFlowStore(
(state) => state.flowBuildStatus[data.id]?.timestamp
);
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
const [inputName, setInputName] = useState(false);
const [nodeName, setNodeName] = useState(data.node!.display_name);
const [inputDescription, setInputDescription] = useState(false);
const [nodeDescription, setNodeDescription] = useState(
data.node?.description!,
data.node?.description!
);
const [isOutdated, setIsOutdated] = useState(false);
const buildStatus = useFlowStore(
(state) => state.flowBuildStatus[data.id]?.status,
(state) => state.flowBuildStatus[data.id]?.status
);
const lastRunTime = useFlowStore(
(state) => state.flowBuildStatus[data.id]?.timestamp,
(state) => state.flowBuildStatus[data.id]?.timestamp
);
const [validationStatus, setValidationStatus] =
useState<validationStatusType | null>(null);
useState<VertexBuildTypeAPI | null>(null);
const [handles, setHandles] = useState<number>(0);
const [validationString, setValidationString] = useState<string>("");
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
useEffect(() => {
// This one should run only once
// first check if data.type in NATIVE_CATEGORIES
// if not return
if (!data.node?.template?.code?.value) return;
const thisNodeTemplate = templates[data.type]?.template;
// if the template does not have a code key
// return
if (!thisNodeTemplate?.code) return;
const currentCode = thisNodeTemplate.code?.value;
const thisNodesCode = data.node!.template?.code?.value;
const componentsToIgnore = ["Custom Component"];
if (
currentCode !== thisNodesCode &&
!componentsToIgnore.includes(data.node!.display_name)
) {
setIsOutdated(true);
} else {
setIsOutdated(false);
}
// template.code can be undefined
}, [data.node?.template?.code?.value]);
const updateNodeCode = useCallback(
(newNodeClass: APIClassType, code: string, name: string) => {
setNode(data.id, (oldNode) => {
let newNode = cloneDeep(oldNode);
newNode.data = {
...newNode.data,
node: newNodeClass,
description: newNodeClass.description ?? data.node!.description,
display_name: newNodeClass.display_name ?? data.node!.display_name,
};
newNode.data.node.template[name].value = code;
setIsOutdated(false);
return newNode;
});
updateNodeInternals(data.id);
},
[data.id, data.node, setNode, setIsOutdated],
);
if (!data.node!.template) {
setErrorData({
title: `Error in component ${data.node!.display_name}`,
list: [
`The component ${data.node!.display_name} has no template.`,
`Please contact the developer of the component to fix this issue.`,
],
});
takeSnapshot();
deleteNode(data.id);
}
function countHandles(): void {
let count = Object.keys(data.node!.template)
.filter((templateField) => templateField.charAt(0) !== "_")
.map((templateCamp) => {
const { template } = data.node!;
if (template[templateCamp].input_types) return true;
if (!template[templateCamp].show) return false;
switch (template[templateCamp].type) {
case "str":
case "bool":
case "float":
case "code":
case "prompt":
case "file":
case "int":
return false;
default:
return true;
}
})
.reduce((total, value) => total + (value ? 1 : 0), 0);
setHandles(count);
}
useEffect(() => {
countHandles();
}, [data, data.node]);
useEffect(() => {
if (!selected) {
setInputName(false);
setInputDescription(false);
}
}, [selected]);
const iconStatus = useIconStatus(buildStatus, validationStatus);
const [showNode, setShowNode] = useState(data.showNode ?? true);
// State for outline color
const isBuilding = useFlowStore((state) => state.isBuilding);
// should be empty string if no duration
// else should be `Duration: ${duration}`
const getDurationString = (duration: number | undefined): string => {
if (duration === undefined) {
return "";
} else {
return `${duration}`;
}
};
const durationString = getDurationString(validationStatus?.data.duration);
const updateNodeCode = useUpdateNodeCode(
data?.id,
data.node!,
setNode,
setIsOutdated,
updateNodeInternals
);
useEffect(() => {
setNodeDescription(data.node!.description);
}, [data.node!.description]);
useEffect(() => {
setNodeName(data.node!.display_name);
}, [data.node!.display_name]);
useEffect(() => {
const relevantData =
flowPool[data.id] && flowPool[data.id]?.length > 0
? flowPool[data.id][flowPool[data.id].length - 1]
: null;
if (relevantData) {
// Extract validation information from relevantData and update the validationStatus state
setValidationStatus(relevantData);
} else {
setValidationStatus(null);
}
}, [flowPool[data.id], data.id]);
useEffect(() => {
if (validationStatus?.params) {
// if it is not a string turn it into a string
let newValidationString = validationStatus.params;
if (typeof newValidationString !== "string") {
newValidationString = JSON.stringify(validationStatus.params);
}
setValidationString(newValidationString);
}
}, [validationStatus, validationStatus?.params]);
const [showNode, setShowNode] = useState(data.showNode ?? true);
useEffect(() => {
setShowNode(data.showNode ?? true);
}, [data.showNode]);
const nameEditable = true;
const isEmoji = emojiRegex().test(data?.node?.icon!);
const iconNodeRender = useCallback(() => {
const iconElement = data?.node?.icon;
const iconColor = nodeColors[types[data.type]];
const iconName =
iconElement || (data.node?.flow ? "group_components" : name);
const iconClassName = `generic-node-icon ${
!showNode ? " absolute inset-x-6 h-12 w-12 " : ""
}`;
if (iconElement && isEmoji) {
return nodeIconFragment(iconElement);
} else {
return checkNodeIconFragment(iconColor, iconName, iconClassName);
}
}, [data, isEmoji, name, showNode]);
const name = nodeIconsLucide[data.type] ? data.type : types[data.type];
const nodeIconFragment = (icon) => {
return <span className="text-lg">{icon}</span>;
@ -252,87 +112,24 @@ export default function GenericNode({
);
};
const isDark = useDarkStore((state) => state.dark);
const renderIconStatus = (
buildStatus: BuildStatus | undefined,
validationStatus: validationStatusType | null,
) => {
const conditionSuccess = validationStatus && validationStatus.valid;
const conditionInactive =
validationStatus &&
!validationStatus.valid &&
buildStatus === BuildStatus.INACTIVE;
const conditionError =
buildStatus === BuildStatus.ERROR ||
(validationStatus && !validationStatus.valid);
if (buildStatus === BuildStatus.BUILDING) {
return <Loading className="text-medium-indigo" />;
} else {
return (
<>
<IconComponent
name="Play"
className={cn(
!conditionSuccess && !conditionInactive && !conditionError
? "opacity-100"
: "opacity-0",
"absolute ml-0.5 h-5 fill-current stroke-2 text-muted-foreground transition-all group-hover:text-medium-indigo group-hover/node:opacity-100",
)}
/>
{conditionSuccess ? (
<Checkmark
className="absolute ml-0.5 h-5 stroke-2 text-status-green opacity-100 transition-all group-hover/node:opacity-0"
isVisible={true}
/>
) : conditionInactive ? (
<IconComponent
name="Play"
className="absolute ml-0.5 h-5 fill-current stroke-2 text-status-gray opacity-30 transition-all group-hover/node:opacity-0"
/>
) : conditionError ? (
<Xmark
isVisible={true}
className="absolute ml-0.5 h-5 fill-current stroke-2 text-status-red opacity-100 transition-all group-hover/node:opacity-0"
/>
) : (
<></>
)}
</>
);
}
};
const getSpecificClassFromBuildStatus = (
buildStatus: BuildStatus | undefined,
validationStatus: validationStatusType | null,
) => {
let isInvalid = validationStatus && !validationStatus.valid;
if (buildStatus === BuildStatus.INACTIVE) {
// INACTIVE should have its own class
return "inactive-status";
}
if (
(buildStatus === BuildStatus.BUILT && isInvalid) ||
buildStatus === BuildStatus.ERROR
) {
return isDark ? "built-invalid-status-dark" : "built-invalid-status";
} else if (buildStatus === BuildStatus.BUILDING) {
return "building-status";
} else {
return "";
}
const renderIconStatus = () => {
return (
<div className="generic-node-status-position flex items-center justify-center">
{iconStatus}
</div>
);
};
const getNodeBorderClassName = (
selected: boolean,
showNode: boolean,
buildStatus: BuildStatus | undefined,
validationStatus: validationStatusType | null,
validationStatus: VertexBuildTypeAPI | null
) => {
const specificClassFromBuildStatus = getSpecificClassFromBuildStatus(
buildStatus,
validationStatus,
isDark
);
const baseBorderClass = getBaseBorderClass(selected);
@ -341,11 +138,13 @@ export default function GenericNode({
baseBorderClass,
nodeSizeClass,
"generic-node-div group/node",
specificClassFromBuildStatus,
specificClassFromBuildStatus
);
return names;
};
// const [openWDoubleCLick, setOpenWDoubleCLick] = useState(false);
const getBaseBorderClass = (selected) => {
let className = selected ? "border border-ring" : "border";
let frozenClass = selected ? "border-ring-frozen" : "border-frozen";
@ -355,10 +154,99 @@ export default function GenericNode({
const getNodeSizeClass = (showNode) =>
showNode ? "w-96 rounded-lg" : "w-26 h-26 rounded-full";
const nameEditable = true;
const isEmoji = emojiRegex().test(data?.node?.icon!);
if (!data.node!.template) {
setErrorData({
title: `Error in component ${data.node!.display_name}`,
list: [
`The component ${data.node!.display_name} has no template.`,
`Please contact the developer of the component to fix this issue.`,
],
});
takeSnapshot();
deleteNode(data.id);
}
useCheckCodeValidity(data, templates, setIsOutdated, types);
useValidationStatusString(validationStatus, setValidationString);
useUpdateValidationStatus(data?.id, flowPool, setValidationStatus);
const iconNodeRender = useIconNodeRender(
data,
types,
nodeColors,
name,
showNode,
isEmoji,
nodeIconFragment,
checkNodeIconFragment
);
function countHandles(): void {
const count = countHandlesFn(data);
setHandles(count);
}
useEffect(() => {
countHandles();
}, [data, data.node]);
useEffect(() => {
if (!selected) {
setInputName(false);
setInputDescription(false);
}
}, [selected]);
useEffect(() => {
setNodeDescription(data.node!.description);
}, [data.node!.description]);
useEffect(() => {
setNodeName(data.node!.display_name);
}, [data.node!.display_name]);
useEffect(() => {
setShowNode(data.showNode ?? true);
}, [data.showNode]);
const [loadingUpdate, setLoadingUpdate] = useState(false);
const handleUpdateCode = () => {
setLoadingUpdate(true);
takeSnapshot();
// to update we must get the code from the templates in useTypesStore
const thisNodeTemplate = templates[data.type]?.template;
// if the template does not have a code key
// return
if (!thisNodeTemplate?.code) return;
const currentCode = thisNodeTemplate.code.value;
if (data.node) {
postCustomComponent(currentCode, data.node)
.then((apiReturn) => {
const { data } = apiReturn;
if (data && updateNodeCode) {
updateNodeCode(data, currentCode, "code");
setLoadingUpdate(false);
}
})
.catch((err) => {
console.log(err);
});
}
};
const shortcuts = useShortcutsStore((state) => state.shortcuts);
const memoizedNodeToolbarComponent = useMemo(() => {
return (
<NodeToolbar>
<NodeToolbarComponent
// openWDoubleClick={openWDoubleCLick}
// setOpenWDoubleClick={setOpenWDoubleCLick}
data={data}
deleteNode={(id) => {
takeSnapshot();
@ -375,8 +263,6 @@ export default function GenericNode({
showNode={showNode}
openAdvancedModal={false}
onCloseAdvancedModal={() => {}}
updateNodeCode={updateNodeCode}
isOutdated={isOutdated}
selected={selected}
/>
</NodeToolbar>
@ -392,16 +278,23 @@ export default function GenericNode({
updateNodeCode,
isOutdated,
selected,
shortcuts,
// openWDoubleCLick,
// setOpenWDoubleCLick,
]);
return (
<>
{memoizedNodeToolbarComponent}
<div
// onDoubleClick={(event) => {
// if (!isWrappedWithClass(event, "nodoubleclick"))
// setOpenWDoubleCLick(true);
// }}
className={getNodeBorderClassName(
selected,
showNode,
buildStatus,
validationStatus,
validationStatus
)}
>
{data.node?.beta && showNode && (
@ -470,7 +363,7 @@ export default function GenericNode({
event.preventDefault();
}}
data-testid={"title-" + data.node?.display_name}
className="generic-node-tooltip-div cursor-text text-primary"
className="nodoubleclick generic-node-tooltip-div cursor-text text-primary"
>
{data.node?.display_name}
</div>
@ -532,7 +425,7 @@ export default function GenericNode({
}
title={getFieldTitle(
data.node?.template!,
templateField,
templateField
)}
info={data.node?.template[templateField].info}
name={templateField}
@ -560,7 +453,7 @@ export default function GenericNode({
proxy={data.node?.template[templateField].proxy}
showNode={showNode}
/>
),
)
)}
{/* <ParameterComponent
index={0}
@ -592,67 +485,73 @@ export default function GenericNode({
)}
</div>
{showNode && (
<ShadTooltip
content={
buildStatus === BuildStatus.BUILDING ? (
<span> {STATUS_BUILDING} </span>
) : !validationStatus ? (
<span className="flex">{STATUS_BUILD}</span>
) : (
<div className="max-h-100 p-2">
<div>
{lastRunTime && (
<>
<div className="flex flex-shrink-0 items-center gap-1">
{isOutdated && (
<ShadTooltip content={TOOLTIP_OUTDATED_NODE}>
<Button
onClick={handleUpdateCode}
variant="secondary"
className={"h-9 px-1.5"}
loading={loadingUpdate}
>
<IconComponent
name="AlertTriangle"
className="h-5 w-5 text-status-yellow"
/>
</Button>
</ShadTooltip>
)}
<ShadTooltip
content={
buildStatus === BuildStatus.BUILDING ? (
<span> {STATUS_BUILDING} </span>
) : !validationStatus ? (
<span className="flex">{STATUS_BUILD}</span>
) : (
<div className="max-h-100 p-2">
<div>
{lastRunTime && (
<div className="justify-left flex font-normal text-muted-foreground">
<div>{RUN_TIMESTAMP_PREFIX}</div>
<div className="ml-1 text-status-blue">
{lastRunTime}
</div>
</div>
)}
</div>
<div className="justify-left flex font-normal text-muted-foreground">
<div>{RUN_TIMESTAMP_PREFIX}</div>
<div>Duration:</div>
<div className="ml-1 text-status-blue">
{lastRunTime}
{validationStatus?.data.duration}
</div>
</div>
)}
</div>
<div className="justify-left flex font-normal text-muted-foreground">
<div>Duration:</div>
<div className="mb-3 ml-1 text-status-blue">
{validationStatus?.data.duration}
</div>
</div>
<hr />
<span className="mb-2 mt-2 flex justify-center font-semibold text-muted-foreground">
Output
</span>
<div className="max-h-96 overflow-auto font-normal custom-scroll">
{validationString.split("\n").map((line, index) => (
<div className="font-normal" key={index}>
{line}
</div>
))}
</div>
</div>
)
}
side="bottom"
>
<Button
onClick={() => {
if (buildStatus === BuildStatus.BUILDING || isBuilding)
return;
setValidationStatus(null);
buildFlow({ stopNodeId: data.id });
}}
variant="secondary"
className={"group h-9 px-1.5"}
>
<div
data-testid={
`button_run_` + data?.node?.display_name.toLowerCase()
)
}
side="bottom"
>
<div className="generic-node-status-position flex items-center justify-center">
{renderIconStatus(buildStatus, validationStatus)}
</div>
</div>
</Button>
</ShadTooltip>
<Button
onClick={() => {
if (buildStatus === BuildStatus.BUILDING || isBuilding)
return;
setValidationStatus(null);
buildFlow({ stopNodeId: data.id });
}}
variant="secondary"
className={"group h-9 px-1.5"}
>
<div
data-testid={
`button_run_` + data?.node?.display_name.toLowerCase()
}
>
{renderIconStatus()}
</div>
</Button>
</ShadTooltip>
</div>
</>
)}
</div>
</div>
@ -714,12 +613,12 @@ export default function GenericNode({
) : (
<div
className={cn(
"generic-node-desc-text cursor-text truncate-multiline word-break-break-word",
"nodoubleclick generic-node-desc-text cursor-text truncate-multiline word-break-break-word",
(data.node?.description === "" ||
!data.node?.description) &&
nameEditable
? "font-light italic"
: "",
: ""
)}
onClick={(e) => {
setInputDescription(true);
@ -781,13 +680,13 @@ export default function GenericNode({
}
title={getFieldTitle(
data.node?.template!,
templateField,
templateField
)}
info={data.node?.template[templateField].info}
name={templateField}
tooltipTitle={
data.node?.template[templateField].input_types?.join(
"\n",
"\n"
) ?? data.node?.template[templateField].type
}
required={data.node!.template[templateField].required}
@ -814,7 +713,7 @@ export default function GenericNode({
<div
className={classNames(
Object.keys(data.node!.template).length < 1 ? "hidden" : "",
"flex-max-width justify-center",
"flex-max-width justify-center"
)}
>
{" "}

View file

@ -7,7 +7,7 @@ import {
CSVViewErrorTitle,
} from "../../constants/constants";
import { useDarkStore } from "../../stores/darkStore";
import { FlowPoolObjectType } from "../../types/chat";
import { VertexBuildTypeAPI } from "../../types/api";
import { NodeType } from "../../types/flow";
import ForwardedIconComponent from "../genericIconComponent";
import TableComponent from "../tableComponent";
@ -19,7 +19,7 @@ function CsvOutputComponent({
flowPool,
}: {
csvNode: NodeType;
flowPool: FlowPoolObjectType;
flowPool: VertexBuildTypeAPI;
}) {
const csvNodeArtifacts = flowPool?.data?.artifacts?.repr;
const jsonString = csvNodeArtifacts?.replace(/'/g, '"');

View file

@ -18,7 +18,7 @@ export default function TableOptions({
}): JSX.Element {
return (
<div className={cn("absolute bottom-3 left-6")}>
<div className="flex items-center gap-2">
<div className="flex items-center gap-3">
<div>
<ShadTooltip content="Reset Columns">
<Button
@ -31,7 +31,10 @@ export default function TableOptions({
>
<IconComponent
name="RotateCcw"
className={cn("h-5 w-5 text-primary transition-all")}
strokeWidth={2}
className={cn(
"h-5 w-5 text-primary transition-all hover:text-accent-foreground"
)}
/>
</Button>
</ShadTooltip>
@ -81,8 +84,8 @@ export default function TableOptions({
<IconComponent
name="Trash2"
className={cn(
"h-5 w-5 text-primary transition-all",
!hasSelection ? "" : "hover:text-destructive"
"h-5 w-5 text-primary transition-all",
!hasSelection ? "" : "hover:text-status-red "
)}
/>
</Button>

View file

@ -159,6 +159,19 @@ const TableComponent = forwardRef<
}, 100);
}}
/>
<TableOptions
stateChange={columnStateChange}
hasSelection={realRef.current?.api?.getSelectedRows().length > 0}
duplicateRow={props.onDuplicate ? props.onDuplicate : undefined}
deleteRow={props.onDelete ? props.onDelete : undefined}
resetGrid={() => {
console.log("teste");
resetGrid(realRef, initialColumnDefs);
setTimeout(() => {
setColumnStateChange(false);
}, 100);
}}
/>
<ResetColumns resetGrid={() => resetGrid(realRef, initialColumnDefs)} />
</div>
);

View file

@ -829,3 +829,9 @@ export const MAX_BATCH_SIZE = 50;
export const MODAL_CLASSES =
"nopan nodelete nodrag noundo nocopy fixed inset-0 bottom-0 left-0 right-0 top-0 z-50 overflow-auto bg-blur-shared backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0";
export const ALLOWED_IMAGE_INPUT_EXTENSIONS = ["png", "jpg", "jpeg"];
export const FS_ERROR_TEXT =
"Please ensure your file has one of the following extensions:";
export const SN_ERROR_TEXT = ALLOWED_IMAGE_INPUT_EXTENSIONS.join(", ");

View file

@ -1092,20 +1092,11 @@ export async function getMessagesTable(
}
const rows = await api.get(`${BASE_URL_API}monitor/messages`, config);
const columns = extractColumnsFromRows(rows.data, mode, excludedFields);
return { rows: rows.data, columns };
}
export async function getSessions(id?: string): Promise<Array<string>> {
const config = {};
if (id) {
config["params"] = { flow_id: id };
}
const rows = await api.get(`${BASE_URL_API}monitor/messages`, config);
const sessions = new Set<string>();
rows.data.forEach((row) => {
sessions.add(row.session_id);
});
return Array.from(sessions);
return { rows: rows.data, columns };
}
export async function deleteMessagesFn(ids: number[]) {

Some files were not shown because too many files have changed in this diff Show more