Merge branch 'form_io' into python_custom_node_component

This commit is contained in:
Gabriel Luiz Freitas Almeida 2023-07-07 01:36:18 -03:00
commit 320989870e
51 changed files with 1012 additions and 606 deletions

View file

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

View file

@ -1,22 +1,107 @@
import asyncio import asyncio
from typing import Any
from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler
from langflow.api.v1.schemas import ChatResponse from langflow.api.v1.schemas import ChatResponse
from typing import Any, Dict, List, Union
from fastapi import WebSocket
from langchain.schema import AgentAction, LLMResult, AgentFinish
# https://github.com/hwchase17/chat-langchain/blob/master/callback.py # https://github.com/hwchase17/chat-langchain/blob/master/callback.py
class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler): class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
"""Callback handler for streaming LLM responses.""" """Callback handler for streaming LLM responses."""
def __init__(self, websocket): def __init__(self, websocket: WebSocket):
self.websocket = websocket self.websocket = websocket
async def on_llm_new_token(self, token: str, **kwargs: Any) -> None: async def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
resp = ChatResponse(message=token, type="stream", intermediate_steps="") resp = ChatResponse(message=token, type="stream", intermediate_steps="")
await self.websocket.send_json(resp.dict()) await self.websocket.send_json(resp.dict())
async def on_llm_start(
self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any
) -> Any:
"""Run when LLM starts running."""
async def on_llm_end(self, response: LLMResult, **kwargs: Any) -> Any:
"""Run when LLM ends running."""
async def on_llm_error(
self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
) -> Any:
"""Run when LLM errors."""
async def on_chain_start(
self, serialized: Dict[str, Any], inputs: Dict[str, Any], **kwargs: Any
) -> Any:
"""Run when chain starts running."""
async def on_chain_end(self, outputs: Dict[str, Any], **kwargs: Any) -> Any:
"""Run when chain ends running."""
async def on_chain_error(
self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
) -> Any:
"""Run when chain errors."""
async def on_tool_start(
self, serialized: Dict[str, Any], input_str: str, **kwargs: Any
) -> Any:
"""Run when tool starts running."""
resp = ChatResponse(
message="",
type="stream",
intermediate_steps=f"Tool input: {input_str}",
)
await self.websocket.send_json(resp.dict())
async def on_tool_end(self, output: str, **kwargs: Any) -> Any:
"""Run when tool ends running."""
resp = ChatResponse(
message="",
type="stream",
intermediate_steps=f"Tool output: {output}",
)
await self.websocket.send_json(resp.dict())
async def on_tool_error(
self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
) -> Any:
"""Run when tool errors."""
async def on_text(self, text: str, **kwargs: Any) -> Any:
"""Run on arbitrary text."""
# This runs when first sending the prompt
# to the LLM, adding it will send the final prompt
# to the frontend
async def on_agent_action(self, action: AgentAction, **kwargs: Any):
log = f"Thought: {action.log}"
# if there are line breaks, split them and send them
# as separate messages
if "\n" in log:
logs = log.split("\n")
for log in logs:
resp = ChatResponse(message="", type="stream", intermediate_steps=log)
await self.websocket.send_json(resp.dict())
else:
resp = ChatResponse(message="", type="stream", intermediate_steps=log)
await self.websocket.send_json(resp.dict())
async def on_agent_finish(self, finish: AgentFinish, **kwargs: Any) -> Any:
"""Run on agent end."""
resp = ChatResponse(
message="",
type="stream",
intermediate_steps=finish.log,
)
await self.websocket.send_json(resp.dict())
class StreamingLLMCallbackHandler(BaseCallbackHandler): class StreamingLLMCallbackHandler(BaseCallbackHandler):
"""Callback handler for streaming LLM responses.""" """Callback handler for streaming LLM responses."""

View file

@ -160,7 +160,13 @@ async def stream_build(flow_id: str):
input_keys_response = build_input_keys_response( input_keys_response = build_input_keys_response(
langchain_object, artifacts langchain_object, artifacts
) )
yield str(StreamData(event="message", data=input_keys_response)) else:
input_keys_response = {
"input_keys": {},
"memory_keys": [],
"handle_keys": [],
}
yield str(StreamData(event="message", data=input_keys_response))
chat_manager.set_cache(flow_id, langchain_object) chat_manager.set_cache(flow_id, langchain_object)
# We need to reset the chat history # We need to reset the chat history

View file

@ -73,12 +73,14 @@ def add_new_variables_to_template(input_variables, prompt_request):
advanced=False, advanced=False,
multiline=True, multiline=True,
input_types=["Document", "BaseOutputParser"], input_types=["Document", "BaseOutputParser"],
value="", # Set the value to empty string
) )
if variable in prompt_request.frontend_node.template: if variable in prompt_request.frontend_node.template:
# Set the new field with the old value # Set the new field with the old value
template_field.value = prompt_request.frontend_node.template[variable][ template_field.value = prompt_request.frontend_node.template[variable][
"value" "value"
] ]
prompt_request.frontend_node.template[variable] = template_field.to_dict() prompt_request.frontend_node.template[variable] = template_field.to_dict()
# Check if variable is not already in the list before appending # Check if variable is not already in the list before appending

View file

@ -23,7 +23,7 @@ async def process_graph(
try: try:
logger.debug("Generating result and thought") logger.debug("Generating result and thought")
result, intermediate_steps = await get_result_and_steps( result, intermediate_steps = await get_result_and_steps(
langchain_object, chat_inputs.message or "", websocket=websocket langchain_object, chat_inputs.message, websocket=websocket
) )
logger.debug("Generated result and intermediate_steps") logger.debug("Generated result and intermediate_steps")
return result, intermediate_steps return result, intermediate_steps

View file

@ -164,8 +164,6 @@ memories:
prompts: prompts:
PromptTemplate: PromptTemplate:
documentation: "https://python.langchain.com/docs/modules/model_io/prompts/prompt_templates/" documentation: "https://python.langchain.com/docs/modules/model_io/prompts/prompt_templates/"
ZeroShotPrompt:
documentation: "https://python.langchain.com/docs/modules/agents/how_to/custom_mrkl_agent"
textsplitters: textsplitters:
CharacterTextSplitter: CharacterTextSplitter:
documentation: "https://python.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/character_text_splitter" documentation: "https://python.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/character_text_splitter"

View file

@ -2,9 +2,9 @@ from langflow.template import frontend_node
# These should always be instantiated # These should always be instantiated
CUSTOM_NODES = { CUSTOM_NODES = {
"prompts": { # "prompts": {
"ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode(), # "ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode(),
}, # },
"tools": { "tools": {
"PythonFunctionTool": frontend_node.tools.PythonFunctionToolNode(), "PythonFunctionTool": frontend_node.tools.PythonFunctionToolNode(),
"PythonFunction": frontend_node.tools.PythonFunctionNode(), "PythonFunction": frontend_node.tools.PythonFunctionNode(),

View file

@ -183,6 +183,8 @@ class Vertex:
# and return the instance # and return the instance
try: try:
if self.base_type is None:
raise ValueError(f"Base type for node {self.vertex_type} not found")
result = loading.instantiate_class( result = loading.instantiate_class(
node_type=self.vertex_type, node_type=self.vertex_type,
base_type=self.base_type, base_type=self.base_type,
@ -224,4 +226,5 @@ class Vertex:
return id(self) return id(self)
def _built_object_repr(self): def _built_object_repr(self):
return repr(self._built_object) # Add a message with an emoji, stars for sucess,
return "Built sucessfully ✨" if self._built_object else "Failed to build 😵‍💫"

View file

@ -201,6 +201,15 @@ class PromptVertex(Vertex):
self._build() self._build()
return self._built_object return self._built_object
def _built_object_repr(self):
if self.artifacts and hasattr(self._built_object, "format"):
# We'll build the prompt with the artifacts
# to show the user what the prompt looks like
# with the variables filled in
return self._built_object.format(**self.artifacts)
else:
return super()._built_object_repr()
class OutputParserVertex(Vertex): class OutputParserVertex(Vertex):
def __init__(self, data: Dict): def __init__(self, data: Dict):

View file

@ -6,13 +6,20 @@ from langflow.custom.customs import get_custom_nodes
from langflow.interface.agents.custom import CUSTOM_AGENTS from langflow.interface.agents.custom import CUSTOM_AGENTS
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.settings import settings from langflow.settings import settings
from langflow.template.frontend_node.agents import AgentFrontendNode
from langflow.utils.logger import logger from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class from langflow.utils.util import build_template_from_class, build_template_from_method
class AgentCreator(LangChainTypeCreator): class AgentCreator(LangChainTypeCreator):
type_name: str = "agents" type_name: str = "agents"
from_method_nodes = {"ZeroShotAgent": "from_llm_and_tools"}
@property
def frontend_node_class(self) -> type[AgentFrontendNode]:
return AgentFrontendNode
@property @property
def type_to_loader_dict(self) -> Dict: def type_to_loader_dict(self) -> Dict:
if self.type_dict is None: if self.type_dict is None:
@ -27,6 +34,13 @@ class AgentCreator(LangChainTypeCreator):
try: try:
if name in get_custom_nodes(self.type_name).keys(): if name in get_custom_nodes(self.type_name).keys():
return get_custom_nodes(self.type_name)[name] return get_custom_nodes(self.type_name)[name]
elif name in self.from_method_nodes:
return build_template_from_method(
name,
type_to_cls_dict=self.type_to_loader_dict,
add_function=True,
method_name=self.from_method_nodes[name],
)
return build_template_from_class( return build_template_from_class(
name, self.type_to_loader_dict, add_function=True name, self.type_to_loader_dict, add_function=True
) )

View file

@ -19,6 +19,8 @@ from langflow.interface.importing.utils import (
import_by_type, import_by_type,
) )
from langflow.interface.custom_lists import CUSTOM_NODES from langflow.interface.custom_lists import CUSTOM_NODES
from langflow.interface.importing.utils import get_function, import_by_type
from langflow.interface.agents.base import agent_creator
from langflow.interface.toolkits.base import toolkits_creator from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.chains.base import chain_creator from langflow.interface.chains.base import chain_creator
from langflow.interface.output_parsers.base import output_parser_creator from langflow.interface.output_parsers.base import output_parser_creator
@ -65,7 +67,7 @@ def convert_kwargs(params):
def instantiate_based_on_type(class_object, base_type, node_type, params): def instantiate_based_on_type(class_object, base_type, node_type, params):
if base_type == "agents": if base_type == "agents":
return instantiate_agent(class_object, params) return instantiate_agent(node_type, class_object, params)
elif base_type == "prompts": elif base_type == "prompts":
return instantiate_prompt(node_type, class_object, params) return instantiate_prompt(node_type, class_object, params)
elif base_type == "tools": elif base_type == "tools":
@ -122,6 +124,12 @@ def instantiate_llm(node_type, class_object, params: Dict):
def instantiate_memory(node_type, class_object, params): def instantiate_memory(node_type, class_object, params):
# process input_key and output_key to remove them if
# they are empty strings
for key in ["input_key", "output_key"]:
if key in params and not params[key]:
params.pop(key)
try: try:
if "retriever" in params and hasattr(params["retriever"], "as_retriever"): if "retriever" in params and hasattr(params["retriever"], "as_retriever"):
params["retriever"] = params["retriever"].as_retriever() params["retriever"] = params["retriever"].as_retriever()
@ -164,7 +172,16 @@ def instantiate_chains(node_type, class_object: Type[Chain], params: Dict):
return class_object(**params) return class_object(**params)
def instantiate_agent(class_object: Type[agent_module.Agent], params: Dict): def instantiate_agent(node_type, class_object: Type[agent_module.Agent], params: Dict):
if node_type in agent_creator.from_method_nodes:
method = agent_creator.from_method_nodes[node_type]
if class_method := getattr(class_object, method, None):
agent = class_method(**params)
tools = params.get("tools", [])
return AgentExecutor.from_agent_and_tools(
agent=agent,
tools=tools,
)
return load_agent_executor(class_object, params) return load_agent_executor(class_object, params)

View file

@ -90,7 +90,7 @@ class ToolCreator(LangChainTypeCreator):
def get_signature(self, name: str) -> Optional[Dict]: def get_signature(self, name: str) -> Optional[Dict]:
"""Get the signature of a tool.""" """Get the signature of a tool."""
base_classes = ["Tool"] base_classes = ["Tool", "BaseTool"]
fields = [] fields = []
params = [] params = []
tool_params = {} tool_params = {}

View file

@ -13,6 +13,17 @@ NON_CHAT_AGENTS = {
} }
class AgentFrontendNode(FrontendNode):
@staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
if field.name in ["suffix", "prefix"]:
field.show = True
if field.name == "Tools" and name == "ZeroShotAgent":
# field.
field.type_name = "BaseTool"
field.is_list = True
class SQLAgentNode(FrontendNode): class SQLAgentNode(FrontendNode):
name: str = "SQLAgent" name: str = "SQLAgent"
template: Template = Template( template: Template = Template(

View file

@ -56,7 +56,7 @@ class ToolNode(FrontendNode):
], ],
) )
description: str = "Converts a chain, agent or function into a tool." description: str = "Converts a chain, agent or function into a tool."
base_classes: list[str] = ["Tool"] base_classes: list[str] = ["Tool", "BaseTool"]
def to_dict(self): def to_dict(self):
return super().to_dict() return super().to_dict()

View file

@ -243,7 +243,11 @@ def format_dict(d, name: Optional[str] = None):
# Check for list type # Check for list type
if "List" in _type or "Sequence" in _type or "Set" in _type: if "List" in _type or "Sequence" in _type or "Set" in _type:
_type = _type.replace("List[", "")[:-1] _type = (
_type.replace("List[", "")
.replace("Sequence[", "")
.replace("Set[", "")[:-1]
)
value["list"] = True value["list"] = True
else: else:
value["list"] = False value["list"] = False

View file

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

View file

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

View file

@ -117,8 +117,7 @@ export default function ParameterComponent({
}} }}
> >
<Icon <Icon
className="h-5 w-5" className="h-5 w-5"
strokeWidth={1.5} strokeWidth={1.5}
style={{ style={{
color: nodeColors[item.family] ?? nodeColors.unknown, color: nodeColors[item.family] ?? nodeColors.unknown,
@ -129,7 +128,7 @@ export default function ParameterComponent({
{nodeNames[item.family] ?? ""}{" "} {nodeNames[item.family] ?? ""}{" "}
<span className="text-xs"> <span className="text-xs">
{" "} {" "}
{item.type == "" ? '' : ' - '} {item.type == "" ? "" : " - "}
{item.type.split(", ").length > 2 {item.type.split(", ").length > 2
? item.type.split(", ").map((el, i) => ( ? item.type.split(", ").map((el, i) => (
<React.Fragment key={el + i}> <React.Fragment key={el + i}>

View file

@ -42,7 +42,7 @@ export default function ExtraSidebar() {
item.href.split("/")[2] === current[4] item.href.split("/")[2] === current[4]
? "text-ring" ? "text-ring"
: "text-ring group-hover:text-accent-foreground", : "text-ring group-hover:text-accent-foreground",
"mr-3 flex-shrink-0 h-6 w-6" "mr-3 h-6 w-6 flex-shrink-0"
)} )}
/> />
{item.name} {item.name}
@ -71,8 +71,8 @@ export default function ExtraSidebar() {
<span className="flex-1">{item.name}</span> <span className="flex-1">{item.name}</span>
<svg <svg
className={classNames( className={classNames(
open ? "text-ring rotate-90" : "text-ring", open ? "rotate-90 text-ring" : "text-ring",
"ml-3 h-5 w-5 flex-shrink-0 transition-rotate duration-150 ease-in-out group-hover:text-accent-foreground" "transition-rotate ml-3 h-5 w-5 flex-shrink-0 duration-150 ease-in-out group-hover:text-accent-foreground"
)} )}
viewBox="0 0 20 20" viewBox="0 0 20 20"
aria-hidden="true" aria-hidden="true"

View file

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

View file

@ -11,7 +11,15 @@ import {
Card, Card,
CardHeader, CardHeader,
} from "../ui/card"; } from "../ui/card";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "../ui/dialog"; import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../ui/dialog";
import { Button } from "@mui/material"; import { Button } from "@mui/material";
export const CardComponent = ({ export const CardComponent = ({
@ -42,24 +50,26 @@ export const CardComponent = ({
</span> </span>
{onDelete && ( {onDelete && (
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<button className="flex self-start"> <button className="flex self-start">
<Trash2 className="w-4 h-4 text-primary opacity-0 group-hover:opacity-100 transition-all" /> <Trash2 className="h-4 w-4 text-primary opacity-0 transition-all group-hover:opacity-100" />
</button> </button>
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>Are you sure absolutely sure?</DialogTitle> <DialogTitle>Are you sure absolutely sure?</DialogTitle>
<DialogDescription> <DialogDescription>
This action cannot be undone. Are you sure you want to permanently This action cannot be undone. Are you sure you want to
delete this file from our servers? permanently delete this file from our servers?
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<DialogFooter> <DialogFooter>
<Button type="submit" onClick={onDelete}>Confirm</Button> <Button type="submit" onClick={onDelete}>
</DialogFooter> Confirm
</DialogContent> </Button>
</Dialog> </DialogFooter>
</DialogContent>
</Dialog>
)} )}
</CardTitle> </CardTitle>
<CardDescription className="pb-2 pt-2"> <CardDescription className="pb-2 pt-2">

View file

@ -8,7 +8,6 @@ import { useSSE } from "../../../contexts/SSEContext";
import { typesContext } from "../../../contexts/typesContext"; import { typesContext } from "../../../contexts/typesContext";
import { alertContext } from "../../../contexts/alertContext"; import { alertContext } from "../../../contexts/alertContext";
import { postBuildInit } from "../../../controllers/API"; import { postBuildInit } from "../../../controllers/API";
import ShadTooltip from "../../ShadTooltipComponent";
import RadialProgressComponent from "../../RadialProgress"; import RadialProgressComponent from "../../RadialProgress";
import { TabsContext } from "../../../contexts/tabsContext"; import { TabsContext } from "../../../contexts/tabsContext";
@ -96,10 +95,9 @@ export default function BuildTrigger({
[flowId]: { [flowId]: {
...old[flowId], ...old[flowId],
formKeysData: parsedData, formKeysData: parsedData,
},
}
}; };
}) });
} else { } else {
// Otherwise, process the data // Otherwise, process the data
const isValid = processStreamResult(parsedData); const isValid = processStreamResult(parsedData);
@ -168,7 +166,7 @@ export default function BuildTrigger({
leaveFrom="translate-y-0" leaveFrom="translate-y-0"
leaveTo="translate-y-96" leaveTo="translate-y-96"
> >
<div className="fixed right-4 bottom-20"> <div className="fixed bottom-20 right-4">
<div <div
className={`${eventClick} align-center shadow-round-btn-shadow hover:shadow-round-btn-shadow flex h-12 w-12 cursor-pointer justify-center rounded-full bg-border px-3 py-1 shadow-md`} className={`${eventClick} align-center shadow-round-btn-shadow hover:shadow-round-btn-shadow flex h-12 w-12 cursor-pointer justify-center rounded-full bg-border px-3 py-1 shadow-md`}
onClick={() => { onClick={() => {
@ -189,7 +187,10 @@ export default function BuildTrigger({
) : isBuilding ? ( ) : isBuilding ? (
<Loading strokeWidth={1.5} className="stroke-build-trigger" /> <Loading strokeWidth={1.5} className="stroke-build-trigger" />
) : ( ) : (
<Zap strokeWidth={1.5} className="sh-6 w-6 fill-build-trigger stroke-1 stroke-build-trigger" /> <Zap
strokeWidth={1.5}
className="sh-6 w-6 fill-build-trigger stroke-build-trigger stroke-1"
/>
)} )}
</div> </div>
</button> </button>

View file

@ -3,18 +3,30 @@ import { MessagesSquare } from "lucide-react";
import { alertContext } from "../../../contexts/alertContext"; import { alertContext } from "../../../contexts/alertContext";
import { useContext } from "react"; import { useContext } from "react";
import ShadTooltip from "../../ShadTooltipComponent"; import {
CHAT_CANNOT_OPEN_DESCRIPTION,
CHAT_CANNOT_OPEN_TITLE,
FLOW_NOT_BUILT_DESCRIPTION,
FLOW_NOT_BUILT_TITLE,
} from "../../../constants";
export default function ChatTrigger({ open, setOpen, isBuilt }) { export default function ChatTrigger({ open, setOpen, isBuilt, canOpen }) {
const { setErrorData } = useContext(alertContext); const { setErrorData } = useContext(alertContext);
function handleClick() { function handleClick() {
if (isBuilt) { if (isBuilt) {
setOpen(true); if (canOpen) {
setOpen(true);
} else {
setErrorData({
title: CHAT_CANNOT_OPEN_TITLE,
list: [CHAT_CANNOT_OPEN_DESCRIPTION],
});
}
} else { } else {
setErrorData({ setErrorData({
title: "Flow not built", title: FLOW_NOT_BUILT_TITLE,
list: ["Please build the flow before chatting"], list: [FLOW_NOT_BUILT_DESCRIPTION],
}); });
} }
} }
@ -30,15 +42,26 @@ export default function ChatTrigger({ open, setOpen, isBuilt }) {
leaveFrom="translate-y-0" leaveFrom="translate-y-0"
leaveTo="translate-y-96" leaveTo="translate-y-96"
> >
<button onClick={handleClick} className={ "transition-all fixed bottom-4 right-4 flex justify-center items-center py-1 px-3 w-12 h-12 rounded-full shadow-md shadow-round-btn-shadow hover:shadow-round-btn-shadow bg-border "+ (!isBuilt ? "cursor-not-allowed" : "cursor-pointer")}> <button
<div className="flex gap-3"> onClick={handleClick}
<MessagesSquare className={
className={"h-6 w-6 transition-all " + (isBuilt ? "fill-chat-trigger stroke-chat-trigger stroke-1" : "fill-chat-trigger-disabled stroke-1 stroke-chat-trigger-disabled")} "shadow-round-btn-shadow hover:shadow-round-btn-shadow fixed bottom-4 right-4 flex h-12 w-12 items-center justify-center rounded-full bg-border px-3 py-1 shadow-md transition-all " +
style={{ color: "white" }} (!isBuilt || !canOpen ? "cursor-not-allowed" : "cursor-pointer")
strokeWidth={1.5} }
/> >
</div> <div className="flex gap-3">
</button> <MessagesSquare
className={
"h-6 w-6 transition-all " +
(isBuilt && canOpen
? "fill-chat-trigger stroke-chat-trigger stroke-1"
: "fill-chat-trigger-disabled stroke-chat-trigger-disabled stroke-1")
}
style={{ color: "white" }}
strokeWidth={1.5}
/>
</div>
</button>
</Transition> </Transition>
); );
} }

View file

@ -13,6 +13,7 @@ import * as _ from "lodash";
export default function Chat({ flow }: ChatType) { export default function Chat({ flow }: ChatType) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [isBuilt, setIsBuilt] = useState(false); const [isBuilt, setIsBuilt] = useState(false);
const [canOpen, setCanOpen] = useState(false);
const { tabsState } = useContext(TabsContext); const { tabsState } = useContext(TabsContext);
useEffect(() => { useEffect(() => {
@ -58,6 +59,17 @@ export default function Chat({ flow }: ChatType) {
) { ) {
setIsBuilt(false); setIsBuilt(false);
} }
if (
tabsState &&
tabsState[flow.id] &&
tabsState[flow.id].formKeysData &&
tabsState[flow.id].formKeysData.input_keys &&
Object.keys(tabsState[flow.id].formKeysData.input_keys).length > 0
) {
setCanOpen(true);
} else {
setCanOpen(false);
}
prevNodesRef.current = currentNodes; prevNodesRef.current = currentNodes;
}, [tabsState]); }, [tabsState]);
@ -71,10 +83,15 @@ export default function Chat({ flow }: ChatType) {
setIsBuilt={setIsBuilt} setIsBuilt={setIsBuilt}
isBuilt={isBuilt} isBuilt={isBuilt}
/> />
{isBuilt && ( {isBuilt && canOpen && (
<FormModal key={flow.id} flow={flow} open={open} setOpen={setOpen} /> <FormModal key={flow.id} flow={flow} open={open} setOpen={setOpen} />
)} )}
<ChatTrigger open={open} setOpen={setOpen} isBuilt={isBuilt} /> <ChatTrigger
canOpen={canOpen}
open={open}
setOpen={setOpen}
isBuilt={isBuilt}
/>
</div> </div>
</> </>
); );

View file

@ -1,9 +1,7 @@
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext"; import { PopUpContext } from "../../contexts/popUpContext";
import CodeAreaModal from "../../modals/codeAreaModal/v2"; import CodeAreaModal from "../../modals/codeAreaModal/v2";
import TextAreaModal from "../../modals/textAreaModal"; import { CodeAreaComponentType } from "../../types/components";
import { CodeAreaComponentType, TextAreaComponentType } from "../../types/components";
import { ExternalLink } from "lucide-react"; import { ExternalLink } from "lucide-react";
@ -54,7 +52,8 @@ export default function CodeAreaComponent({
className={ className={
editNode editNode
? "input-edit-node input-dialog " ? "input-edit-node input-dialog "
: "input-primary input-dialog " + (disabled ? "input-disable" : "") : "input-dialog input-primary " +
(disabled ? "input-disable" : "")
} }
> >
{myValue !== "" ? myValue : "Type something..."} {myValue !== "" ? myValue : "Type something..."}
@ -63,9 +62,9 @@ export default function CodeAreaComponent({
onClick={() => { onClick={() => {
openPopUp( openPopUp(
<CodeAreaModal <CodeAreaModal
key={myValue} key={myValue}
dynamic={dynamic} dynamic={dynamic}
setNodeClass={setNodeClass} setNodeClass={setNodeClass}
value={myValue} value={myValue}
nodeClass={nodeClass} nodeClass={nodeClass}
setValue={(t: string) => { setValue={(t: string) => {
@ -77,7 +76,10 @@ export default function CodeAreaComponent({
}} }}
> >
{!editNode && ( {!editNode && (
<ExternalLink strokeWidth={1.5} className="w-6 h-6 hover:text-accent-foreground ml-3" /> <ExternalLink
strokeWidth={1.5}
className="ml-3 h-6 w-6 hover:text-accent-foreground"
/>
)} )}
</button> </button>
</div> </div>

View file

@ -39,8 +39,8 @@ export default function Dropdown({
<Listbox.Button <Listbox.Button
className={ className={
editNode editNode
? "border-1 relative pr-8 input-edit-node" ? "border-1 input-edit-node relative pr-8"
: "py-2 pl-3 pr-10 text-left input-primary" : "input-primary py-2 pl-3 pr-10 text-left"
} }
> >
<span className="block w-full truncate bg-background"> <span className="block w-full truncate bg-background">

View file

@ -75,7 +75,7 @@ export default function Header() {
href="https://github.com/logspace-ai/langflow" href="https://github.com/logspace-ai/langflow"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
className="inline-flex shadow-sm items-center justify-center text-sm font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background text-muted-foreground border border-input hover:bg-accent hover:text-accent-foreground h-9 px-3 pr-0 rounded-md" className="inline-flex h-9 items-center justify-center rounded-md border border-input px-3 pr-0 text-sm font-medium text-muted-foreground shadow-sm ring-offset-background hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
> >
<FaGithub className="mr-2 h-5 w-5" /> <FaGithub className="mr-2 h-5 w-5" />
Star Star
@ -114,7 +114,7 @@ export default function Header() {
)} )}
</button> </button>
<button <button
className="text-muted-foreground hover:text-accent-foreground relative" className="relative text-muted-foreground hover:text-accent-foreground"
onClick={(event: React.MouseEvent<HTMLElement>) => { onClick={(event: React.MouseEvent<HTMLElement>) => {
setNotificationCenter(false); setNotificationCenter(false);
const { top, left } = ( const { top, left } = (

View file

@ -110,7 +110,10 @@ export default function InputFileComponent({
</span> </span>
<button onClick={handleButtonClick}> <button onClick={handleButtonClick}>
{!editNode && !loading && ( {!editNode && !loading && (
<FileSearch2 strokeWidth={1.5} className="w-6 h-6 hover:text-accent-foreground" /> <FileSearch2
strokeWidth={1.5}
className="h-6 w-6 hover:text-accent-foreground"
/>
)} )}
{!editNode && loading && ( {!editNode && loading && (
<span className="loading loading-spinner loading-sm pointer-events-none h-8 pl-3"></span> <span className="loading loading-spinner loading-sm pointer-events-none h-8 pl-3"></span>

View file

@ -47,65 +47,66 @@ export default function PromptAreaComponent({
}, [reactFlowInstance, field_name]); }, [reactFlowInstance, field_name]);
return ( return (
<div <div className={disabled ? "cursor-not-allowed" : ""}>
className={ <div className={disabled ? "pointer-events-none w-full " : " w-full"}>
disabled ? "pointer-events-none w-full cursor-not-allowed" : " w-full" <div className="flex w-full items-center">
} <span
> onClick={() => {
<div className="flex w-full items-center"> openPopUp(
<span <GenericModal
onClick={() => { type={TypeModal.PROMPT}
openPopUp( value={myValue}
<GenericModal buttonText="Check & Save"
type={TypeModal.PROMPT} modalTitle="Edit Prompt"
value={myValue} setValue={(t: string) => {
buttonText="Check & Save" setMyValue(t);
modalTitle="Edit Prompt" onChange(t);
setValue={(t: string) => { }}
setMyValue(t); nodeClass={nodeClass}
onChange(t); setNodeClass={setNodeClass}
}} />
nodeClass={nodeClass} );
setNodeClass={setNodeClass} }}
className={
editNode
? " input-edit-node " + " input-dialog "
: (disabled ? " input-disable " : "") +
" input-primary " +
" input-dialog "
}
>
{myValue !== "" ? myValue : "Type your prompt here"}
</span>
<button
onClick={() => {
openPopUp(
<GenericModal
field_name={field_name}
type={TypeModal.PROMPT}
value={myValue}
buttonText="Check & Save"
modalTitle="Edit Prompt"
setValue={(t: string) => {
setMyValue(t);
onChange(t);
}}
nodeClass={nodeClass}
setNodeClass={setNodeClass}
/>
);
}}
>
{!editNode && (
<ExternalLink
strokeWidth={1.5}
className={
"ml-3 h-6 w-6" +
(disabled ? " text-ring" : " hover:text-accent-foreground")
}
/> />
); )}
}} </button>
className={ </div>
editNode
? " input-edit-node " + " input-dialog "
: (disabled ? " input-disable " : "") +
" input-primary " +
" input-dialog "
}
>
{myValue !== "" ? myValue : "Type your prompt here"}
</span>
<button
onClick={() => {
openPopUp(
<GenericModal
field_name={field_name}
type={TypeModal.PROMPT}
value={myValue}
buttonText="Check & Save"
modalTitle="Edit Prompt"
setValue={(t: string) => {
setMyValue(t);
onChange(t);
}}
nodeClass={nodeClass}
setNodeClass={setNodeClass}
/>
);
}}
>
{!editNode && (
<ExternalLink
strokeWidth={1.5}
className="ml-3 h-6 w-6 hover:text-accent-foreground"
/>
)}
</button>
</div> </div>
</div> </div>
); );

View file

@ -27,12 +27,11 @@ export default function TextAreaComponent({
}, [closePopUp]); }, [closePopUp]);
return ( return (
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}> <div className={disabled ? "cursor-not-allowed" : ""}>
<div <div
className={ className={
editNode (editNode ? "w-full items-center" : "flex w-full items-center") +
? "w-full items-center" (disabled ? " pointer-events-none" : "")
: "w-full flex items-center"
} }
> >
<span <span
@ -52,12 +51,14 @@ export default function TextAreaComponent({
}} }}
className={ className={
editNode editNode
? "input-edit-node input-dialog " ? "input-edit-node input-dialog "
: "input-primary input-dialog " + (disabled ? "input-disable" : "") : "input-dialog input-primary " +
(disabled ? "input-disable" : "")
} }
> >
{myValue !== "" ? myValue : "Type something..."} {myValue !== "" ? myValue : "Type something..."}
</span> </span>
<button <button
onClick={() => { onClick={() => {
openPopUp( openPopUp(
@ -74,7 +75,15 @@ export default function TextAreaComponent({
); );
}} }}
> >
{!editNode && <ExternalLink strokeWidth={1.5} className="w-6 h-6 hover:text-accent-foreground ml-3" />} {!editNode && (
<ExternalLink
strokeWidth={1.5}
className={
"ml-3 h-6 w-6" +
(disabled ? " text-ring" : " hover:text-accent-foreground")
}
/>
)}
</button> </button>
</div> </div>
</div> </div>

View file

@ -27,7 +27,7 @@ const AccordionTrigger = React.forwardRef<
<AccordionPrimitive.Trigger <AccordionPrimitive.Trigger
ref={ref} ref={ref}
className={cn( className={cn(
"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180", "flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all [&[data-state=open]>svg]:rotate-180",
className className
)} )}
{...props} {...props}

View file

@ -9,8 +9,7 @@ const badgeVariants = cva(
variant: { variant: {
default: default:
"bg-primary hover:bg-primary/80 border-transparent text-primary-foreground", "bg-primary hover:bg-primary/80 border-transparent text-primary-foreground",
gray: gray: "bg-border hover:bg-border/80 text-secondary-foreground",
"bg-border hover:bg-border/80 text-secondary-foreground",
secondary: secondary:
"bg-secondary hover:bg-secondary/80 border-transparent text-secondary-foreground", "bg-secondary hover:bg-secondary/80 border-transparent text-secondary-foreground",
destructive: destructive:
@ -21,7 +20,7 @@ const badgeVariants = cva(
sm: "h-4 text-xs", sm: "h-4 text-xs",
md: "h-5 text-sm", md: "h-5 text-sm",
lg: "h-6 text-base", lg: "h-6 text-base",
} },
}, },
defaultVariants: { defaultVariants: {
variant: "default", variant: "default",
@ -35,7 +34,10 @@ export interface BadgeProps
function Badge({ className, variant, size, ...props }: BadgeProps) { function Badge({ className, variant, size, ...props }: BadgeProps) {
return ( return (
<div className={cn(badgeVariants({ variant, size }), className)} {...props} /> <div
className={cn(badgeVariants({ variant, size }), className)}
{...props}
/>
); );
} }

View file

@ -44,7 +44,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content <DialogPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"fixed gap-3 z-50 grid w-full rounded-b-lg border bg-background p-6 shadow-lg animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0", "fixed z-50 grid w-full gap-3 rounded-b-lg border bg-background p-6 shadow-lg animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0",
className className
)} )}
{...props} {...props}

View file

@ -19,7 +19,7 @@ const Switch = React.forwardRef<
<SwitchPrimitives.Thumb <SwitchPrimitives.Thumb
aria-disabled={props.disabled} aria-disabled={props.disabled}
className={cn( className={cn(
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 aria-disabled:bg-muted/70 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0" "pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform aria-disabled:bg-muted/70 data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
)} )}
/> />
</SwitchPrimitives.Root> </SwitchPrimitives.Root>

View file

@ -50,6 +50,15 @@ export const CODE_PROMPT_DIALOG_SUBTITLE =
export const PROMPT_DIALOG_SUBTITLE = export const PROMPT_DIALOG_SUBTITLE =
"Create your prompt. Prompts can help guide the behavior of a Language Model."; "Create your prompt. Prompts can help guide the behavior of a Language Model.";
export const CHAT_CANNOT_OPEN_TITLE = "Chat Cannot Open";
export const CHAT_CANNOT_OPEN_DESCRIPTION = "This is not a chat flow.";
export const FLOW_NOT_BUILT_TITLE = "Flow not built";
export const FLOW_NOT_BUILT_DESCRIPTION =
"Please build the flow before chatting.";
/** /**
* The base text for subtitle of Text Dialog * The base text for subtitle of Text Dialog
* @constant * @constant
@ -180,7 +189,7 @@ export const EXPORT_CODE_DIALOG =
export const COLUMN_DIV_STYLE = export const COLUMN_DIV_STYLE =
" w-full h-full flex overflow-auto flex-col bg-muted px-16 "; " w-full h-full flex overflow-auto flex-col bg-muted px-16 ";
export const NAV_DISPLAY_STYLE = export const NAV_DISPLAY_STYLE =
" w-full flex justify-between py-12 pb-2 px-6 "; " w-full flex justify-between py-12 pb-2 px-6 ";
/** /**
@ -210,7 +219,8 @@ export const DESCRIPTIONS: string[] = [
"Conversational Cartography Unlocked.", "Conversational Cartography Unlocked.",
"Design, Develop, Dialogize.", "Design, Develop, Dialogize.",
]; ];
export const BUTTON_DIV_STYLE = " flex gap-2 focus:ring-1 focus:ring-offset-1 focus:ring-ring focus:outline-none "; export const BUTTON_DIV_STYLE =
" flex gap-2 focus:ring-1 focus:ring-offset-1 focus:ring-ring focus:outline-none ";
/** /**
* The base text for subtitle of code dialog * The base text for subtitle of code dialog
@ -513,3 +523,10 @@ export const NOUNS: string[] = [
* *
*/ */
export const USER_PROJECTS_HEADER = "My Collection"; export const USER_PROJECTS_HEADER = "My Collection";
/**
* CSS for highlight HTML
* @constant
*
*/
export const HIGHLIGH_CSS =
"block pl-3 pr-14 py-2 w-full h-full text-sm outline-0 border-0 break-all";

View file

@ -285,7 +285,11 @@ export function TabsProvider({ children }: { children: ReactNode }) {
// create a link element and set its properties // create a link element and set its properties
const link = document.createElement("a"); const link = document.createElement("a");
link.href = jsonString; link.href = jsonString;
link.download = `${flowName && flowName != "" ? flowName : flows.find((f) => f.id === tabId).name}.json`; link.download = `${
flowName && flowName != ""
? flowName
: flows.find((f) => f.id === tabId).name
}.json`;
// simulate a click on the link element to trigger the download // simulate a click on the link element to trigger the download
link.click(); link.click();

View file

@ -104,11 +104,10 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
useEffect(() => { useEffect(() => {
if (closeEdit !== "") { if (closeEdit !== "") {
tweak.current = getTweak; tweak.current = getTweak;
if(tweak.current.length > 0){ if (tweak.current.length > 0) {
setActiveTab("3"); setActiveTab("3");
openAccordions(); openAccordions();
} } else {
else{
startTweaks(); startTweaks();
} }
} else { } else {
@ -250,25 +249,25 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
function openAccordions() { function openAccordions() {
let accordionsToOpen = []; let accordionsToOpen = [];
tweak.current.forEach((el) => { tweak.current.forEach((el) => {
Object.keys(el).forEach((key) => { Object.keys(el).forEach((key) => {
if (Object.keys(el[key]).length > 0) { if (Object.keys(el[key]).length > 0) {
accordionsToOpen.push(key); accordionsToOpen.push(key);
setOpenAccordion(accordionsToOpen); setOpenAccordion(accordionsToOpen);
} }
});
}); });
});
} }
return ( return (
<Dialog open={true} onOpenChange={setModalOpen}> <Dialog open={true} onOpenChange={setModalOpen}>
<DialogTrigger></DialogTrigger> <DialogTrigger></DialogTrigger>
<DialogContent className="lg:max-w-[80vw] h-[80vh]"> <DialogContent className="h-[80vh] lg:max-w-[80vw]">
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center"> <DialogTitle className="flex items-center">
<span className="pr-2">Code</span> <span className="pr-2">Code</span>
<Code2 <Code2
className="h-6 w-6 text-gray-800 pl-1 dark:text-white" className="h-6 w-6 pl-1 text-gray-800 dark:text-white"
aria-hidden="true" aria-hidden="true"
/> />
</DialogTitle> </DialogTitle>
@ -277,7 +276,7 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
<Tabs <Tabs
value={activeTab} value={activeTab}
className="w-full h-full overflow-hidden text-center bg-muted rounded-md border" className="h-full w-full overflow-hidden rounded-md border bg-muted text-center"
onValueChange={(value) => { onValueChange={(value) => {
setActiveTab(value); setActiveTab(value);
if (value === "3") { if (value === "3") {
@ -296,7 +295,7 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
{Number(activeTab) < 3 && ( {Number(activeTab) < 3 && (
<div className="float-right"> <div className="float-right">
<button <button
className="flex gap-1.5 items-center rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300" className="flex items-center gap-1.5 rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"
onClick={copyToClipboard} onClick={copyToClipboard}
> >
{isCopied ? <Check size={18} /> : <Clipboard size={15} />} {isCopied ? <Check size={18} /> : <Clipboard size={15} />}
@ -309,12 +308,12 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
{tabs.map((tab, index) => ( {tabs.map((tab, index) => (
<TabsContent <TabsContent
value={index.toString()} value={index.toString()}
className="overflow-hidden w-full h-full px-4 pb-4 -mt-1" className="-mt-1 h-full w-full overflow-hidden px-4 pb-4"
key={index} // Remember to add a unique key prop key={index} // Remember to add a unique key prop
> >
{index < 3 ? ( {index < 3 ? (
<SyntaxHighlighter <SyntaxHighlighter
className="w-full overflow-auto h-[60vh]" className="h-[60vh] w-full overflow-auto"
language={tab.mode} language={tab.mode}
style={oneDark} style={oneDark}
> >
@ -322,10 +321,10 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
</SyntaxHighlighter> </SyntaxHighlighter>
) : index === 3 ? ( ) : index === 3 ? (
<> <>
<div className="flex w-full h-full mt-2"> <div className="mt-2 flex h-full w-full">
<div <div
className={classNames( className={classNames(
"w-full rounded-lg bg-muted h-[60vh]", "h-[60vh] w-full rounded-lg bg-muted",
1 == 1 1 == 1
? "overflow-scroll overflow-x-hidden custom-scroll" ? "overflow-scroll overflow-x-hidden custom-scroll"
: "overflow-hidden" : "overflow-hidden"
@ -338,14 +337,14 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
trigger={t["data"]["id"]} trigger={t["data"]["id"]}
open={openAccordion} open={openAccordion}
> >
<div className="flex flex-col gap-5 h-fit"> <div className="flex h-fit flex-col gap-5">
<Table className="table-fixed bg-muted outline-1"> <Table className="table-fixed bg-muted outline-1">
<TableHeader className="border-input text-xs font-medium text-ring h-10"> <TableHeader className="h-10 border-input text-xs font-medium text-ring">
<TableRow className="dark:border-b-muted"> <TableRow className="dark:border-b-muted">
<TableHead className="h-7 text-center"> <TableHead className="h-7 text-center">
PARAM PARAM
</TableHead> </TableHead>
<TableHead className="p-0 h-7 text-center"> <TableHead className="h-7 p-0 text-center">
VALUE VALUE
</TableHead> </TableHead>
</TableRow> </TableRow>
@ -379,11 +378,11 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
key={i} key={i}
className="h-10 dark:border-b-muted" className="h-10 dark:border-b-muted"
> >
<TableCell className="p-0 text-center text-gray-900 text-sm"> <TableCell className="p-0 text-center text-sm text-gray-900">
{n} {n}
</TableCell> </TableCell>
<TableCell className="p-0 text-center text-gray-900 text-xs dark:text-gray-300"> <TableCell className="p-0 text-center text-xs text-gray-900 dark:text-gray-300">
<div className="w-[250px] m-auto"> <div className="m-auto w-[250px]">
{t.data.node.template[n] {t.data.node.template[n]
.type === "str" && .type === "str" &&
!t.data.node.template[n] !t.data.node.template[n]

View file

@ -5,7 +5,7 @@ import "ace-builds/src-noconflict/mode-python";
import "ace-builds/src-noconflict/theme-github"; import "ace-builds/src-noconflict/theme-github";
import "ace-builds/src-noconflict/theme-twilight"; import "ace-builds/src-noconflict/theme-twilight";
import "ace-builds/src-noconflict/ext-language_tools"; import "ace-builds/src-noconflict/ext-language_tools";
import 'ace-builds/src-noconflict/ace'; import "ace-builds/src-noconflict/ace";
// import "ace-builds/webpack-resolver"; // import "ace-builds/webpack-resolver";
import { darkContext } from "../../contexts/darkContext"; import { darkContext } from "../../contexts/darkContext";
import { postCustomComponent, postValidateCode } from "../../controllers/API"; import { postCustomComponent, postValidateCode } from "../../controllers/API";
@ -29,14 +29,13 @@ import {
TabsList, TabsList,
TabsTrigger, TabsTrigger,
} from "../../components/ui/tabs"; } from "../../components/ui/tabs";
import axios from "axios";
export default function CodeAreaModal({ export default function CodeAreaModal({
value, value,
setValue, setValue,
nodeClass, nodeClass,
setNodeClass, setNodeClass,
dynamic dynamic,
}: { }: {
setValue: (value: string) => void; setValue: (value: string) => void;
value: string; value: string;
@ -50,7 +49,9 @@ export default function CodeAreaModal({
const { dark } = useContext(darkContext); const { dark } = useContext(darkContext);
const { setErrorData, setSuccessData } = useContext(alertContext); const { setErrorData, setSuccessData } = useContext(alertContext);
const [activeTab, setActiveTab] = useState("0"); const [activeTab, setActiveTab] = useState("0");
const [error, setError] = useState<{ detail: { error: string, traceback: string } }>(null) const [error, setError] = useState<{
detail: { error: string; traceback: string };
}>(null);
const { closePopUp, setCloseEdit } = useContext(PopUpContext); const { closePopUp, setCloseEdit } = useContext(PopUpContext);
const ref = useRef(); const ref = useRef();
function setModalOpen(x: boolean) { function setModalOpen(x: boolean) {
@ -64,9 +65,9 @@ export default function CodeAreaModal({
} }
console.log(dynamic); console.log(dynamic);
useEffect(()=>{ useEffect(() => {
setValue(code); setValue(code);
},[code,setValue]) }, [code, setValue]);
function handleClick() { function handleClick() {
setLoading(true); setLoading(true);
@ -108,39 +109,40 @@ export default function CodeAreaModal({
title: "There is something wrong with this code, please review it", title: "There is something wrong with this code, please review it",
}); });
}); });
} } else {
else { postCustomComponent(code, nodeClass)
postCustomComponent(code, nodeClass).then((apiReturn) => { .then((apiReturn) => {
const { data } = apiReturn; const { data } = apiReturn;
if (data) { if (data) {
setNodeClass(data); setNodeClass(data);
setModalOpen(false); setModalOpen(false);
} }
}).catch((err) => { })
setErrorData({ .catch((err) => {
title: "There is something wrong with this code, please see the error on the errors tab", setErrorData({
title:
"There is something wrong with this code, please see the error on the errors tab",
});
console.log(err.response.data);
setError(err.response.data);
}); });
console.log(err.response.data);
setError(err.response.data);
});
} }
// axios.get("/api/v1/custom_component_error").catch((err) => { // axios.get("/api/v1/custom_component_error").catch((err) => {
// }) // })
} }
const tabs = [{ name: "code" }, { name: "errors" }] const tabs = [{ name: "code" }, { name: "errors" }];
return ( return (
<Dialog open={true} onOpenChange={setModalOpen}> <Dialog open={true} onOpenChange={setModalOpen}>
<DialogTrigger></DialogTrigger> <DialogTrigger></DialogTrigger>
<DialogContent className="h-[500px] lg:max-w-[700px]"> <DialogContent className="min-w-[80vw]">
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center"> <DialogTitle className="flex items-center">
<span className="pr-2">Edit Code</span> <span className="pr-2">Edit Code</span>
<TerminalSquare <TerminalSquare
strokeWidth={1.5} strokeWidth={1.5}
className="h-6 w-6 text-primary pl-1 " className="h-6 w-6 pl-1 text-primary "
aria-hidden="true" aria-hidden="true"
/> />
</DialogTitle> </DialogTitle>
@ -148,44 +150,65 @@ export default function CodeAreaModal({
</DialogHeader> </DialogHeader>
<Tabs <Tabs
defaultValue={"0"} defaultValue={"0"}
className="w-full h-full overflow-hidden text-center bg-muted rounded-md border" className="h-full w-full overflow-hidden rounded-md border bg-muted text-center"
onValueChange={(value) => setActiveTab(value)} onValueChange={(value) => setActiveTab(value)}
> >
<div className="flex flex-col items-start h-72 px-2"> <div className="flex h-72 flex-col items-start px-2">
<TabsList> <TabsList>
{tabs.map((tab, index) => ( {tabs.map((tab, index) => (
<TabsTrigger disabled={index === 1 && error?.detail.error === undefined} key={index} value={index.toString()}> <TabsTrigger
<span className={error?.detail.error !== undefined && index===1 ? "text-destructive" : ""}>{tab.name}</span></TabsTrigger> disabled={index === 1 && error?.detail.error === undefined}
key={index}
value={index.toString()}
>
<span
className={
error?.detail.error !== undefined && index === 1
? "text-destructive"
: ""
}
>
{tab.name}
</span>
</TabsTrigger>
))} ))}
</TabsList> </TabsList>
{tabs.map((tab, index) => ( {tabs.map((tab, index) => (
<TabsContent <TabsContent
value={index.toString()} value={index.toString()}
className="overflow-hidden w-full h-full px-4 pb-4 mt-1" className="mt-1 h-full w-full overflow-hidden px-4 pb-4"
> >
{tab.name === "code" ? <div className="h-full w-full"> {tab.name === "code" ? (
<AceEditor <div className="h-full w-full">
value={code} <AceEditor
mode="python" value={code}
highlightActiveLine={true} mode="python"
showPrintMargin={false} highlightActiveLine={true}
fontSize={14} showPrintMargin={false}
showGutter fontSize={14}
enableLiveAutocompletion showGutter
theme={dark ? "twilight" : "github"} enableLiveAutocompletion
name="CodeEditor" theme={dark ? "twilight" : "github"}
onChange={(value) => { name="CodeEditor"
setCode(value); onChange={(value) => {
}} setCode(value);
className="w-full rounded-lg h-full custom-scroll border-[1px] border-gray-300 dark:border-gray-600" }}
/> className="h-full w-full rounded-lg border-[1px] border-gray-300 custom-scroll dark:border-gray-600"
</div> : <div className="w-full h-full bg-red-200 p-2 flex flex-col overflow-scroll custom-scroll text-left"> />
<h1 className="text-red-600 text-lg">{error?.detail?.error}</h1> </div>
<span className="border border-red-300 w-full"></span> ) : (
<div className="text-red-500 text-sm">{error?.detail?.traceback}</div> <div className="flex h-full w-full flex-col overflow-scroll bg-red-200 p-2 text-left custom-scroll">
</div>} <h1 className="text-lg text-red-600">
</TabsContent>)) {error?.detail?.error}
} </h1>
<span className="w-full border border-red-300"></span>
<div className="text-sm text-red-500">
{error?.detail?.traceback}
</div>
</div>
)}
</TabsContent>
))}
</div> </div>
</Tabs> </Tabs>
<DialogFooter> <DialogFooter>

View file

@ -47,8 +47,8 @@ export default function ExportModal() {
<DialogTitle className="flex items-center"> <DialogTitle className="flex items-center">
<span className="pr-2">Export</span> <span className="pr-2">Export</span>
<Download <Download
strokeWidth={1.5} strokeWidth={1.5}
className="h-6 w-6 text-foreground pl-1" className="h-6 w-6 pl-1 text-foreground"
aria-hidden="true" aria-hidden="true"
/> />
</DialogTitle> </DialogTitle>

View file

@ -52,20 +52,27 @@ export default function ChatInput({
lockChat lockChat
? " bg-input text-black dark:bg-gray-700 dark:text-gray-300" ? " bg-input text-black dark:bg-gray-700 dark:text-gray-300"
: " bg-white-200 text-black dark:bg-gray-900 dark:text-gray-300", : " bg-white-200 text-black dark:bg-gray-900 dark:text-gray-300",
"p-4 form-input block w-full custom-scroll rounded-md border-gray-300 dark:border-gray-600 pr-16 sm:text-sm" "form-input block w-full rounded-md border-gray-300 p-4 pr-16 custom-scroll dark:border-gray-600 sm:text-sm"
)} )}
placeholder={"Send a message..."} placeholder={"Send a message..."}
/> />
<div className="absolute bottom-2 right-4"> <div className="absolute bottom-2 right-4">
<button className={classNames("p-2 px-1 transition-all duration-300 rounded-md",chatValue == "" ? "text-primary" : " bg-indigo-600 text-background")} disabled={lockChat} onClick={() => sendMessage()}> <button
className={classNames(
"rounded-md p-2 px-1 transition-all duration-300",
chatValue == "" ? "text-primary" : " bg-indigo-600 text-background"
)}
disabled={lockChat}
onClick={() => sendMessage()}
>
{lockChat ? ( {lockChat ? (
<Lock <Lock
className="h-5 w-5 ml-1 mr-1 animate-pulse" className="ml-1 mr-1 h-5 w-5 animate-pulse"
aria-hidden="true" aria-hidden="true"
/> />
) : ( ) : (
<LucideSend <LucideSend
className="h-5 w-5 mr-2 rotate-[44deg] " className="mr-2 h-5 w-5 rotate-[44deg] "
aria-hidden="true" aria-hidden="true"
/> />
)} )}

View file

@ -11,6 +11,13 @@ import remarkMath from "remark-math";
import { CodeBlock } from "./codeBlock"; import { CodeBlock } from "./codeBlock";
import Convert from "ansi-to-html"; import Convert from "ansi-to-html";
import { User2, MessageSquare } from "lucide-react"; import { User2, MessageSquare } from "lucide-react";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "../../../components/ui/accordion";
import { Badge } from "../../../components/ui/badge";
export default function ChatMessage({ export default function ChatMessage({
chat, chat,
@ -22,145 +29,173 @@ export default function ChatMessage({
lastMessage: boolean; lastMessage: boolean;
}) { }) {
const convert = new Convert({ newline: true }); const convert = new Convert({ newline: true });
const [message, setMessage] = useState("");
const imgRef = useRef(null);
useEffect(() => {
setMessage(chat.message);
}, [chat.message]);
const [hidden, setHidden] = useState(true); const [hidden, setHidden] = useState(true);
const [template, setTemplate] = useState(chat.template);
return ( return (
<div <div
className={classNames( className={classNames(
"w-full py-2 px-2 pl-4 pr-9 flex", "flex w-full px-2 py-6 pl-4 pr-9",
chat.isSend chat.isSend ? " bg-border" : " "
? " bg-border" )}
: " " >
)} <div className={classNames("mb-3 ml-3 mr-6 mt-1 ")}>
>
<div
className={classNames(
"rounded-full overflow-hidden w-8 h-8 flex items-center my-3 mx-3 justify-center"
)}
>
{!chat.isSend && (
<div className="relative w-8 h-8">
<img
className={
"absolute transition-opacity duration-500 scale-150 " +
(lockChat ? "opacity-100" : "opacity-0")
}
src={lastMessage ? AiIcon : AiIconStill}
/>
<img
className={
"absolute transition-opacity duration-500 scale-150 " +
(lockChat ? "opacity-0" : "opacity-100")
}
src={AiIconStill}
/>
</div>
)}
{chat.isSend && (
<User2 className="w-6 h-6 text-gray-800 dark:text-gray-200" />
)}
</div>
{!chat.isSend ? ( {!chat.isSend ? (
<div className="w-full text-start flex items-center"> <div className="flex h-8 w-8 items-center justify-center overflow-hidden rounded-md bg-[#afe6ef] p-5 text-2xl ">
<div className="w-full relative text-start inline-block text-primary text-sm font-normal"> 🤖
{hidden && chat.thought && chat.thought !== "" && (
<div
onClick={() => setHidden((prev) => !prev)}
className="absolute -top-1 -left-2 cursor-pointer"
>
<MessageSquare className="w-5 h-5 animate-bounce dark:text-white" />
</div>
)}
{chat.thought && chat.thought !== "" && !hidden && (
<div
onClick={() => setHidden((prev) => !prev)}
className=" text-start inline-block rounded-md text-primary h-full border border-gray-300 dark:border-gray-500
bg-muted dark:bg-gray-800 w-[95%] pb-3 pt-3 px-2 ml-3 cursor-pointer scrollbar-hide overflow-scroll"
dangerouslySetInnerHTML={{
__html: convert.toHtml(chat.thought),
}}
></div>
)}
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
<div className="w-full pb-3 pt-3">
<div className="dark:text-white w-full">
<div className="w-full">
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax]}
className="markdown prose dark:prose-invert text-primary max-w-full"
components={{
code({ node, inline, className, children, ...props }) {
if (children.length) {
if (children[0] == "▍") {
return (
<span className="animate-pulse cursor-default mt-1">
▍
</span>
);
}
children[0] = (children[0] as string).replace(
"`▍`",
"▍"
);
}
const match = /language-(\w+)/.exec(className || "");
return !inline ? (
<CodeBlock
key={Math.random()}
language={(match && match[1]) || ""}
value={String(children).replace(/\n$/, "")}
{...props}
/>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
}}
>
{message}
</ReactMarkdown>
</div>
{chat.files && (
<div className="my-2 w-full">
{chat.files.map((file, index) => {
return (
<div key={index} className="my-2 w-full">
<FileCard
fileName={"Generated File"}
fileType={file.data_type}
content={file.data}
/>
</div>
);
})}
</div>
)}
</div>
</div>
</div>
</div> </div>
) : ( ) : (
<div className="w-full flex items-center"> <div className="flex h-8 w-8 items-center justify-center overflow-hidden rounded-md bg-[#aface9] p-5 text-2xl ">
<div className="text-start inline-block"> 👨‍💻
<span
className="text-primary"
dangerouslySetInnerHTML={{
__html: message.replace(/\n/g, "<br>"),
}}
></span>
</div>
</div> </div>
)} )}
</div> </div>
{!chat.isSend ? (
<div className="flex w-full items-center text-start">
<div className="relative inline-block w-full text-start text-sm font-normal text-primary">
{hidden && chat.thought && chat.thought !== "" && (
<div
onClick={() => setHidden((prev) => !prev)}
className="absolute -left-2 -top-1 cursor-pointer"
>
<MessageSquare className="h-5 w-5 animate-bounce dark:text-white" />
</div>
)}
{chat.thought && chat.thought !== "" && !hidden && (
<div
onClick={() => setHidden((prev) => !prev)}
className=" ml-3 inline-block h-full w-[95%] cursor-pointer overflow-scroll rounded-md border
border-gray-300 bg-muted px-2 text-start text-primary scrollbar-hide dark:border-gray-500 dark:bg-gray-800"
dangerouslySetInnerHTML={{
__html: convert.toHtml(chat.thought),
}}
></div>
)}
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
<div className="w-full">
<div className="w-full dark:text-white">
<div className="w-full">
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax]}
className="markdown prose max-w-full text-primary dark:prose-invert"
components={{
code({ node, inline, className, children, ...props }) {
if (children.length) {
if (children[0] == "▍") {
return (
<span className="mt-1 animate-pulse cursor-default">
▍
</span>
);
}
children[0] = (children[0] as string).replace(
"`▍`",
"▍"
);
}
const match = /language-(\w+)/.exec(className || "");
return !inline ? (
<CodeBlock
key={Math.random()}
language={(match && match[1]) || ""}
value={String(children).replace(/\n$/, "")}
{...props}
/>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
}}
>
{chat.message.toString()}
</ReactMarkdown>
</div>
{chat.files && (
<div className="my-2 w-full">
{chat.files.map((file, index) => {
return (
<div key={index} className="my-2 w-full">
<FileCard
fileName={"Generated File"}
fileType={file.data_type}
content={file.data}
/>
</div>
);
})}
</div>
)}
</div>
</div>
</div>
</div>
) : (
<div className="flex w-full items-center">
<div className="inline-block text-start">
<span className=" break-all text-primary">
<Accordion type="single" collapsible className="mb-4">
<AccordionItem
className=" rounded-md border border-ring/60 bg-muted px-4"
value="prompt"
>
<AccordionTrigger className="flex gap-4 text-base font-semibold">
Initial Prompt
</AccordionTrigger>
<AccordionContent className="max-h-96 overflow-auto break-all p-2 text-base">
{
// Make all the variables that are inside curly braces bold
template.split("\n").map((line, index) => {
const regex = /{([^}]+)}/g;
let match;
let parts = [];
let lastIndex = 0;
while ((match = regex.exec(line)) !== null) {
// Push text up to the match
if (match.index !== lastIndex) {
parts.push(line.substring(lastIndex, match.index));
}
// Push div with matched text
parts.push(
<div>
<div className="my-1 inline-block rounded-md bg-indigo-100 px-2">
<span key={match.index}>
<span className=" text-xs font-semibold text-high-indigo">
{match[1]}
</span>
{chat.message[match[1]] ? (
<span>
&nbsp;&nbsp;{chat.message[match[1]]}
</span>
) : (
""
)}
</span>
</div>
</div>
);
// Update last index
lastIndex = regex.lastIndex;
}
// Push text after the last match
if (lastIndex !== line.length) {
parts.push(line.substring(lastIndex));
}
return <p>{parts}</p>;
})
}
</AccordionContent>
</AccordionItem>
</Accordion>
{chat.message[chat.chatKey]}
</span>
</div>
</div>
)}
</div>
); );
} }

View file

@ -91,19 +91,23 @@ export default function FormModal({
var isStream = false; var isStream = false;
const addChatHistory = ( const addChatHistory = (
message: string, message: string | Object,
isSend: boolean, isSend: boolean,
chatKey: string,
template?: string,
thought?: string, thought?: string,
files?: Array<any> files?: Array<any>
) => { ) => {
setChatHistory((old) => { setChatHistory((old) => {
let newChat = _.cloneDeep(old); let newChat = _.cloneDeep(old);
if (files) { if (files) {
newChat.push({ message, isSend, files, thought }); newChat.push({ message, isSend, files, thought, chatKey });
} else if (thought) { } else if (thought) {
newChat.push({ message, isSend, thought }); newChat.push({ message, isSend, thought, chatKey });
} else if (template) {
newChat.push({ message, isSend, chatKey, template });
} else { } else {
newChat.push({ message, isSend }); newChat.push({ message, isSend, chatKey });
} }
return newChat; return newChat;
}); });
@ -174,7 +178,9 @@ export default function FormModal({
intermediate_steps?: string; intermediate_steps?: string;
is_bot: boolean; is_bot: boolean;
message: string; message: string;
template: string;
type: string; type: string;
chatKey: string;
files?: Array<any>; files?: Array<any>;
}) => { }) => {
if (chatItem.message) { if (chatItem.message) {
@ -182,14 +188,18 @@ export default function FormModal({
chatItem.files chatItem.files
? { ? {
isSend: !chatItem.is_bot, isSend: !chatItem.is_bot,
message: formatMessage(chatItem.message), message: chatItem.message,
template: chatItem.template,
thought: chatItem.intermediate_steps, thought: chatItem.intermediate_steps,
files: chatItem.files, files: chatItem.files,
chatKey: chatItem.chatKey,
} }
: { : {
isSend: !chatItem.is_bot, isSend: !chatItem.is_bot,
message: formatMessage(chatItem.message), message: chatItem.message,
template: chatItem.template,
thought: chatItem.intermediate_steps, thought: chatItem.intermediate_steps,
chatKey: chatItem.chatKey,
} }
); );
} }
@ -199,7 +209,7 @@ export default function FormModal({
}); });
} }
if (data.type === "start") { if (data.type === "start") {
addChatHistory("", false); addChatHistory("", false, chatKey);
isStream = true; isStream = true;
} }
if (data.type === "end") { if (data.type === "end") {
@ -319,23 +329,6 @@ export default function FormModal({
ref.current.focus(); ref.current.focus();
} }
}, [open]); }, [open]);
function formatMessage(inputs: any): string {
if (inputs) {
if (typeof inputs == "string") return inputs;
// inputs is a object with the keys and values being input_keys and keysValue
// so the formated message is a string with the keys and values separated by ": "
let message = "";
for (const [key, value] of Object.entries(inputs)) {
// make key bold
// dangerouslySetInnerHTML={{
// __html: message.replace(/\n/g, "<br>"),
// }}
message += `<b>${key}</b>: ${value}\n`;
}
return message;
}
return "";
}
function sendMessage() { function sendMessage() {
if (chatValue !== "") { if (chatValue !== "") {
@ -344,8 +337,13 @@ export default function FormModal({
setLockChat(true); setLockChat(true);
let inputs = tabsState[id.current].formKeysData.input_keys; let inputs = tabsState[id.current].formKeysData.input_keys;
setChatValue(""); setChatValue("");
const message = formatMessage(inputs); const message = inputs;
addChatHistory(message, true); addChatHistory(
message,
true,
chatKey,
tabsState[flow.id].formKeysData.template
);
sendAll({ sendAll({
...reactFlowInstance.toObject(), ...reactFlowInstance.toObject(),
inputs: inputs, inputs: inputs,
@ -404,7 +402,7 @@ export default function FormModal({
</DialogHeader> </DialogHeader>
<div className="mt-2 flex h-[80vh] w-full "> <div className="mt-2 flex h-[80vh] w-full ">
<div className="mr-6 flex h-full w-2/5 flex-col justify-start overflow-auto scrollbar-hide"> <div className="mr-6 flex h-full w-2/6 flex-col justify-start overflow-auto scrollbar-hide">
<div className="flex items-center py-2"> <div className="flex items-center py-2">
<Variable className=" -ml-px mr-1 h-4 w-4 text-primary"></Variable> <Variable className=" -ml-px mr-1 h-4 w-4 text-primary"></Variable>
<span className="text-md font-semibold text-primary"> <span className="text-md font-semibold text-primary">
@ -496,7 +494,7 @@ export default function FormModal({
))} ))}
</Accordion> </Accordion>
</div> </div>
<div className="w-full"> <div className="flex w-full flex-1 flex-col">
<div className="relative flex h-full w-full flex-col rounded-md border bg-muted"> <div className="relative flex h-full w-full flex-col rounded-md border bg-muted">
<div className="absolute right-3 top-3 z-50"> <div className="absolute right-3 top-3 z-50">
<button disabled={lockChat} onClick={() => clearChat()}> <button disabled={lockChat} onClick={() => clearChat()}>

View file

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

View file

@ -160,7 +160,10 @@ export default function ImportModal() {
bgColor="bg-medium-emerald " bgColor="bg-medium-emerald "
description={example.description ?? "Prebuilt Examples"} description={example.description ?? "Prebuilt Examples"}
icon={ icon={
<DocumentDuplicateIcon strokeWidth={1.5} className="h-6 w-6 flex-shrink-0" /> <DocumentDuplicateIcon
strokeWidth={1.5}
className="h-6 w-6 flex-shrink-0"
/>
} }
onClick={() => { onClick={() => {
addFlow(example, false); addFlow(example, false);

View file

@ -1,124 +0,0 @@
import { Dialog, Transition } from "@headlessui/react";
import {
XMarkIcon,
ClipboardDocumentListIcon,
} from "@heroicons/react/24/outline";
import { Fragment, useContext, useRef, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
export default function TextAreaModal({
value,
setValue,
}: {
setValue: (value: string) => void;
value: string | string[];
}) {
const [open, setOpen] = useState(true);
const [myValue, setMyValue] = useState(value);
const { closePopUp, setCloseEdit } = useContext(PopUpContext);
const ref = useRef();
function setModalOpen(x: boolean) {
setOpen(x);
if (x === false) {
setTimeout(() => {
setCloseEdit("textarea");
closePopUp();
}, 300);
}
}
return (
<Transition.Root show={open} appear={true} as={Fragment}>
<Dialog
as="div"
className="relative z-10"
onClose={setModalOpen}
initialFocus={ref}
>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-ring bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative flex h-[600px] w-[700px] transform flex-col justify-between overflow-hidden rounded-lg bg-background text-left shadow-xl transition-all sm:my-8">
<div className=" absolute right-0 top-0 z-50 hidden pr-4 pt-4 sm:block">
<button
type="button"
className="rounded-md text-ring hover:text-accent-foreground"
onClick={() => {
setModalOpen(false);
}}
>
<span className="sr-only">Close</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="flex h-full w-full flex-col items-center justify-center">
<div className="z-10 flex w-full justify-center pb-4 shadow-sm">
<div className="mx-auto mt-4 flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-almost-light-blue sm:mx-0 sm:h-10 sm:w-10">
<ClipboardDocumentListIcon
className="h-6 w-6 text-almost-medium-blue"
aria-hidden="true"
/>
</div>
<div className="mt-4 text-center sm:ml-4 sm:text-left">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-10 text-foreground"
>
Edit text
</Dialog.Title>
</div>
</div>
<div className="flex h-full w-full flex-row items-center justify-center gap-4 bg-input p-4">
<div className="flex h-full w-full">
<div className="w-full overflow-hidden rounded-lg bg-background px-4 py-5 shadow sm:p-6">
<textarea
ref={ref}
className="form-input h-full w-full form-primary"
value={myValue}
onChange={(e) => {
setMyValue(e.target.value);
setValue(e.target.value);
}}
/>
</div>
</div>
</div>
<div className="flex w-full flex-row-reverse bg-input px-4 pb-3">
<button
type="button"
className="inline-flex w-full justify-center rounded-md border border-transparent px-4 py-2 text-base font-medium text-background shadow-sm hover:bg-ring focus:outline-none focus:ring-1 focus:ring-ring focus:ring-offset-1 sm:ml-3 sm:w-auto sm:text-sm"
onClick={() => {
setModalOpen(false);
}}
>
Finish editing
</button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
}

View file

@ -135,7 +135,7 @@ export default function ExtraSidebar() {
</div> </div>
</div> </div>
<div className="w-full overflow-auto scrollbar-hide pb-10"> <div className="w-full overflow-auto pb-10 scrollbar-hide">
{Object.keys(dataFilter) {Object.keys(dataFilter)
.sort() .sort()
.map((d: keyof APIObjectType, i) => .map((d: keyof APIObjectType, i) =>

View file

@ -37,7 +37,7 @@ export type sendAllProps = {
viewport: Viewport; viewport: Viewport;
inputs: any; inputs: any;
chatHistory: { message: string; isSend: boolean }[]; chatHistory: { message: string | object; isSend: boolean }[];
}; };
export type errorsTypeAPI = { export type errorsTypeAPI = {
function: { errors: Array<string> }; function: { errors: Array<string> };

View file

@ -3,8 +3,10 @@ import { FlowType } from "../flow";
export type ChatType = { flow: FlowType; reactFlowInstance: ReactFlowInstance }; export type ChatType = { flow: FlowType; reactFlowInstance: ReactFlowInstance };
export type ChatMessageType = { export type ChatMessageType = {
message: string; message: string | Object;
template?: string;
isSend: boolean; isSend: boolean;
thought?: string; thought?: string;
files?: Array<{ data: string; type: string; data_type: string }>; files?: Array<{ data: string; type: string; data_type: string }>;
chatKey: string;
}; };

View file

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

View file

@ -39,6 +39,11 @@ export type TabsContextType = {
export type TabsState = { export type TabsState = {
[key: string]: { [key: string]: {
isPending: boolean; isPending: boolean;
formKeysData: {input_keys?: Object, memory_keys?: Array<string>, handle_keys?: Array<string>}; formKeysData: {
template?: string;
input_keys?: Object;
memory_keys?: Array<string>;
handle_keys?: Array<string>;
};
}; };
}; };

View file

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