merge fix
This commit is contained in:
commit
cf278a4c74
29 changed files with 348 additions and 823 deletions
|
|
@ -126,7 +126,7 @@ async def stream_build(flow_id: str):
|
||||||
params = vertex._built_object_repr()
|
params = vertex._built_object_repr()
|
||||||
valid = True
|
valid = True
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Building node {params[:50]}{'...' if len(params) > 50 else ''}"
|
f"Building node {str(params)[:50]}{'...' if len(str(params)) > 50 else ''}"
|
||||||
)
|
)
|
||||||
if vertex.artifacts:
|
if vertex.artifacts:
|
||||||
# The artifacts will be prompt variables
|
# The artifacts will be prompt variables
|
||||||
|
|
|
||||||
|
|
@ -213,23 +213,27 @@ class PromptVertex(Vertex):
|
||||||
|
|
||||||
def _built_object_repr(self):
|
def _built_object_repr(self):
|
||||||
if (
|
if (
|
||||||
self.artifacts
|
not self.artifacts
|
||||||
and self._built_object is not None
|
or self._built_object is None
|
||||||
and hasattr(self._built_object, "format")
|
or not 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
|
|
||||||
artifacts = self.artifacts.copy()
|
|
||||||
# Remove the handle_keys from the artifacts
|
|
||||||
# so the prompt format doesn't break
|
|
||||||
artifacts.pop("handle_keys", None)
|
|
||||||
try:
|
|
||||||
return self._built_object.format(**artifacts)
|
|
||||||
except KeyError:
|
|
||||||
return super()._built_object_repr()
|
|
||||||
else:
|
|
||||||
return super()._built_object_repr()
|
return super()._built_object_repr()
|
||||||
|
# We'll build the prompt with the artifacts
|
||||||
|
# to show the user what the prompt looks like
|
||||||
|
# with the variables filled in
|
||||||
|
artifacts = self.artifacts.copy()
|
||||||
|
# Remove the handle_keys from the artifacts
|
||||||
|
# so the prompt format doesn't break
|
||||||
|
artifacts.pop("handle_keys", None)
|
||||||
|
try:
|
||||||
|
template = self._built_object.format(**artifacts)
|
||||||
|
return (
|
||||||
|
template
|
||||||
|
if isinstance(template, str)
|
||||||
|
else f"{self.vertex_type}({template})"
|
||||||
|
)
|
||||||
|
except KeyError:
|
||||||
|
return str(self._built_object)
|
||||||
|
|
||||||
|
|
||||||
class OutputParserVertex(Vertex):
|
class OutputParserVertex(Vertex):
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import contextlib
|
||||||
import json
|
import json
|
||||||
from typing import Any, Callable, Dict, List, Sequence, Type
|
from typing import Any, Callable, Dict, List, Sequence, Type
|
||||||
|
|
||||||
|
|
@ -67,7 +68,11 @@ def instantiate_based_on_type(class_object, base_type, node_type, 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":
|
||||||
return instantiate_tool(node_type, class_object, params)
|
tool = instantiate_tool(node_type, class_object, params)
|
||||||
|
if hasattr(tool, "name") and isinstance(tool, BaseTool):
|
||||||
|
# tool name shouldn't contain spaces
|
||||||
|
tool.name = tool.name.replace(" ", "_")
|
||||||
|
return tool
|
||||||
elif base_type == "toolkits":
|
elif base_type == "toolkits":
|
||||||
return instantiate_toolkit(node_type, class_object, params)
|
return instantiate_toolkit(node_type, class_object, params)
|
||||||
elif base_type == "embeddings":
|
elif base_type == "embeddings":
|
||||||
|
|
@ -132,6 +137,9 @@ 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
|
# process input_key and output_key to remove them if
|
||||||
# they are empty strings
|
# they are empty strings
|
||||||
|
if node_type == "ConversationEntityMemory":
|
||||||
|
params.pop("memory_key", None)
|
||||||
|
|
||||||
for key in ["input_key", "output_key"]:
|
for key in ["input_key", "output_key"]:
|
||||||
if key in params and (params[key] == "" or not params[key]):
|
if key in params and (params[key] == "" or not params[key]):
|
||||||
params.pop(key)
|
params.pop(key)
|
||||||
|
|
@ -196,7 +204,7 @@ def instantiate_prompt(node_type, class_object, params: Dict):
|
||||||
if "tools" not in params:
|
if "tools" not in params:
|
||||||
params["tools"] = []
|
params["tools"] = []
|
||||||
return ZeroShotAgent.create_prompt(**params)
|
return ZeroShotAgent.create_prompt(**params)
|
||||||
if "MessagePromptTemplate" in node_type:
|
elif "MessagePromptTemplate" in node_type:
|
||||||
# Then we only need the template
|
# Then we only need the template
|
||||||
from_template_params = {
|
from_template_params = {
|
||||||
"template": params.pop("prompt", params.pop("template", ""))
|
"template": params.pop("prompt", params.pop("template", ""))
|
||||||
|
|
@ -204,12 +212,12 @@ def instantiate_prompt(node_type, class_object, params: Dict):
|
||||||
|
|
||||||
if not from_template_params.get("template"):
|
if not from_template_params.get("template"):
|
||||||
raise ValueError("Prompt template is required")
|
raise ValueError("Prompt template is required")
|
||||||
return class_object.from_template(**from_template_params)
|
prompt = class_object.from_template(**from_template_params)
|
||||||
|
|
||||||
if node_type == "ChatPromptTemplate":
|
elif node_type == "ChatPromptTemplate":
|
||||||
return class_object.from_messages(**params)
|
prompt = class_object.from_messages(**params)
|
||||||
|
else:
|
||||||
prompt = class_object(**params)
|
prompt = class_object(**params)
|
||||||
|
|
||||||
format_kwargs: Dict[str, Any] = {}
|
format_kwargs: Dict[str, Any] = {}
|
||||||
for input_variable in prompt.input_variables:
|
for input_variable in prompt.input_variables:
|
||||||
|
|
@ -221,18 +229,23 @@ def instantiate_prompt(node_type, class_object, params: Dict):
|
||||||
variable, "get_format_instructions"
|
variable, "get_format_instructions"
|
||||||
):
|
):
|
||||||
format_kwargs[input_variable] = variable.get_format_instructions()
|
format_kwargs[input_variable] = variable.get_format_instructions()
|
||||||
# check if is a list of Document
|
|
||||||
elif isinstance(variable, List) and all(
|
elif isinstance(variable, List) and all(
|
||||||
isinstance(item, Document) for item in variable
|
isinstance(item, Document) for item in variable
|
||||||
):
|
):
|
||||||
# Format document to contain page_content and metadata
|
# Format document to contain page_content and metadata
|
||||||
# as one string separated by a newline
|
# as one string separated by a newline
|
||||||
format_kwargs[input_variable] = "\n".join(
|
if len(variable) > 1:
|
||||||
[
|
content = "\n".join(
|
||||||
f"Document:{item.page_content}\nMetadata:{item.metadata}"
|
[item.page_content for item in variable if item.page_content]
|
||||||
for item in variable
|
)
|
||||||
]
|
else:
|
||||||
)
|
content = variable[0].page_content
|
||||||
|
# content could be a json list of strings
|
||||||
|
with contextlib.suppress(json.JSONDecodeError):
|
||||||
|
content = json.loads(content)
|
||||||
|
if isinstance(content, list):
|
||||||
|
content = ",".join([str(item) for item in content])
|
||||||
|
format_kwargs[input_variable] = content
|
||||||
# handle_keys will be a list but it does not exist yet
|
# handle_keys will be a list but it does not exist yet
|
||||||
# so we need to create it
|
# so we need to create it
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,11 @@ async def get_result_and_steps(langchain_object, inputs: Union[dict, str], **kwa
|
||||||
# Deactivating until we have a frontend solution
|
# Deactivating until we have a frontend solution
|
||||||
# to display intermediate steps
|
# to display intermediate steps
|
||||||
langchain_object.return_intermediate_steps = True
|
langchain_object.return_intermediate_steps = True
|
||||||
|
try:
|
||||||
|
fix_memory_inputs(langchain_object)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(exc)
|
||||||
|
|
||||||
fix_memory_inputs(langchain_object)
|
|
||||||
try:
|
try:
|
||||||
async_callbacks = [AsyncStreamingLLMCallbackHandler(**kwargs)]
|
async_callbacks = [AsyncStreamingLLMCallbackHandler(**kwargs)]
|
||||||
output = await langchain_object.acall(inputs, callbacks=async_callbacks)
|
output = await langchain_object.acall(inputs, callbacks=async_callbacks)
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,10 @@ def fix_memory_inputs(langchain_object):
|
||||||
if not hasattr(langchain_object, "memory") or langchain_object.memory is None:
|
if not hasattr(langchain_object, "memory") or langchain_object.memory is None:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
if langchain_object.memory.memory_key in langchain_object.input_variables:
|
if (
|
||||||
|
hasattr(langchain_object.memory, "memory_key")
|
||||||
|
and langchain_object.memory.memory_key in langchain_object.input_variables
|
||||||
|
):
|
||||||
return
|
return
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
input_variables = (
|
input_variables = (
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ class ChainFrontendNode(FrontendNode):
|
||||||
field.advanced = False
|
field.advanced = False
|
||||||
if field.name == "verbose":
|
if field.name == "verbose":
|
||||||
field.required = False
|
field.required = False
|
||||||
field.show = True
|
field.show = False
|
||||||
field.advanced = True
|
field.advanced = True
|
||||||
if field.name == "llm":
|
if field.name == "llm":
|
||||||
field.required = True
|
field.required = True
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,9 @@ class MemoryFrontendNode(FrontendNode):
|
||||||
field.show = True
|
field.show = True
|
||||||
if field.name == "entity_store":
|
if field.name == "entity_store":
|
||||||
field.show = False
|
field.show = False
|
||||||
|
if name == "ConversationEntityMemory" and field.name == "memory_key":
|
||||||
|
field.show = False
|
||||||
|
field.required = False
|
||||||
|
|
||||||
|
|
||||||
class PostgresChatMessageHistoryFrontendNode(MemoryFrontendNode):
|
class PostgresChatMessageHistoryFrontendNode(MemoryFrontendNode):
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ class PythonFunctionToolNode(FrontendNode):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
description: str = "Python function to be executed."
|
description: str = "Python function to be executed."
|
||||||
base_classes: list[str] = ["Tool"]
|
base_classes: list[str] = ["BaseTool", "Tool"]
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return super().to_dict()
|
return super().to_dict()
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import { PopUpContext } from "../../../../contexts/popUpContext";
|
||||||
import { TabsContext } from "../../../../contexts/tabsContext";
|
import { TabsContext } from "../../../../contexts/tabsContext";
|
||||||
import { typesContext } from "../../../../contexts/typesContext";
|
import { typesContext } from "../../../../contexts/typesContext";
|
||||||
import { ParameterComponentType } from "../../../../types/components";
|
import { ParameterComponentType } from "../../../../types/components";
|
||||||
|
import { cleanEdges } from "../../../../util/reactflowUtils";
|
||||||
import {
|
import {
|
||||||
classNames,
|
classNames,
|
||||||
getRandomKeyByssmm,
|
getRandomKeyByssmm,
|
||||||
|
|
@ -25,6 +26,7 @@ import {
|
||||||
nodeIconsLucide,
|
nodeIconsLucide,
|
||||||
nodeNames,
|
nodeNames,
|
||||||
} from "../../../../utils";
|
} from "../../../../utils";
|
||||||
|
import { MAX_LENGTH_TO_SCROLL_TOOLTIP } from "../../../../constants";
|
||||||
|
|
||||||
export default function ParameterComponent({
|
export default function ParameterComponent({
|
||||||
left,
|
left,
|
||||||
|
|
@ -96,7 +98,8 @@ export default function ParameterComponent({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const groupedObj = groupByFamily(myData, tooltipTitle, left, data.type);
|
const groupedObj = groupByFamily(myData, tooltipTitle, left, data.type);
|
||||||
|
|
||||||
refNumberComponents.current = groupedObj[0]?.component?.length;
|
refNumberComponents.current = groupedObj[0]?.type?.length;
|
||||||
|
console.log(refNumberComponents);
|
||||||
|
|
||||||
refHtml.current = groupedObj.map((item, i) => {
|
refHtml.current = groupedObj.map((item, i) => {
|
||||||
const Icon: any = nodeIconsLucide[item.family];
|
const Icon: any = nodeIconsLucide[item.family];
|
||||||
|
|
@ -180,7 +183,7 @@ export default function ParameterComponent({
|
||||||
<></>
|
<></>
|
||||||
) : (
|
) : (
|
||||||
<ShadTooltip
|
<ShadTooltip
|
||||||
style={refNumberComponents.current > 10 ? "tooltip-fixed-width custom-scroll overflow-y-scroll nowheel" : "tooltip-fixed-width"}
|
styleClasses={refNumberComponents.current > MAX_LENGTH_TO_SCROLL_TOOLTIP ? "tooltip-fixed-width custom-scroll overflow-y-scroll nowheel" : "tooltip-fixed-width"}
|
||||||
delayDuration={0}
|
delayDuration={0}
|
||||||
content={refHtml.current}
|
content={refHtml.current}
|
||||||
side={left ? "left" : "right"}
|
side={left ? "left" : "right"}
|
||||||
|
|
@ -306,6 +309,13 @@ export default function ParameterComponent({
|
||||||
field_name={name}
|
field_name={name}
|
||||||
setNodeClass={(nodeClass) => {
|
setNodeClass={(nodeClass) => {
|
||||||
data.node = nodeClass;
|
data.node = nodeClass;
|
||||||
|
cleanEdges({
|
||||||
|
flow: {
|
||||||
|
edges: reactFlowInstance.getEdges(),
|
||||||
|
nodes: reactFlowInstance.getNodes(),
|
||||||
|
},
|
||||||
|
updateEdge: (edge) => reactFlowInstance.setEdges(edge),
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
nodeClass={data.node}
|
nodeClass={data.node}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export default function ShadTooltip({
|
||||||
side,
|
side,
|
||||||
asChild = true,
|
asChild = true,
|
||||||
children,
|
children,
|
||||||
style,
|
styleClasses,
|
||||||
delayDuration = 500,
|
delayDuration = 500,
|
||||||
}: ShadToolTipType) {
|
}: ShadToolTipType) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -14,7 +14,7 @@ export default function ShadTooltip({
|
||||||
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
|
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
|
||||||
|
|
||||||
<TooltipContent
|
<TooltipContent
|
||||||
className={style}
|
className={styleClasses}
|
||||||
side={side}
|
side={side}
|
||||||
avoidCollisions={false}
|
avoidCollisions={false}
|
||||||
sticky="always"
|
sticky="always"
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ export default function Chat({ flow }: ChatType) {
|
||||||
}
|
}
|
||||||
|
|
||||||
prevNodesRef.current = currentNodes;
|
prevNodesRef.current = currentNodes;
|
||||||
}, [tabsState, flow.id]);
|
}, [tabsState, flow.id, nodes]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,8 @@ export default function CodeAreaComponent({
|
||||||
className={
|
className={
|
||||||
editNode
|
editNode
|
||||||
? "input-edit-node input-dialog"
|
? "input-edit-node input-dialog"
|
||||||
: (disabled ? " input-disable " : "") +
|
: (disabled ? " input-disable input-ring " : "") +
|
||||||
" input-dialog input-primary"
|
" input-primary text-muted-foreground "
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{myValue !== "" ? myValue : "Type something..."}
|
{myValue !== "" ? myValue : "Type something..."}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ export default function PromptAreaComponent({
|
||||||
})
|
})
|
||||||
.catch((error) => {});
|
.catch((error) => {});
|
||||||
}
|
}
|
||||||
}, [reactFlowInstance, field_name, myValue, nodeClass, setNodeClass]);
|
}, [reactFlowInstance, field_name, myValue, nodeClass, setNodeClass, value]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={disabled ? "pointer-events-none w-full " : " w-full"}>
|
<div className={disabled ? "pointer-events-none w-full " : " w-full"}>
|
||||||
|
|
@ -69,8 +69,8 @@ export default function PromptAreaComponent({
|
||||||
className={
|
className={
|
||||||
editNode
|
editNode
|
||||||
? "input-edit-node input-dialog"
|
? "input-edit-node input-dialog"
|
||||||
: (disabled ? " input-disable " : "") +
|
: (disabled ? " input-disable text-ring " : "") +
|
||||||
" input-dialog input-primary"
|
" input-primary text-muted-foreground "
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{myValue !== "" ? myValue : "Type your prompt here"}
|
{myValue !== "" ? myValue : "Type your prompt here"}
|
||||||
|
|
|
||||||
|
|
@ -14,15 +14,17 @@ const TooltipContent = React.forwardRef<
|
||||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||||
<TooltipPrimitive.Content
|
<TooltipPrimitive.Portal>
|
||||||
ref={ref}
|
<TooltipPrimitive.Content
|
||||||
sideOffset={sideOffset}
|
ref={ref}
|
||||||
className={cn(
|
sideOffset={sideOffset}
|
||||||
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
|
className={cn(
|
||||||
className
|
"overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
|
||||||
)}
|
className
|
||||||
{...props}
|
)}
|
||||||
/>
|
{...props}
|
||||||
|
/>
|
||||||
|
</TooltipPrimitive.Portal>
|
||||||
));
|
));
|
||||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,12 @@ import { FlowType } from "./types/flow";
|
||||||
import { TabsState } from "./types/tabs";
|
import { TabsState } from "./types/tabs";
|
||||||
import { buildInputs, buildTweaks } from "./utils";
|
import { buildInputs, buildTweaks } from "./utils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number maximum of components to scroll on tooltips
|
||||||
|
* @constant
|
||||||
|
*/
|
||||||
|
export const MAX_LENGTH_TO_SCROLL_TOOLTIP = 200;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base text for subtitle of Export Dialog (Toolbar)
|
* The base text for subtitle of Export Dialog (Toolbar)
|
||||||
* @constant
|
* @constant
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: 0 0% 100%; /* hsl(0 0% 100%) */
|
--background: 0 0% 100%; /* hsl(0 0% 100%) */
|
||||||
--foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
|
--foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
|
||||||
--muted: 210 40% 98%; /* hsl(210 40% 98%) */
|
--muted: 210 40% 98%; /* hsl(210 40% 98%) */
|
||||||
|
|
@ -43,6 +43,9 @@
|
||||||
--high-indigo: #4338ca;
|
--high-indigo: #4338ca;
|
||||||
--medium-indigo: #6366f1;
|
--medium-indigo: #6366f1;
|
||||||
|
|
||||||
|
--chat-bot-icon: #afe6ef;
|
||||||
|
--chat-user-icon: #aface9;
|
||||||
|
|
||||||
/* Colors that are shared in dark and light mode */
|
/* Colors that are shared in dark and light mode */
|
||||||
--blur-shared: #151923de;
|
--blur-shared: #151923de;
|
||||||
--build-trigger: #dc735b;
|
--build-trigger: #dc735b;
|
||||||
|
|
@ -213,7 +216,7 @@ The cursor: default; property value restores the browser's default cursor style
|
||||||
@apply input-primary pr-7 mx-2
|
@apply input-primary pr-7 mx-2
|
||||||
}
|
}
|
||||||
.input-disable{
|
.input-disable{
|
||||||
@apply bg-input text-muted-foreground
|
@apply bg-border placeholder:text-ring border-transparent
|
||||||
}
|
}
|
||||||
.input-dialog{
|
.input-dialog{
|
||||||
@apply text-ring cursor-pointer bg-transparent
|
@apply text-ring cursor-pointer bg-transparent
|
||||||
|
|
@ -915,13 +918,16 @@ The cursor: default; property value restores the browser's default cursor style
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-modal-lock-true {
|
.form-modal-lock-true {
|
||||||
@apply bg-input text-black dark:bg-gray-700 dark:text-gray-300
|
@apply bg-input text-black
|
||||||
|
}
|
||||||
|
.form-modal-no-input {
|
||||||
|
@apply bg-input text-center text-black dark:bg-gray-700 dark:text-gray-300
|
||||||
}
|
}
|
||||||
.form-modal-lock-false {
|
.form-modal-lock-false {
|
||||||
@apply bg-white text-black dark:bg-gray-900 dark:text-gray-300
|
@apply bg-white text-black
|
||||||
}
|
}
|
||||||
.form-modal-lockchat {
|
.form-modal-lockchat {
|
||||||
@apply form-input block w-full rounded-md border-gray-300 p-4 pr-16 custom-scroll dark:border-gray-600 sm:text-sm
|
@apply form-input focus:ring-ring focus:border-ring block w-full rounded-md border-border p-4 pr-16 custom-scroll sm:text-sm
|
||||||
}
|
}
|
||||||
.form-modal-send-icon-position {
|
.form-modal-send-icon-position {
|
||||||
@apply absolute bottom-2 right-4
|
@apply absolute bottom-2 right-4
|
||||||
|
|
@ -935,6 +941,9 @@ The cursor: default; property value restores the browser's default cursor style
|
||||||
.form-modal-send-icon {
|
.form-modal-send-icon {
|
||||||
@apply mr-2 h-5 w-5 rotate-[44deg]
|
@apply mr-2 h-5 w-5 rotate-[44deg]
|
||||||
}
|
}
|
||||||
|
.form-modal-play-icon {
|
||||||
|
@apply h-5 w-5 mx-1
|
||||||
|
}
|
||||||
.form-modal-chat-position {
|
.form-modal-chat-position {
|
||||||
@apply flex-max-width px-2 py-6 pl-4 pr-9
|
@apply flex-max-width px-2 py-6 pl-4 pr-9
|
||||||
}
|
}
|
||||||
|
|
@ -948,10 +957,10 @@ The cursor: default; property value restores the browser's default cursor style
|
||||||
@apply relative flex h-8 w-8 items-center justify-center overflow-hidden rounded-md p-5 text-2xl
|
@apply relative flex h-8 w-8 items-center justify-center overflow-hidden rounded-md p-5 text-2xl
|
||||||
}
|
}
|
||||||
.form-modal-chat-bot-icon {
|
.form-modal-chat-bot-icon {
|
||||||
@apply form-modal-chat-img-box bg-[#afe6ef]
|
@apply form-modal-chat-img-box bg-chat-bot-icon
|
||||||
}
|
}
|
||||||
.form-modal-chat-user-icon {
|
.form-modal-chat-user-icon {
|
||||||
@apply form-modal-chat-img-box bg-[#aface9]
|
@apply form-modal-chat-img-box bg-chat-user-icon
|
||||||
}
|
}
|
||||||
.form-modal-chat-icon-img {
|
.form-modal-chat-icon-img {
|
||||||
@apply absolute scale-[60%]
|
@apply absolute scale-[60%]
|
||||||
|
|
@ -966,7 +975,7 @@ The cursor: default; property value restores the browser's default cursor style
|
||||||
@apply absolute -left-6 -top-3 cursor-pointer
|
@apply absolute -left-6 -top-3 cursor-pointer
|
||||||
}
|
}
|
||||||
.form-modal-chat-icon {
|
.form-modal-chat-icon {
|
||||||
@apply h-4 w-4 animate-bounce dark:text-white
|
@apply h-4 w-4 animate-bounce
|
||||||
}
|
}
|
||||||
.form-modal-chat-thought-border {
|
.form-modal-chat-thought-border {
|
||||||
@apply rounded-md border border-ring/60
|
@apply rounded-md border border-ring/60
|
||||||
|
|
@ -1035,13 +1044,13 @@ The cursor: default; property value restores the browser's default cursor style
|
||||||
@apply flex-max-width h-full flex-col items-center justify-center text-center align-middle
|
@apply flex-max-width h-full flex-col items-center justify-center text-center align-middle
|
||||||
}
|
}
|
||||||
.langflow-chat-span {
|
.langflow-chat-span {
|
||||||
@apply text-lg text-gray-600 dark:text-gray-300
|
@apply text-lg text-foreground
|
||||||
}
|
}
|
||||||
.langflow-chat-desc {
|
.langflow-chat-desc {
|
||||||
@apply w-2/4 rounded-md border border-gray-200 bg-muted px-6 py-8 dark:border-gray-700 dark:bg-gray-900
|
@apply w-2/4 rounded-md border border-border bg-muted px-6 py-8
|
||||||
}
|
}
|
||||||
.langflow-chat-desc-span {
|
.langflow-chat-desc-span {
|
||||||
@apply text-base text-gray-500
|
@apply text-base text-muted-foreground
|
||||||
}
|
}
|
||||||
.langflow-chat-input-div {
|
.langflow-chat-input-div {
|
||||||
@apply flex-max-width flex-col items-center justify-between px-8 pb-6
|
@apply flex-max-width flex-col items-center justify-between px-8 pb-6
|
||||||
|
|
|
||||||
|
|
@ -1,156 +0,0 @@
|
||||||
import Convert from "ansi-to-html";
|
|
||||||
import { MessageCircle, User2 } from "lucide-react";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
|
||||||
import ReactMarkdown from "react-markdown";
|
|
||||||
import rehypeMathjax from "rehype-mathjax";
|
|
||||||
import remarkGfm from "remark-gfm";
|
|
||||||
import remarkMath from "remark-math";
|
|
||||||
import AiIcon from "../../../assets/Gooey Ring-5s-271px.svg";
|
|
||||||
import AiIconStill from "../../../assets/froze-flow.png";
|
|
||||||
import SanitizedHTMLWrapper from "../../../components/SanitizedHTMLWrapper";
|
|
||||||
import { ChatMessageType } from "../../../types/chat";
|
|
||||||
import { classNames } from "../../../utils";
|
|
||||||
import { CodeBlock } from "../../formModal/chatMessage/codeBlock";
|
|
||||||
import FileCard from "../../formModal/fileComponent";
|
|
||||||
export default function ChatMessage({
|
|
||||||
chat,
|
|
||||||
lockChat,
|
|
||||||
lastMessage,
|
|
||||||
}: {
|
|
||||||
chat: ChatMessageType;
|
|
||||||
lockChat: boolean;
|
|
||||||
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);
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classNames(
|
|
||||||
"chat-message-modal",
|
|
||||||
chat.isSend ? "bg-background " : "bg-input"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className={classNames("chat-message-modal-div")}>
|
|
||||||
{!chat.isSend && (
|
|
||||||
<div className="relative h-8 w-8">
|
|
||||||
<img
|
|
||||||
className={
|
|
||||||
"chat-message-modal-img " +
|
|
||||||
(lockChat ? "opacity-100" : "opacity-0")
|
|
||||||
}
|
|
||||||
src={lastMessage ? AiIcon : AiIconStill}
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
className={
|
|
||||||
"chat-message-modal-img " +
|
|
||||||
(lockChat ? "opacity-0" : "opacity-100")
|
|
||||||
}
|
|
||||||
src={AiIconStill}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{chat.isSend && <User2 className="-mb-1 h-6 w-6 text-primary " />}
|
|
||||||
</div>
|
|
||||||
{!chat.isSend ? (
|
|
||||||
<div className="chat-message-modal-display">
|
|
||||||
<div className="chat-message-modal-text">
|
|
||||||
{hidden && chat.thought && chat.thought !== "" && (
|
|
||||||
<div
|
|
||||||
onClick={() => setHidden((prev) => !prev)}
|
|
||||||
className="chat-message-modal-icon-div"
|
|
||||||
>
|
|
||||||
<MessageCircle className="h-5 w-5 animate-bounce " />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{chat.thought && chat.thought !== "" && !hidden && (
|
|
||||||
<SanitizedHTMLWrapper
|
|
||||||
className=" chat-message-modal-thought"
|
|
||||||
content={convert.toHtml(chat.thought)}
|
|
||||||
suppressWarning={false}
|
|
||||||
onClick={() => setHidden((prev) => !prev)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
|
|
||||||
<div className="chat-message-modal-markdown">
|
|
||||||
<div className="w-full">
|
|
||||||
<div className="w-full">
|
|
||||||
<ReactMarkdown
|
|
||||||
remarkPlugins={[remarkGfm, remarkMath]}
|
|
||||||
rehypePlugins={[rehypeMathjax]}
|
|
||||||
className="markdown prose text-muted-foreground "
|
|
||||||
components={{
|
|
||||||
code({ node, inline, className, children, ...props }) {
|
|
||||||
if (children.length) {
|
|
||||||
if (children[0] === "▍") {
|
|
||||||
return (
|
|
||||||
<span className="chat-message-modal-markdown-span">
|
|
||||||
▍
|
|
||||||
</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 className="flex w-full items-center">
|
|
||||||
<div className="chat-message-modal-alert ">
|
|
||||||
{message.split("\n").map((line, index) => (
|
|
||||||
<span key={index} className="text-muted-foreground ">
|
|
||||||
{line}
|
|
||||||
<br />
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,423 +0,0 @@
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
|
||||||
import { Eraser, MessagesSquare, X } from "lucide-react";
|
|
||||||
import { Fragment, useContext, useEffect, useRef, useState } from "react";
|
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
|
||||||
import { typesContext } from "../../contexts/typesContext";
|
|
||||||
import { sendAllProps } from "../../types/api";
|
|
||||||
import { ChatMessageType } from "../../types/chat";
|
|
||||||
import { FlowType } from "../../types/flow";
|
|
||||||
import { validateNodes } from "../../utils";
|
|
||||||
import ChatInput from "./chatInput";
|
|
||||||
import ChatMessage from "./chatMessage";
|
|
||||||
|
|
||||||
import _ from "lodash";
|
|
||||||
import { getHealth } from "../../controllers/API";
|
|
||||||
|
|
||||||
export default function ChatModal({
|
|
||||||
flow,
|
|
||||||
open,
|
|
||||||
setOpen,
|
|
||||||
}: {
|
|
||||||
open: boolean;
|
|
||||||
setOpen: Function;
|
|
||||||
flow: FlowType;
|
|
||||||
}) {
|
|
||||||
const [chatValue, setChatValue] = useState("");
|
|
||||||
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
|
|
||||||
const { reactFlowInstance } = useContext(typesContext);
|
|
||||||
const { setErrorData } = useContext(alertContext);
|
|
||||||
const ws = useRef<WebSocket | null>(null);
|
|
||||||
const [lockChat, setLockChat] = useState(false);
|
|
||||||
const isOpen = useRef(open);
|
|
||||||
const messagesRef = useRef(null);
|
|
||||||
const id = useRef(flow.id);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (messagesRef.current) {
|
|
||||||
messagesRef.current.scrollTop = messagesRef.current.scrollHeight;
|
|
||||||
}
|
|
||||||
}, [chatHistory]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
isOpen.current = open;
|
|
||||||
}, [open]);
|
|
||||||
useEffect(() => {
|
|
||||||
id.current = flow.id;
|
|
||||||
}, [flow.id]);
|
|
||||||
|
|
||||||
var isStream = false;
|
|
||||||
|
|
||||||
const addChatHistory = (
|
|
||||||
message: string,
|
|
||||||
isSend: boolean,
|
|
||||||
thought?: string,
|
|
||||||
files?: Array<any>
|
|
||||||
) => {
|
|
||||||
setChatHistory((old) => {
|
|
||||||
let newChat = _.cloneDeep(old);
|
|
||||||
if (files) {
|
|
||||||
newChat.push({ message, isSend, files, thought });
|
|
||||||
} else if (thought) {
|
|
||||||
newChat.push({ message, isSend, thought });
|
|
||||||
} else {
|
|
||||||
newChat.push({ message, isSend });
|
|
||||||
}
|
|
||||||
return newChat;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//add proper type signature for function
|
|
||||||
|
|
||||||
function updateLastMessage({
|
|
||||||
str,
|
|
||||||
thought,
|
|
||||||
end = false,
|
|
||||||
files,
|
|
||||||
}: {
|
|
||||||
str?: string;
|
|
||||||
thought?: string;
|
|
||||||
// end param default is false
|
|
||||||
end?: boolean;
|
|
||||||
files?: Array<any>;
|
|
||||||
}) {
|
|
||||||
setChatHistory((old) => {
|
|
||||||
let newChat = [...old];
|
|
||||||
if (str) {
|
|
||||||
if (end) {
|
|
||||||
newChat[newChat.length - 1].message = str;
|
|
||||||
} else {
|
|
||||||
newChat[newChat.length - 1].message += str;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (thought) {
|
|
||||||
if (end) {
|
|
||||||
newChat[newChat.length - 1].thought = thought;
|
|
||||||
} else {
|
|
||||||
newChat[newChat.length - 1].thought += thought;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (files) {
|
|
||||||
newChat[newChat.length - 1].files = files;
|
|
||||||
}
|
|
||||||
return newChat;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleOnClose(event: CloseEvent) {
|
|
||||||
if (isOpen.current) {
|
|
||||||
setErrorData({ title: event.reason });
|
|
||||||
setTimeout(() => {
|
|
||||||
connectWS();
|
|
||||||
setLockChat(false);
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getWebSocketUrl(chatId, isDevelopment = false) {
|
|
||||||
const isSecureProtocol = window.location.protocol === "https:";
|
|
||||||
const webSocketProtocol = isSecureProtocol ? "wss" : "ws";
|
|
||||||
const host = isDevelopment ? "localhost:7860" : window.location.host;
|
|
||||||
const chatEndpoint = `/api/v1/chat/${chatId}`;
|
|
||||||
|
|
||||||
return `${
|
|
||||||
isDevelopment ? "ws" : webSocketProtocol
|
|
||||||
}://${host}${chatEndpoint}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleWsMessage(data: any) {
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
//set chat history
|
|
||||||
setChatHistory((_) => {
|
|
||||||
let newChatHistory: ChatMessageType[] = [];
|
|
||||||
data.forEach(
|
|
||||||
(chatItem: {
|
|
||||||
intermediate_steps?: "string";
|
|
||||||
is_bot: boolean;
|
|
||||||
message: string;
|
|
||||||
type: string;
|
|
||||||
files?: Array<any>;
|
|
||||||
}) => {
|
|
||||||
if (chatItem.message) {
|
|
||||||
newChatHistory.push(
|
|
||||||
chatItem.files
|
|
||||||
? {
|
|
||||||
isSend: !chatItem.is_bot,
|
|
||||||
message: chatItem.message,
|
|
||||||
thought: chatItem.intermediate_steps,
|
|
||||||
files: chatItem.files,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
isSend: !chatItem.is_bot,
|
|
||||||
message: chatItem.message,
|
|
||||||
thought: chatItem.intermediate_steps,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return newChatHistory;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (data.type === "start") {
|
|
||||||
addChatHistory("", false);
|
|
||||||
isStream = true;
|
|
||||||
}
|
|
||||||
if (data.type === "end") {
|
|
||||||
if (data.message) {
|
|
||||||
updateLastMessage({ str: data.message, end: true });
|
|
||||||
}
|
|
||||||
if (data.intermediate_steps) {
|
|
||||||
updateLastMessage({
|
|
||||||
str: data.message,
|
|
||||||
thought: data.intermediate_steps,
|
|
||||||
end: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (data.files) {
|
|
||||||
updateLastMessage({
|
|
||||||
end: true,
|
|
||||||
files: data.files,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setLockChat(false);
|
|
||||||
isStream = false;
|
|
||||||
}
|
|
||||||
if (data.type === "stream" && isStream) {
|
|
||||||
updateLastMessage({
|
|
||||||
str: data.message,
|
|
||||||
thought: data.intermediate_steps,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function connectWS() {
|
|
||||||
try {
|
|
||||||
const urlWs = getWebSocketUrl(
|
|
||||||
id.current,
|
|
||||||
process.env.NODE_ENV === "development"
|
|
||||||
);
|
|
||||||
const newWs = new WebSocket(urlWs);
|
|
||||||
newWs.onopen = () => {
|
|
||||||
console.log("WebSocket connection established!");
|
|
||||||
};
|
|
||||||
newWs.onmessage = (event) => {
|
|
||||||
const data = JSON.parse(event.data);
|
|
||||||
console.log("Received data:", data);
|
|
||||||
handleWsMessage(data);
|
|
||||||
//get chat history
|
|
||||||
};
|
|
||||||
newWs.onclose = (event) => {
|
|
||||||
handleOnClose(event);
|
|
||||||
};
|
|
||||||
newWs.onerror = (ev) => {
|
|
||||||
getHealth()
|
|
||||||
.then((res) => {
|
|
||||||
if (res.status === 200) {
|
|
||||||
connectWS();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
setErrorData({
|
|
||||||
// message when the backend failed
|
|
||||||
title: "The backend is not responding. Please try again later.",
|
|
||||||
// possible solution list
|
|
||||||
list: [
|
|
||||||
"Check your internet connection.",
|
|
||||||
"Check if the backend is running.",
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
ws.current = newWs;
|
|
||||||
} catch (error) {
|
|
||||||
connectWS();
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
connectWS();
|
|
||||||
return () => {
|
|
||||||
console.log("unmount");
|
|
||||||
console.log(ws);
|
|
||||||
if (ws.current) {
|
|
||||||
ws.current.close();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
ws.current &&
|
|
||||||
(ws.current.readyState === ws.current.CLOSED ||
|
|
||||||
ws.current.readyState === ws.current.CLOSING)
|
|
||||||
) {
|
|
||||||
connectWS();
|
|
||||||
setLockChat(false);
|
|
||||||
}
|
|
||||||
}, [lockChat]);
|
|
||||||
|
|
||||||
async function sendAll(data: sendAllProps) {
|
|
||||||
try {
|
|
||||||
if (ws) {
|
|
||||||
ws.current.send(JSON.stringify(data));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setErrorData({
|
|
||||||
title: "There was an error sending the message",
|
|
||||||
list: [error.message],
|
|
||||||
});
|
|
||||||
setChatValue(data.message);
|
|
||||||
connectWS();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" });
|
|
||||||
}, [chatHistory]);
|
|
||||||
|
|
||||||
const ref = useRef(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (open && ref.current) {
|
|
||||||
ref.current.focus();
|
|
||||||
}
|
|
||||||
}, [open]);
|
|
||||||
|
|
||||||
function sendMessage() {
|
|
||||||
if (chatValue !== "") {
|
|
||||||
let nodeValidationErrors = validateNodes(reactFlowInstance);
|
|
||||||
if (nodeValidationErrors.length === 0) {
|
|
||||||
setLockChat(true);
|
|
||||||
let message = chatValue;
|
|
||||||
setChatValue("");
|
|
||||||
addChatHistory(message, true);
|
|
||||||
sendAll({
|
|
||||||
...reactFlowInstance.toObject(),
|
|
||||||
message,
|
|
||||||
chatHistory,
|
|
||||||
name: flow.name,
|
|
||||||
description: flow.description,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setErrorData({
|
|
||||||
title: "Oops! Looks like you missed some required information:",
|
|
||||||
list: nodeValidationErrors,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setErrorData({
|
|
||||||
title: "Error sending message",
|
|
||||||
list: ["The message cannot be empty."],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function clearChat() {
|
|
||||||
setChatHistory([]);
|
|
||||||
ws.current.send(JSON.stringify({ clear_history: true }));
|
|
||||||
if (lockChat) setLockChat(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setModalOpen(x: boolean) {
|
|
||||||
setOpen(x);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Transition.Root show={open} appear={open} 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="send-message-modal-transition" />
|
|
||||||
</Transition.Child>
|
|
||||||
|
|
||||||
<div className="chat-modal-box">
|
|
||||||
<div className="chat-modal-box-div">
|
|
||||||
<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=" chat-modal-dialog-panel">
|
|
||||||
<div className="chat-modal-dialog-panel-div">
|
|
||||||
<button
|
|
||||||
onClick={() => clearChat()}
|
|
||||||
className="chat-modal-dialog-trash-panel"
|
|
||||||
>
|
|
||||||
<Eraser className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setModalOpen(false)}
|
|
||||||
className="chat-modal-dialog-x-panel"
|
|
||||||
>
|
|
||||||
<X className="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div ref={messagesRef} className="chat-modal-dialog-history">
|
|
||||||
{chatHistory.length > 0 ? (
|
|
||||||
chatHistory.map((c, i) => (
|
|
||||||
<ChatMessage
|
|
||||||
lockChat={lockChat}
|
|
||||||
chat={c}
|
|
||||||
lastMessage={
|
|
||||||
chatHistory.length - 1 === i ? true : false
|
|
||||||
}
|
|
||||||
key={i}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<div className="chat-modal-dialog-span-box">
|
|
||||||
<span>
|
|
||||||
👋{" "}
|
|
||||||
<span className="text-lg text-muted-foreground">
|
|
||||||
Langflow Chat
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<br />
|
|
||||||
<div className="chat-modal-dialog-desc">
|
|
||||||
<span className="text-base text-ring">
|
|
||||||
Start a conversation and click the agent’s thoughts{" "}
|
|
||||||
<span>
|
|
||||||
<MessagesSquare className="mx-1 inline h-5 w-5 animate-bounce " />
|
|
||||||
</span>{" "}
|
|
||||||
to inspect the chaining process.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div ref={ref}></div>
|
|
||||||
</div>
|
|
||||||
<div className="chat-modal-input-div">
|
|
||||||
<div className="chat-modal-input">
|
|
||||||
<ChatInput
|
|
||||||
chatValue={chatValue}
|
|
||||||
lockChat={lockChat}
|
|
||||||
sendMessage={sendMessage}
|
|
||||||
setChatValue={setChatValue}
|
|
||||||
inputRef={ref}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog.Panel>
|
|
||||||
</Transition.Child>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
</Transition.Root>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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 { TerminalSquare } from "lucide-react";
|
import { TerminalSquare } from "lucide-react";
|
||||||
import { useContext, useEffect, useRef, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
import AceEditor from "react-ace";
|
import AceEditor from "react-ace";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { CODE_PROMPT_DIALOG_SUBTITLE } from "../../constants";
|
import { CODE_PROMPT_DIALOG_SUBTITLE } from "../../constants";
|
||||||
|
|
@ -27,19 +27,10 @@ export default function CodeAreaModal({
|
||||||
nodeClass: APIClassType;
|
nodeClass: APIClassType;
|
||||||
setNodeClass: (Class: APIClassType) => void;
|
setNodeClass: (Class: APIClassType) => void;
|
||||||
}) {
|
}) {
|
||||||
const [open, setOpen] = useState(true);
|
|
||||||
const [code, setCode] = useState(value);
|
const [code, setCode] = useState(value);
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const { dark } = useContext(darkContext);
|
const { dark } = useContext(darkContext);
|
||||||
const { closePopUp, setCloseEdit } = useContext(PopUpContext);
|
const { closePopUp, setCloseEdit } = useContext(PopUpContext);
|
||||||
const { setErrorData, setSuccessData } = useContext(alertContext);
|
const { setErrorData, setSuccessData } = useContext(alertContext);
|
||||||
const [error, setError] = useState<{
|
|
||||||
detail: { error: string; traceback: string };
|
|
||||||
}>(null);
|
|
||||||
const ref = useRef();
|
|
||||||
useEffect(() => {
|
|
||||||
setValue(code);
|
|
||||||
}, [code, setValue]);
|
|
||||||
|
|
||||||
function setModalOpen(x: boolean) {
|
function setModalOpen(x: boolean) {
|
||||||
if (x === false) {
|
if (x === false) {
|
||||||
|
|
@ -49,10 +40,8 @@ export default function CodeAreaModal({
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClick() {
|
function handleClick() {
|
||||||
setLoading(true);
|
|
||||||
postValidateCode(code)
|
postValidateCode(code)
|
||||||
.then((apiReturn) => {
|
.then((apiReturn) => {
|
||||||
setLoading(false);
|
|
||||||
if (apiReturn.data) {
|
if (apiReturn.data) {
|
||||||
let importsErrors = apiReturn.data.imports.errors;
|
let importsErrors = apiReturn.data.imports.errors;
|
||||||
let funcErrors = apiReturn.data.function.errors;
|
let funcErrors = apiReturn.data.function.errors;
|
||||||
|
|
@ -61,7 +50,6 @@ export default function CodeAreaModal({
|
||||||
title: "Code is ready to run",
|
title: "Code is ready to run",
|
||||||
});
|
});
|
||||||
setValue(code);
|
setValue(code);
|
||||||
setOpen((old) => !old);
|
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
} else {
|
} else {
|
||||||
if (funcErrors.length !== 0) {
|
if (funcErrors.length !== 0) {
|
||||||
|
|
@ -84,7 +72,6 @@ export default function CodeAreaModal({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((_) => {
|
.catch((_) => {
|
||||||
setLoading(false);
|
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: "There is something wrong with this code, please review it",
|
title: "There is something wrong with this code, please review it",
|
||||||
});
|
});
|
||||||
|
|
@ -92,7 +79,7 @@ export default function CodeAreaModal({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseModal open={true} setOpen={setOpen}>
|
<BaseModal open={true} setOpen={setModalOpen}>
|
||||||
<BaseModal.Header description={CODE_PROMPT_DIALOG_SUBTITLE}>
|
<BaseModal.Header description={CODE_PROMPT_DIALOG_SUBTITLE}>
|
||||||
<DialogTitle className="flex items-center">
|
<DialogTitle className="flex items-center">
|
||||||
<span className="pr-2">Edit Code</span>
|
<span className="pr-2">Edit Code</span>
|
||||||
|
|
@ -122,23 +109,6 @@ export default function CodeAreaModal({
|
||||||
className="h-full w-full rounded-lg border-[1px] border-gray-300 custom-scroll dark:border-gray-600"
|
className="h-full w-full rounded-lg border-[1px] border-gray-300 custom-scroll dark:border-gray-600"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
className={
|
|
||||||
"w-full transition-all delay-500 " +
|
|
||||||
(error?.detail.error !== undefined ? "h-2/6" : "h-0")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="mt-1 h-full w-full overflow-x-clip overflow-y-scroll text-left custom-scroll">
|
|
||||||
<h1 className="text-lg text-status-red">
|
|
||||||
{error?.detail?.error}
|
|
||||||
</h1>
|
|
||||||
<div className="ml-2 w-full break-all text-sm text-status-red">
|
|
||||||
<pre className="w-full whitespace-pre-wrap break-all">
|
|
||||||
{error?.detail?.traceback}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex h-fit w-full justify-end">
|
<div className="flex h-fit w-full justify-end">
|
||||||
<Button className="mt-3" onClick={handleClick} type="submit">
|
<Button className="mt-3" onClick={handleClick} type="submit">
|
||||||
Check & Save
|
Check & Save
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Lock, LucideSend } from "lucide-react";
|
import { Lock, LucideSend, Sparkles } from "lucide-react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { classNames } from "../../../utils";
|
import { classNames } from "../../../utils";
|
||||||
|
|
||||||
|
|
@ -8,6 +8,7 @@ export default function ChatInput({
|
||||||
sendMessage,
|
sendMessage,
|
||||||
setChatValue,
|
setChatValue,
|
||||||
inputRef,
|
inputRef,
|
||||||
|
noInput,
|
||||||
}) {
|
}) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!lockChat && inputRef.current) {
|
if (!lockChat && inputRef.current) {
|
||||||
|
|
@ -32,7 +33,7 @@ export default function ChatInput({
|
||||||
}}
|
}}
|
||||||
rows={1}
|
rows={1}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
disabled={lockChat}
|
disabled={lockChat || noInput}
|
||||||
style={{
|
style={{
|
||||||
resize: "none",
|
resize: "none",
|
||||||
bottom: `${inputRef?.current?.scrollHeight}px`,
|
bottom: `${inputRef?.current?.scrollHeight}px`,
|
||||||
|
|
@ -49,23 +50,36 @@ export default function ChatInput({
|
||||||
}}
|
}}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
lockChat
|
lockChat
|
||||||
? " form-modal-lock-true bg-input "
|
? " form-modal-lock-true bg-input"
|
||||||
|
: noInput
|
||||||
|
? "form-modal-no-input bg-input"
|
||||||
: " form-modal-lock-false",
|
: " form-modal-lock-false",
|
||||||
|
|
||||||
"form-modal-lockchat"
|
"form-modal-lockchat"
|
||||||
)}
|
)}
|
||||||
placeholder={"Send a message..."}
|
placeholder={
|
||||||
|
noInput
|
||||||
|
? "No chat input variables found. Click to run your flow."
|
||||||
|
: "Send a message..."
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<div className="form-modal-send-icon-position">
|
<div className="form-modal-send-icon-position">
|
||||||
<button
|
<button
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"form-modal-send-button",
|
"form-modal-send-button",
|
||||||
chatValue === "" ? "text-primary" : " bg-indigo-600 text-background"
|
noInput
|
||||||
|
? "bg-indigo-600 text-background"
|
||||||
|
: chatValue === ""
|
||||||
|
? "text-primary"
|
||||||
|
: "bg-emerald-600 text-background"
|
||||||
)}
|
)}
|
||||||
disabled={lockChat}
|
disabled={lockChat}
|
||||||
onClick={() => sendMessage()}
|
onClick={() => sendMessage()}
|
||||||
>
|
>
|
||||||
{lockChat ? (
|
{lockChat ? (
|
||||||
<Lock className="form-modal-lock-icon" aria-hidden="true" />
|
<Lock className="form-modal-lock-icon" aria-hidden="true" />
|
||||||
|
) : noInput ? (
|
||||||
|
<Sparkles className="form-modal-play-icon" aria-hidden="true" />
|
||||||
) : (
|
) : (
|
||||||
<LucideSend className="form-modal-send-icon " aria-hidden="true" />
|
<LucideSend className="form-modal-send-icon " aria-hidden="true" />
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { IconCheck, IconClipboard, IconDownload } from "@tabler/icons-react";
|
import { IconCheck, IconClipboard, IconDownload } from "@tabler/icons-react";
|
||||||
import { FC, memo, useState } from "react";
|
import { useState } from "react";
|
||||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||||
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||||
import { programmingLanguages } from "../../../../utils";
|
import { programmingLanguages } from "../../../../utils";
|
||||||
|
|
@ -9,7 +9,7 @@ interface Props {
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CodeBlock: FC<Props> = memo(({ language, value }) => {
|
export function CodeBlock({ language, value }) {
|
||||||
const [isCopied, setIsCopied] = useState<Boolean>(false);
|
const [isCopied, setIsCopied] = useState<Boolean>(false);
|
||||||
|
|
||||||
const copyToClipboard = () => {
|
const copyToClipboard = () => {
|
||||||
|
|
@ -63,7 +63,7 @@ export const CodeBlock: FC<Props> = memo(({ language, value }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SyntaxHighlighter
|
<SyntaxHighlighter
|
||||||
className=" w-[47vw]"
|
className="overflow-auto"
|
||||||
language={language}
|
language={language}
|
||||||
style={oneDark}
|
style={oneDark}
|
||||||
customStyle={{ margin: 0 }}
|
customStyle={{ margin: 0 }}
|
||||||
|
|
@ -72,5 +72,5 @@ export const CodeBlock: FC<Props> = memo(({ language, value }) => {
|
||||||
</SyntaxHighlighter>
|
</SyntaxHighlighter>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
CodeBlock.displayName = "CodeBlock";
|
CodeBlock.displayName = "CodeBlock";
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import Convert from "ansi-to-html";
|
import Convert from "ansi-to-html";
|
||||||
import { ChevronDown } from "lucide-react";
|
import { ChevronDown } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import rehypeMathjax from "rehype-mathjax";
|
import rehypeMathjax from "rehype-mathjax";
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
|
|
@ -24,7 +24,7 @@ export default function ChatMessage({
|
||||||
}) {
|
}) {
|
||||||
const convert = new Convert({ newline: true });
|
const convert = new Convert({ newline: true });
|
||||||
const [hidden, setHidden] = useState(true);
|
const [hidden, setHidden] = useState(true);
|
||||||
const [template, setTemplate] = useState(chat.template);
|
const template = chat.template;
|
||||||
const [promptOpen, setPromptOpen] = useState(false);
|
const [promptOpen, setPromptOpen] = useState(false);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -75,47 +75,60 @@ export default function ChatMessage({
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="w-full dark:text-white">
|
<div className="w-full dark:text-white">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<ReactMarkdown
|
{useMemo(
|
||||||
remarkPlugins={[remarkGfm, remarkMath]}
|
() => (
|
||||||
rehypePlugins={[rehypeMathjax]}
|
<ReactMarkdown
|
||||||
className="markdown prose inline-block break-words text-primary
|
remarkPlugins={[remarkGfm, remarkMath]}
|
||||||
|
rehypePlugins={[rehypeMathjax]}
|
||||||
|
className="markdown prose inline-block break-words text-primary
|
||||||
dark:prose-invert sm:max-w-[30vw] lg:max-w-[40vw]"
|
dark:prose-invert sm:max-w-[30vw] lg:max-w-[40vw]"
|
||||||
components={{
|
components={{
|
||||||
code({ node, inline, className, children, ...props }) {
|
code: ({
|
||||||
if (children.length) {
|
node,
|
||||||
if (children[0] === "▍") {
|
inline,
|
||||||
return (
|
className,
|
||||||
<span className="form-modal-markdown-span">
|
children,
|
||||||
▍
|
...props
|
||||||
</span>
|
}) => {
|
||||||
|
if (children.length) {
|
||||||
|
if (children[0] === "▍") {
|
||||||
|
return (
|
||||||
|
<span className="form-modal-markdown-span">
|
||||||
|
▍
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
children[0] = (children[0] as string).replace(
|
||||||
|
"`▍`",
|
||||||
|
"▍"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = /language-(\w+)/.exec(
|
||||||
|
className || ""
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
children[0] = (children[0] as string).replace(
|
return !inline ? (
|
||||||
"`▍`",
|
<CodeBlock
|
||||||
"▍"
|
key={Math.random()}
|
||||||
);
|
language={(match && match[1]) || ""}
|
||||||
}
|
value={String(children).replace(/\n$/, "")}
|
||||||
|
{...props}
|
||||||
const match = /language-(\w+)/.exec(className || "");
|
/>
|
||||||
|
) : (
|
||||||
return !inline ? (
|
<code className={className} {...props}>
|
||||||
<CodeBlock
|
{children}
|
||||||
key={Math.random()}
|
</code>
|
||||||
language={(match && match[1]) || ""}
|
);
|
||||||
value={String(children).replace(/\n$/, "")}
|
},
|
||||||
{...props}
|
}}
|
||||||
/>
|
>
|
||||||
) : (
|
{chat.message.toString()}
|
||||||
<code className={className} {...props}>
|
</ReactMarkdown>
|
||||||
{children}
|
),
|
||||||
</code>
|
[chat.message, chat.message.toString()]
|
||||||
);
|
)}
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{chat.message.toString()}
|
|
||||||
</ReactMarkdown>
|
|
||||||
</div>
|
</div>
|
||||||
{chat.files && (
|
{chat.files && (
|
||||||
<div className="my-2 w-full">
|
<div className="my-2 w-full">
|
||||||
|
|
@ -138,51 +151,56 @@ export default function ChatMessage({
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
<button
|
{template && (
|
||||||
className="form-modal-initial-prompt-btn"
|
<>
|
||||||
onClick={() => {
|
<button
|
||||||
setPromptOpen((old) => !old);
|
className="form-modal-initial-prompt-btn"
|
||||||
}}
|
onClick={() => {
|
||||||
>
|
setPromptOpen((old) => !old);
|
||||||
Display Prompt
|
}}
|
||||||
<ChevronDown
|
>
|
||||||
className={
|
Display Prompt
|
||||||
"h-3 w-3 transition-all " + (promptOpen ? "rotate-180" : "")
|
<ChevronDown
|
||||||
}
|
className={
|
||||||
/>
|
"h-3 w-3 transition-all " + (promptOpen ? "rotate-180" : "")
|
||||||
</button>
|
}
|
||||||
<span className="prose inline-block break-words text-primary dark:prose-invert">
|
/>
|
||||||
{promptOpen
|
</button>
|
||||||
? template?.split("\n")?.map((line, index) => {
|
<span className="prose inline-block break-words text-primary dark:prose-invert">
|
||||||
const regex = /{([^}]+)}/g;
|
{promptOpen
|
||||||
let match;
|
? template?.split("\n")?.map((line, index) => {
|
||||||
let parts = [];
|
const regex = /{([^}]+)}/g;
|
||||||
let lastIndex = 0;
|
let match;
|
||||||
while ((match = regex.exec(line)) !== null) {
|
let parts = [];
|
||||||
// Push text up to the match
|
let lastIndex = 0;
|
||||||
if (match.index !== lastIndex) {
|
while ((match = regex.exec(line)) !== null) {
|
||||||
parts.push(line.substring(lastIndex, match.index));
|
// Push text up to the match
|
||||||
}
|
if (match.index !== lastIndex) {
|
||||||
// Push div with matched text
|
parts.push(line.substring(lastIndex, match.index));
|
||||||
if (chat.message[match[1]]) {
|
}
|
||||||
parts.push(
|
// Push div with matched text
|
||||||
<span className="my-1 rounded-md bg-indigo-100">
|
if (chat.message[match[1]]) {
|
||||||
{chat.message[match[1]]}
|
parts.push(
|
||||||
</span>
|
<span className="my-1 rounded-md bg-indigo-100">
|
||||||
);
|
{chat.message[match[1]]}
|
||||||
}
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Update last index
|
// Update last index
|
||||||
lastIndex = regex.lastIndex;
|
lastIndex = regex.lastIndex;
|
||||||
}
|
}
|
||||||
// Push text after the last match
|
// Push text after the last match
|
||||||
if (lastIndex !== line.length) {
|
if (lastIndex !== line.length) {
|
||||||
parts.push(line.substring(lastIndex));
|
parts.push(line.substring(lastIndex));
|
||||||
}
|
}
|
||||||
return <p>{parts}</p>;
|
return <p>{parts}</p>;
|
||||||
})
|
})
|
||||||
: chat.message[chat.chatKey]}
|
: chat.message[chat.chatKey]}
|
||||||
</span>
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{chat.message[chat.chatKey]}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -292,7 +292,7 @@ export default function FormModal({
|
||||||
ws.current.close();
|
ws.current.close();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, [connectWS]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
|
|
@ -303,7 +303,7 @@ export default function FormModal({
|
||||||
connectWS();
|
connectWS();
|
||||||
setLockChat(false);
|
setLockChat(false);
|
||||||
}
|
}
|
||||||
}, [lockChat]);
|
}, [lockChat, connectWS]);
|
||||||
|
|
||||||
async function sendAll(data: sendAllProps) {
|
async function sendAll(data: sendAllProps) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -333,41 +333,35 @@ export default function FormModal({
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
function sendMessage() {
|
function sendMessage() {
|
||||||
if (chatValue !== "") {
|
let nodeValidationErrors = validateNodes(reactFlowInstance);
|
||||||
let nodeValidationErrors = validateNodes(reactFlowInstance);
|
if (nodeValidationErrors.length === 0) {
|
||||||
if (nodeValidationErrors.length === 0) {
|
setLockChat(true);
|
||||||
setLockChat(true);
|
let inputs = tabsState[id.current].formKeysData.input_keys;
|
||||||
let inputs = tabsState[id.current].formKeysData.input_keys;
|
setChatValue("");
|
||||||
setChatValue("");
|
const message = inputs;
|
||||||
const message = inputs;
|
addChatHistory(
|
||||||
addChatHistory(
|
message,
|
||||||
message,
|
true,
|
||||||
true,
|
chatKey,
|
||||||
chatKey,
|
tabsState[flow.id].formKeysData.template
|
||||||
tabsState[flow.id].formKeysData.template
|
);
|
||||||
);
|
sendAll({
|
||||||
sendAll({
|
...reactFlowInstance.toObject(),
|
||||||
...reactFlowInstance.toObject(),
|
inputs: inputs,
|
||||||
inputs: inputs,
|
chatHistory,
|
||||||
chatHistory,
|
name: flow.name,
|
||||||
name: flow.name,
|
description: flow.description,
|
||||||
description: flow.description,
|
});
|
||||||
});
|
setTabsState((old) => {
|
||||||
setTabsState((old) => {
|
if (!chatKey) return old;
|
||||||
let newTabsState = _.cloneDeep(old);
|
let newTabsState = _.cloneDeep(old);
|
||||||
newTabsState[id.current].formKeysData.input_keys[chatKey] = "";
|
newTabsState[id.current].formKeysData.input_keys[chatKey] = "";
|
||||||
return newTabsState;
|
return newTabsState;
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
setErrorData({
|
|
||||||
title: "Oops! Looks like you missed some required information:",
|
|
||||||
list: nodeValidationErrors,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: "Error sending message",
|
title: "Oops! Looks like you missed some required information:",
|
||||||
list: ["The message cannot be empty."],
|
list: nodeValidationErrors,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -549,13 +543,12 @@ export default function FormModal({
|
||||||
<div className="langflow-chat-input">
|
<div className="langflow-chat-input">
|
||||||
<ChatInput
|
<ChatInput
|
||||||
chatValue={chatValue}
|
chatValue={chatValue}
|
||||||
|
noInput={!chatKey}
|
||||||
lockChat={lockChat}
|
lockChat={lockChat}
|
||||||
sendMessage={sendMessage}
|
sendMessage={sendMessage}
|
||||||
setChatValue={(value) => {
|
setChatValue={(value) => {
|
||||||
setChatValue(value);
|
setChatValue(value);
|
||||||
setTabsState((old) => {
|
setTabsState((old) => {
|
||||||
// chatKey is undefined when there are no chat input keys
|
|
||||||
if (!chatKey) return old;
|
|
||||||
let newTabsState = _.cloneDeep(old);
|
let newTabsState = _.cloneDeep(old);
|
||||||
newTabsState[id.current].formKeysData.input_keys[
|
newTabsState[id.current].formKeysData.input_keys[
|
||||||
chatKey
|
chatKey
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,6 @@ export default function GenericModal({
|
||||||
const [myModalType] = useState(type);
|
const [myModalType] = useState(type);
|
||||||
const [inputValue, setInputValue] = useState(value);
|
const [inputValue, setInputValue] = useState(value);
|
||||||
const [isEdit, setIsEdit] = useState(true);
|
const [isEdit, setIsEdit] = useState(true);
|
||||||
const [wordsHighlightInvalid, setWordsHighlightInvalid] = useState([]);
|
|
||||||
const [wordsHighlight, setWordsHighlight] = useState([]);
|
const [wordsHighlight, setWordsHighlight] = useState([]);
|
||||||
const { dark } = useContext(darkContext);
|
const { dark } = useContext(darkContext);
|
||||||
const { setErrorData, setSuccessData, setNoticeData } =
|
const { setErrorData, setSuccessData, setNoticeData } =
|
||||||
|
|
@ -102,15 +101,14 @@ export default function GenericModal({
|
||||||
(word) => !invalid_chars.includes(word)
|
(word) => !invalid_chars.includes(word)
|
||||||
);
|
);
|
||||||
|
|
||||||
setWordsHighlightInvalid(invalid_chars);
|
|
||||||
setWordsHighlight(filteredWordsHighlight);
|
setWordsHighlight(filteredWordsHighlight);
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (type == TypeModal.PROMPT && inputValue && inputValue != "") {
|
if (type === TypeModal.PROMPT && inputValue && inputValue != "") {
|
||||||
checkVariables(inputValue);
|
checkVariables(inputValue);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [inputValue, type]);
|
||||||
|
|
||||||
const coloredContent = (inputValue || "")
|
const coloredContent = (inputValue || "")
|
||||||
.replace(/</g, "<")
|
.replace(/</g, "<")
|
||||||
|
|
@ -135,10 +133,9 @@ export default function GenericModal({
|
||||||
postValidatePrompt(field_name, inputValue, nodeClass)
|
postValidatePrompt(field_name, inputValue, nodeClass)
|
||||||
.then((apiReturn) => {
|
.then((apiReturn) => {
|
||||||
if (apiReturn.data) {
|
if (apiReturn.data) {
|
||||||
setNodeClass(apiReturn.data.frontend_node);
|
setNodeClass(apiReturn.data?.frontend_node);
|
||||||
|
let inputVariables = apiReturn.data.input_variables ?? [];
|
||||||
let inputVariables = apiReturn.data.input_variables;
|
if (inputVariables && inputVariables.length === 0) {
|
||||||
if (inputVariables.length === 0) {
|
|
||||||
setIsEdit(true);
|
setIsEdit(true);
|
||||||
setNoticeData({
|
setNoticeData({
|
||||||
title: "Your template does not have any variables.",
|
title: "Your template does not have any variables.",
|
||||||
|
|
@ -159,6 +156,7 @@ export default function GenericModal({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
console.log(error);
|
||||||
setIsEdit(true);
|
setIsEdit(true);
|
||||||
return setErrorData({
|
return setErrorData({
|
||||||
title: "There is something wrong with this prompt, please review it",
|
title: "There is something wrong with this prompt, please review it",
|
||||||
|
|
|
||||||
|
|
@ -288,7 +288,7 @@ export default function Page({ flow }: { flow: FlowType }) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Specify dependencies for useCallback
|
// Specify dependencies for useCallback
|
||||||
[getNodeId, reactFlowInstance, setErrorData, setNodes, takeSnapshot]
|
[getNodeId, reactFlowInstance, setNodes, takeSnapshot]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -322,7 +322,7 @@ export default function Page({ flow }: { flow: FlowType }) {
|
||||||
setEdges((els) => updateEdge(oldEdge, newConnection, els));
|
setEdges((els) => updateEdge(oldEdge, newConnection, els));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[]
|
[reactFlowInstance, setEdges]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onEdgeUpdateEnd = useCallback((_, edge) => {
|
const onEdgeUpdateEnd = useCallback((_, edge) => {
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,7 @@ export type ShadToolTipType = {
|
||||||
asChild?: boolean;
|
asChild?: boolean;
|
||||||
children?: ReactElement;
|
children?: ReactElement;
|
||||||
delayDuration?: number;
|
delayDuration?: number;
|
||||||
style?: string;
|
styleClasses?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TextHighlightType = {
|
export type TextHighlightType = {
|
||||||
|
|
|
||||||
10
src/frontend/src/types/utils/reactflowUtils.ts
Normal file
10
src/frontend/src/types/utils/reactflowUtils.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { Edge } from "reactflow";
|
||||||
|
import { NodeType } from "../flow";
|
||||||
|
|
||||||
|
export type cleanEdgesType = {
|
||||||
|
flow: {
|
||||||
|
edges: Edge[];
|
||||||
|
nodes: NodeType[];
|
||||||
|
};
|
||||||
|
updateEdge: (edge: Edge[]) => void;
|
||||||
|
};
|
||||||
46
src/frontend/src/util/reactflowUtils.ts
Normal file
46
src/frontend/src/util/reactflowUtils.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import _ from "lodash";
|
||||||
|
import { cleanEdgesType } from "./../types/utils/reactflowUtils";
|
||||||
|
|
||||||
|
export function cleanEdges({
|
||||||
|
flow: { edges, nodes },
|
||||||
|
updateEdge,
|
||||||
|
}: cleanEdgesType) {
|
||||||
|
let newEdges = _.cloneDeep(edges);
|
||||||
|
edges.forEach((edge) => {
|
||||||
|
// check if the source and target node still exists
|
||||||
|
const sourceNode = nodes.find((node) => node.id === edge.source);
|
||||||
|
const targetNode = nodes.find((node) => node.id === edge.target);
|
||||||
|
if (!sourceNode || !targetNode) {
|
||||||
|
newEdges = newEdges.filter((e) => e.id !== edge.id);
|
||||||
|
}
|
||||||
|
// check if the source and target handle still exists
|
||||||
|
if (sourceNode && targetNode) {
|
||||||
|
const sourceHandle = edge.sourceHandle; //right
|
||||||
|
const targetHandle = edge.targetHandle; //left
|
||||||
|
if (targetHandle) {
|
||||||
|
const field = targetHandle.split("|")[1];
|
||||||
|
const id =
|
||||||
|
(targetNode.data.node.template[field]?.input_types?.join(";") ??
|
||||||
|
targetNode.data.node.template[field]?.type) +
|
||||||
|
"|" +
|
||||||
|
field +
|
||||||
|
"|" +
|
||||||
|
targetNode.data.id;
|
||||||
|
if (id !== targetHandle) {
|
||||||
|
newEdges = newEdges.filter((e) => e.id !== edge.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sourceHandle) {
|
||||||
|
const id = [
|
||||||
|
sourceNode.data.type,
|
||||||
|
sourceNode.data.id,
|
||||||
|
...sourceNode.data.node.base_classes,
|
||||||
|
].join("|");
|
||||||
|
if (id !== sourceHandle) {
|
||||||
|
newEdges = newEdges.filter((e) => e.id !== edge.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
updateEdge(newEdges);
|
||||||
|
}
|
||||||
|
|
@ -82,6 +82,8 @@ module.exports = {
|
||||||
"status-yellow": "var(--status-yellow)",
|
"status-yellow": "var(--status-yellow)",
|
||||||
"success-background": "var(--success-background)",
|
"success-background": "var(--success-background)",
|
||||||
"success-foreground": "var(--success-foreground)",
|
"success-foreground": "var(--success-foreground)",
|
||||||
|
"chat-bot-icon": "var(--chat-bot-icon)",
|
||||||
|
"chat-user-icon": "var(--chat-user-icon)",
|
||||||
|
|
||||||
white: "var(--white)",
|
white: "var(--white)",
|
||||||
border: "hsl(var(--border))",
|
border: "hsl(var(--border))",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue