refactor: Enhance error handling, message editing and prompt display (#5310)
* 📝 (newChatMessage.tsx): add event listener to handle tab visibility change and update state accordingly 📝 (newChatMessage.tsx): remove event listener when component unmounts to prevent memory leaks * ✨ (use-tab-visibility.tsx): introduce custom hook useTabVisibility to track tab visibility changes in the browser 📝 (newChatMessage.tsx, newChatView.tsx): import and use useTabVisibility hook to handle tab visibility changes and update chat behavior accordingly * 📝 (newChatMessage.tsx): remove duplicate import of useTabVisibility and update import path 📝 (newChatView.tsx): remove duplicate import of useTabVisibility and update import path ✨ (use-tab-visibility.tsx): create a new custom hook to track tab visibility changes in the browser * reducing to smaller components * 📝 (frontend): remove unused imports and clean up code in various files to improve code readability and maintainability * 📝 (editMessage/index.tsx): Rename EditMessage component to MarkdownField for better clarity and consistency 📝 (newChatMessage.tsx): Replace EditMessage component with MarkdownField component for rendering chat messages with markdown support 📝 (newChatView.tsx): Add conditional rendering to display a message when no chat messages are fetched ✨ (chatViewWrapper/index.tsx): Add messageFetched prop to ChatView component to handle messages fetching status and improve component functionality. * prompt viedw * Refactor chat view component and remove unused prop * [autofix.ci] apply automated fixes --------- Co-authored-by: anovazzi1 <otavio2204@gmail.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
9a8f721515
commit
01c1d47ff5
13 changed files with 703 additions and 590 deletions
|
|
@ -1,6 +1,5 @@
|
||||||
import IconComponent from "@/components/common/genericIconComponent";
|
import IconComponent from "@/components/common/genericIconComponent";
|
||||||
import ShadTooltip from "@/components/common/shadTooltipComponent";
|
import ShadTooltip from "@/components/common/shadTooltipComponent";
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,197 @@
|
||||||
|
import { ForwardedIconComponent } from "@/components/common/genericIconComponent";
|
||||||
|
import { TextShimmer } from "@/components/ui/TextShimmer";
|
||||||
|
import { cn } from "@/utils/utils";
|
||||||
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
|
import Markdown from "react-markdown";
|
||||||
|
import remarkGfm from "remark-gfm";
|
||||||
|
import CodeTabsComponent from "../../../../../../../components/core/codeTabsComponent/ChatCodeTabComponent";
|
||||||
|
import LogoIcon from "../chatLogoIcon";
|
||||||
|
|
||||||
|
export const ErrorView = ({
|
||||||
|
closeChat,
|
||||||
|
fitViewNode,
|
||||||
|
chat,
|
||||||
|
showError,
|
||||||
|
lastMessage,
|
||||||
|
blocks,
|
||||||
|
}: {
|
||||||
|
blocks: any;
|
||||||
|
showError: boolean;
|
||||||
|
lastMessage: boolean;
|
||||||
|
closeChat?: () => void;
|
||||||
|
fitViewNode: (id: string) => void;
|
||||||
|
chat: any;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="w-5/6 max-w-[768px] py-4 word-break-break-word">
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
{!showError && lastMessage ? (
|
||||||
|
<motion.div
|
||||||
|
key="loading"
|
||||||
|
initial={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
className="flex w-full gap-4 rounded-md p-2"
|
||||||
|
>
|
||||||
|
<LogoIcon />
|
||||||
|
<div className="flex items-center">
|
||||||
|
<TextShimmer className="" duration={1}>
|
||||||
|
Flow running...
|
||||||
|
</TextShimmer>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
) : (
|
||||||
|
<motion.div
|
||||||
|
key="error"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
className="flex w-full gap-4 rounded-md p-2"
|
||||||
|
>
|
||||||
|
<LogoIcon />
|
||||||
|
{blocks.map((block, blockIndex) => (
|
||||||
|
<div
|
||||||
|
key={blockIndex}
|
||||||
|
className="w-full rounded-xl border border-error-red-border bg-error-red p-4 text-[14px] text-foreground"
|
||||||
|
>
|
||||||
|
{block.contents.map((content, contentIndex) => {
|
||||||
|
if (content.type === "error") {
|
||||||
|
return (
|
||||||
|
<div className="" key={contentIndex}>
|
||||||
|
<div className="mb-2 flex items-center">
|
||||||
|
<ForwardedIconComponent
|
||||||
|
className="mr-2 h-[18px] w-[18px] text-destructive"
|
||||||
|
name="OctagonAlert"
|
||||||
|
/>
|
||||||
|
{content.component && (
|
||||||
|
<>
|
||||||
|
<span>
|
||||||
|
An error occured in the{" "}
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
closeChat ?? "cursor-pointer underline",
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
fitViewNode(
|
||||||
|
chat.properties?.source?.id ?? "",
|
||||||
|
);
|
||||||
|
closeChat?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<strong>{content.component}</strong>
|
||||||
|
</span>{" "}
|
||||||
|
Component, stopping your flow. See below for
|
||||||
|
more details.
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="pb-3 font-semibold">
|
||||||
|
Error details:
|
||||||
|
</h3>
|
||||||
|
{content.field && (
|
||||||
|
<p className="pb-1">Field: {content.field}</p>
|
||||||
|
)}
|
||||||
|
{content.reason && (
|
||||||
|
<span className="">
|
||||||
|
<Markdown
|
||||||
|
linkTarget="_blank"
|
||||||
|
remarkPlugins={[remarkGfm]}
|
||||||
|
components={{
|
||||||
|
a: ({ node, ...props }) => (
|
||||||
|
<a
|
||||||
|
href={props.href}
|
||||||
|
target="_blank"
|
||||||
|
className="underline"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
p({ node, ...props }) {
|
||||||
|
return (
|
||||||
|
<span className="inline-block w-fit max-w-full">
|
||||||
|
{props.children}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
code: ({
|
||||||
|
node,
|
||||||
|
inline,
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
let content = children as string;
|
||||||
|
if (
|
||||||
|
Array.isArray(children) &&
|
||||||
|
children.length === 1 &&
|
||||||
|
typeof children[0] === "string"
|
||||||
|
) {
|
||||||
|
content = children[0] as string;
|
||||||
|
}
|
||||||
|
if (typeof content === "string") {
|
||||||
|
if (content.length) {
|
||||||
|
if (content[0] === "▍") {
|
||||||
|
return (
|
||||||
|
<span className="form-modal-markdown-span"></span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = /language-(\w+)/.exec(
|
||||||
|
className || "",
|
||||||
|
);
|
||||||
|
|
||||||
|
return !inline ? (
|
||||||
|
<CodeTabsComponent
|
||||||
|
language={(match && match[1]) || ""}
|
||||||
|
code={String(content).replace(
|
||||||
|
/\n$/,
|
||||||
|
"",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<code
|
||||||
|
className={className}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</code>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{content.reason}
|
||||||
|
</Markdown>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{content.solution && (
|
||||||
|
<div className="mt-4">
|
||||||
|
<h3 className="pb-3 font-semibold">
|
||||||
|
Steps to fix:
|
||||||
|
</h3>
|
||||||
|
<ol className="list-decimal pl-5">
|
||||||
|
<li>Check the component settings</li>
|
||||||
|
<li>Ensure all required fields are filled</li>
|
||||||
|
<li>Re-run your flow</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { cn } from "@/utils/utils";
|
||||||
|
|
||||||
|
import { EMPTY_OUTPUT_SEND_MESSAGE } from "@/constants/constants";
|
||||||
|
import Markdown from "react-markdown";
|
||||||
|
import rehypeMathjax from "rehype-mathjax";
|
||||||
|
import remarkGfm from "remark-gfm";
|
||||||
|
import CodeTabsComponent from "../../../../../../../components/core/codeTabsComponent/ChatCodeTabComponent";
|
||||||
|
import EditMessageField from "../editMessageField";
|
||||||
|
|
||||||
|
type MarkdownFieldProps = {
|
||||||
|
chat: any;
|
||||||
|
isEmpty: boolean;
|
||||||
|
chatMessage: string;
|
||||||
|
editedFlag: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MarkdownField = ({
|
||||||
|
chat,
|
||||||
|
isEmpty,
|
||||||
|
chatMessage,
|
||||||
|
editedFlag,
|
||||||
|
}: MarkdownFieldProps) => {
|
||||||
|
return (
|
||||||
|
<div className="w-full items-baseline gap-2">
|
||||||
|
<Markdown
|
||||||
|
remarkPlugins={[remarkGfm]}
|
||||||
|
linkTarget="_blank"
|
||||||
|
rehypePlugins={[rehypeMathjax]}
|
||||||
|
className={cn(
|
||||||
|
"markdown prose flex w-fit max-w-full flex-col items-baseline text-[14px] font-normal word-break-break-word dark:prose-invert",
|
||||||
|
isEmpty ? "text-muted-foreground" : "text-primary",
|
||||||
|
)}
|
||||||
|
components={{
|
||||||
|
p({ node, ...props }) {
|
||||||
|
return <span className="w-fit max-w-full">{props.children}</span>;
|
||||||
|
},
|
||||||
|
ol({ node, ...props }) {
|
||||||
|
return <ol className="max-w-full">{props.children}</ol>;
|
||||||
|
},
|
||||||
|
ul({ node, ...props }) {
|
||||||
|
return <ul className="max-w-full">{props.children}</ul>;
|
||||||
|
},
|
||||||
|
pre({ node, ...props }) {
|
||||||
|
return <>{props.children}</>;
|
||||||
|
},
|
||||||
|
code: ({ node, inline, className, children, ...props }) => {
|
||||||
|
let content = children as string;
|
||||||
|
if (
|
||||||
|
Array.isArray(children) &&
|
||||||
|
children.length === 1 &&
|
||||||
|
typeof children[0] === "string"
|
||||||
|
) {
|
||||||
|
content = children[0] as string;
|
||||||
|
}
|
||||||
|
if (typeof content === "string") {
|
||||||
|
if (content.length) {
|
||||||
|
if (content[0] === "▍") {
|
||||||
|
return <span className="form-modal-markdown-span"></span>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = /language-(\w+)/.exec(className || "");
|
||||||
|
|
||||||
|
return !inline ? (
|
||||||
|
<CodeTabsComponent
|
||||||
|
language={(match && match[1]) || ""}
|
||||||
|
code={String(content).replace(/\n$/, "")}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<code className={className} {...props}>
|
||||||
|
{content}
|
||||||
|
</code>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isEmpty && !chat.stream_url ? EMPTY_OUTPUT_SEND_MESSAGE : chatMessage}
|
||||||
|
</Markdown>
|
||||||
|
{editedFlag}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
export const convertFiles = (
|
||||||
|
files:
|
||||||
|
| (
|
||||||
|
| string
|
||||||
|
| {
|
||||||
|
path: string;
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
)[]
|
||||||
|
| undefined,
|
||||||
|
) => {
|
||||||
|
if (!files) return [];
|
||||||
|
return files.map((file) => {
|
||||||
|
if (typeof file === "string") {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
return file.path;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { ProfileIcon } from "@/components/core/appHeaderComponent/components/ProfileIcon";
|
import { ProfileIcon } from "@/components/core/appHeaderComponent/components/ProfileIcon";
|
||||||
import { ContentBlockDisplay } from "@/components/core/chatComponents/ContentBlockDisplay";
|
import { ContentBlockDisplay } from "@/components/core/chatComponents/ContentBlockDisplay";
|
||||||
import { TextShimmer } from "@/components/ui/TextShimmer";
|
|
||||||
import { useUpdateMessage } from "@/controllers/API/queries/messages";
|
import { useUpdateMessage } from "@/controllers/API/queries/messages";
|
||||||
import { CustomProfileIcon } from "@/customization/components/custom-profile-icon";
|
import { CustomProfileIcon } from "@/customization/components/custom-profile-icon";
|
||||||
import { ENABLE_DATASTAX_LANGFLOW } from "@/customization/feature-flags";
|
import { ENABLE_DATASTAX_LANGFLOW } from "@/customization/feature-flags";
|
||||||
|
|
@ -8,11 +7,7 @@ import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||||
import useFlowStore from "@/stores/flowStore";
|
import useFlowStore from "@/stores/flowStore";
|
||||||
import { useUtilityStore } from "@/stores/utilityStore";
|
import { useUtilityStore } from "@/stores/utilityStore";
|
||||||
import Convert from "ansi-to-html";
|
import Convert from "ansi-to-html";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import Markdown from "react-markdown";
|
|
||||||
import rehypeMathjax from "rehype-mathjax";
|
|
||||||
import remarkGfm from "remark-gfm";
|
|
||||||
import Robot from "../../../../../assets/robot.png";
|
import Robot from "../../../../../assets/robot.png";
|
||||||
import IconComponent, {
|
import IconComponent, {
|
||||||
ForwardedIconComponent,
|
ForwardedIconComponent,
|
||||||
|
|
@ -27,10 +22,12 @@ import useTabVisibility from "../../../../../shared/hooks/use-tab-visibility";
|
||||||
import useAlertStore from "../../../../../stores/alertStore";
|
import useAlertStore from "../../../../../stores/alertStore";
|
||||||
import { chatMessagePropsType } from "../../../../../types/components";
|
import { chatMessagePropsType } from "../../../../../types/components";
|
||||||
import { cn } from "../../../../../utils/utils";
|
import { cn } from "../../../../../utils/utils";
|
||||||
import LogoIcon from "./components/chatLogoIcon";
|
import { ErrorView } from "./components/contentView";
|
||||||
|
import { MarkdownField } from "./components/editMessage";
|
||||||
import { EditMessageButton } from "./components/editMessageButton/newMessageOptions";
|
import { EditMessageButton } from "./components/editMessageButton/newMessageOptions";
|
||||||
import EditMessageField from "./components/editMessageField/newEditMessageField";
|
import EditMessageField from "./components/editMessageField/newEditMessageField";
|
||||||
import FileCardWrapper from "./components/fileCardWrapper";
|
import FileCardWrapper from "./components/fileCardWrapper";
|
||||||
|
import { convertFiles } from "./helpers/convert-files";
|
||||||
|
|
||||||
export default function ChatMessage({
|
export default function ChatMessage({
|
||||||
chat,
|
chat,
|
||||||
|
|
@ -63,6 +60,7 @@ export default function ChatMessage({
|
||||||
const chatMessageString = chat.message ? chat.message.toString() : "";
|
const chatMessageString = chat.message ? chat.message.toString() : "";
|
||||||
setChatMessage(chatMessageString);
|
setChatMessage(chatMessageString);
|
||||||
}, [chat]);
|
}, [chat]);
|
||||||
|
|
||||||
const playgroundScrollBehaves = useUtilityStore(
|
const playgroundScrollBehaves = useUtilityStore(
|
||||||
(state) => state.playgroundScrollBehaves,
|
(state) => state.playgroundScrollBehaves,
|
||||||
);
|
);
|
||||||
|
|
@ -167,27 +165,6 @@ export default function ChatMessage({
|
||||||
const isEmpty = decodedMessage?.trim() === "";
|
const isEmpty = decodedMessage?.trim() === "";
|
||||||
const { mutate: updateMessageMutation } = useUpdateMessage();
|
const { mutate: updateMessageMutation } = useUpdateMessage();
|
||||||
|
|
||||||
const convertFiles = (
|
|
||||||
files:
|
|
||||||
| (
|
|
||||||
| string
|
|
||||||
| {
|
|
||||||
path: string;
|
|
||||||
type: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
)[]
|
|
||||||
| undefined,
|
|
||||||
) => {
|
|
||||||
if (!files) return [];
|
|
||||||
return files.map((file) => {
|
|
||||||
if (typeof file === "string") {
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
return file.path;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditMessage = (message: string) => {
|
const handleEditMessage = (message: string) => {
|
||||||
updateMessageMutation(
|
updateMessageMutation(
|
||||||
{
|
{
|
||||||
|
|
@ -252,176 +229,14 @@ export default function ChatMessage({
|
||||||
const blocks = chat.content_blocks ?? [];
|
const blocks = chat.content_blocks ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-5/6 max-w-[768px] py-4 word-break-break-word">
|
<ErrorView
|
||||||
<AnimatePresence mode="wait">
|
blocks={blocks}
|
||||||
{!showError && lastMessage ? (
|
showError={showError}
|
||||||
<motion.div
|
lastMessage={lastMessage}
|
||||||
key="loading"
|
closeChat={closeChat}
|
||||||
initial={{ opacity: 1 }}
|
fitViewNode={fitViewNode}
|
||||||
exit={{ opacity: 0 }}
|
chat={chat}
|
||||||
className="flex w-full gap-4 rounded-md p-2"
|
/>
|
||||||
>
|
|
||||||
<LogoIcon />
|
|
||||||
<div className="flex items-center">
|
|
||||||
<TextShimmer className="" duration={1}>
|
|
||||||
Flow running...
|
|
||||||
</TextShimmer>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
) : (
|
|
||||||
<motion.div
|
|
||||||
key="error"
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
transition={{ duration: 0.3 }}
|
|
||||||
className="flex w-full gap-4 rounded-md p-2"
|
|
||||||
>
|
|
||||||
<LogoIcon />
|
|
||||||
{blocks.map((block, blockIndex) => (
|
|
||||||
<div
|
|
||||||
key={blockIndex}
|
|
||||||
className="w-full rounded-xl border border-error-red-border bg-error-red p-4 text-[14px] text-foreground"
|
|
||||||
>
|
|
||||||
{block.contents.map((content, contentIndex) => {
|
|
||||||
if (content.type === "error") {
|
|
||||||
return (
|
|
||||||
<div className="" key={contentIndex}>
|
|
||||||
<div className="mb-2 flex items-center">
|
|
||||||
<ForwardedIconComponent
|
|
||||||
className="mr-2 h-[18px] w-[18px] text-destructive"
|
|
||||||
name="OctagonAlert"
|
|
||||||
/>
|
|
||||||
{content.component && (
|
|
||||||
<>
|
|
||||||
<span>
|
|
||||||
An error occured in the{" "}
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
closeChat
|
|
||||||
? "cursor-pointer underline"
|
|
||||||
: "",
|
|
||||||
)}
|
|
||||||
onClick={() => {
|
|
||||||
fitViewNode(
|
|
||||||
chat.properties?.source?.id ?? "",
|
|
||||||
);
|
|
||||||
closeChat?.();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<strong>{content.component}</strong>
|
|
||||||
</span>{" "}
|
|
||||||
Component, stopping your flow. See below for
|
|
||||||
more details.
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="pb-3 font-semibold">
|
|
||||||
Error details:
|
|
||||||
</h3>
|
|
||||||
{content.field && (
|
|
||||||
<p className="pb-1">Field: {content.field}</p>
|
|
||||||
)}
|
|
||||||
{content.reason && (
|
|
||||||
<span className="">
|
|
||||||
<Markdown
|
|
||||||
linkTarget="_blank"
|
|
||||||
remarkPlugins={[remarkGfm]}
|
|
||||||
components={{
|
|
||||||
a: ({ node, ...props }) => (
|
|
||||||
<a
|
|
||||||
href={props.href}
|
|
||||||
target="_blank"
|
|
||||||
className="underline"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</a>
|
|
||||||
),
|
|
||||||
p({ node, ...props }) {
|
|
||||||
return (
|
|
||||||
<span className="inline-block w-fit max-w-full">
|
|
||||||
{props.children}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
code: ({
|
|
||||||
node,
|
|
||||||
inline,
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}) => {
|
|
||||||
let content = children as string;
|
|
||||||
if (
|
|
||||||
Array.isArray(children) &&
|
|
||||||
children.length === 1 &&
|
|
||||||
typeof children[0] === "string"
|
|
||||||
) {
|
|
||||||
content = children[0] as string;
|
|
||||||
}
|
|
||||||
if (typeof content === "string") {
|
|
||||||
if (content.length) {
|
|
||||||
if (content[0] === "▍") {
|
|
||||||
return (
|
|
||||||
<span className="form-modal-markdown-span"></span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const match = /language-(\w+)/.exec(
|
|
||||||
className || "",
|
|
||||||
);
|
|
||||||
|
|
||||||
return !inline ? (
|
|
||||||
<CodeTabsComponent
|
|
||||||
language={(match && match[1]) || ""}
|
|
||||||
code={String(content).replace(
|
|
||||||
/\n$/,
|
|
||||||
"",
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<code
|
|
||||||
className={className}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{content}
|
|
||||||
</code>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{content.reason}
|
|
||||||
</Markdown>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{content.solution && (
|
|
||||||
<div className="mt-4">
|
|
||||||
<h3 className="pb-3 font-semibold">
|
|
||||||
Steps to fix:
|
|
||||||
</h3>
|
|
||||||
<ol className="list-decimal pl-5">
|
|
||||||
<li>Check the component settings</li>
|
|
||||||
<li>Ensure all required fields are filled</li>
|
|
||||||
<li>Re-run your flow</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -568,100 +383,12 @@ export default function ChatMessage({
|
||||||
onCancel={() => setEditMessage(false)}
|
onCancel={() => setEditMessage(false)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<MarkdownField
|
||||||
<div className="w-full items-baseline gap-2">
|
chat={chat}
|
||||||
<Markdown
|
isEmpty={isEmpty}
|
||||||
remarkPlugins={[remarkGfm]}
|
chatMessage={chatMessage}
|
||||||
linkTarget="_blank"
|
editedFlag={editedFlag}
|
||||||
rehypePlugins={[rehypeMathjax]}
|
/>
|
||||||
className={cn(
|
|
||||||
"markdown prose flex w-fit max-w-full flex-col items-baseline text-[14px] font-normal word-break-break-word dark:prose-invert",
|
|
||||||
isEmpty
|
|
||||||
? "text-muted-foreground"
|
|
||||||
: "text-primary",
|
|
||||||
)}
|
|
||||||
components={{
|
|
||||||
p({ node, ...props }) {
|
|
||||||
return (
|
|
||||||
<span className="w-fit max-w-full">
|
|
||||||
{props.children}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
ol({ node, ...props }) {
|
|
||||||
return (
|
|
||||||
<ol className="max-w-full">
|
|
||||||
{props.children}
|
|
||||||
</ol>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
ul({ node, ...props }) {
|
|
||||||
return (
|
|
||||||
<ul className="max-w-full">
|
|
||||||
{props.children}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
pre({ node, ...props }) {
|
|
||||||
return <>{props.children}</>;
|
|
||||||
},
|
|
||||||
code: ({
|
|
||||||
node,
|
|
||||||
inline,
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}) => {
|
|
||||||
let content = children as string;
|
|
||||||
if (
|
|
||||||
Array.isArray(children) &&
|
|
||||||
children.length === 1 &&
|
|
||||||
typeof children[0] === "string"
|
|
||||||
) {
|
|
||||||
content = children[0] as string;
|
|
||||||
}
|
|
||||||
if (typeof content === "string") {
|
|
||||||
if (content.length) {
|
|
||||||
if (content[0] === "▍") {
|
|
||||||
return (
|
|
||||||
<span className="form-modal-markdown-span"></span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const match = /language-(\w+)/.exec(
|
|
||||||
className || "",
|
|
||||||
);
|
|
||||||
|
|
||||||
return !inline ? (
|
|
||||||
<CodeTabsComponent
|
|
||||||
language={
|
|
||||||
(match && match[1]) || ""
|
|
||||||
}
|
|
||||||
code={String(content).replace(
|
|
||||||
/\n$/,
|
|
||||||
"",
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<code
|
|
||||||
className={className}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{content}
|
|
||||||
</code>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isEmpty && !chat.stream_url
|
|
||||||
? EMPTY_OUTPUT_SEND_MESSAGE
|
|
||||||
: chatMessage}
|
|
||||||
</Markdown>
|
|
||||||
{editedFlag}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -672,95 +399,37 @@ export default function ChatMessage({
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="form-modal-chat-text-position flex-grow">
|
<div className="form-modal-chat-text-position flex-grow">
|
||||||
{template ? (
|
<div className="flex w-full flex-col">
|
||||||
<>
|
{editMessage ? (
|
||||||
<button
|
<EditMessageField
|
||||||
className="form-modal-initial-prompt-btn"
|
key={`edit-message-${chat.id}`}
|
||||||
onClick={() => {
|
message={decodedMessage}
|
||||||
setPromptOpen((old) => !old);
|
onEdit={(message) => {
|
||||||
|
handleEditMessage(message);
|
||||||
}}
|
}}
|
||||||
>
|
onCancel={() => setEditMessage(false)}
|
||||||
Display Prompt
|
/>
|
||||||
<IconComponent
|
) : (
|
||||||
name="ChevronDown"
|
<>
|
||||||
className={`h-3 w-3 transition-all ${promptOpen ? "rotate-180" : ""}`}
|
<div
|
||||||
/>
|
className={`w-full items-baseline whitespace-pre-wrap break-words text-[14px] font-normal ${
|
||||||
</button>
|
isEmpty ? "text-muted-foreground" : "text-primary"
|
||||||
<span
|
}`}
|
||||||
className={cn(
|
data-testid={`chat-message-${chat.sender_name}-${chatMessage}`}
|
||||||
"prose text-[14px] font-normal word-break-break-word dark:prose-invert",
|
>
|
||||||
!isEmpty ? "text-primary" : "text-muted-foreground",
|
{isEmpty ? EMPTY_INPUT_SEND_MESSAGE : decodedMessage}
|
||||||
)}
|
{editedFlag}
|
||||||
>
|
|
||||||
{promptOpen
|
|
||||||
? template?.split("\n")?.map((line, index) => {
|
|
||||||
const regex = /{([^}]+)}/g;
|
|
||||||
let match;
|
|
||||||
let parts: Array<JSX.Element | string> = [];
|
|
||||||
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
|
|
||||||
if (chat.message[match[1]]) {
|
|
||||||
parts.push(
|
|
||||||
<span className="chat-message-highlight">
|
|
||||||
{chat.message[match[1]]}
|
|
||||||
</span>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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>;
|
|
||||||
})
|
|
||||||
: isEmpty
|
|
||||||
? EMPTY_INPUT_SEND_MESSAGE
|
|
||||||
: chatMessage}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div className="flex w-full flex-col">
|
|
||||||
{editMessage ? (
|
|
||||||
<EditMessageField
|
|
||||||
key={`edit-message-${chat.id}`}
|
|
||||||
message={decodedMessage}
|
|
||||||
onEdit={(message) => {
|
|
||||||
handleEditMessage(message);
|
|
||||||
}}
|
|
||||||
onCancel={() => setEditMessage(false)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
className={`w-full items-baseline whitespace-pre-wrap break-words text-[14px] font-normal ${
|
|
||||||
isEmpty ? "text-muted-foreground" : "text-primary"
|
|
||||||
}`}
|
|
||||||
data-testid={`chat-message-${chat.sender_name}-${chatMessage}`}
|
|
||||||
>
|
|
||||||
{isEmpty ? EMPTY_INPUT_SEND_MESSAGE : decodedMessage}
|
|
||||||
{editedFlag}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{chat.files && (
|
|
||||||
<div className="my-2 flex flex-col gap-5">
|
|
||||||
{chat.files?.map((file, index) => {
|
|
||||||
return <FileCardWrapper index={index} path={file} />;
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</>
|
||||||
</div>
|
)}
|
||||||
)}
|
{chat.files && (
|
||||||
|
<div className="my-2 flex flex-col gap-5">
|
||||||
|
{chat.files?.map((file, index) => {
|
||||||
|
return <FileCardWrapper index={index} path={file} />;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -91,8 +91,8 @@ export default function ChatView({
|
||||||
|
|
||||||
if (messages.length === 0 && !lockChat && chatInputNode) {
|
if (messages.length === 0 && !lockChat && chatInputNode) {
|
||||||
setChatValue(chatInputNode.data.node.template["input_value"].value ?? "");
|
setChatValue(chatInputNode.data.node.template["input_value"].value ?? "");
|
||||||
} else if (isTabHidden) {
|
} else {
|
||||||
setChatValue("");
|
isTabHidden ? setChatValue("") : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setChatHistory(finalChatHistory);
|
setChatHistory(finalChatHistory);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
import ShadTooltip from "@/components/common/shadTooltipComponent";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { cn } from "@/utils/utils";
|
||||||
|
import IconComponent from "../../../../components/common/genericIconComponent";
|
||||||
|
import { ChatViewWrapperProps } from "../../types/chat-view-wrapper";
|
||||||
|
import ChatView from "../chatView/newChatView";
|
||||||
|
|
||||||
|
export const ChatViewWrapper = ({
|
||||||
|
selectedViewField,
|
||||||
|
visibleSession,
|
||||||
|
sessions,
|
||||||
|
sidebarOpen,
|
||||||
|
currentFlowId,
|
||||||
|
setSidebarOpen,
|
||||||
|
isPlayground,
|
||||||
|
setvisibleSession,
|
||||||
|
setSelectedViewField,
|
||||||
|
messagesFetched,
|
||||||
|
sessionId,
|
||||||
|
sendMessage,
|
||||||
|
chatValue,
|
||||||
|
setChatValue,
|
||||||
|
lockChat,
|
||||||
|
setLockChat,
|
||||||
|
canvasOpen,
|
||||||
|
setOpen,
|
||||||
|
}: ChatViewWrapperProps) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex h-full w-full flex-col justify-between p-4",
|
||||||
|
selectedViewField ? "hidden" : "",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="mb-4 h-[5%] text-[16px] font-semibold">
|
||||||
|
{visibleSession && sessions.length > 0 && sidebarOpen && (
|
||||||
|
<div className="hidden lg:block">
|
||||||
|
{visibleSession === currentFlowId
|
||||||
|
? "Default Session"
|
||||||
|
: `${visibleSession}`}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={cn(sidebarOpen ? "lg:hidden" : "")}>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => setSidebarOpen(true)}
|
||||||
|
className="h-8 w-8"
|
||||||
|
>
|
||||||
|
<IconComponent
|
||||||
|
name="PanelLeftOpen"
|
||||||
|
className="h-[18px] w-[18px] text-ring"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
<div className="font-semibold">Playground</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
sidebarOpen ? "pointer-events-none opacity-0" : "",
|
||||||
|
"absolute flex h-8 items-center justify-center rounded-sm ring-offset-background transition-opacity focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||||
|
isPlayground ? "right-2 top-4" : "right-12 top-2",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ShadTooltip side="bottom" styleClasses="z-50" content="New Chat">
|
||||||
|
<Button
|
||||||
|
className="mr-2 h-[32px] w-[32px] hover:bg-secondary-hover"
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => {
|
||||||
|
setvisibleSession(undefined);
|
||||||
|
setSelectedViewField(undefined);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconComponent
|
||||||
|
name="Plus"
|
||||||
|
className="!h-[18px] !w-[18px] text-ring"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</ShadTooltip>
|
||||||
|
{!isPlayground && <Separator orientation="vertical" />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
visibleSession ? "h-[95%]" : "h-full",
|
||||||
|
sidebarOpen
|
||||||
|
? "pointer-events-none blur-sm lg:pointer-events-auto lg:blur-0"
|
||||||
|
: "",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{messagesFetched && (
|
||||||
|
<ChatView
|
||||||
|
focusChat={sessionId}
|
||||||
|
sendMessage={sendMessage}
|
||||||
|
chatValue={chatValue}
|
||||||
|
setChatValue={setChatValue}
|
||||||
|
lockChat={lockChat}
|
||||||
|
setLockChat={setLockChat}
|
||||||
|
visibleSession={visibleSession}
|
||||||
|
closeChat={
|
||||||
|
!canvasOpen
|
||||||
|
? undefined
|
||||||
|
: () => {
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { InputOutput } from "@/constants/enums";
|
||||||
|
import { cn } from "@/utils/utils";
|
||||||
|
import IconComponent from "../../../../components/common/genericIconComponent";
|
||||||
|
import { SelectedViewFieldProps } from "../../types/selected-view-field";
|
||||||
|
import IOFieldView from "../IOFieldView";
|
||||||
|
import SessionView from "../SessionView";
|
||||||
|
|
||||||
|
export const SelectedViewField = ({
|
||||||
|
selectedViewField,
|
||||||
|
setSelectedViewField,
|
||||||
|
haveChat,
|
||||||
|
inputs,
|
||||||
|
outputs,
|
||||||
|
sessions,
|
||||||
|
currentFlowId,
|
||||||
|
nodes,
|
||||||
|
}: SelectedViewFieldProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex h-full w-full flex-col items-start gap-4 p-4",
|
||||||
|
!selectedViewField ? "hidden" : "",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="font-xl flex items-center justify-center gap-3 font-semibold">
|
||||||
|
{haveChat && (
|
||||||
|
<button onClick={() => setSelectedViewField(undefined)}>
|
||||||
|
<IconComponent
|
||||||
|
name={"ArrowLeft"}
|
||||||
|
className="h-6 w-6"
|
||||||
|
></IconComponent>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{
|
||||||
|
nodes.find((node) => node.id === selectedViewField?.id)?.data.node
|
||||||
|
.display_name
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className="h-full w-full">
|
||||||
|
{inputs.some((input) => input.id === selectedViewField?.id) && (
|
||||||
|
<IOFieldView
|
||||||
|
type={InputOutput.INPUT}
|
||||||
|
left={false}
|
||||||
|
fieldType={selectedViewField?.type!}
|
||||||
|
fieldId={selectedViewField?.id!}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{outputs.some((output) => output.id === selectedViewField?.id) && (
|
||||||
|
<IOFieldView
|
||||||
|
type={InputOutput.OUTPUT}
|
||||||
|
left={false}
|
||||||
|
fieldType={selectedViewField?.type!}
|
||||||
|
fieldId={selectedViewField?.id!}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{sessions.some((session) => session === selectedViewField?.id) && (
|
||||||
|
<SessionView session={selectedViewField?.id} id={currentFlowId} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
import ShadTooltip from "@/components/common/shadTooltipComponent";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import IconComponent from "../../../../components/common/genericIconComponent";
|
||||||
|
import { SidebarOpenViewProps } from "../../types/sidebar-open-view";
|
||||||
|
import SessionSelector from "../IOFieldView/components/sessionSelector/newSessionSelector";
|
||||||
|
|
||||||
|
export const SidebarOpenView = ({
|
||||||
|
sessions,
|
||||||
|
setSelectedViewField,
|
||||||
|
setvisibleSession,
|
||||||
|
handleDeleteSession,
|
||||||
|
visibleSession,
|
||||||
|
selectedViewField,
|
||||||
|
}: SidebarOpenViewProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col pl-3">
|
||||||
|
<div className="flex flex-col gap-2 pb-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<IconComponent
|
||||||
|
name="MessagesSquare"
|
||||||
|
className="h-[18px] w-[18px] text-ring"
|
||||||
|
/>
|
||||||
|
<div className="text-[13px] font-normal">Chat</div>
|
||||||
|
</div>
|
||||||
|
<ShadTooltip styleClasses="z-50" content="New Chat">
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
data-testid="new-chat"
|
||||||
|
variant="ghost"
|
||||||
|
className="flex h-8 w-8 items-center justify-center !p-0 hover:bg-secondary-hover"
|
||||||
|
onClick={(_) => {
|
||||||
|
setvisibleSession(undefined);
|
||||||
|
setSelectedViewField(undefined);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconComponent
|
||||||
|
name="Plus"
|
||||||
|
className="h-[18px] w-[18px] text-ring"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</ShadTooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
{sessions.map((session, index) => (
|
||||||
|
<SessionSelector
|
||||||
|
setSelectedView={setSelectedViewField}
|
||||||
|
selectedView={selectedViewField}
|
||||||
|
key={index}
|
||||||
|
session={session}
|
||||||
|
deleteSession={(session) => {
|
||||||
|
handleDeleteSession(session);
|
||||||
|
if (selectedViewField?.id === session) {
|
||||||
|
setSelectedViewField(undefined);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
updateVisibleSession={(session) => {
|
||||||
|
setvisibleSession(session);
|
||||||
|
}}
|
||||||
|
toggleVisibility={() => {
|
||||||
|
setvisibleSession(session);
|
||||||
|
}}
|
||||||
|
isVisible={visibleSession === session}
|
||||||
|
inspectSession={(session) => {
|
||||||
|
setSelectedViewField({
|
||||||
|
id: session,
|
||||||
|
type: "Session",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -8,7 +8,6 @@ import { useCallback, useEffect, useState } from "react";
|
||||||
import IconComponent from "../../components/common/genericIconComponent";
|
import IconComponent from "../../components/common/genericIconComponent";
|
||||||
import ShadTooltip from "../../components/common/shadTooltipComponent";
|
import ShadTooltip from "../../components/common/shadTooltipComponent";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { InputOutput } from "../../constants/enums";
|
|
||||||
import useAlertStore from "../../stores/alertStore";
|
import useAlertStore from "../../stores/alertStore";
|
||||||
import useFlowStore from "../../stores/flowStore";
|
import useFlowStore from "../../stores/flowStore";
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
|
|
@ -16,10 +15,10 @@ import { useMessagesStore } from "../../stores/messagesStore";
|
||||||
import { IOModalPropsType } from "../../types/components";
|
import { IOModalPropsType } from "../../types/components";
|
||||||
import { cn } from "../../utils/utils";
|
import { cn } from "../../utils/utils";
|
||||||
import BaseModal from "../baseModal";
|
import BaseModal from "../baseModal";
|
||||||
import IOFieldView from "./components/IOFieldView";
|
|
||||||
import SessionSelector from "./components/IOFieldView/components/sessionSelector/newSessionSelector";
|
|
||||||
import SessionView from "./components/SessionView";
|
|
||||||
import ChatView from "./components/chatView/newChatView";
|
import ChatView from "./components/chatView/newChatView";
|
||||||
|
import { ChatViewWrapper } from "./components/chatViewWrapper";
|
||||||
|
import { SelectedViewField } from "./components/selectedViewField";
|
||||||
|
import { SidebarOpenView } from "./components/sidebarOpenView";
|
||||||
|
|
||||||
export default function IOModal({
|
export default function IOModal({
|
||||||
children,
|
children,
|
||||||
|
|
@ -292,217 +291,51 @@ export default function IOModal({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{sidebarOpen && (
|
{sidebarOpen && (
|
||||||
<div className="flex flex-col pl-3">
|
<SidebarOpenView
|
||||||
<div className="flex flex-col gap-2 pb-2">
|
sessions={sessions}
|
||||||
<div className="flex items-center justify-between">
|
setSelectedViewField={setSelectedViewField}
|
||||||
<div className="flex items-center gap-2">
|
setvisibleSession={setvisibleSession}
|
||||||
<IconComponent
|
handleDeleteSession={handleDeleteSession}
|
||||||
name="MessagesSquare"
|
visibleSession={visibleSession}
|
||||||
className="h-[18px] w-[18px] text-ring"
|
selectedViewField={selectedViewField}
|
||||||
/>
|
/>
|
||||||
<div className="text-[13px] font-normal">Chat</div>
|
|
||||||
</div>
|
|
||||||
<ShadTooltip styleClasses="z-50" content="New Chat">
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
data-testid="new-chat"
|
|
||||||
variant="ghost"
|
|
||||||
className="flex h-8 w-8 items-center justify-center !p-0 hover:bg-secondary-hover"
|
|
||||||
onClick={(_) => {
|
|
||||||
setvisibleSession(undefined);
|
|
||||||
setSelectedViewField(undefined);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconComponent
|
|
||||||
name="Plus"
|
|
||||||
className="h-[18px] w-[18px] text-ring"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</ShadTooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col">
|
|
||||||
{sessions.map((session, index) => (
|
|
||||||
<SessionSelector
|
|
||||||
setSelectedView={setSelectedViewField}
|
|
||||||
selectedView={selectedViewField}
|
|
||||||
key={index}
|
|
||||||
session={session}
|
|
||||||
deleteSession={(session) => {
|
|
||||||
handleDeleteSession(session);
|
|
||||||
if (selectedViewField?.id === session) {
|
|
||||||
setSelectedViewField(undefined);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
updateVisibleSession={(session) => {
|
|
||||||
setvisibleSession(session);
|
|
||||||
}}
|
|
||||||
toggleVisibility={() => {
|
|
||||||
setvisibleSession(session);
|
|
||||||
}}
|
|
||||||
isVisible={visibleSession === session}
|
|
||||||
inspectSession={(session) => {
|
|
||||||
setSelectedViewField({
|
|
||||||
id: session,
|
|
||||||
type: "Session",
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex h-full min-w-96 flex-grow bg-background">
|
<div className="flex h-full min-w-96 flex-grow bg-background">
|
||||||
{selectedViewField && (
|
{selectedViewField && (
|
||||||
<div
|
<SelectedViewField
|
||||||
className={cn(
|
selectedViewField={selectedViewField}
|
||||||
"flex h-full w-full flex-col items-start gap-4 p-4",
|
setSelectedViewField={setSelectedViewField}
|
||||||
!selectedViewField ? "hidden" : "",
|
haveChat={haveChat}
|
||||||
)}
|
inputs={inputs}
|
||||||
>
|
outputs={outputs}
|
||||||
<div className="font-xl flex items-center justify-center gap-3 font-semibold">
|
sessions={sessions}
|
||||||
{haveChat && (
|
currentFlowId={currentFlowId}
|
||||||
<button onClick={() => setSelectedViewField(undefined)}>
|
nodes={nodes}
|
||||||
<IconComponent
|
/>
|
||||||
name={"ArrowLeft"}
|
|
||||||
className="h-6 w-6"
|
|
||||||
></IconComponent>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{
|
|
||||||
nodes.find((node) => node.id === selectedViewField.id)
|
|
||||||
?.data.node.display_name
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div className="h-full w-full">
|
|
||||||
{inputs.some(
|
|
||||||
(input) => input.id === selectedViewField.id,
|
|
||||||
) && (
|
|
||||||
<IOFieldView
|
|
||||||
type={InputOutput.INPUT}
|
|
||||||
left={false}
|
|
||||||
fieldType={selectedViewField.type!}
|
|
||||||
fieldId={selectedViewField.id!}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{outputs.some(
|
|
||||||
(output) => output.id === selectedViewField.id,
|
|
||||||
) && (
|
|
||||||
<IOFieldView
|
|
||||||
type={InputOutput.OUTPUT}
|
|
||||||
left={false}
|
|
||||||
fieldType={selectedViewField.type!}
|
|
||||||
fieldId={selectedViewField.id!}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{sessions.some(
|
|
||||||
(session) => session === selectedViewField.id,
|
|
||||||
) && (
|
|
||||||
<SessionView
|
|
||||||
session={selectedViewField.id}
|
|
||||||
id={currentFlowId}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
<div
|
<ChatViewWrapper
|
||||||
className={cn(
|
selectedViewField={selectedViewField}
|
||||||
"flex h-full w-full flex-col justify-between p-4",
|
visibleSession={visibleSession}
|
||||||
selectedViewField ? "hidden" : "",
|
sessions={sessions}
|
||||||
)}
|
sidebarOpen={sidebarOpen}
|
||||||
>
|
currentFlowId={currentFlowId}
|
||||||
<div className="mb-4 h-[5%] text-[16px] font-semibold">
|
setSidebarOpen={setSidebarOpen}
|
||||||
{visibleSession && sessions.length > 0 && sidebarOpen && (
|
isPlayground={isPlayground}
|
||||||
<div className="hidden lg:block">
|
setvisibleSession={setvisibleSession}
|
||||||
{visibleSession === currentFlowId
|
setSelectedViewField={setSelectedViewField}
|
||||||
? "Default Session"
|
haveChat={haveChat}
|
||||||
: `${visibleSession}`}
|
messagesFetched={messagesFetched}
|
||||||
</div>
|
sessionId={sessionId}
|
||||||
)}
|
sendMessage={sendMessage}
|
||||||
<div className={cn(sidebarOpen ? "lg:hidden" : "")}>
|
chatValue={chatValue}
|
||||||
<div className="flex items-center gap-2">
|
setChatValue={setChatValue}
|
||||||
<Button
|
lockChat={lockChat}
|
||||||
variant="ghost"
|
setLockChat={setLockChat}
|
||||||
size="icon"
|
canvasOpen={canvasOpen}
|
||||||
onClick={() => setSidebarOpen(true)}
|
setOpen={setOpen}
|
||||||
className="h-8 w-8"
|
/>
|
||||||
>
|
|
||||||
<IconComponent
|
|
||||||
name={"PanelLeftOpen"}
|
|
||||||
className="h-[18px] w-[18px] text-ring"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
<div className="font-semibold">Playground</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
sidebarOpen ? "pointer-events-none opacity-0" : "",
|
|
||||||
"absolute flex h-8 items-center justify-center rounded-sm ring-offset-background transition-opacity focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
||||||
isPlayground ? "right-2 top-4" : "right-12 top-2",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<ShadTooltip
|
|
||||||
side="bottom"
|
|
||||||
styleClasses="z-50"
|
|
||||||
content="New Chat"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
className="mr-2 h-[32px] w-[32px] hover:bg-secondary-hover"
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
onClick={(_) => {
|
|
||||||
setvisibleSession(undefined);
|
|
||||||
setSelectedViewField(undefined);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconComponent
|
|
||||||
name="Plus"
|
|
||||||
className="!h-[18px] !w-[18px] text-ring"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</ShadTooltip>
|
|
||||||
{!isPlayground && <Separator orientation="vertical" />}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{haveChat ? (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
visibleSession ? "h-[95%]" : "h-full",
|
|
||||||
sidebarOpen
|
|
||||||
? "pointer-events-none blur-sm lg:pointer-events-auto lg:blur-0"
|
|
||||||
: "",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{messagesFetched && (
|
|
||||||
<ChatView
|
|
||||||
focusChat={sessionId}
|
|
||||||
sendMessage={sendMessage}
|
|
||||||
chatValue={chatValue}
|
|
||||||
setChatValue={setChatValue}
|
|
||||||
lockChat={lockChat}
|
|
||||||
setLockChat={setLockChat}
|
|
||||||
visibleSession={visibleSession}
|
|
||||||
closeChat={
|
|
||||||
!canvasOpen
|
|
||||||
? undefined
|
|
||||||
: () => {
|
|
||||||
setOpen(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span className="flex h-full w-full items-center justify-center font-thin text-muted-foreground">
|
|
||||||
Select an IO component to view
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
23
src/frontend/src/modals/IOModal/types/chat-view-wrapper.ts
Normal file
23
src/frontend/src/modals/IOModal/types/chat-view-wrapper.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
export type ChatViewWrapperProps = {
|
||||||
|
selectedViewField: { type: string; id: string } | undefined;
|
||||||
|
visibleSession: string | undefined;
|
||||||
|
sessions: string[];
|
||||||
|
sidebarOpen: boolean;
|
||||||
|
currentFlowId: string;
|
||||||
|
setSidebarOpen: (open: boolean) => void;
|
||||||
|
isPlayground: boolean | undefined;
|
||||||
|
setvisibleSession: (session: string | undefined) => void;
|
||||||
|
setSelectedViewField: (
|
||||||
|
field: { type: string; id: string } | undefined,
|
||||||
|
) => void;
|
||||||
|
haveChat: { type: string; id: string; displayName: string } | undefined;
|
||||||
|
messagesFetched: boolean;
|
||||||
|
sessionId: string;
|
||||||
|
sendMessage: (options: { repeat: number; files?: string[] }) => Promise<void>;
|
||||||
|
chatValue: string;
|
||||||
|
setChatValue: (value: string) => void;
|
||||||
|
lockChat: boolean;
|
||||||
|
setLockChat: (locked: boolean) => void;
|
||||||
|
canvasOpen: boolean | undefined;
|
||||||
|
setOpen: (open: boolean) => void;
|
||||||
|
};
|
||||||
21
src/frontend/src/modals/IOModal/types/selected-view-field.ts
Normal file
21
src/frontend/src/modals/IOModal/types/selected-view-field.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { Node } from "reactflow";
|
||||||
|
export type SelectedViewFieldProps = {
|
||||||
|
selectedViewField: { type: string; id: string } | undefined;
|
||||||
|
setSelectedViewField: (
|
||||||
|
field: { type: string; id: string } | undefined,
|
||||||
|
) => void;
|
||||||
|
haveChat: { type: string; id: string; displayName: string } | undefined;
|
||||||
|
inputs: Array<{
|
||||||
|
type: string;
|
||||||
|
id: string;
|
||||||
|
displayName: string;
|
||||||
|
}>;
|
||||||
|
outputs: Array<{
|
||||||
|
type: string;
|
||||||
|
id: string;
|
||||||
|
displayName: string;
|
||||||
|
}>;
|
||||||
|
sessions: string[];
|
||||||
|
currentFlowId: string;
|
||||||
|
nodes: Node[];
|
||||||
|
};
|
||||||
10
src/frontend/src/modals/IOModal/types/sidebar-open-view.ts
Normal file
10
src/frontend/src/modals/IOModal/types/sidebar-open-view.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
export type SidebarOpenViewProps = {
|
||||||
|
sessions: string[];
|
||||||
|
setSelectedViewField: (
|
||||||
|
field: { type: string; id: string } | undefined,
|
||||||
|
) => void;
|
||||||
|
setvisibleSession: (session: string | undefined) => void;
|
||||||
|
handleDeleteSession: (session: string) => void;
|
||||||
|
visibleSession: string | undefined;
|
||||||
|
selectedViewField: { type: string; id: string } | undefined;
|
||||||
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue