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:
raise ValueError(str(exc)) from exc
# if len(input_variables) > 1:
# # If there's more than one input variable
return input_variables
def check_input_variables(input_variables: list):
invalid_chars = []
fixed_variables = []
wrong_variables = []
empty_variables = []
for variable in input_variables:
new_var = variable
for char in INVALID_CHARACTERS:
if char in variable:
invalid_chars.append(char)
new_var = new_var.replace(char, "")
# if variable is empty, then we should add that to the wrong variables
if not variable:
empty_variables.append(variable)
continue
# if variable starts with a number we should add that to the invalid chars
# and wrong variables
if variable[0].isdigit():
invalid_chars.append(variable[0])
new_var = new_var.replace(variable[0], "")
wrong_variables.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)
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
# there are invalid characters in the input_variables
if any(var not in fixed_variables for var in input_variables):
raise ValueError(
f"Invalid input variables: {input_variables}. Please, use something like {fixed_variables} instead."
)
if any(var not in fixed_variables for var in input_variables):
error_message = build_error_message(
input_variables,
invalid_chars,
wrong_variables,
fixed_variables,
empty_variables,
)
raise ValueError(error_message)
return input_variables
def build_error_message(
input_variables, invalid_chars, wrong_variables, fixed_variables, empty_variables
):
input_variables_str = ", ".join([f"'{var}'" for var in input_variables])
error_string = f"Invalid input variables: {input_variables_str}. "
if wrong_variables and invalid_chars:
# 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
from typing import Any
from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler
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
class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
"""Callback handler for streaming LLM responses."""
def __init__(self, websocket):
def __init__(self, websocket: WebSocket):
self.websocket = websocket
async def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
resp = ChatResponse(message=token, type="stream", intermediate_steps="")
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):
"""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(
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)
# 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,
multiline=True,
input_types=["Document", "BaseOutputParser"],
value="", # Set the value to empty string
)
if variable in prompt_request.frontend_node.template:
# Set the new field with the old value
template_field.value = prompt_request.frontend_node.template[variable][
"value"
]
prompt_request.frontend_node.template[variable] = template_field.to_dict()
# Check if variable is not already in the list before appending

View file

@ -23,7 +23,7 @@ async def process_graph(
try:
logger.debug("Generating result and thought")
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")
return result, intermediate_steps

View file

@ -164,8 +164,6 @@ memories:
prompts:
PromptTemplate:
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:
CharacterTextSplitter:
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
CUSTOM_NODES = {
"prompts": {
"ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode(),
},
# "prompts": {
# "ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode(),
# },
"tools": {
"PythonFunctionTool": frontend_node.tools.PythonFunctionToolNode(),
"PythonFunction": frontend_node.tools.PythonFunctionNode(),

View file

@ -183,6 +183,8 @@ class Vertex:
# and return the instance
try:
if self.base_type is None:
raise ValueError(f"Base type for node {self.vertex_type} not found")
result = loading.instantiate_class(
node_type=self.vertex_type,
base_type=self.base_type,
@ -224,4 +226,5 @@ class Vertex:
return id(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()
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):
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.base import LangChainTypeCreator
from langflow.settings import settings
from langflow.template.frontend_node.agents import AgentFrontendNode
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):
type_name: str = "agents"
from_method_nodes = {"ZeroShotAgent": "from_llm_and_tools"}
@property
def frontend_node_class(self) -> type[AgentFrontendNode]:
return AgentFrontendNode
@property
def type_to_loader_dict(self) -> Dict:
if self.type_dict is None:
@ -27,6 +34,13 @@ class AgentCreator(LangChainTypeCreator):
try:
if name in get_custom_nodes(self.type_name).keys():
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(
name, self.type_to_loader_dict, add_function=True
)

View file

@ -19,6 +19,8 @@ from langflow.interface.importing.utils import (
import_by_type,
)
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.chains.base import chain_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):
if base_type == "agents":
return instantiate_agent(class_object, params)
return instantiate_agent(node_type, class_object, params)
elif base_type == "prompts":
return instantiate_prompt(node_type, class_object, params)
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):
# 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:
if "retriever" in params and hasattr(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)
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)

View file

@ -90,7 +90,7 @@ class ToolCreator(LangChainTypeCreator):
def get_signature(self, name: str) -> Optional[Dict]:
"""Get the signature of a tool."""
base_classes = ["Tool"]
base_classes = ["Tool", "BaseTool"]
fields = []
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):
name: str = "SQLAgent"
template: Template = Template(

View file

@ -56,7 +56,7 @@ class ToolNode(FrontendNode):
],
)
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):
return super().to_dict()

View file

@ -243,7 +243,11 @@ def format_dict(d, name: Optional[str] = None):
# Check for list 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
else:
value["list"] = False

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,7 +8,6 @@ import { useSSE } from "../../../contexts/SSEContext";
import { typesContext } from "../../../contexts/typesContext";
import { alertContext } from "../../../contexts/alertContext";
import { postBuildInit } from "../../../controllers/API";
import ShadTooltip from "../../ShadTooltipComponent";
import RadialProgressComponent from "../../RadialProgress";
import { TabsContext } from "../../../contexts/tabsContext";
@ -96,10 +95,9 @@ export default function BuildTrigger({
[flowId]: {
...old[flowId],
formKeysData: parsedData,
}
},
};
})
});
} else {
// Otherwise, process the data
const isValid = processStreamResult(parsedData);
@ -168,7 +166,7 @@ export default function BuildTrigger({
leaveFrom="translate-y-0"
leaveTo="translate-y-96"
>
<div className="fixed right-4 bottom-20">
<div className="fixed bottom-20 right-4">
<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`}
onClick={() => {
@ -189,7 +187,10 @@ export default function BuildTrigger({
) : isBuilding ? (
<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>
</button>

View file

@ -3,18 +3,30 @@ import { MessagesSquare } from "lucide-react";
import { alertContext } from "../../../contexts/alertContext";
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);
function handleClick() {
if (isBuilt) {
setOpen(true);
if (canOpen) {
setOpen(true);
} else {
setErrorData({
title: CHAT_CANNOT_OPEN_TITLE,
list: [CHAT_CANNOT_OPEN_DESCRIPTION],
});
}
} else {
setErrorData({
title: "Flow not built",
list: ["Please build the flow before chatting"],
title: FLOW_NOT_BUILT_TITLE,
list: [FLOW_NOT_BUILT_DESCRIPTION],
});
}
}
@ -30,15 +42,26 @@ export default function ChatTrigger({ open, setOpen, isBuilt }) {
leaveFrom="translate-y-0"
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")}>
<div className="flex gap-3">
<MessagesSquare
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")}
style={{ color: "white" }}
strokeWidth={1.5}
/>
</div>
</button>
<button
onClick={handleClick}
className={
"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 " +
(!isBuilt || !canOpen ? "cursor-not-allowed" : "cursor-pointer")
}
>
<div className="flex gap-3">
<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>
);
}

View file

@ -13,6 +13,7 @@ import * as _ from "lodash";
export default function Chat({ flow }: ChatType) {
const [open, setOpen] = useState(false);
const [isBuilt, setIsBuilt] = useState(false);
const [canOpen, setCanOpen] = useState(false);
const { tabsState } = useContext(TabsContext);
useEffect(() => {
@ -58,6 +59,17 @@ export default function Chat({ flow }: ChatType) {
) {
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;
}, [tabsState]);
@ -71,10 +83,15 @@ export default function Chat({ flow }: ChatType) {
setIsBuilt={setIsBuilt}
isBuilt={isBuilt}
/>
{isBuilt && (
{isBuilt && canOpen && (
<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>
</>
);

View file

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

View file

@ -39,8 +39,8 @@ export default function Dropdown({
<Listbox.Button
className={
editNode
? "border-1 relative pr-8 input-edit-node"
: "py-2 pl-3 pr-10 text-left input-primary"
? "border-1 input-edit-node relative pr-8"
: "input-primary py-2 pl-3 pr-10 text-left"
}
>
<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"
target="_blank"
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" />
Star
@ -114,7 +114,7 @@ export default function Header() {
)}
</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>) => {
setNotificationCenter(false);
const { top, left } = (

View file

@ -110,7 +110,10 @@ export default function InputFileComponent({
</span>
<button onClick={handleButtonClick}>
{!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 && (
<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]);
return (
<div
className={
disabled ? "pointer-events-none w-full cursor-not-allowed" : " w-full"
}
>
<div className="flex w-full items-center">
<span
onClick={() => {
openPopUp(
<GenericModal
type={TypeModal.PROMPT}
value={myValue}
buttonText="Check & Save"
modalTitle="Edit Prompt"
setValue={(t: string) => {
setMyValue(t);
onChange(t);
}}
nodeClass={nodeClass}
setNodeClass={setNodeClass}
<div className={disabled ? "cursor-not-allowed" : ""}>
<div className={disabled ? "pointer-events-none w-full " : " w-full"}>
<div className="flex w-full items-center">
<span
onClick={() => {
openPopUp(
<GenericModal
type={TypeModal.PROMPT}
value={myValue}
buttonText="Check & Save"
modalTitle="Edit Prompt"
setValue={(t: string) => {
setMyValue(t);
onChange(t);
}}
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")
}
/>
);
}}
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 hover:text-accent-foreground"
/>
)}
</button>
)}
</button>
</div>
</div>
</div>
);

View file

@ -27,12 +27,11 @@ export default function TextAreaComponent({
}, [closePopUp]);
return (
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
<div className={disabled ? "cursor-not-allowed" : ""}>
<div
className={
editNode
? "w-full items-center"
: "w-full flex items-center"
(editNode ? "w-full items-center" : "flex w-full items-center") +
(disabled ? " pointer-events-none" : "")
}
>
<span
@ -52,12 +51,14 @@ export default function TextAreaComponent({
}}
className={
editNode
? "input-edit-node input-dialog "
: "input-primary input-dialog " + (disabled ? "input-disable" : "")
? "input-edit-node input-dialog "
: "input-dialog input-primary " +
(disabled ? "input-disable" : "")
}
>
{myValue !== "" ? myValue : "Type something..."}
</span>
<button
onClick={() => {
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>
</div>
</div>

View file

@ -27,7 +27,7 @@ const AccordionTrigger = React.forwardRef<
<AccordionPrimitive.Trigger
ref={ref}
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
)}
{...props}

View file

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

View file

@ -19,7 +19,7 @@ const Switch = React.forwardRef<
<SwitchPrimitives.Thumb
aria-disabled={props.disabled}
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>

View file

@ -50,6 +50,15 @@ export const CODE_PROMPT_DIALOG_SUBTITLE =
export const PROMPT_DIALOG_SUBTITLE =
"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
* @constant
@ -180,7 +189,7 @@ export const EXPORT_CODE_DIALOG =
export const COLUMN_DIV_STYLE =
" 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 ";
/**
@ -210,7 +219,8 @@ export const DESCRIPTIONS: string[] = [
"Conversational Cartography Unlocked.",
"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
@ -513,3 +523,10 @@ export const NOUNS: string[] = [
*
*/
export const USER_PROJECTS_HEADER = "My Collection";
/**
* CSS for highlight HTML
* @constant
*
*/
export const HIGHLIGH_CSS =
"block pl-3 pr-14 py-2 w-full h-full text-sm outline-0 border-0 break-all";

View file

@ -285,7 +285,11 @@ export function TabsProvider({ children }: { children: ReactNode }) {
// create a link element and set its properties
const link = document.createElement("a");
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
link.click();

View file

@ -104,11 +104,10 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
useEffect(() => {
if (closeEdit !== "") {
tweak.current = getTweak;
if(tweak.current.length > 0){
if (tweak.current.length > 0) {
setActiveTab("3");
openAccordions();
}
else{
} else {
startTweaks();
}
} else {
@ -250,25 +249,25 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
function openAccordions() {
let accordionsToOpen = [];
tweak.current.forEach((el) => {
Object.keys(el).forEach((key) => {
if (Object.keys(el[key]).length > 0) {
accordionsToOpen.push(key);
setOpenAccordion(accordionsToOpen);
}
});
tweak.current.forEach((el) => {
Object.keys(el).forEach((key) => {
if (Object.keys(el[key]).length > 0) {
accordionsToOpen.push(key);
setOpenAccordion(accordionsToOpen);
}
});
});
}
return (
<Dialog open={true} onOpenChange={setModalOpen}>
<DialogTrigger></DialogTrigger>
<DialogContent className="lg:max-w-[80vw] h-[80vh]">
<DialogContent className="h-[80vh] lg:max-w-[80vw]">
<DialogHeader>
<DialogTitle className="flex items-center">
<span className="pr-2">Code</span>
<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"
/>
</DialogTitle>
@ -277,7 +276,7 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
<Tabs
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) => {
setActiveTab(value);
if (value === "3") {
@ -296,7 +295,7 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
{Number(activeTab) < 3 && (
<div className="float-right">
<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}
>
{isCopied ? <Check size={18} /> : <Clipboard size={15} />}
@ -309,12 +308,12 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
{tabs.map((tab, index) => (
<TabsContent
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
>
{index < 3 ? (
<SyntaxHighlighter
className="w-full overflow-auto h-[60vh]"
className="h-[60vh] w-full overflow-auto"
language={tab.mode}
style={oneDark}
>
@ -322,10 +321,10 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
</SyntaxHighlighter>
) : index === 3 ? (
<>
<div className="flex w-full h-full mt-2">
<div className="mt-2 flex h-full w-full">
<div
className={classNames(
"w-full rounded-lg bg-muted h-[60vh]",
"h-[60vh] w-full rounded-lg bg-muted",
1 == 1
? "overflow-scroll overflow-x-hidden custom-scroll"
: "overflow-hidden"
@ -338,14 +337,14 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
trigger={t["data"]["id"]}
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">
<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">
<TableHead className="h-7 text-center">
PARAM
</TableHead>
<TableHead className="p-0 h-7 text-center">
<TableHead className="h-7 p-0 text-center">
VALUE
</TableHead>
</TableRow>
@ -379,11 +378,11 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
key={i}
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}
</TableCell>
<TableCell className="p-0 text-center text-gray-900 text-xs dark:text-gray-300">
<div className="w-[250px] m-auto">
<TableCell className="p-0 text-center text-xs text-gray-900 dark:text-gray-300">
<div className="m-auto w-[250px]">
{t.data.node.template[n]
.type === "str" &&
!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-twilight";
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 { darkContext } from "../../contexts/darkContext";
import { postCustomComponent, postValidateCode } from "../../controllers/API";
@ -29,14 +29,13 @@ import {
TabsList,
TabsTrigger,
} from "../../components/ui/tabs";
import axios from "axios";
export default function CodeAreaModal({
value,
setValue,
nodeClass,
setNodeClass,
dynamic
dynamic,
}: {
setValue: (value: string) => void;
value: string;
@ -50,7 +49,9 @@ export default function CodeAreaModal({
const { dark } = useContext(darkContext);
const { setErrorData, setSuccessData } = useContext(alertContext);
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 ref = useRef();
function setModalOpen(x: boolean) {
@ -64,9 +65,9 @@ export default function CodeAreaModal({
}
console.log(dynamic);
useEffect(()=>{
useEffect(() => {
setValue(code);
},[code,setValue])
}, [code, setValue]);
function handleClick() {
setLoading(true);
@ -108,39 +109,40 @@ export default function CodeAreaModal({
title: "There is something wrong with this code, please review it",
});
});
}
else {
postCustomComponent(code, nodeClass).then((apiReturn) => {
const { data } = apiReturn;
if (data) {
setNodeClass(data);
setModalOpen(false);
}
}).catch((err) => {
setErrorData({
title: "There is something wrong with this code, please see the error on the errors tab",
} else {
postCustomComponent(code, nodeClass)
.then((apiReturn) => {
const { data } = apiReturn;
if (data) {
setNodeClass(data);
setModalOpen(false);
}
})
.catch((err) => {
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) => {
// })
}
const tabs = [{ name: "code" }, { name: "errors" }]
const tabs = [{ name: "code" }, { name: "errors" }];
return (
<Dialog open={true} onOpenChange={setModalOpen}>
<DialogTrigger></DialogTrigger>
<DialogContent className="h-[500px] lg:max-w-[700px]">
<DialogContent className="min-w-[80vw]">
<DialogHeader>
<DialogTitle className="flex items-center">
<span className="pr-2">Edit Code</span>
<TerminalSquare
strokeWidth={1.5}
className="h-6 w-6 text-primary pl-1 "
strokeWidth={1.5}
className="h-6 w-6 pl-1 text-primary "
aria-hidden="true"
/>
</DialogTitle>
@ -148,44 +150,65 @@ export default function CodeAreaModal({
</DialogHeader>
<Tabs
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)}
>
<div className="flex flex-col items-start h-72 px-2">
<div className="flex h-72 flex-col items-start px-2">
<TabsList>
{tabs.map((tab, index) => (
<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>
<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>
{tabs.map((tab, index) => (
<TabsContent
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">
<AceEditor
value={code}
mode="python"
highlightActiveLine={true}
showPrintMargin={false}
fontSize={14}
showGutter
enableLiveAutocompletion
theme={dark ? "twilight" : "github"}
name="CodeEditor"
onChange={(value) => {
setCode(value);
}}
className="w-full rounded-lg h-full custom-scroll border-[1px] border-gray-300 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>
<span className="border border-red-300 w-full"></span>
<div className="text-red-500 text-sm">{error?.detail?.traceback}</div>
</div>}
</TabsContent>))
}
{tab.name === "code" ? (
<div className="h-full w-full">
<AceEditor
value={code}
mode="python"
highlightActiveLine={true}
showPrintMargin={false}
fontSize={14}
showGutter
enableLiveAutocompletion
theme={dark ? "twilight" : "github"}
name="CodeEditor"
onChange={(value) => {
setCode(value);
}}
className="h-full w-full rounded-lg border-[1px] border-gray-300 custom-scroll dark:border-gray-600"
/>
</div>
) : (
<div className="flex h-full w-full flex-col overflow-scroll bg-red-200 p-2 text-left custom-scroll">
<h1 className="text-lg text-red-600">
{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>
</Tabs>
<DialogFooter>

View file

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

View file

@ -52,20 +52,27 @@ export default function ChatInput({
lockChat
? " 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",
"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..."}
/>
<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 ? (
<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"
/>
) : (
<LucideSend
className="h-5 w-5 mr-2 rotate-[44deg] "
className="mr-2 h-5 w-5 rotate-[44deg] "
aria-hidden="true"
/>
)}

View file

@ -11,6 +11,13 @@ import remarkMath from "remark-math";
import { CodeBlock } from "./codeBlock";
import Convert from "ansi-to-html";
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({
chat,
@ -22,145 +29,173 @@ export default function ChatMessage({
lastMessage: boolean;
}) {
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 [template, setTemplate] = useState(chat.template);
return (
<div
className={classNames(
"w-full py-2 px-2 pl-4 pr-9 flex",
chat.isSend
? " bg-border"
: " "
)}
>
<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>
<div
className={classNames(
"flex w-full px-2 py-6 pl-4 pr-9",
chat.isSend ? " bg-border" : " "
)}
>
<div className={classNames("mb-3 ml-3 mr-6 mt-1 ")}>
{!chat.isSend ? (
<div className="w-full text-start flex items-center">
<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 className="flex h-8 w-8 items-center justify-center overflow-hidden rounded-md bg-[#afe6ef] p-5 text-2xl ">
🤖
</div>
) : (
<div className="w-full flex items-center">
<div className="text-start inline-block">
<span
className="text-primary"
dangerouslySetInnerHTML={{
__html: message.replace(/\n/g, "<br>"),
}}
></span>
</div>
<div className="flex h-8 w-8 items-center justify-center overflow-hidden rounded-md bg-[#aface9] p-5 text-2xl ">
👨💻
</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;
const addChatHistory = (
message: string,
message: string | Object,
isSend: boolean,
chatKey: string,
template?: string,
thought?: string,
files?: Array<any>
) => {
setChatHistory((old) => {
let newChat = _.cloneDeep(old);
if (files) {
newChat.push({ message, isSend, files, thought });
newChat.push({ message, isSend, files, thought, chatKey });
} else if (thought) {
newChat.push({ message, isSend, thought });
newChat.push({ message, isSend, thought, chatKey });
} else if (template) {
newChat.push({ message, isSend, chatKey, template });
} else {
newChat.push({ message, isSend });
newChat.push({ message, isSend, chatKey });
}
return newChat;
});
@ -174,7 +178,9 @@ export default function FormModal({
intermediate_steps?: string;
is_bot: boolean;
message: string;
template: string;
type: string;
chatKey: string;
files?: Array<any>;
}) => {
if (chatItem.message) {
@ -182,14 +188,18 @@ export default function FormModal({
chatItem.files
? {
isSend: !chatItem.is_bot,
message: formatMessage(chatItem.message),
message: chatItem.message,
template: chatItem.template,
thought: chatItem.intermediate_steps,
files: chatItem.files,
chatKey: chatItem.chatKey,
}
: {
isSend: !chatItem.is_bot,
message: formatMessage(chatItem.message),
message: chatItem.message,
template: chatItem.template,
thought: chatItem.intermediate_steps,
chatKey: chatItem.chatKey,
}
);
}
@ -199,7 +209,7 @@ export default function FormModal({
});
}
if (data.type === "start") {
addChatHistory("", false);
addChatHistory("", false, chatKey);
isStream = true;
}
if (data.type === "end") {
@ -319,23 +329,6 @@ export default function FormModal({
ref.current.focus();
}
}, [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() {
if (chatValue !== "") {
@ -344,8 +337,13 @@ export default function FormModal({
setLockChat(true);
let inputs = tabsState[id.current].formKeysData.input_keys;
setChatValue("");
const message = formatMessage(inputs);
addChatHistory(message, true);
const message = inputs;
addChatHistory(
message,
true,
chatKey,
tabsState[flow.id].formKeysData.template
);
sendAll({
...reactFlowInstance.toObject(),
inputs: inputs,
@ -404,7 +402,7 @@ export default function FormModal({
</DialogHeader>
<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">
<Variable className=" -ml-px mr-1 h-4 w-4 text-primary"></Variable>
<span className="text-md font-semibold text-primary">
@ -496,7 +494,7 @@ export default function FormModal({
))}
</Accordion>
</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="absolute right-3 top-3 z-50">
<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 { darkContext } from "../../contexts/darkContext";
import { postValidatePrompt } from "../../controllers/API";
@ -14,9 +14,24 @@ import {
} from "../../components/ui/dialog";
import { Button } from "../../components/ui/button";
import { Textarea } from "../../components/ui/textarea";
import { PROMPT_DIALOG_SUBTITLE, TEXT_DIALOG_SUBTITLE } from "../../constants";
import {
HIGHLIGH_CSS,
PROMPT_DIALOG_SUBTITLE,
TEXT_DIALOG_SUBTITLE,
} from "../../constants";
import { FileText } from "lucide-react";
import { APIClassType } from "../../types/api";
import {
INVALID_CHARACTERS,
TypeModal,
classNames,
getRandomKeyByssmm,
regexHighlight,
varHighlightHTML,
} from "../../utils";
import { Badge } from "../../components/ui/badge";
import ShadTooltip from "../../components/ShadTooltipComponent";
import DOMPurify from "dompurify";
export default function GenericModal({
field_name = "",
@ -40,24 +55,122 @@ export default function GenericModal({
const [myButtonText] = useState(buttonText);
const [myModalTitle] = useState(modalTitle);
const [myModalType] = useState(type);
const [open, setOpen] = useState(true);
const [myValue, setMyValue] = useState(value);
const [inputValue, setInputValue] = useState(value);
const [isEdit, setIsEdit] = useState(true);
const [wordsHighlightInvalid, setWordsHighlightInvalid] = useState([]);
const [wordsHighlight, setWordsHighlight] = useState([]);
const { dark } = useContext(darkContext);
const { setErrorData, setSuccessData } = useContext(alertContext);
const { setErrorData, setSuccessData, setNoticeData } =
useContext(alertContext);
const { closePopUp, setCloseEdit } = useContext(PopUpContext);
const ref = useRef();
function setModalOpen(x: boolean) {
setOpen(x);
if (x === false) {
setCloseEdit("generic");
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 (
<Dialog open={true} onOpenChange={setModalOpen}>
<DialogTrigger></DialogTrigger>
<DialogContent className="lg:max-w-[700px]">
<DialogContent className="min-w-[80vw]">
<DialogHeader>
<DialogTitle className="flex items-center">
<span className="pr-2">{myModalTitle}</span>
@ -83,17 +196,76 @@ export default function GenericModal({
</DialogDescription>
</DialogHeader>
<div className="mt-2 flex h-full w-full">
<Textarea
ref={ref}
className=" form-primary h-[300px] w-full "
value={myValue}
onChange={(e) => {
setMyValue(e.target.value);
setValue(e.target.value);
}}
placeholder="Type message here."
/>
{type == TypeModal.PROMPT &&
inputValue &&
inputValue != "" &&
wordsHighlight.length > 0 && (
<>
<div>
<span className="">Variables: </span>
{wordsHighlight.map((word, index) => (
<ShadTooltip
key={getRandomKeyByssmm() + index}
content={word.replace(/[{}]/g, "")}
asChild={false}
delayDuration={1500}
>
<Badge
key={index}
size="lg"
className="m-1 max-w-[40vw] cursor-default truncate p-2.5 text-sm"
>
<div className="relative bottom-[1px]">
<span>
{word.replace(/[{}]/g, "").length > 59
? word.replace(/[{}]/g, "").slice(0, 56) + "..."
: word.replace(/[{}]/g, "")}
</span>
</div>
</Badge>
</ShadTooltip>
))}
</div>
</>
)}
<div
className={classNames(
!isEdit ? "rounded-lg border" : "",
"flex h-[60vh] w-full"
)}
>
{type == TypeModal.PROMPT && isEdit ? (
<Textarea
ref={ref}
className="form-input h-full w-full rounded-lg border-gray-300 focus-visible:ring-1 dark:border-gray-700 dark:bg-gray-900 dark:text-white"
value={inputValue}
onBlur={() => {
blur();
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>
<DialogFooter>
@ -105,39 +277,7 @@ export default function GenericModal({
setModalOpen(false);
break;
case 2:
console.log("postValidatePrompt");
postValidatePrompt(field_name, myValue, nodeClass)
.then((apiReturn) => {
if (apiReturn.data) {
setNodeClass(apiReturn.data.frontend_node);
setModalOpen(false);
let inputVariables = apiReturn.data.input_variables;
if (inputVariables.length === 0) {
setErrorData({
title:
"The template you are attempting to use does not contain any variables for data entry.",
});
} else {
setSuccessData({
title: "Prompt is ready",
});
setModalOpen(false);
setValue(myValue);
}
} else {
setErrorData({
title: "Something went wrong, please try again",
});
}
})
.catch((error) => {
return setErrorData({
title:
"There is something wrong with this prompt, please review it",
list: [error.response.data.detail],
});
});
validatePrompt(false);
break;
default:

View file

@ -160,7 +160,10 @@ export default function ImportModal() {
bgColor="bg-medium-emerald "
description={example.description ?? "Prebuilt Examples"}
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={() => {
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 className="w-full overflow-auto scrollbar-hide pb-10">
<div className="w-full overflow-auto pb-10 scrollbar-hide">
{Object.keys(dataFilter)
.sort()
.map((d: keyof APIObjectType, i) =>

View file

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

View file

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

View file

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

View file

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