v1 of AutoGen Studio on AgentChat (#4097)

* add skeleton worflow manager

* add test notebook

* update test nb

* add sample team spec

* refactor requirements to agentchat and ext

* add base provider to return agentchat agents from json spec

* initial api refactor, update dbmanager

* api refactor

* refactor tests

* ags api tutorial update

* ui refactor

* general refactor

* minor refactor updates

* backend api refaactor

* ui refactor and update

* implement v1 for streaming connection with ui updates

* backend refactor

* ui refactor

* minor ui tweak

* minor refactor and tweaks

* general refactor

* update tests

* sync uv.lock with main

* uv lock update
This commit is contained in:
Victor Dibia
2024-11-09 14:32:24 -08:00
committed by GitHub
parent f40b0c2730
commit 0e985d4b40
117 changed files with 20736 additions and 13600 deletions

View File

@@ -1,873 +0,0 @@
import {
ChevronDownIcon,
ChevronUpIcon,
Cog8ToothIcon,
XMarkIcon,
ClipboardIcon,
InformationCircleIcon,
} from "@heroicons/react/24/outline";
import React, { ReactNode, useEffect, useRef, useState } from "react";
import Icon from "./icons";
import { Modal, Table, Tooltip, theme } from "antd";
import Editor from "@monaco-editor/react";
import Papa from "papaparse";
import remarkGfm from "remark-gfm";
import ReactMarkdown from "react-markdown";
import { atomDark } from "react-syntax-highlighter/dist/esm/styles/prism";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { truncateText } from "./utils";
const { useToken } = theme;
interface CodeProps {
node?: any;
inline?: any;
className?: any;
children?: React.ReactNode;
}
interface IProps {
children?: ReactNode;
title?: string | ReactNode;
subtitle?: string | ReactNode;
count?: number;
active?: boolean;
cursor?: string;
icon?: ReactNode;
padding?: string;
className?: string;
open?: boolean;
hoverable?: boolean;
onClick?: () => void;
loading?: boolean;
}
export const SectionHeader = ({
children,
title,
subtitle,
count,
icon,
}: IProps) => {
return (
<div id="section-header" className="mb-4">
<h1 className="text-primary text-2xl">
{/* {count !== null && <span className="text-accent mr-1">{count}</span>} */}
{icon && <>{icon}</>}
{title}
{count !== null && (
<span className="text-accent mr-1 ml-2 text-xs">{count}</span>
)}
</h1>
{subtitle && <span className="inline-block">{subtitle}</span>}
{children}
</div>
);
};
export const IconButton = ({
onClick,
icon,
className,
active = false,
}: IProps) => {
return (
<span
id="icon-button"
role={"button"}
onClick={onClick}
className={`inline-block mr-2 hover:text-accent transition duration-300 ${className} ${
active ? "border-accent border rounded text-accent" : ""
}`}
>
{icon}
</span>
);
};
export const LaunchButton = ({
children,
onClick,
className = "p-3 px-5 ",
}: any) => {
return (
<button
id="launch-button"
role={"button"}
className={` focus:ring ring-accent ring-l-none rounded cursor-pointer hover:brightness-110 bg-accent transition duration-500 text-white ${className} `}
onClick={onClick}
>
{children}
</button>
);
};
export const SecondaryButton = ({ children, onClick, className }: any) => {
return (
<button
id="secondary-button"
role={"button"}
className={` ${className} focus:ring ring-accent p-2 px-5 rounded cursor-pointer hover:brightness-90 bg-secondary transition duration-500 text-primary`}
onClick={onClick}
>
{children}
</button>
);
};
export const Card = ({
children,
title,
subtitle,
hoverable = true,
active,
cursor = "cursor-pointer",
className = "p-3",
onClick,
}: IProps) => {
let border = active
? "border-accent"
: "border-secondary hover:border-accent ";
border = hoverable ? border : "border-secondary";
return (
<button
id="card"
tabIndex={0}
onClick={onClick}
role={"button"}
className={`${border} border-2 bg-secondary group ${className} w-full text-left rounded ${cursor} transition duration-300`}
>
<div className="mt- text-sm text-secondary h-full break-words">
{title && (
<div className="text-accent rounded font-semibold text-xs pb-1">
{title}
</div>
)}
<div>{subtitle}</div>
{children}
</div>
</button>
);
};
export const CollapseBox = ({
title,
subtitle,
children,
className = " p-3",
open = false,
}: IProps) => {
const [isOpen, setIsOpen] = React.useState<boolean>(open);
const chevronClass = "h-4 cursor-pointer inline-block mr-1";
return (
<div
id="collapse-box"
onMouseDown={(e) => {
if (e.detail > 1) {
e.preventDefault();
}
}}
className="bordper border-secondary rounded"
>
<div
onClick={() => {
setIsOpen(!isOpen);
}}
className={`cursor-pointer bg-secondary p-2 rounded ${
isOpen ? "rounded-b-none " : " "
}"}`}
>
{isOpen && <ChevronUpIcon className={chevronClass} />}
{!isOpen && <ChevronDownIcon className={chevronClass} />}
<span className=" inline-block -mt-2 mb-2 text-xs">
{" "}
{/* {isOpen ? "hide" : "show"} section | */}
{title}
</span>
</div>
{isOpen && (
<div className={`${className} bg-tertiary rounded rounded-t-none`}>
{children}
</div>
)}
</div>
);
};
export const HighLight = ({ children }: IProps) => {
return <span id="highlight" className="border-b border-accent">{children}</span>;
};
export const LoadBox = ({
subtitle,
className = "my-2 text-accent ",
}: IProps) => {
return (
<div id="load-box" className={`${className} `}>
<span className="mr-2 ">
{" "}
<Icon size={5} icon="loading" />
</span>{" "}
{subtitle}
</div>
);
};
export const LoadingBar = ({ children }: IProps) => {
return (
<>
<div id="loading-bar" className="rounded bg-secondary p-3">
<span className="inline-block h-6 w-6 relative mr-2">
<Cog8ToothIcon className="animate-ping text-accent absolute inline-flex h-full w-full rounded-ful opacity-75" />
<Cog8ToothIcon className="relative text-accent animate-spin inline-flex rounded-full h-6 w-6" />
</span>
{children}
</div>
<div className="relative">
<div className="loadbar rounded-b"></div>
</div>
</>
);
};
export const MessageBox = ({ title, children, className }: IProps) => {
const messageBox = useRef<HTMLDivElement>(null);
const closeMessage = () => {
if (messageBox.current) {
messageBox.current.remove();
}
};
return (
<div
id="message-box"
ref={messageBox}
className={`${className} p-3 rounded bg-secondary transition duration-1000 ease-in-out overflow-hidden`}
>
{" "}
<div className="flex gap-2 mb-2">
<div className="flex-1">
{/* <span className="mr-2 text-accent">
<InformationCircleIcon className="h-6 w-6 inline-block" />
</span>{" "} */}
<span className="font-semibold text-primary text-base">{title}</span>
</div>
<div>
<span
onClick={() => {
closeMessage();
}}
className=" border border-secondary bg-secondary brightness-125 hover:brightness-100 cursor-pointer transition duration-200 inline-block px-1 pb-1 rounded text-primary"
>
<XMarkIcon className="h-4 w-4 inline-block" />
</span>
</div>
</div>
{children}
</div>
);
};
export const GroupView = ({
children,
title,
className = "text-primary bg-primary ",
}: any) => {
return (
<div id="group-view" className={`rounded mt-4 border-secondary ${className}`}>
<div className="mt-4 p-2 rounded border relative">
<div className={`absolute -top-3 inline-block ${className}`}>
{title}
</div>
<div className="mt-2"> {children}</div>
</div>
</div>
);
};
export const ExpandView = ({
children,
icon = null,
className = "",
title = "Detail View",
}: any) => {
const [isOpen, setIsOpen] = React.useState(false);
let windowAspect = 1;
if (typeof window !== "undefined") {
windowAspect = window.innerWidth / window.innerHeight;
}
const minImageWidth = 400;
return (
<div
id="expand-view"
style={{
minHeight: "100px",
}}
className={`h-full rounded mb-6 border-secondary ${className}`}
>
<div
role="button"
onClick={() => {
setIsOpen(true);
}}
className="text-xs mb-2 h-full w-full break-words"
>
{icon ? icon : children}
</div>
{isOpen && (
<Modal
title={title}
width={800}
open={isOpen}
onCancel={() => setIsOpen(false)}
footer={null}
>
{/* <ResizableBox
// handle={<span className="text-accent">resize</span>}
lockAspectRatio={false}
handle={
<div className="absolute right-0 bottom-0 cursor-se-resize font-semibold boprder p-3 bg-secondary">
<ArrowDownRightIcon className="h-4 w-4 inline-block" />
</div>
}
width={800}
height={minImageWidth * windowAspect}
minConstraints={[minImageWidth, minImageWidth * windowAspect]}
maxConstraints={[900, 900 * windowAspect]}
className="overflow-auto w-full rounded select-none "
> */}
{children}
{/* </ResizableBox> */}
</Modal>
)}
</div>
);
};
export const LoadingOverlay = ({ children, loading }: IProps) => {
return (
<>
{loading && (
<>
<div
id="loading-overlay"
className="absolute inset-0 bg-secondary flex pointer-events-none"
style={{ opacity: 0.5 }}
>
{/* Overlay background */}
</div>
<div
className="absolute inset-0 flex items-center justify-center"
style={{ pointerEvents: "none" }}
>
{/* Center BounceLoader without inheriting the opacity */}
<BounceLoader />
</div>
</>
)}
<div className="relative">{children}</div>
</>
);
};
export const MarkdownView = ({
data,
className = "",
showCode = true,
}: {
data: string;
className?: string;
showCode?: boolean;
}) => {
function processString(inputString: string): string {
// TODO: Had to add this temp measure while debugging. Why is it null?
if (!inputString) {
console.log("inputString is null!")
}
inputString = inputString && inputString.replace(/\n/g, " \n");
const markdownPattern = /```markdown\s+([\s\S]*?)\s+```/g;
return inputString?.replace(markdownPattern, (match, content) => content);
}
const [showCopied, setShowCopied] = React.useState(false);
const CodeView = ({ props, children, language }: any) => {
const [codeVisible, setCodeVisible] = React.useState(showCode);
return (
<div>
<div className=" flex ">
<div
role="button"
onClick={() => {
setCodeVisible(!codeVisible);
}}
className=" flex-1 mr-4 "
>
{!codeVisible && (
<div className=" text-white hover:text-accent duration-300">
<ChevronDownIcon className="inline-block w-5 h-5" />
<span className="text-xs"> show</span>
</div>
)}
{codeVisible && (
<div className=" text-white hover:text-accent duration-300">
{" "}
<ChevronUpIcon className="inline-block w-5 h-5" />
<span className="text-xs"> hide</span>
</div>
)}
</div>
{/* <div className="flex-1"></div> */}
<div>
{showCopied && (
<div className="inline-block text-sm text-white">
{" "}
🎉 Copied!{" "}
</div>
)}
<ClipboardIcon
role={"button"}
onClick={() => {
navigator.clipboard.writeText(data);
// message.success("Code copied to clipboard");
setShowCopied(true);
setTimeout(() => {
setShowCopied(false);
}, 3000);
}}
className=" inline-block duration-300 text-white hover:text-accent w-5 h-5"
/>
</div>
</div>
{codeVisible && (
<SyntaxHighlighter
{...props}
style={atomDark}
language={language}
className="rounded w-full"
PreTag="div"
wrapLongLines={true}
>
{String(children).replace(/\n$/, "")}
</SyntaxHighlighter>
)}
</div>
);
};
return (
<div
id="markdown-view"
className={` w-full chatbox prose dark:prose-invert text-primary rounded ${className}`}
>
<ReactMarkdown
className=" w-full"
remarkPlugins={[remarkGfm]}
components={{
code({ node, inline, className, children, ...props }: CodeProps) {
const match = /language-(\w+)/.exec(className || "");
const language = match ? match[1] : "text";
return !inline && match ? (
<CodeView props={props} children={children} language={language} />
) : (
<code {...props} className={className}>
{children}
</code>
);
},
}}
>
{processString(data)}
</ReactMarkdown>
</div>
);
};
interface ICodeProps {
code: string;
language: string;
title?: string;
showLineNumbers?: boolean;
className?: string | undefined;
wrapLines?: boolean;
maxWidth?: string;
maxHeight?: string;
minHeight?: string;
}
export const CodeBlock = ({
code,
language = "python",
showLineNumbers = false,
className = " ",
wrapLines = false,
maxHeight = "400px",
minHeight = "auto",
}: ICodeProps) => {
const codeString = code;
const [showCopied, setShowCopied] = React.useState(false);
return (
<div id="code-block" className="relative">
<div className=" rounded absolute right-5 top-4 z-10 ">
<div className="relative border border-transparent w-full h-full">
<div
style={{ zIndex: -1 }}
className="w-full absolute top-0 h-full bg-gray-900 hover:bg-opacity-0 duration-300 bg-opacity-50 rounded"
></div>
<div className=" ">
{showCopied && (
<div className="inline-block px-2 pl-3 text-white">
{" "}
🎉 Copied!{" "}
</div>
)}
<ClipboardIcon
role={"button"}
onClick={() => {
navigator.clipboard.writeText(codeString);
// message.success("Code copied to clipboard");
setShowCopied(true);
setTimeout(() => {
setShowCopied(false);
}, 6000);
}}
className="m-2 inline-block duration-300 text-white hover:text-accent w-5 h-5"
/>
</div>
</div>
</div>
<div
id="codeDivBox"
className={`rounded w-full overflow-auto overflow-y-scroll scroll ${className}`}
style={{ maxHeight: maxHeight, minHeight: minHeight }}
>
<SyntaxHighlighter
id="codeDiv"
className="rounded-sm h-full break-all"
language={language}
showLineNumbers={showLineNumbers}
style={atomDark}
wrapLines={wrapLines}
wrapLongLines={wrapLines}
>
{codeString}
</SyntaxHighlighter>
</div>
</div>
);
};
// Controls Row
export const ControlRowView = ({
title,
description,
value,
control,
className,
truncateLength = 20,
}: {
title: string;
description: string;
value: string | number | boolean;
control: any;
className?: string;
truncateLength?: number;
}) => {
return (
<div id="control-row-view" className={`${className}`}>
<div>
<span className="text-primary inline-block">{title} </span>
<span className="text-xs ml-1 text-accent -mt-2 inline-block">
{truncateText(value + "", truncateLength)}
</span>{" "}
<Tooltip title={description}>
<InformationCircleIcon className="text-gray-400 inline-block w-4 h-4" />
</Tooltip>
</div>
{control}
<div className="bordper-b border-secondary border-dashed pb-2 mxp-2"></div>
</div>
);
};
export const BounceLoader = ({
className,
title = "",
}: {
className?: string;
title?: string;
}) => {
return (
<div id="bounce-loader" className="inline-block">
<div className="inline-flex gap-2">
<span className=" rounded-full bg-accent h-2 w-2 inline-block"></span>
<span className="animate-bounce rounded-full bg-accent h-3 w-3 inline-block"></span>
<span className=" rounded-full bg-accent h-2 w-2 inline-block"></span>
</div>
<span className=" text-sm">{title}</span>
</div>
);
};
export const ImageLoader = ({
src,
className = "",
}: {
src: string;
className?: string;
}) => {
const [isLoading, setIsLoading] = useState(true);
return (
<div id="image-loader" className="w-full rounded relative">
{isLoading && (
<div className="absolute h-24 inset-0 flex items-center justify-center">
<BounceLoader title=" loading .." />
</div>
)}
<img
alt="Dynamic content"
src={src}
className={`w-full rounded ${
isLoading ? "opacity-0" : "opacity-100"
} ${className}`}
onLoad={() => setIsLoading(false)}
/>
</div>
);
};
type DataRow = { [key: string]: any };
export const CsvLoader = ({
csvUrl,
className,
}: {
csvUrl: string;
className?: string;
}) => {
const [data, setData] = useState<DataRow[]>([]);
const [columns, setColumns] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [pageSize, setPageSize] = useState<number>(50);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(csvUrl);
const csvString = await response.text();
const parsedData = Papa.parse(csvString, {
header: true,
dynamicTyping: true,
skipEmptyLines: true,
});
setData(parsedData.data as DataRow[]);
// Use the keys of the first object for column headers
const firstRow = parsedData.data[0] as DataRow; // Type assertion
const columnHeaders: any[] = Object.keys(firstRow).map((key) => {
const val = {
title: key.charAt(0).toUpperCase() + key.slice(1), // Capitalize the key for the title
dataIndex: key,
key: key,
};
if (typeof firstRow[key] === "number") {
return {
...val,
sorter: (a: DataRow, b: DataRow) => a[key] - b[key],
};
}
return val;
});
setColumns(columnHeaders);
setIsLoading(false);
} catch (error) {
console.error("Error fetching CSV data:", error);
setIsLoading(false);
}
};
fetchData();
}, [csvUrl]);
// calculate x scroll, based on number of columns
const scrollX = columns.length * 150;
return (
<div id="csv-loader" className={`CsvLoader ${className}`}>
<Table
dataSource={data}
columns={columns}
loading={isLoading}
pagination={{ pageSize: pageSize }}
scroll={{ y: 450, x: scrollX }}
onChange={(pagination) => {
setPageSize(pagination.pageSize || 50);
}}
/>
</div>
);
};
export const CodeLoader = ({
url,
className,
}: {
url: string;
className?: string;
}) => {
const [isLoading, setIsLoading] = useState(true);
const [code, setCode] = useState<string | null>(null);
React.useEffect(() => {
fetch(url)
.then((response) => response.text())
.then((data) => {
setCode(data);
setIsLoading(false);
});
}, [url]);
return (
<div id="code-loader" className={`w-full rounded relative ${className}`}>
{isLoading && (
<div className="absolute h-24 inset-0 flex items-center justify-center">
<BounceLoader />
</div>
)}
{!isLoading && <CodeBlock code={code || ""} language={"python"} />}
</div>
);
};
export const PdfViewer = ({ url }: { url: string }) => {
const [loading, setLoading] = useState<boolean>(true);
React.useEffect(() => {
// Assuming the URL is directly usable as the source for the <object> tag
setLoading(false);
// Note: No need to handle the creation and cleanup of a blob URL or converting file content as it's not provided anymore.
}, [url]);
// Render the PDF viewer
return (
<div id="pdf-viewer" className="h-full">
{loading && <p>Loading PDF...</p>}
{!loading && (
<object
className="w-full rounded"
data={url}
type="application/pdf"
width="100%"
style={{ height: "calc(90vh - 200px)" }}
>
<p>PDF cannot be displayed.</p>
</object>
)}
</div>
);
};
export const MonacoEditor = ({
value,
editorRef,
language,
onChange,
minimap = true,
}: {
value: string;
onChange?: (value: string) => void;
editorRef: any;
language: string;
minimap?: boolean;
}) => {
const [isEditorReady, setIsEditorReady] = useState(false);
const onEditorDidMount = (editor: any, monaco: any) => {
editorRef.current = editor;
setIsEditorReady(true);
};
return (
<div id="monaco-editor" className="h-full rounded">
<Editor
height="100%"
className="h-full rounded"
defaultLanguage={language}
defaultValue={value}
value={value}
onChange={(value: string | undefined) => {
if (onChange && value) {
onChange(value);
}
}}
onMount={onEditorDidMount}
theme="vs-dark"
options={{
wordWrap: "on",
wrappingIndent: "indent",
wrappingStrategy: "advanced",
minimap: {
enabled: minimap,
},
}}
/>
</div>
);
};
export const CardHoverBar = ({
items,
}: {
items: {
title: string;
icon: any;
hoverText: string;
onClick: (e: any) => void;
}[];
}) => {
const itemRows = items.map((item, i) => {
return (
<div
key={"cardhoverrow" + i}
id={`card-hover-bar-item-${i}`}
role="button"
className="text-accent text-xs inline-block hover:bg-primary p-2 rounded"
onClick={item.onClick}
>
<Tooltip title={item.hoverText}>
<item.icon className=" w-5, h-5 cursor-pointer inline-block" />
</Tooltip>
</div>
);
});
return (
<div
id="card-hover-bar"
onMouseEnter={(e) => {
e.stopPropagation();
}}
className=" mt-2 text-right opacity-0 group-hover:opacity-100 "
>
{itemRows}
</div>
);
};
export const AgentRow = ({ message }: { message: any }) => {
return (
<GroupView
title={
<div className="rounded p-1 px-2 inline-block text-xs bg-secondary">
<span className="font-semibold">{message.sender}</span> ( to{" "}
{message.recipient} )
</div>
}
className="m"
>
<MarkdownView data={message.message?.content} className="text-sm" />
</GroupView>
);
};

View File

@@ -0,0 +1,157 @@
import React from "react";
import { Menu } from "@headlessui/react";
import {
BellIcon,
MoonIcon,
SunIcon,
MagnifyingGlassIcon,
} from "@heroicons/react/24/outline";
import {
ChevronDown,
PanelLeftClose,
PanelLeftOpen,
Menu as MenuIcon,
} from "lucide-react";
import { Tooltip } from "antd";
import { appContext } from "../hooks/provider";
import { useConfigStore } from "../hooks/store";
type ContentHeaderProps = {
title?: string;
onMobileMenuToggle: () => void;
isMobileMenuOpen: boolean;
};
const classNames = (...classes: (string | undefined | boolean)[]) => {
return classes.filter(Boolean).join(" ");
};
const ContentHeader = ({
title,
onMobileMenuToggle,
isMobileMenuOpen,
}: ContentHeaderProps) => {
const { darkMode, setDarkMode, user, logout } = React.useContext(appContext);
const { sidebar, setSidebarState } = useConfigStore();
const { isExpanded } = sidebar;
return (
<div className="sticky top-0 z-40 bg-primary border-b border-secondary">
<div className="flex h-16 items-center gap-x-4 px-4">
{/* Mobile Menu Button */}
<button
onClick={onMobileMenuToggle}
className="md:hidden p-2 rounded-md hover:bg-secondary text-secondary hover:text-accent transition-colors"
aria-label="Toggle mobile menu"
>
<MenuIcon className="h-6 w-6" />
</button>
{/* Desktop Sidebar Toggle - Hidden on Mobile */}
<div className="hidden md:block">
<Tooltip title={isExpanded ? "Close Sidebar" : "Open Sidebar"}>
<button
onClick={() => setSidebarState({ isExpanded: !isExpanded })}
className={classNames(
"p-2 rounded-md hover:bg-secondary",
"hover:text-accent text-secondary transition-colors",
"focus:outline-none focus:ring-2 focus:ring-accent focus:ring-opacity-50"
)}
>
{isExpanded ? (
<PanelLeftClose strokeWidth={1.5} className="h-6 w-6" />
) : (
<PanelLeftOpen strokeWidth={1.5} className="h-6 w-6" />
)}
</button>
</Tooltip>
</div>
<div className="flex flex-1 gap-x-4 self-stretch lg:gap-x-6">
{/* Search */}
<div className="flex flex-1 items-center">
<form className="hidden relative flex flex-1">
<label htmlFor="search-field" className="sr-only">
Search
</label>
<MagnifyingGlassIcon className="pointer-events-none absolute inset-y-0 left-0 h-full w-5 text-secondary" />
<input
id="search-field"
type="search"
placeholder="Search..."
className="block h-full w-full border-0 bg-primary py-0 pl-8 pr-0 text-primary placeholder:text-secondary focus:ring-0 sm:text-sm"
/>
</form>
</div>
{/* Right side header items */}
<div className="flex items-center gap-x-4 lg:gap-x-6 ml-auto">
{/* Dark Mode Toggle */}
<button
onClick={() =>
setDarkMode(darkMode === "dark" ? "light" : "dark")
}
className="text-secondary hover:text-primary"
>
{darkMode === "dark" ? (
<MoonIcon className="h-6 w-6" />
) : (
<SunIcon className="h-6 w-6" />
)}
</button>
{/* Notifications */}
<button className="text-secondary hidden hover:text-primary">
<BellIcon className="h-6 w-6" />
</button>
{/* Separator */}
<div className="hidden lg:block lg:h-6 lg:w-px lg:bg-secondary" />
{/* User Menu */}
{user && (
<Menu as="div" className="relative">
<Menu.Button className="flex items-center">
{user.avatar_url ? (
<img
className="h-8 w-8 rounded-full"
src={user.avatar_url}
alt={user.name}
/>
) : (
<div className="border-2 bg-accent h-8 w-8 rounded-full flex items-center justify-center text-white">
{user.name?.[0]}
</div>
)}
<span className="hidden lg:flex lg:items-center">
<span className="ml-4 text-sm text-primary">
{user.name}
</span>
<ChevronDown className="ml-2 h-5 w-5 text-secondary" />
</span>
</Menu.Button>
<Menu.Items className="absolute right-0 mt-2 w-48 origin-top-right rounded-md bg-primary py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<Menu.Item>
{({ active }) => (
<a
href="#"
onClick={() => logout()}
className={`${
active ? "bg-secondary" : ""
} block px-4 py-2 text-sm text-primary`}
>
Sign out
</a>
)}
</Menu.Item>
</Menu.Items>
</Menu>
)}
</div>
</div>
</div>
</div>
);
};
export default ContentHeader;

View File

@@ -17,7 +17,7 @@ const Footer = () => {
}
}, []);
return (
<div className=" mt-4 text-primary p-3 border-t border-secondary flex ">
<div className=" text-primary p-3 border-t border-secondary flex ">
<div className="text-xs flex-1">
Maintained by the AutoGen{" "}
<a

View File

@@ -208,20 +208,62 @@ const Icon = ({ icon = "app", size = 4, className = "" }: Props) => {
className={` ${sizeClass} inline-block `}
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 93 90"
viewBox="0 0 290 264"
>
<path
d="M44.0471 0H31.0006C28.3399 0 25.997 1.75225 25.2451 4.30449L2.26702 82.3045C1.13478 86.1479 4.01575 90 8.02249 90H21.776C24.4553 90 26.8098 88.2236 27.5454 85.6473L49.8165 7.64732C50.9108 3.81465 48.0329 0 44.0471 0Z"
fill="#3F9447"
d="M112.233 104.244C106.455 104.242 100.778 105.756 95.7684 108.635C90.7586 111.513 86.5912 115.655 83.6823 120.647L0 264H38.456C44.2308 263.999 49.9048 262.486 54.9137 259.613C59.9226 256.739 64.0919 252.603 67.0068 247.618L86.2185 214.713L97.1033 196.079L141.807 119.548L150.733 104.244H112.233Z"
fill="url(#paint0_linear_149_456)"
/>
<path
d="M61.8267 39H51.7524C49.9425 39 48.3581 40.2153 47.8891 41.9634L36.3514 84.9634C35.6697 87.5042 37.5841 90 40.2148 90H50.644C52.4654 90 54.0568 88.7695 54.5153 87.0068L65.6979 44.0068C66.3568 41.4731 64.4446 39 61.8267 39Z"
fill="#D9D9D9"
d="M111.547 33.2857L130.813 0L212.939 144.278C215.795 149.3 217.285 154.982 217.262 160.759C217.239 166.535 215.704 172.205 212.809 177.205L193.532 210.49L111.417 66.2122C108.559 61.1911 107.068 55.5088 107.091 49.7316C107.114 43.9544 108.65 38.284 111.547 33.2857Z"
fill="url(#paint1_linear_149_456)"
/>
<path
d="M90.1629 84.234L77.2698 58.0311C77.0912 57.668 76.8537 57.337 76.5672 57.0514C74.5514 55.0426 71.1154 55.9917 70.4166 58.7504L63.7622 85.0177C63.1219 87.5453 65.0322 90 67.6397 90H86.5738C89.5362 90 91.4707 86.8921 90.1629 84.234Z"
fill="#3F9447"
d="M289.285 245.714H123.281C117.498 245.714 111.815 244.199 106.8 241.319C101.785 238.439 97.6121 234.295 94.6976 229.3L86.1748 214.714L97.0596 196.079H241.348C247.134 196.075 252.82 197.588 257.837 200.468C262.855 203.349 267.029 207.495 269.943 212.493L289.285 245.714Z"
fill="url(#paint2_linear_149_456)"
/>
<defs>
<linearGradient
id="paint0_linear_149_456"
x1="116.173"
y1="107.901"
x2="37.3131"
y2="255.118"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#2314CC" />
<stop offset="0.22" stopColor="#234CE4" />
<stop offset="1" stopColor="#4081FF" />
</linearGradient>
<linearGradient
id="paint1_linear_149_456"
x1="200.792"
y1="184.508"
x2="134.199"
y2="47.8169"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#7215D4" />
<stop offset="0.11" stopColor="#7554D5" />
<stop offset="0.56" stopColor="#9E8AE9" />
<stop offset="1" stopColor="#CC99FF" />
</linearGradient>
<linearGradient
id="paint2_linear_149_456"
x1="107.651"
y1="220.896"
x2="271.108"
y2="220.896"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#2E31F0" />
<stop offset="0.2" stopColor="#4081FF" />
<stop offset="0.39" stopColor="#848EE5" />
<stop offset="0.49" stopColor="#8183E2" />
<stop offset="0.65" stopColor="#7866DA" />
<stop offset="0.75" stopColor="#7251D4" />
</linearGradient>
</defs>
</svg>
);
}

View File

@@ -1,10 +1,16 @@
import * as React from "react";
import Header from "./header";
import { Dialog } from "@headlessui/react";
import { X } from "lucide-react";
import { appContext } from "../hooks/provider";
import { useConfigStore } from "../hooks/store";
import Footer from "./footer";
/// import ant css
import "antd/dist/reset.css";
import SideBar from "./sidebar";
import ContentHeader from "./contentheader";
const classNames = (...classes: (string | undefined | boolean)[]) => {
return classes.filter(Boolean).join(" ");
};
type Props = {
title: string;
@@ -23,38 +29,96 @@ const Layout = ({
showHeader = true,
restricted = false,
}: Props) => {
const layoutContent = (
<div
// style={{ height: "calc(100vh - 64px)" }}
className={` h-full flex flex-col`}
>
{showHeader && <Header meta={meta} link={link} />}
<div className="flex-1 text-primary ">
<title>{meta?.title + " | " + title}</title>
<div className=" h-full text-primary">{children}</div>
</div>
<Footer />
</div>
);
const { darkMode } = React.useContext(appContext);
const { sidebar } = useConfigStore();
const { isExpanded } = sidebar;
const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false);
// Close mobile menu on route change
React.useEffect(() => {
setIsMobileMenuOpen(false);
}, [link]);
React.useEffect(() => {
document.getElementsByTagName("html")[0].className = `${
darkMode === "dark" ? "dark bg-primary" : "light bg-primary"
} `;
}`;
}, [darkMode]);
return (
<appContext.Consumer>
{(context: any) => {
if (restricted) {
return <div className="h-full ">{context.user && layoutContent}</div>;
} else {
return layoutContent;
}
}}
</appContext.Consumer>
const layoutContent = (
<div className="min-h-screen flex">
{/* Mobile menu */}
<Dialog
as="div"
open={isMobileMenuOpen}
onClose={() => setIsMobileMenuOpen(false)}
className="relative z-50 md:hidden"
>
{/* Backdrop */}
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
{/* Mobile Sidebar Container */}
<div className="fixed inset-0 flex">
<Dialog.Panel className="relative mr-16 flex w-full max-w-xs flex-1">
<div className="absolute right-0 top-0 flex w-16 justify-center pt-5">
<button
type="button"
className="text-secondary"
onClick={() => setIsMobileMenuOpen(false)}
>
<span className="sr-only">Close sidebar</span>
<X className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<SideBar link={link} meta={meta} isMobile={true} />
</Dialog.Panel>
</div>
</Dialog>
{/* Desktop sidebar */}
<div className="hidden md:flex md:flex-col md:fixed md:inset-y-0">
<SideBar link={link} meta={meta} isMobile={false} />
</div>
{/* Content area */}
<div
className={classNames(
"flex-1 flex flex-col min-h-screen",
"transition-all duration-300 ease-in-out",
"md:pl-16",
isExpanded ? "md:pl-72" : "md:pl-16"
)}
>
{showHeader && (
<ContentHeader
title={title}
isMobileMenuOpen={isMobileMenuOpen}
onMobileMenuToggle={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
/>
)}
<main className="flex-1 p-2 text-primary">{children}</main>
<Footer />
</div>
</div>
);
// Handle restricted content
if (restricted) {
return (
<appContext.Consumer>
{(context: any) => {
if (context.user) {
return layoutContent;
}
return null;
}}
</appContext.Consumer>
);
}
return layoutContent;
};
export default Layout;

View File

@@ -0,0 +1,148 @@
import React from "react";
import { Link } from "gatsby";
import { useConfigStore } from "../hooks/store";
import { Tooltip } from "antd";
import { Blocks, Settings, MessagesSquare } from "lucide-react";
import Icon from "./icons";
const navigation = [
// { name: "Build", href: "/build", icon: Blocks },
{ name: "Playground", href: "/", icon: MessagesSquare },
];
const classNames = (...classes: (string | undefined | boolean)[]) => {
return classes.filter(Boolean).join(" ");
};
type SidebarProps = {
link: string;
meta?: {
title: string;
description: string;
};
isMobile: boolean;
};
const Sidebar = ({ link, meta, isMobile }: SidebarProps) => {
const { sidebar } = useConfigStore();
const { isExpanded } = sidebar;
// Always show full sidebar in mobile view
const showFull = isMobile || isExpanded;
return (
<div
className={classNames(
"flex grow flex-col gap-y-5 overflow-y-auto border-r border-secondary bg-primary",
"transition-all duration-300 ease-in-out",
showFull ? "w-72 px-6" : "w-16 px-2"
)}
>
{/* App Logo/Title */}
<div
className={`flex h-16 items-center ${showFull ? "gap-x-3" : "ml-2"}`}
>
<div className="w-8 text-right text-accent">
<Icon icon="app" size={8} />
</div>
{showFull && (
<div className="flex flex-col" style={{ minWidth: "200px" }}>
<span className="text-base font-semibold text-primary">
{meta?.title}
</span>
<span className="text-xs text-secondary">{meta?.description}</span>
</div>
)}
</div>
{/* Navigation */}
<nav className="flex flex-1 flex-col">
<ul role="list" className="flex flex-1 flex-col gap-y-7">
{/* Main Navigation */}
<li>
<ul
role="list"
className={classNames(
"-mx-2 space-y-1",
!showFull && "items-center"
)}
>
{navigation.map((item) => {
const isActive = item.href === link;
const IconComponent = item.icon;
const navLink = (
<Link
to={item.href}
className={classNames(
isActive
? "text-accent"
: "text-primary hover:text-accent hover:bg-secondary",
"group flex gap-x-3 rounded-md p-2 text-sm font-medium",
!showFull && "justify-center"
)}
>
<IconComponent
className={classNames(
isActive
? "text-accent"
: "text-secondary group-hover:text-accent",
"h-6 w-6 shrink-0"
)}
/>
{showFull && item.name}
</Link>
);
return (
<li key={item.name}>
{!showFull && !isMobile ? (
<Tooltip title={item.name} placement="right">
{navLink}
</Tooltip>
) : (
navLink
)}
</li>
);
})}
</ul>
</li>
{/* Settings at bottom */}
<li
className={classNames(
"mt-auto -mx-2 mb-4",
!showFull && "flex justify-center"
)}
>
{!showFull && !isMobile ? (
<Tooltip title="Settings" placement="right">
<Link
to="/settings"
className={classNames(
"group flex gap-x-3 rounded-md p-2 text-sm font-medium",
"text-primary hover:text-accent hover:bg-secondary",
!showFull && "justify-center"
)}
>
<Settings className="h-6 w-6 shrink-0 text-secondary group-hover:text-accent" />
</Link>
</Tooltip>
) : (
<Link
to="/settings"
className="group flex gap-x-3 rounded-md p-2 text-sm font-medium text-primary hover:text-accent hover:bg-secondary"
>
<Settings className="h-6 w-6 shrink-0 text-secondary group-hover:text-accent" />
{showFull && "Settings"}
</Link>
)}
</li>
</ul>
</nav>
</div>
);
};
export default Sidebar;

View File

@@ -1,127 +0,0 @@
export type NotificationType = "success" | "info" | "warning" | "error";
export interface IMessage {
user_id: string;
role: string;
content: string;
created_at?: string;
updated_at?: string;
session_id?: number;
connection_id?: string;
workflow_id?: number;
meta?: any;
id?: number;
}
export interface IStatus {
message: string;
status: boolean;
data?: any;
}
export interface IChatMessage {
text: string;
sender: "user" | "bot";
meta?: any;
id?: number;
}
export interface ILLMConfig {
config_list: Array<IModelConfig>;
timeout?: number;
cache_seed?: number | null;
temperature: number;
max_tokens: number;
}
export interface IAgentConfig {
name: string;
llm_config?: ILLMConfig | false;
human_input_mode: string;
max_consecutive_auto_reply: number;
system_message: string | "";
is_termination_msg?: boolean | string;
default_auto_reply?: string | null;
code_execution_config?: "none" | "local" | "docker";
description?: string;
admin_name?: string;
messages?: Array<IMessage>;
max_round?: number;
speaker_selection_method?: string;
allow_repeat_speaker?: boolean;
}
export interface IAgent {
type?: "assistant" | "userproxy" | "groupchat";
config: IAgentConfig;
created_at?: string;
updated_at?: string;
id?: number;
skills?: Array<ISkill>;
user_id?: string;
}
export interface IWorkflow {
name: string;
description: string;
sender?: IAgent;
receiver?: IAgent;
type?: "autonomous" | "sequential";
created_at?: string;
updated_at?: string;
summary_method?: "none" | "last" | "llm";
id?: number;
user_id?: string;
}
export interface IModelConfig {
model: string;
api_key?: string;
api_version?: string;
base_url?: string;
api_type?: "open_ai" | "azure" | "google" | "anthropic" | "mistral";
user_id?: string;
created_at?: string;
updated_at?: string;
description?: string;
id?: number;
}
export interface IMetadataFile {
name: string;
path: string;
extension: string;
content: string;
type: string;
}
export interface IChatSession {
id?: number;
user_id: string;
workflow_id?: number;
created_at?: string;
updated_at?: string;
name: string;
}
export interface IGalleryItem {
id: number;
messages: Array<IMessage>;
session: IChatSession;
tags: Array<string>;
created_at: string;
updated_at: string;
}
export interface ISkill {
name: string;
content: string;
secrets?: any[];
libraries?: string[];
id?: number;
description?: string;
user_id?: string;
created_at?: string;
updated_at?: string;
}

View File

@@ -0,0 +1,5 @@
export interface IStatus {
message: string;
status: boolean;
data?: any;
}

View File

@@ -0,0 +1,86 @@
export interface RequestUsage {
prompt_tokens: number;
completion_tokens: number;
}
export interface MessageConfig {
source: string;
content: string;
models_usage?: RequestUsage;
}
export interface DBModel {
id?: number;
user_id?: string;
created_at?: string;
updated_at?: string;
}
export interface Message extends DBModel {
config: MessageConfig;
session_id: number;
run_id: string;
}
export interface Session extends DBModel {
name: string;
team_id?: string;
}
export interface TeamConfig {
name: string;
participants: AgentConfig[];
team_type: TeamTypes;
model_client?: ModelConfig;
termination_condition?: TerminationConfig;
}
export interface Team extends DBModel {
config: TeamConfig;
}
export type ModelTypes = "OpenAIChatCompletionClient";
export type AgentTypes = "AssistantAgent" | "CodingAssistantAgent";
export type TeamTypes = "RoundRobinGroupChat" | "SelectorGroupChat";
export type TerminationTypes =
| "MaxMessageTermination"
| "StopMessageTermination"
| "TextMentionTermination";
export interface ModelConfig {
model: string;
model_type: ModelTypes;
api_key?: string;
base_url?: string;
}
export interface ToolConfig {
name: string;
description: string;
content: string;
}
export interface AgentConfig {
name: string;
agent_type: AgentTypes;
system_message?: string;
model_client?: ModelConfig;
tools?: ToolConfig[];
description?: string;
}
export interface TerminationConfig {
termination_type: TerminationTypes;
max_messages?: number;
text?: string;
}
export interface TaskResult {
messages: MessageConfig[];
usage: string;
duration: number;
stop_reason?: string;
}

View File

@@ -1,12 +1,4 @@
import {
IAgent,
IAgentConfig,
ILLMConfig,
IModelConfig,
ISkill,
IStatus,
IWorkflow,
} from "./types";
import { IStatus } from "./types";
export const getServerUrl = () => {
return process.env.GATSBY_API_URL || "/api";
@@ -100,10 +92,6 @@ export function fetchJSON(
onFinal();
});
}
export const capitalize = (s: string) => {
if (typeof s !== "string") return "";
return s.charAt(0).toUpperCase() + s.slice(1);
};
export function eraseCookie(name: string) {
document.cookie = name + "=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;";
@@ -116,435 +104,6 @@ export function truncateText(text: string, length = 50) {
return text;
}
export const getCaretCoordinates = () => {
let caretX, caretY;
const selection = window.getSelection();
if (selection && selection?.rangeCount !== 0) {
const range = selection.getRangeAt(0).cloneRange();
range.collapse(false);
const rect = range.getClientRects()[0];
if (rect) {
caretX = rect.left;
caretY = rect.top;
}
}
return { caretX, caretY };
};
export const getPrefixSuffix = (container: any) => {
let prefix = "";
let suffix = "";
if (window.getSelection) {
const sel = window.getSelection();
if (sel && sel.rangeCount > 0) {
let range = sel.getRangeAt(0).cloneRange();
range.collapse(true);
range.setStart(container!, 0);
prefix = range.toString();
range = sel.getRangeAt(0).cloneRange();
range.collapse(true);
range.setEnd(container, container.childNodes.length);
suffix = range.toString();
console.log("prefix", prefix);
console.log("suffix", suffix);
}
}
return { prefix, suffix };
};
export const uid = () => {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
};
export const setCaretToEnd = (element: HTMLElement) => {
const range = document.createRange();
const selection = window.getSelection();
range.selectNodeContents(element);
range.collapse(false);
selection?.removeAllRanges();
selection?.addRange(range);
element.focus();
};
// return a color between a start and end color using a percentage
export const ColorTween = (
startColor: string,
endColor: string,
percent: number
) => {
// example startColor = "#ff0000" endColor = "#0000ff" percent = 0.5
const start = {
r: parseInt(startColor.substring(1, 3), 16),
g: parseInt(startColor.substring(3, 5), 16),
b: parseInt(startColor.substring(5, 7), 16),
};
const end = {
r: parseInt(endColor.substring(1, 3), 16),
g: parseInt(endColor.substring(3, 5), 16),
b: parseInt(endColor.substring(5, 7), 16),
};
const r = Math.floor(start.r + (end.r - start.r) * percent);
const g = Math.floor(start.g + (end.g - start.g) * percent);
const b = Math.floor(start.b + (end.b - start.b) * percent);
return `rgb(${r}, ${g}, ${b})`;
};
export const guid = () => {
var w = () => {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
};
return `${w()}${w()}-${w()}-${w()}-${w()}-${w()}${w()}${w()}`;
};
/**
* Takes a string and returns the first n characters followed by asterisks.
* @param {string} str - The string to obscure
* @param {number} n - Number of characters to show before obscuring
* @returns {string} The obscured string with first n characters in clear text
*/
export const obscureString = (str: string, n: number = 3) => {
if (n < 0 || n > str.length) {
console.log("n cannot be less than 0 or greater than the string length.");
return str;
}
// First n characters in clear text
var clearText = str.substring(0, n);
// Remaining characters replaced with asterisks
var obscured = clearText + "*".repeat(str.length - n);
return obscured;
};
/**
* Converts a number of seconds into a human-readable string representing the duration in days, hours, minutes, and seconds.
* @param {number} seconds - The number of seconds to convert.
* @returns {string} A well-formatted duration string.
*/
export const formatDuration = (seconds: number) => {
const units = [
{ label: " day", seconds: 86400 },
{ label: " hr", seconds: 3600 },
{ label: " min", seconds: 60 },
{ label: " sec", seconds: 1 },
];
let remainingSeconds = seconds;
const parts = [];
for (const { label, seconds: unitSeconds } of units) {
const count = Math.floor(remainingSeconds / unitSeconds);
if (count > 0) {
parts.push(count + (count > 1 ? label + "s" : label));
remainingSeconds -= count * unitSeconds;
}
}
return parts.length > 0 ? parts.join(" ") : "0 sec";
};
export const sampleModelConfig = (modelType: string = "open_ai") => {
const openaiConfig: IModelConfig = {
model: "gpt-4-1106-preview",
api_type: "open_ai",
description: "OpenAI GPT-4 model",
};
const azureConfig: IModelConfig = {
model: "gpt-4",
api_type: "azure",
api_version: "v1",
base_url: "https://youazureendpoint.azure.com/",
description: "Azure model",
};
const googleConfig: IModelConfig = {
model: "gemini-1.0-pro",
api_type: "google",
description: "Google Gemini Model model",
};
const anthropicConfig: IModelConfig = {
model: "claude-3-5-sonnet-20240620",
api_type: "anthropic",
description: "Claude 3.5 Sonnet model",
};
const mistralConfig: IModelConfig = {
model: "mistral",
api_type: "mistral",
description: "Mistral model",
};
switch (modelType) {
case "open_ai":
return openaiConfig;
case "azure":
return azureConfig;
case "google":
return googleConfig;
case "anthropic":
return anthropicConfig;
case "mistral":
return mistralConfig;
default:
return openaiConfig;
}
};
export const getRandomIntFromDateAndSalt = (salt: number = 43444) => {
const currentDate = new Date();
const seed = currentDate.getTime() + salt;
const randomValue = Math.sin(seed) * 10000;
const randomInt = Math.floor(randomValue) % 100;
return randomInt;
};
export const getSampleWorkflow = (workflow_type: string = "autonomous") => {
const autonomousWorkflow: IWorkflow = {
name: "Default Chat Workflow",
description: "Autonomous Workflow",
type: "autonomous",
summary_method: "llm",
};
const sequentialWorkflow: IWorkflow = {
name: "Default Sequential Workflow",
description: "Sequential Workflow",
type: "sequential",
summary_method: "llm",
};
if (workflow_type === "autonomous") {
return autonomousWorkflow;
} else if (workflow_type === "sequential") {
return sequentialWorkflow;
} else {
return autonomousWorkflow;
}
};
export const sampleAgentConfig = (agent_type: string = "assistant") => {
const llm_config: ILLMConfig = {
config_list: [],
temperature: 0.1,
timeout: 600,
cache_seed: null,
max_tokens: 4000,
};
const userProxyConfig: IAgentConfig = {
name: "userproxy",
human_input_mode: "NEVER",
description: "User Proxy",
max_consecutive_auto_reply: 25,
system_message: "You are a helpful assistant.",
default_auto_reply: "TERMINATE",
llm_config: false,
code_execution_config: "local",
};
const userProxyFlowSpec: IAgent = {
type: "userproxy",
config: userProxyConfig,
};
const assistantConfig: IAgentConfig = {
name: "primary_assistant",
description: "Primary Assistant",
llm_config: llm_config,
human_input_mode: "NEVER",
max_consecutive_auto_reply: 25,
code_execution_config: "none",
system_message:
"You are a helpful AI assistant. Solve tasks using your coding and language skills. In the following cases, suggest python code (in a python coding block) or shell script (in a sh coding block) for the user to execute. 1. When you need to collect info, use the code to output the info you need, for example, browse or search the web, download/read a file, print the content of a webpage or a file, get the current date/time, check the operating system. After sufficient info is printed and the task is ready to be solved based on your language skill, you can solve the task by yourself. 2. When you need to perform some task with code, use the code to perform the task and output the result. Finish the task smartly. Solve the task step by step if you need to. If a plan is not provided, explain your plan first. Be clear which step uses code, and which step uses your language skill. When using code, you must indicate the script type in the code block. The user cannot provide any other feedback or perform any other action beyond executing the code you suggest. The user can't modify your code. So do not suggest incomplete code which requires users to modify. Don't use a code block if it's not intended to be executed by the user. If you want the user to save the code in a file before executing it, put # filename: <filename> inside the code block as the first line. Don't include multiple code blocks in one response. Do not ask users to copy and paste the result. Instead, use 'print' function for the output when relevant. Check the execution result returned by the user. If the result indicates there is an error, fix the error and output the code again. Suggest the full code instead of partial code or code changes. If the error can't be fixed or if the task is not solved even after the code is executed successfully, analyze the problem, revisit your assumption, collect additional info you need, and think of a different approach to try. When you find an answer, verify the answer carefully. Include verifiable evidence in your response if possible. Reply 'TERMINATE' in the end when everything is done.",
};
const assistantFlowSpec: IAgent = {
type: "assistant",
config: assistantConfig,
};
const groupChatAssistantConfig = Object.assign(
{
admin_name: "groupchat_assistant",
messages: [],
max_round: 10,
speaker_selection_method: "auto",
allow_repeat_speaker: false,
},
assistantConfig
);
groupChatAssistantConfig.name = "groupchat_assistant";
groupChatAssistantConfig.system_message =
"You are a helpful assistant skilled at cordinating a group of other assistants to solve a task. ";
groupChatAssistantConfig.description = "Group Chat Assistant";
const groupChatFlowSpec: IAgent = {
type: "groupchat",
config: groupChatAssistantConfig,
};
if (agent_type === "userproxy") {
return userProxyFlowSpec;
} else if (agent_type === "assistant") {
return assistantFlowSpec;
} else if (agent_type === "groupchat") {
return groupChatFlowSpec;
} else {
return assistantFlowSpec;
}
};
export const getSampleSkill = () => {
const content = `
from typing import List
import uuid
import requests # to perform HTTP requests
from pathlib import Path
from openai import OpenAI
def generate_and_save_images(query: str, image_size: str = "1024x1024") -> List[str]:
"""
Function to paint, draw or illustrate images based on the users query or request. Generates images from a given query using OpenAI's DALL-E model and saves them to disk. Use the code below anytime there is a request to create an image.
:param query: A natural language description of the image to be generated.
:param image_size: The size of the image to be generated. (default is "1024x1024")
:return: A list of filenames for the saved images.
"""
client = OpenAI() # Initialize the OpenAI client
response = client.images.generate(model="dall-e-3", prompt=query, n=1, size=image_size) # Generate images
# List to store the file names of saved images
saved_files = []
# Check if the response is successful
if response.data:
for image_data in response.data:
# Generate a random UUID as the file name
file_name = str(uuid.uuid4()) + ".png" # Assuming the image is a PNG
file_path = Path(file_name)
img_url = image_data.url
img_response = requests.get(img_url)
if img_response.status_code == 200:
# Write the binary content to a file
with open(file_path, "wb") as img_file:
img_file.write(img_response.content)
print(f"Image saved to {file_path}")
saved_files.append(str(file_path))
else:
print(f"Failed to download the image from {img_url}")
else:
print("No image data found in the response!")
# Return the list of saved files
return saved_files
# Example usage of the function:
# generate_and_save_images("A cute baby sea otter")
`;
const skill: ISkill = {
name: "generate_and_save_images",
description: "Generate and save images based on a user's query.",
content: content,
};
return skill;
};
export const timeAgo = (
dateString: string,
returnFormatted: boolean = false
): string => {
// if dateStr is empty, return empty string
if (!dateString) {
return "";
}
// Parse the date string into a Date object
const timestamp = new Date(dateString);
// Check for invalid date
if (isNaN(timestamp.getTime())) {
throw new Error("Invalid date string provided.");
}
// Get the current time
const now = new Date();
// Calculate the difference in milliseconds
const timeDifference = now.getTime() - timestamp.getTime();
// Convert time difference to minutes and hours
const minutesAgo = Math.floor(timeDifference / (1000 * 60));
const hoursAgo = Math.floor(minutesAgo / 60);
// Format the date into a readable format e.g. "November 27, 2021, 3:45 PM"
const options: Intl.DateTimeFormatOptions = {
month: "long",
day: "numeric",
year: "numeric",
hour: "numeric",
minute: "numeric",
};
const formattedDate = timestamp.toLocaleDateString(undefined, options);
if (returnFormatted) {
return formattedDate;
}
// Determine the time difference string
let timeAgoStr: string;
if (minutesAgo < 1) {
timeAgoStr = "just now";
} else if (minutesAgo < 60) {
// Less than an hour ago, display minutes
timeAgoStr = `${minutesAgo} ${minutesAgo === 1 ? "minute" : "minutes"} ago`;
} else if (hoursAgo < 24) {
// Less than a day ago, display hours
timeAgoStr = `${hoursAgo} ${hoursAgo === 1 ? "hour" : "hours"} ago`;
} else {
// More than a day ago, display the formatted date
timeAgoStr = formattedDate;
}
// Return the final readable string
return timeAgoStr;
};
export const examplePrompts = [
{
title: "Stock Price",
prompt:
"Plot a chart of NVDA and TESLA stock price for 2023. Save the result to a file named nvda_tesla.png",
},
{
title: "Sine Wave",
prompt:
"Write a python script to plot a sine wave and save it to disc as a png file sine_wave.png",
},
{
title: "Markdown",
prompt:
"List out the top 5 rivers in africa and their length and return that as a markdown table. Do not try to write any code, just write the table",
},
{
title: "Paint",
prompt:
"paint a picture of a glass of ethiopian coffee, freshly brewed in a tall glass cup, on a table right in front of a lush green forest scenery",
},
{
title: "Travel",
prompt:
"Plan a 2 day trip to hawaii. Limit to 3 activities per day, be as brief as possible!",
},
];
export const fetchVersion = () => {
const versionUrl = getServerUrl() + "/version";
return fetch(versionUrl)
@@ -557,128 +116,3 @@ export const fetchVersion = () => {
return null;
});
};
/**
* Recursively sanitizes JSON objects by replacing specific keys with a given value.
* @param {JsonValue} data - The JSON data to be sanitized.
* @param {string[]} keys - An array of keys to be replaced in the JSON object.
* @param {string} replacement - The value to use as replacement for the specified keys.
* @returns {JsonValue} - The sanitized JSON data.
*/
export const sanitizeConfig = (
data: any,
keys: string[] = ["api_key", "id", "created_at", "updated_at", "secrets"]
): any => {
if (Array.isArray(data)) {
return data.map((item) => sanitizeConfig(item, keys));
} else if (typeof data === "object" && data !== null) {
Object.keys(data).forEach((key) => {
if (keys.includes(key)) {
delete data[key];
} else {
data[key] = sanitizeConfig(data[key], keys);
}
});
}
return data;
};
/**
* Checks the input text against the regex '^[a-zA-Z0-9_-]{1,64}$' and returns an object with
* status, message, and sanitizedText. Status is boolean indicating whether input text is valid,
* message provides information about the outcome, and sanitizedText contains a valid version
* of the input text or the original text if it was already valid.
*
* @param text - The input string to be checked and sanitized.
* @returns An object containing a status, a message, and sanitizedText.
*/
export const checkAndSanitizeInput = (
text: string
): { status: boolean; message: string; sanitizedText: string } => {
// Create a regular expression pattern to match valid characters
const regexPattern: RegExp = /^[a-zA-Z0-9_-]{1,64}$/;
let status: boolean = true;
let message: string;
let sanitizedText: string;
// Check if the input text matches the pattern
if (regexPattern.test(text)) {
// Text already adheres to the pattern
message = `The text '${text}' is valid.`;
sanitizedText = text;
} else {
// The text does not match; sanitize the input
status = false;
sanitizedText = text.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
message = `'${text}' is invalid. Consider using '${sanitizedText}' instead.`;
}
return { status, message, sanitizedText };
};
export const isValidConfig = (
jsonObj: any,
templateObj: any,
diffThreshold: number = 4
): {
status: boolean;
message: string;
} => {
// Check if both parameters are indeed objects and not null
if (
typeof jsonObj !== "object" ||
jsonObj === null ||
Array.isArray(jsonObj) ||
typeof templateObj !== "object" ||
templateObj === null ||
Array.isArray(templateObj)
) {
return {
status: false,
message:
"Invalid input: One or both parameters are not objects, or are null or arrays.",
};
}
const jsonKeys = new Set(Object.keys(jsonObj));
const templateKeys = new Set(Object.keys(templateObj));
if (jsonKeys.size !== templateKeys.size) {
if (Math.abs(jsonKeys.size - templateKeys.size) > diffThreshold) {
return {
status: false,
message:
"Configuration does not match template: Number of keys differ.",
};
}
}
for (const key of templateKeys) {
if (!jsonKeys.has(key)) {
return {
status: false,
message: `Configuration does not match template: Missing key '${key}' in configuration.`,
};
}
// If the value is an object, recursively validate
if (
typeof templateObj[key] === "object" &&
templateObj[key] !== null &&
!Array.isArray(templateObj[key])
) {
const result = isValidConfig(jsonObj[key], templateObj[key]);
if (!result.status) {
return {
status: false,
message: `Configuration error in nested key '${key}': ${result.message}`,
};
}
}
}
return {
status: true,
message: "Configuration is valid.",
};
};

View File

@@ -1,385 +0,0 @@
import {
ArrowDownTrayIcon,
ArrowUpTrayIcon,
DocumentDuplicateIcon,
InformationCircleIcon,
PlusIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import { Dropdown, MenuProps, Modal, message } from "antd";
import * as React from "react";
import { IAgent, IStatus } from "../../types";
import { appContext } from "../../../hooks/provider";
import {
fetchJSON,
getServerUrl,
sanitizeConfig,
timeAgo,
truncateText,
} from "../../utils";
import { BounceLoader, Card, CardHoverBar, LoadingOverlay } from "../../atoms";
import { AgentViewer } from "./utils/agentconfig";
const AgentsView = ({}: any) => {
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const listAgentsUrl = `${serverUrl}/agents?user_id=${user?.email}`;
const [agents, setAgents] = React.useState<IAgent[] | null>([]);
const [selectedAgent, setSelectedAgent] = React.useState<IAgent | null>(null);
const [showNewAgentModal, setShowNewAgentModal] = React.useState(false);
const [showAgentModal, setShowAgentModal] = React.useState(false);
const sampleAgent = {
config: {
name: "sample_agent",
description: "Sample agent description",
human_input_mode: "NEVER",
max_consecutive_auto_reply: 3,
system_message: "",
},
};
const [newAgent, setNewAgent] = React.useState<IAgent | null>(sampleAgent);
const deleteAgent = (agent: IAgent) => {
setError(null);
setLoading(true);
const deleteAgentUrl = `${serverUrl}/agents/delete?user_id=${user?.email}&agent_id=${agent.id}`;
// const fetch;
const payLoad = {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: user?.email,
agent: agent,
}),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
fetchAgents();
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(deleteAgentUrl, payLoad, onSuccess, onError);
};
const fetchAgents = () => {
setError(null);
setLoading(true);
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
setAgents(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(listAgentsUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user) {
// console.log("fetching messages", messages);
fetchAgents();
}
}, []);
const agentRows = (agents || []).map((agent: IAgent, i: number) => {
const cardItems = [
{
title: "Download",
icon: ArrowDownTrayIcon,
onClick: (e: any) => {
e.stopPropagation();
// download workflow as workflow.name.json
const element = document.createElement("a");
const sanitizedAgent = sanitizeConfig(agent);
const file = new Blob([JSON.stringify(sanitizedAgent)], {
type: "application/json",
});
element.href = URL.createObjectURL(file);
element.download = `agent_${agent.config.name}.json`;
document.body.appendChild(element); // Required for this to work in FireFox
element.click();
},
hoverText: "Download",
},
{
title: "Make a Copy",
icon: DocumentDuplicateIcon,
onClick: (e: any) => {
e.stopPropagation();
let newAgent = { ...sanitizeConfig(agent) };
newAgent.config.name = `${agent.config.name}_copy`;
console.log("newAgent", newAgent);
setNewAgent(newAgent);
setShowNewAgentModal(true);
},
hoverText: "Make a Copy",
},
{
title: "Delete",
icon: TrashIcon,
onClick: (e: any) => {
e.stopPropagation();
deleteAgent(agent);
},
hoverText: "Delete",
},
];
return (
<li
role="listitem"
key={"agentrow" + i}
className=" "
style={{ width: "200px" }}
>
<Card
className="h-full p-2 cursor-pointer"
title={
<div className=" ">
{truncateText(agent.config.name || "", 25)}
</div>
}
onClick={() => {
setSelectedAgent(agent);
setShowAgentModal(true);
}}
>
<div
style={{ minHeight: "65px" }}
aria-hidden="true"
className="my-2 break-words"
>
<div className="text-xs mb-2">{agent.type}</div>{" "}
{truncateText(agent.config.description || "", 70)}
</div>
<div
aria-label={`Updated ${timeAgo(agent.updated_at || "")}`}
className="text-xs"
>
{timeAgo(agent.updated_at || "")}
</div>
<CardHoverBar items={cardItems} />
</Card>
</li>
);
});
const AgentModal = ({
agent,
setAgent,
showAgentModal,
setShowAgentModal,
handler,
}: {
agent: IAgent | null;
setAgent: (agent: IAgent | null) => void;
showAgentModal: boolean;
setShowAgentModal: (show: boolean) => void;
handler?: (agent: IAgent | null) => void;
}) => {
const [localAgent, setLocalAgent] = React.useState<IAgent | null>(agent);
const closeModal = () => {
setShowAgentModal(false);
if (handler) {
handler(localAgent);
}
};
return (
<Modal
title={<>Agent Configuration</>}
width={800}
open={showAgentModal}
onOk={() => {
closeModal();
}}
onCancel={() => {
closeModal();
}}
footer={[]}
>
{agent && (
<AgentViewer
agent={localAgent || agent}
setAgent={setLocalAgent}
close={closeModal}
/>
)}
{/* {JSON.stringify(localAgent)} */}
</Modal>
);
};
const uploadAgent = () => {
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
input.onchange = (e: any) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (e: any) => {
const contents = e.target.result;
if (contents) {
try {
const agent = JSON.parse(contents);
// TBD validate that it is a valid agent
if (!agent.config) {
throw new Error(
"Invalid agent file. An agent must have a config"
);
}
setNewAgent(agent);
setShowNewAgentModal(true);
} catch (err) {
message.error(
"Invalid agent file. Please upload a valid agent file."
);
}
}
};
reader.readAsText(file);
};
input.click();
};
const agentsMenuItems: MenuProps["items"] = [
// {
// type: "divider",
// },
{
key: "uploadagent",
label: (
<div>
<ArrowUpTrayIcon className="w-5 h-5 inline-block mr-2" />
Upload Agent
</div>
),
},
];
const agentsMenuItemOnClick: MenuProps["onClick"] = ({ key }) => {
if (key === "uploadagent") {
uploadAgent();
return;
}
};
return (
<div className="text-primary ">
<AgentModal
agent={selectedAgent}
setAgent={setSelectedAgent}
setShowAgentModal={setShowAgentModal}
showAgentModal={showAgentModal}
handler={(agent: IAgent | null) => {
fetchAgents();
}}
/>
<AgentModal
agent={newAgent || sampleAgent}
setAgent={setNewAgent}
setShowAgentModal={setShowNewAgentModal}
showAgentModal={showNewAgentModal}
handler={(agent: IAgent | null) => {
fetchAgents();
}}
/>
<div className="mb-2 relative">
<div className=" rounded ">
<div className="flex mt-2 pb-2 mb-2 border-b">
<div className="flex-1 font-semibold mb-2 ">
{" "}
Agents ({agentRows.length}){" "}
</div>
<div>
<Dropdown.Button
type="primary"
menu={{
items: agentsMenuItems,
onClick: agentsMenuItemOnClick,
}}
placement="bottomRight"
trigger={["click"]}
onClick={() => {
setShowNewAgentModal(true);
}}
>
<PlusIcon className="w-5 h-5 inline-block mr-1" />
New Agent
</Dropdown.Button>
</div>
</div>
<div className="text-xs mb-2 pb-1 ">
{" "}
Configure an agent that can reused in your agent workflow .
<div>
Tip: You can also create a Group of Agents ( New Agent -
GroupChat) which can have multiple agents in it.
</div>
</div>
{agents && agents.length > 0 && (
<div className="w-full relative">
<LoadingOverlay loading={loading} />
<ul className=" flex flex-wrap gap-3">{agentRows}</ul>
</div>
)}
{agents && agents.length === 0 && !loading && (
<div className="text-sm border mt-4 rounded text-secondary p-2">
<InformationCircleIcon className="h-4 w-4 inline mr-1" />
No agents found. Please create a new agent.
</div>
)}
{loading && (
<div className=" w-full text-center">
{" "}
<BounceLoader />{" "}
<span className="inline-block"> loading .. </span>
</div>
)}
</div>
</div>
</div>
);
};
export default AgentsView;

View File

@@ -1,81 +0,0 @@
import * as React from "react";
import SkillsView from "./skills";
import AgentsView from "./agents";
import WorkflowView from "./workflow";
import { Tabs } from "antd";
import {
BugAntIcon,
CpuChipIcon,
Square2StackIcon,
Square3Stack3DIcon,
} from "@heroicons/react/24/outline";
import ModelsView from "./models";
const BuildView = () => {
return (
<div className=" ">
{/* <div className="mb-4 text-2xl">Build </div> */}
<div className="mb-6 text-sm hidden text-secondary">
{" "}
Create skills, agents and workflows for building multiagent capabilities{" "}
</div>
<div className="mb-4 text-primary">
{" "}
<Tabs
tabBarStyle={{ paddingLeft: 0, marginLeft: 0 }}
defaultActiveKey="4"
tabPosition="left"
items={[
{
label: (
<div className="w-full ">
{" "}
<BugAntIcon className="h-4 w-4 inline-block mr-1" />
Skills
</div>
),
key: "1",
children: <SkillsView />,
},
{
label: (
<div className="w-full ">
{" "}
<CpuChipIcon className="h-4 w-4 inline-block mr-1" />
Models
</div>
),
key: "2",
children: <ModelsView />,
},
{
label: (
<>
<Square2StackIcon className="h-4 w-4 inline-block mr-1" />
Agents
</>
),
key: "3",
children: <AgentsView />,
},
{
label: (
<>
<Square3Stack3DIcon className="h-4 w-4 inline-block mr-1" />
Workflows
</>
),
key: "4",
children: <WorkflowView />,
},
]}
/>
</div>
<div></div>
</div>
);
};
export default BuildView;

View File

@@ -1,403 +0,0 @@
import {
ArrowDownTrayIcon,
ArrowUpTrayIcon,
DocumentDuplicateIcon,
InformationCircleIcon,
PlusIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import { Dropdown, MenuProps, Modal, message } from "antd";
import * as React from "react";
import { IModelConfig, IStatus } from "../../types";
import { appContext } from "../../../hooks/provider";
import {
fetchJSON,
getServerUrl,
sanitizeConfig,
timeAgo,
truncateText,
} from "../../utils";
import { BounceLoader, Card, CardHoverBar, LoadingOverlay } from "../../atoms";
import { ModelConfigView } from "./utils/modelconfig";
const ModelsView = ({}: any) => {
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const listModelsUrl = `${serverUrl}/models?user_id=${user?.email}`;
const createModelUrl = `${serverUrl}/models`;
const testModelUrl = `${serverUrl}/models/test`;
const defaultModel: IModelConfig = {
model: "gpt-4-1106-preview",
description: "Sample OpenAI GPT-4 model",
user_id: user?.email,
};
const [models, setModels] = React.useState<IModelConfig[] | null>([]);
const [selectedModel, setSelectedModel] = React.useState<IModelConfig | null>(
null
);
const [newModel, setNewModel] = React.useState<IModelConfig | null>(
defaultModel
);
const [showNewModelModal, setShowNewModelModal] = React.useState(false);
const [showModelModal, setShowModelModal] = React.useState(false);
const deleteModel = (model: IModelConfig) => {
setError(null);
setLoading(true);
const deleteModelUrl = `${serverUrl}/models/delete?user_id=${user?.email}&model_id=${model.id}`;
const payLoad = {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
fetchModels();
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(deleteModelUrl, payLoad, onSuccess, onError);
};
const fetchModels = () => {
setError(null);
setLoading(true);
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
setModels(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(listModelsUrl, payLoad, onSuccess, onError);
};
const createModel = (model: IModelConfig) => {
setError(null);
setLoading(true);
model.user_id = user?.email;
const payLoad = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(model),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
const updatedModels = [data.data].concat(models || []);
setModels(updatedModels);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(createModelUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user) {
// console.log("fetching messages", messages);
fetchModels();
}
}, []);
const modelRows = (models || []).map((model: IModelConfig, i: number) => {
const cardItems = [
{
title: "Download",
icon: ArrowDownTrayIcon,
onClick: (e: any) => {
e.stopPropagation();
// download workflow as workflow.name.json
const element = document.createElement("a");
const sanitizedSkill = sanitizeConfig(model);
const file = new Blob([JSON.stringify(sanitizedSkill)], {
type: "application/json",
});
element.href = URL.createObjectURL(file);
element.download = `model_${model.model}.json`;
document.body.appendChild(element); // Required for this to work in FireFox
element.click();
},
hoverText: "Download",
},
{
title: "Make a Copy",
icon: DocumentDuplicateIcon,
onClick: (e: any) => {
e.stopPropagation();
let newModel = { ...sanitizeConfig(model) };
newModel.model = `${model.model}_copy`;
setNewModel(newModel);
setShowNewModelModal(true);
},
hoverText: "Make a Copy",
},
{
title: "Delete",
icon: TrashIcon,
onClick: (e: any) => {
e.stopPropagation();
deleteModel(model);
},
hoverText: "Delete",
},
];
return (
<li
role="listitem"
key={"modelrow" + i}
className=" "
style={{ width: "200px" }}
>
<Card
className="h-full p-2 cursor-pointer"
title={
<div className=" ">{truncateText(model.model || "", 20)}</div>
}
onClick={() => {
setSelectedModel(model);
setShowModelModal(true);
}}
>
<div style={{ minHeight: "65px" }} className="my-2 break-words">
{" "}
{truncateText(model.description || model.model || "", 70)}
</div>
<div
aria-label={`Updated ${timeAgo(model.updated_at || "")} `}
className="text-xs"
>
{timeAgo(model.updated_at || "")}
</div>
<CardHoverBar items={cardItems} />
</Card>
</li>
);
});
const ModelModal = ({
model,
setModel,
showModelModal,
setShowModelModal,
handler,
}: {
model: IModelConfig;
setModel: (model: IModelConfig | null) => void;
showModelModal: boolean;
setShowModelModal: (show: boolean) => void;
handler?: (agent: IModelConfig) => void;
}) => {
const [localModel, setLocalModel] = React.useState<IModelConfig>(model);
const closeModal = () => {
setModel(null);
setShowModelModal(false);
if (handler) {
handler(model);
}
};
return (
<Modal
title={
<>
Model Specification{" "}
<span className="text-accent font-normal">{model?.model}</span>{" "}
</>
}
width={800}
open={showModelModal}
footer={[]}
onOk={() => {
closeModal();
}}
onCancel={() => {
closeModal();
}}
>
{model && (
<ModelConfigView
model={localModel}
setModel={setLocalModel}
close={closeModal}
/>
)}
</Modal>
);
};
const uploadModel = () => {
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
input.onchange = (e: any) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (e: any) => {
const contents = e.target.result;
if (contents) {
try {
const model = JSON.parse(contents);
if (model) {
setNewModel(model);
setShowNewModelModal(true);
}
} catch (e) {
message.error("Invalid model file");
}
}
};
reader.readAsText(file);
};
input.click();
};
const modelsMenuItems: MenuProps["items"] = [
// {
// type: "divider",
// },
{
key: "uploadmodel",
label: (
<div>
<ArrowUpTrayIcon className="w-5 h-5 inline-block mr-2" />
Upload Model
</div>
),
},
];
const modelsMenuItemOnClick: MenuProps["onClick"] = ({ key }) => {
if (key === "uploadmodel") {
uploadModel();
return;
}
};
return (
<div className="text-primary ">
{selectedModel && (
<ModelModal
model={selectedModel}
setModel={setSelectedModel}
setShowModelModal={setShowModelModal}
showModelModal={showModelModal}
handler={(model: IModelConfig | null) => {
fetchModels();
}}
/>
)}
<ModelModal
model={newModel || defaultModel}
setModel={setNewModel}
setShowModelModal={setShowNewModelModal}
showModelModal={showNewModelModal}
handler={(model: IModelConfig | null) => {
fetchModels();
}}
/>
<div className="mb-2 relative">
<div className=" rounded ">
<div className="flex mt-2 pb-2 mb-2 border-b">
<div className="flex-1 font-semibold mb-2 ">
{" "}
Models ({modelRows.length}){" "}
</div>
<div>
<Dropdown.Button
type="primary"
menu={{
items: modelsMenuItems,
onClick: modelsMenuItemOnClick,
}}
placement="bottomRight"
trigger={["click"]}
onClick={() => {
setShowNewModelModal(true);
}}
>
<PlusIcon className="w-5 h-5 inline-block mr-1" />
New Model
</Dropdown.Button>
</div>
</div>
<div className="text-xs mb-2 pb-1 ">
{" "}
Create model configurations that can be reused in your agents and
workflows. {selectedModel?.model}
</div>
{models && models.length > 0 && (
<div className="w-full relative">
<LoadingOverlay loading={loading} />
<ul className=" flex flex-wrap gap-3">{modelRows}</ul>
</div>
)}
{models && models.length === 0 && !loading && (
<div className="text-sm border mt-4 rounded text-secondary p-2">
<InformationCircleIcon className="h-4 w-4 inline mr-1" />
No models found. Please create a new model which can be reused
with agents.
</div>
)}
{loading && (
<div className=" w-full text-center">
{" "}
<BounceLoader />{" "}
<span className="inline-block"> loading .. </span>
</div>
)}
</div>
</div>
</div>
);
};
export default ModelsView;

View File

@@ -1,380 +0,0 @@
import {
ArrowDownTrayIcon,
ArrowUpTrayIcon,
CodeBracketIcon,
CodeBracketSquareIcon,
DocumentDuplicateIcon,
InformationCircleIcon,
KeyIcon,
PlusIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import { Button, Input, Modal, message, MenuProps, Dropdown, Tabs } from "antd";
import * as React from "react";
import { ISkill, IStatus } from "../../types";
import { appContext } from "../../../hooks/provider";
import {
fetchJSON,
getSampleSkill,
getServerUrl,
sanitizeConfig,
timeAgo,
truncateText,
} from "../../utils";
import {
BounceLoader,
Card,
CardHoverBar,
LoadingOverlay,
MonacoEditor,
} from "../../atoms";
import { SkillSelector } from "./utils/selectors";
import { SkillConfigView } from "./utils/skillconfig";
const SkillsView = ({}: any) => {
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const listSkillsUrl = `${serverUrl}/skills?user_id=${user?.email}`;
const saveSkillsUrl = `${serverUrl}/skills`;
const [skills, setSkills] = React.useState<ISkill[] | null>([]);
const [selectedSkill, setSelectedSkill] = React.useState<any>(null);
const [showSkillModal, setShowSkillModal] = React.useState(false);
const [showNewSkillModal, setShowNewSkillModal] = React.useState(false);
const sampleSkill = getSampleSkill();
const [newSkill, setNewSkill] = React.useState<ISkill | null>(sampleSkill);
const deleteSkill = (skill: ISkill) => {
setError(null);
setLoading(true);
// const fetch;
const deleteSkillUrl = `${serverUrl}/skills/delete?user_id=${user?.email}&skill_id=${skill.id}`;
const payLoad = {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: user?.email,
skill: skill,
}),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
fetchSkills();
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(deleteSkillUrl, payLoad, onSuccess, onError);
};
const fetchSkills = () => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
// message.success(data.message);
console.log("skills", data.data);
setSkills(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(listSkillsUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user) {
// console.log("fetching messages", messages);
fetchSkills();
}
}, []);
const skillRows = (skills || []).map((skill: ISkill, i: number) => {
const cardItems = [
{
title: "Download",
icon: ArrowDownTrayIcon,
onClick: (e: any) => {
e.stopPropagation();
// download workflow as workflow.name.json
const element = document.createElement("a");
const sanitizedSkill = sanitizeConfig(skill);
const file = new Blob([JSON.stringify(sanitizedSkill)], {
type: "application/json",
});
element.href = URL.createObjectURL(file);
element.download = `skill_${skill.name}.json`;
document.body.appendChild(element); // Required for this to work in FireFox
element.click();
},
hoverText: "Download",
},
{
title: "Make a Copy",
icon: DocumentDuplicateIcon,
onClick: (e: any) => {
e.stopPropagation();
let newSkill = { ...sanitizeConfig(skill) };
newSkill.name = `${skill.name}_copy`;
setNewSkill(newSkill);
setShowNewSkillModal(true);
},
hoverText: "Make a Copy",
},
{
title: "Delete",
icon: TrashIcon,
onClick: (e: any) => {
e.stopPropagation();
deleteSkill(skill);
},
hoverText: "Delete",
},
];
return (
<li key={"skillrow" + i} className=" " style={{ width: "200px" }}>
<div>
{" "}
<Card
className="h-full p-2 cursor-pointer group"
title={truncateText(skill.name, 25)}
onClick={() => {
setSelectedSkill(skill);
setShowSkillModal(true);
}}
>
<div
style={{ minHeight: "65px" }}
className="my-2 break-words"
aria-hidden="true"
>
{" "}
{skill.description
? truncateText(skill.description || "", 70)
: truncateText(skill.content || "", 70)}
</div>
<div
aria-label={`Updated ${timeAgo(skill.updated_at || "")}`}
className="text-xs"
>
{timeAgo(skill.updated_at || "")}
</div>
<CardHoverBar items={cardItems} />
</Card>
<div className="text-right mt-2"></div>
</div>
</li>
);
});
const SkillModal = ({
skill,
setSkill,
showSkillModal,
setShowSkillModal,
handler,
}: {
skill: ISkill | null;
setSkill: any;
showSkillModal: boolean;
setShowSkillModal: any;
handler: any;
}) => {
const editorRef = React.useRef<any | null>(null);
const [localSkill, setLocalSkill] = React.useState<ISkill | null>(skill);
const closeModal = () => {
setSkill(null);
setShowSkillModal(false);
if (handler) {
handler(skill);
}
};
return (
<Modal
title={
<>
Skill Specification{" "}
<span className="text-accent font-normal">{localSkill?.name}</span>{" "}
</>
}
width={800}
open={showSkillModal}
onCancel={() => {
setShowSkillModal(false);
}}
footer={[]}
>
{localSkill && (
<SkillConfigView
skill={localSkill}
setSkill={setLocalSkill}
close={closeModal}
/>
)}
</Modal>
);
};
const uploadSkill = () => {
const fileInput = document.createElement("input");
fileInput.type = "file";
fileInput.accept = ".json";
fileInput.onchange = (e: any) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (e) => {
const content = e.target?.result;
if (content) {
try {
const skill = JSON.parse(content as string);
if (skill) {
setNewSkill(skill);
setShowNewSkillModal(true);
}
} catch (e) {
message.error("Invalid skill file");
}
}
};
reader.readAsText(file);
};
fileInput.click();
};
const skillsMenuItems: MenuProps["items"] = [
// {
// type: "divider",
// },
{
key: "uploadskill",
label: (
<div>
<ArrowUpTrayIcon className="w-5 h-5 inline-block mr-2" />
Upload Skill
</div>
),
},
];
const skillsMenuItemOnClick: MenuProps["onClick"] = ({ key }) => {
if (key === "uploadskill") {
uploadSkill();
return;
}
};
return (
<div className=" text-primary ">
<SkillModal
skill={selectedSkill}
setSkill={setSelectedSkill}
showSkillModal={showSkillModal}
setShowSkillModal={setShowSkillModal}
handler={(skill: ISkill) => {
fetchSkills();
}}
/>
<SkillModal
skill={newSkill || sampleSkill}
setSkill={setNewSkill}
showSkillModal={showNewSkillModal}
setShowSkillModal={setShowNewSkillModal}
handler={(skill: ISkill) => {
fetchSkills();
}}
/>
<div className="mb-2 relative">
<div className="">
<div className="flex mt-2 pb-2 mb-2 border-b">
<ul className="flex-1 font-semibold mb-2 ">
{" "}
Skills ({skillRows.length}){" "}
</ul>
<div>
<Dropdown.Button
type="primary"
menu={{
items: skillsMenuItems,
onClick: skillsMenuItemOnClick,
}}
placement="bottomRight"
trigger={["click"]}
onClick={() => {
setShowNewSkillModal(true);
}}
>
<PlusIcon className="w-5 h-5 inline-block mr-1" />
New Skill
</Dropdown.Button>
</div>
</div>
<div className="text-xs mb-2 pb-1 ">
{" "}
Skills are python functions that agents can use to solve tasks.{" "}
</div>
{skills && skills.length > 0 && (
<div
// style={{ height: "400px" }}
className="w-full relative"
>
<LoadingOverlay loading={loading} />
<div className=" flex flex-wrap gap-3">{skillRows}</div>
</div>
)}
{skills && skills.length === 0 && !loading && (
<div className="text-sm border mt-4 rounded text-secondary p-2">
<InformationCircleIcon className="h-4 w-4 inline mr-1" />
No skills found. Please create a new skill.
</div>
)}
{loading && (
<div className=" w-full text-center">
{" "}
<BounceLoader />{" "}
<span className="inline-block"> loading .. </span>
</div>
)}
</div>
</div>
</div>
);
};
export default SkillsView;

View File

@@ -1,517 +0,0 @@
import React from "react";
import { CollapseBox, ControlRowView } from "../../../atoms";
import { checkAndSanitizeInput, fetchJSON, getServerUrl } from "../../../utils";
import {
Button,
Form,
Input,
Select,
Slider,
Tabs,
message,
theme,
} from "antd";
import {
BugAntIcon,
CpuChipIcon,
UserGroupIcon,
} from "@heroicons/react/24/outline";
import { appContext } from "../../../../hooks/provider";
import {
AgentSelector,
AgentTypeSelector,
ModelSelector,
SkillSelector,
} from "./selectors";
import { IAgent, ILLMConfig } from "../../../types";
import TextArea from "antd/es/input/TextArea";
const { useToken } = theme;
export const AgentConfigView = ({
agent,
setAgent,
close,
}: {
agent: IAgent;
setAgent: (agent: IAgent) => void;
close: () => void;
}) => {
const nameValidation = checkAndSanitizeInput(agent?.config?.name);
const [error, setError] = React.useState<any>(null);
const [loading, setLoading] = React.useState<boolean>(false);
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const createAgentUrl = `${serverUrl}/agents`;
const [controlChanged, setControlChanged] = React.useState<boolean>(false);
const onControlChange = (value: any, key: string) => {
// if (key === "llm_config") {
// if (value.config_list.length === 0) {
// value = false;
// }
// }
const updatedAgent = {
...agent,
config: { ...agent.config, [key]: value },
};
setAgent(updatedAgent);
setControlChanged(true);
};
const llm_config: ILLMConfig = agent?.config?.llm_config || {
config_list: [],
temperature: 0.1,
max_tokens: 4000,
};
const createAgent = (agent: IAgent) => {
setError(null);
setLoading(true);
// const fetch;
console.log("agent", agent);
agent.user_id = user?.email;
const payLoad = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(agent),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
console.log("agents", data.data);
const newAgent = data.data;
setAgent(newAgent);
} else {
message.error(data.message);
}
setLoading(false);
// setNewAgent(sampleAgent);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
const onFinal = () => {
setLoading(false);
setControlChanged(false);
};
fetchJSON(createAgentUrl, payLoad, onSuccess, onError, onFinal);
};
const hasChanged =
(!controlChanged || !nameValidation.status) && agent?.id !== undefined;
return (
<div className="text-primary">
<Form>
<div
className={`grid gap-3 ${
agent.type === "groupchat" ? "grid-cols-2" : "grid-cols-1"
}`}
>
<div className="">
<ControlRowView
title="Agent Name"
className=""
description="Name of the agent"
value={agent?.config?.name}
control={
<>
<Input
className="mt-2"
placeholder="Agent Name"
value={agent?.config?.name}
onChange={(e) => {
onControlChange(e.target.value, "name");
}}
/>
{!nameValidation.status && (
<div className="text-xs text-red-500 mt-2">
{nameValidation.message}
</div>
)}
</>
}
/>
<ControlRowView
title="Agent Description"
className="mt-4"
description="Description of the agent, used by other agents
(e.g. the GroupChatManager) to decide when to call upon this agent. (Default: system_message)"
value={agent.config.description || ""}
control={
<Input
className="mt-2"
placeholder="Agent Description"
value={agent.config.description || ""}
onChange={(e) => {
onControlChange(e.target.value, "description");
}}
/>
}
/>
<ControlRowView
title="Max Consecutive Auto Reply"
className="mt-4"
description="Max consecutive auto reply messages before termination."
value={agent.config?.max_consecutive_auto_reply}
control={
<Slider
min={1}
max={agent.type === "groupchat" ? 600 : 30}
defaultValue={agent.config.max_consecutive_auto_reply}
step={1}
onChange={(value: any) => {
onControlChange(value, "max_consecutive_auto_reply");
}}
/>
}
/>
<ControlRowView
title="Human Input Mode"
description="Defines when to request human input"
value={agent.config.human_input_mode}
control={
<Select
className="mt-2 w-full"
defaultValue={agent.config.human_input_mode}
onChange={(value: any) => {
onControlChange(value, "human_input_mode");
}}
options={
[
{ label: "NEVER", value: "NEVER" },
{ label: "TERMINATE", value: "TERMINATE" },
{ label: "ALWAYS", value: "ALWAYS" },
] as any
}
/>
}
/>
<ControlRowView
title="System Message"
className="mt-4"
description="Free text to control agent behavior"
value={agent.config.system_message}
control={
<TextArea
className="mt-2 w-full"
value={agent.config.system_message}
rows={3}
onChange={(e) => {
onControlChange(e.target.value, "system_message");
}}
/>
}
/>
<div className="mt-4">
{" "}
<CollapseBox
className="bg-secondary mt-4"
open={false}
title="Advanced Options"
>
<ControlRowView
title="Temperature"
className="mt-4"
description="Defines the randomness of the agent's response."
value={llm_config.temperature}
control={
<Slider
min={0}
max={2}
step={0.1}
defaultValue={llm_config.temperature || 0.1}
onChange={(value: any) => {
const llm_config = {
...agent.config.llm_config,
temperature: value,
};
onControlChange(llm_config, "llm_config");
}}
/>
}
/>
<ControlRowView
title="Agent Default Auto Reply"
className="mt-4"
description="Default auto reply when no code execution or llm-based reply is generated."
value={agent.config.default_auto_reply || ""}
control={
<Input
className="mt-2"
placeholder="Agent Description"
value={agent.config.default_auto_reply || ""}
onChange={(e) => {
onControlChange(e.target.value, "default_auto_reply");
}}
/>
}
/>
<ControlRowView
title="Max Tokens"
description="Max tokens generated by LLM used in the agent's response."
value={llm_config.max_tokens}
className="mt-4"
control={
<Slider
min={100}
max={50000}
defaultValue={llm_config.max_tokens || 1000}
onChange={(value: any) => {
const llm_config = {
...agent.config.llm_config,
max_tokens: value,
};
onControlChange(llm_config, "llm_config");
}}
/>
}
/>
<ControlRowView
title="Code Execution Config"
className="mt-4"
description="Determines if and where code execution is done."
value={agent.config.code_execution_config || "none"}
control={
<Select
className="mt-2 w-full"
defaultValue={
agent.config.code_execution_config || "none"
}
onChange={(value: any) => {
onControlChange(value, "code_execution_config");
}}
options={
[
{ label: "None", value: "none" },
{ label: "Local", value: "local" },
{ label: "Docker", value: "docker" },
] as any
}
/>
}
/>
</CollapseBox>
</div>
</div>
{/* ====================== Group Chat Config ======================= */}
{agent.type === "groupchat" && (
<div>
<ControlRowView
title="Speaker Selection Method"
description="How the next speaker is selected"
className=""
value={agent?.config?.speaker_selection_method || "auto"}
control={
<Select
className="mt-2 w-full"
defaultValue={
agent?.config?.speaker_selection_method || "auto"
}
onChange={(value: any) => {
if (agent?.config) {
onControlChange(value, "speaker_selection_method");
}
}}
options={
[
{ label: "Auto", value: "auto" },
{ label: "Round Robin", value: "round_robin" },
{ label: "Random", value: "random" },
] as any
}
/>
}
/>
<ControlRowView
title="Admin Name"
className="mt-4"
description="Name of the admin of the group chat"
value={agent.config.admin_name || ""}
control={
<Input
className="mt-2"
placeholder="Agent Description"
value={agent.config.admin_name || ""}
onChange={(e) => {
onControlChange(e.target.value, "admin_name");
}}
/>
}
/>
<ControlRowView
title="Max Rounds"
className="mt-4"
description="Max rounds before termination."
value={agent.config?.max_round || 10}
control={
<Slider
min={10}
max={600}
defaultValue={agent.config.max_round}
step={1}
onChange={(value: any) => {
onControlChange(value, "max_round");
}}
/>
}
/>
<ControlRowView
title="Allow Repeat Speaker"
className="mt-4"
description="Allow the same speaker to speak multiple times in a row"
value={agent.config?.allow_repeat_speaker || false}
control={
<Select
className="mt-2 w-full"
defaultValue={agent.config.allow_repeat_speaker}
onChange={(value: any) => {
onControlChange(value, "allow_repeat_speaker");
}}
options={
[
{ label: "True", value: true },
{ label: "False", value: false },
] as any
}
/>
}
/>
</div>
)}
</div>
</Form>
<div className="w-full mt-4 text-right">
{" "}
{!hasChanged && (
<Button
type="primary"
onClick={() => {
createAgent(agent);
setAgent(agent);
}}
loading={loading}
>
{agent.id ? "Update Agent" : "Create Agent"}
</Button>
)}
<Button
className="ml-2"
key="close"
type="default"
onClick={() => {
close();
}}
>
Close
</Button>
</div>
</div>
);
};
export const AgentViewer = ({
agent,
setAgent,
close,
}: {
agent: IAgent | null;
setAgent: (newAgent: IAgent) => void;
close: () => void;
}) => {
let items = [
{
label: (
<div className="w-full ">
{" "}
<BugAntIcon className="h-4 w-4 inline-block mr-1" />
Agent Configuration
</div>
),
key: "1",
children: (
<div>
{!agent?.type && (
<AgentTypeSelector agent={agent} setAgent={setAgent} />
)}
{agent?.type && agent && (
<AgentConfigView agent={agent} setAgent={setAgent} close={close} />
)}
</div>
),
},
];
if (agent) {
if (agent?.id) {
if (agent.type && agent.type === "groupchat") {
items.push({
label: (
<div className="w-full ">
{" "}
<UserGroupIcon className="h-4 w-4 inline-block mr-1" />
Agents
</div>
),
key: "2",
children: <AgentSelector agentId={agent?.id} />,
});
}
items.push({
label: (
<div className="w-full ">
{" "}
<CpuChipIcon className="h-4 w-4 inline-block mr-1" />
Models
</div>
),
key: "3",
children: <ModelSelector agentId={agent?.id} />,
});
items.push({
label: (
<>
<BugAntIcon className="h-4 w-4 inline-block mr-1" />
Skills
</>
),
key: "4",
children: <SkillSelector agentId={agent?.id} />,
});
}
}
return (
<div className="text-primary">
{/* <RenderView viewIndex={currentViewIndex} /> */}
<Tabs
tabBarStyle={{ paddingLeft: 0, marginLeft: 0 }}
defaultActiveKey="1"
items={items}
/>
</div>
);
};

View File

@@ -1,207 +0,0 @@
import { Button, Modal, message } from "antd";
import * as React from "react";
import { IWorkflow } from "../../../types";
import { ArrowDownTrayIcon } from "@heroicons/react/24/outline";
import {
checkAndSanitizeInput,
fetchJSON,
getServerUrl,
sanitizeConfig,
} from "../../../utils";
import { appContext } from "../../../../hooks/provider";
import { CodeBlock } from "../../../atoms";
export const ExportWorkflowModal = ({
workflow,
show,
setShow,
}: {
workflow: IWorkflow | null;
show: boolean;
setShow: (show: boolean) => void;
}) => {
const serverUrl = getServerUrl();
const { user } = React.useContext(appContext);
const [error, setError] = React.useState<any>(null);
const [loading, setLoading] = React.useState<boolean>(false);
const [workflowDetails, setWorkflowDetails] = React.useState<any>(null);
const getWorkflowCode = (workflow: IWorkflow) => {
const workflowCode = `from autogenstudio import WorkflowManager
# load workflow from exported json workflow file.
workflow_manager = WorkflowManager(workflow="path/to/your/workflow_.json")
# run the workflow on a task
task_query = "What is the height of the Eiffel Tower?. Dont write code, just respond to the question."
workflow_manager.run(message=task_query)`;
return workflowCode;
};
const getCliWorkflowCode = (workflow: IWorkflow) => {
const workflowCode = `autogenstudio serve --workflow=workflow.json --port=5000
`;
return workflowCode;
};
const getGunicornWorkflowCode = (workflow: IWorkflow) => {
const workflowCode = `gunicorn -w $((2 * $(getconf _NPROCESSORS_ONLN) + 1)) --timeout 12600 -k uvicorn.workers.UvicornWorker autogenstudio.web.app:app --bind `;
return workflowCode;
};
const fetchWorkFlow = (workflow: IWorkflow) => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const downloadWorkflowUrl = `${serverUrl}/workflows/export/${workflow.id}?user_id=${user?.email}`;
const onSuccess = (data: any) => {
if (data && data.status) {
setWorkflowDetails(data.data);
console.log("workflow details", data.data);
const sanitized_name =
checkAndSanitizeInput(workflow.name).sanitizedText || workflow.name;
const file_name = `workflow_${sanitized_name}.json`;
const workflowData = sanitizeConfig(data.data);
const file = new Blob([JSON.stringify(workflowData)], {
type: "application/json",
});
const downloadUrl = URL.createObjectURL(file);
const a = document.createElement("a");
a.href = downloadUrl;
a.download = file_name;
a.click();
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(downloadWorkflowUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (workflow && workflow.id && show) {
// fetchWorkFlow(workflow.id);
console.log("workflow modal ... component loaded", workflow);
}
}, [show]);
return (
<Modal
title={
<>
Export Workflow
<span className="text-accent font-normal ml-2">
{workflow?.name}
</span>{" "}
</>
}
width={800}
open={show}
onOk={() => {
setShow(false);
}}
onCancel={() => {
setShow(false);
}}
footer={[]}
>
<div>
<div>
{" "}
You can use the following steps to start integrating your workflow
into your application.{" "}
</div>
{workflow && workflow.id && (
<>
<div className="flex mt-2 gap-3">
<div>
<div className="text-sm mt-2 mb-2 pb-1 font-bold">Step 1</div>
<div className="mt-2 mb-2 pb-1 text-xs">
Download your workflow as a JSON file by clicking the button
below.
</div>
<div className="text-sm mt-2 mb-2 pb-1">
<Button
type="primary"
loading={loading}
onClick={() => {
fetchWorkFlow(workflow);
}}
>
Download
<ArrowDownTrayIcon className="h-4 w-4 inline-block ml-2 -mt-1" />
</Button>
</div>
</div>
<div>
<div className="text-sm mt-2 mb-2 pb-1 font-bold">Step 2</div>
<div className=" mt-2 mb-2 pb-1 text-xs">
Copy the following code snippet and paste it into your
application to run your workflow on a task.
</div>
<div className="text-sm mt-2 mb-2 pb-1">
<CodeBlock
className="text-xs"
code={getWorkflowCode(workflow)}
language="python"
wrapLines={true}
/>
</div>
</div>
</div>
<div>
<div className="text-sm mt-2 mb-2 pb-1 font-bold">
Step 3 (Deploy)
</div>
<div className=" mt-2 mb-2 pb-1 text-xs">
You can also deploy your workflow as an API endpoint using the
autogenstudio python CLI.
</div>
<div className="text-sm mt-2 mb-2 pb-1">
<CodeBlock
className="text-xs"
code={getCliWorkflowCode(workflow)}
language="bash"
wrapLines={true}
/>
<div className="text-xs mt-2">
Note: this will start a endpoint on port 5000. You can change
the port by changing the port number. You can also scale this
using multiple workers (e.g., via an application server like
gunicorn) or wrap it in a docker container and deploy on a
cloud provider like Azure.
</div>
<CodeBlock
className="text-xs"
code={getGunicornWorkflowCode(workflow)}
language="bash"
wrapLines={true}
/>
</div>
</div>
</>
)}
</div>
</Modal>
);
};

View File

@@ -1,388 +0,0 @@
import React from "react";
import { fetchJSON, getServerUrl, sampleModelConfig } from "../../../utils";
import { Button, Input, message, theme } from "antd";
import {
CpuChipIcon,
InformationCircleIcon,
} from "@heroicons/react/24/outline";
import { IModelConfig, IStatus } from "../../../types";
import { Card, ControlRowView } from "../../../atoms";
import TextArea from "antd/es/input/TextArea";
import { appContext } from "../../../../hooks/provider";
const ModelTypeSelector = ({
model,
setModel,
}: {
model: IModelConfig;
setModel: (newModel: IModelConfig) => void;
}) => {
const modelTypes = [
{
label: "OpenAI",
value: "open_ai",
description: "OpenAI or other endpoints that implement the OpenAI API",
icon: <CpuChipIcon className="h-6 w-6 text-primary" />,
hint: "In addition to OpenAI models, You can also use OSS models via tools like Ollama, vLLM, LMStudio etc. that provide OpenAI compatible endpoint.",
},
{
label: "Azure OpenAI",
value: "azure",
description: "Azure OpenAI endpoint",
icon: <CpuChipIcon className="h-6 w-6 text-primary" />,
hint: "Azure OpenAI endpoint",
},
{
label: "Gemini",
value: "google",
description: "Gemini",
icon: <CpuChipIcon className="h-6 w-6 text-primary" />,
hint: "Gemini",
},
{
label: "Claude",
value: "anthropic",
description: "Anthropic Claude",
icon: <CpuChipIcon className="h-6 w-6 text-primary" />,
hint: "Anthropic Claude models",
},
{
label: "Mistral",
value: "mistral",
description: "Mistral",
icon: <CpuChipIcon className="h-6 w-6 text-primary" />,
hint: "Mistral models",
},
];
const [selectedType, setSelectedType] = React.useState<string | undefined>(
model?.api_type
);
const modelTypeRows = modelTypes.map((modelType: any, i: number) => {
return (
<li
onMouseEnter={() => {
setSelectedHint(modelType.hint);
}}
role="listitem"
key={"modeltype" + i}
className="w-36"
>
<Card
active={selectedType === modelType.value}
className="h-full p-2 cursor-pointer"
title={<div className=" ">{modelType.label}</div>}
onClick={() => {
setSelectedType(modelType.value);
if (model) {
const sampleModel = sampleModelConfig(modelType.value);
setModel(sampleModel);
// setAgent(sampleAgent);
}
}}
>
<div style={{ minHeight: "35px" }} className="my-2 break-words ">
{" "}
<div className="mb-2">{modelType.icon}</div>
<span className="text-secondary tex-sm">
{" "}
{modelType.description}
</span>
</div>
</Card>
</li>
);
});
const [selectedHint, setSelectedHint] = React.useState<string>("open_ai");
return (
<>
<div className="pb-3">Select Model Type</div>
<ul className="inline-flex gap-2">{modelTypeRows}</ul>
<div className="text-xs mt-4">
<InformationCircleIcon className="h-4 w-4 inline mr-1 -mt-1" />
{selectedHint}
</div>
</>
);
};
const ModelConfigMainView = ({
model,
setModel,
close,
}: {
model: IModelConfig;
setModel: (newModel: IModelConfig) => void;
close: () => void;
}) => {
const [loading, setLoading] = React.useState(false);
const [modelStatus, setModelStatus] = React.useState<IStatus | null>(null);
const serverUrl = getServerUrl();
const { user } = React.useContext(appContext);
const testModelUrl = `${serverUrl}/models/test`;
const createModelUrl = `${serverUrl}/models`;
// const [model, setmodel] = React.useState<IModelConfig | null>(
// model
// );
const testModel = (model: IModelConfig) => {
setModelStatus(null);
setLoading(true);
model.user_id = user?.email;
const payLoad = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(model),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
setModelStatus(data.data);
} else {
message.error(data.message);
}
setLoading(false);
setModelStatus(data);
};
const onError = (err: any) => {
message.error(err.message);
setLoading(false);
};
fetchJSON(testModelUrl, payLoad, onSuccess, onError);
};
const createModel = (model: IModelConfig) => {
setLoading(true);
model.user_id = user?.email;
const payLoad = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(model),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
setModel(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
message.error(err.message);
setLoading(false);
};
const onFinal = () => {
setLoading(false);
setControlChanged(false);
};
fetchJSON(createModelUrl, payLoad, onSuccess, onError, onFinal);
};
const [controlChanged, setControlChanged] = React.useState<boolean>(false);
const updateModelConfig = (key: string, value: string) => {
if (model) {
const updatedModelConfig = { ...model, [key]: value };
// setmodel(updatedModelConfig);
setModel(updatedModelConfig);
}
setControlChanged(true);
};
const hasChanged = !controlChanged && model.id !== undefined;
return (
<div className="relative ">
<div className="text-sm my-2">
Enter parameters for your{" "}
<span className="mx-1 text-accent">{model.api_type}</span> model.
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<ControlRowView
title="Model"
className=""
description="Model name"
value={model?.model || ""}
control={
<Input
className="mt-2 w-full"
value={model?.model}
onChange={(e) => {
updateModelConfig("model", e.target.value);
}}
/>
}
/>
<ControlRowView
title="Base URL"
className=""
description="Base URL for Model Endpoint"
value={model?.base_url || ""}
control={
<Input
className="mt-2 w-full"
value={model?.base_url}
onChange={(e) => {
updateModelConfig("base_url", e.target.value);
}}
/>
}
/>
</div>
<div>
<ControlRowView
title="API Key"
className=""
description="API Key"
value={model?.api_key || ""}
truncateLength={5}
control={
<Input.Password
className="mt-2 w-full"
value={model?.api_key}
onChange={(e) => {
updateModelConfig("api_key", e.target.value);
}}
/>
}
/>
{model?.api_type == "azure" && (
<ControlRowView
title="API Version"
className=" "
description="API Version, required by Azure Models"
value={model?.api_version || ""}
control={
<Input
className="mt-2 w-full"
value={model?.api_version}
onChange={(e) => {
updateModelConfig("api_version", e.target.value);
}}
/>
}
/>
)}
</div>
</div>
<ControlRowView
title="Description"
className="mt-4"
description="Description of the model"
value={model?.description || ""}
control={
<TextArea
className="mt-2 w-full"
value={model?.description}
onChange={(e) => {
updateModelConfig("description", e.target.value);
}}
/>
}
/>
{model?.api_type === "azure" && (
<div className="mt-4 text-xs">
Note: For Azure OAI models, you will need to specify all fields.
</div>
)}
{modelStatus && (
<div
className={`text-sm border mt-4 rounded text-secondary p-2 ${
modelStatus.status ? "border-accent" : " border-red-500 "
}`}
>
<InformationCircleIcon className="h-4 w-4 inline mr-1" />
{modelStatus.message}
{/* <span className="block"> Note </span> */}
</div>
)}
<div className="w-full mt-4 text-right">
<Button
key="test"
type="primary"
loading={loading}
onClick={() => {
if (model) {
testModel(model);
}
}}
>
Test Model
</Button>
{!hasChanged && (
<Button
className="ml-2"
key="save"
type="primary"
onClick={() => {
if (model) {
createModel(model);
setModel(model);
}
}}
>
{model?.id ? "Update Model" : "Save Model"}
</Button>
)}
<Button
className="ml-2"
key="close"
type="default"
onClick={() => {
close();
}}
>
Close
</Button>
</div>
</div>
);
};
export const ModelConfigView = ({
model,
setModel,
close,
}: {
model: IModelConfig;
setModel: (newModel: IModelConfig) => void;
close: () => void;
}) => {
return (
<div className="text-primary">
<div>
{!model?.api_type && (
<ModelTypeSelector model={model} setModel={setModel} />
)}
{model?.api_type && model && (
<ModelConfigMainView
model={model}
setModel={setModel}
close={close}
/>
)}
</div>
</div>
);
};

View File

@@ -1,295 +0,0 @@
import React from "react";
import { fetchJSON, getServerUrl, sampleModelConfig } from "../../../utils";
import { Button, Input, message, theme } from "antd";
import {
CpuChipIcon,
EyeIcon,
EyeSlashIcon,
InformationCircleIcon,
PlusIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import { ISkill, IStatus } from "../../../types";
import { Card, ControlRowView, MonacoEditor } from "../../../atoms";
import TextArea from "antd/es/input/TextArea";
import { appContext } from "../../../../hooks/provider";
const SecretsEditor = ({
secrets = [],
updateSkillConfig,
}: {
secrets: { secret: string; value: string }[];
updateSkillConfig: (key: string, value: any) => void;
}) => {
const [editingIndex, setEditingIndex] = React.useState<number | null>(null);
const [newSecret, setNewSecret] = React.useState<string>("");
const [newValue, setNewValue] = React.useState<string>("");
const toggleEditing = (index: number) => {
setEditingIndex(editingIndex === index ? null : index);
};
const handleAddSecret = () => {
if (newSecret && newValue) {
const updatedSecrets = [
...secrets,
{ secret: newSecret, value: newValue },
];
updateSkillConfig("secrets", updatedSecrets);
setNewSecret("");
setNewValue("");
}
};
const handleRemoveSecret = (index: number) => {
const updatedSecrets = secrets.filter((_, i) => i !== index);
updateSkillConfig("secrets", updatedSecrets);
};
const handleSecretChange = (index: number, key: string, value: string) => {
const updatedSecrets = secrets.map((item, i) =>
i === index ? { ...item, [key]: value } : item
);
updateSkillConfig("secrets", updatedSecrets);
};
return (
<div className="mt-4">
{secrets && (
<div className="flex flex-col gap-2">
{secrets.map((secret, index) => (
<div key={index} className="flex items-center gap-2">
<Input
value={secret.secret}
disabled={editingIndex !== index}
onChange={(e) =>
handleSecretChange(index, "secret", e.target.value)
}
className="flex-1"
/>
<Input.Password
value={secret.value}
visibilityToggle
disabled={editingIndex !== index}
onChange={(e) =>
handleSecretChange(index, "value", e.target.value)
}
className="flex-1"
/>
<Button
icon={
editingIndex === index ? (
<EyeSlashIcon className="h-5 w-5" />
) : (
<EyeIcon className="h-5 w-5" />
)
}
onClick={() => toggleEditing(index)}
/>
<Button
icon={<TrashIcon className="h-5 w-5" />}
onClick={() => handleRemoveSecret(index)}
/>
</div>
))}
</div>
)}
<div className="flex items-center gap-2 mt-2">
<Input
placeholder="New Secret"
value={newSecret}
onChange={(e) => setNewSecret(e.target.value)}
className="flex-1"
/>
<Input.Password
placeholder="New Value"
value={newValue}
onChange={(e) => setNewValue(e.target.value)}
className="flex-1"
/>
<Button
icon={<PlusIcon className="h-5 w-5" />}
onClick={handleAddSecret}
/>
</div>
</div>
);
};
export const SkillConfigView = ({
skill,
setSkill,
close,
}: {
skill: ISkill;
setSkill: (newModel: ISkill) => void;
close: () => void;
}) => {
const [loading, setLoading] = React.useState(false);
const serverUrl = getServerUrl();
const { user } = React.useContext(appContext);
const testModelUrl = `${serverUrl}/skills/test`;
const createSkillUrl = `${serverUrl}/skills`;
const createSkill = (skill: ISkill) => {
setLoading(true);
skill.user_id = user?.email;
const payLoad = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(skill),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
setSkill(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
message.error(err.message);
setLoading(false);
};
const onFinal = () => {
setLoading(false);
setControlChanged(false);
};
fetchJSON(createSkillUrl, payLoad, onSuccess, onError, onFinal);
};
const [controlChanged, setControlChanged] = React.useState<boolean>(false);
const updateSkillConfig = (key: string, value: string) => {
if (skill) {
const updatedSkill = { ...skill, [key]: value };
// setSkill(updatedModelConfig);
setSkill(updatedSkill);
}
setControlChanged(true);
};
const hasChanged = !controlChanged && skill.id !== undefined;
const editorRef = React.useRef<any | null>(null);
return (
<div className="relative ">
{skill && (
<div style={{ minHeight: "65vh" }}>
<div className="flex gap-3">
<div className="h-ful flex-1 ">
<div className="mb-2 h-full" style={{ minHeight: "65vh" }}>
<div className="h-full mt-2" style={{ height: "65vh" }}>
<MonacoEditor
value={skill?.content}
language="python"
editorRef={editorRef}
onChange={(value: string) => {
updateSkillConfig("content", value);
}}
/>
</div>
</div>
</div>
<div className="w-72 ">
<div className="">
<ControlRowView
title="Name"
className=""
description="Skill name, should match function name"
value={skill?.name || ""}
control={
<Input
className="mt-2 w-full"
value={skill?.name}
onChange={(e) => {
updateSkillConfig("name", e.target.value);
}}
/>
}
/>
<ControlRowView
title="Description"
className="mt-4"
description="Description of the skill"
value={skill?.description || ""}
control={
<TextArea
className="mt-2 w-full"
value={skill?.description}
onChange={(e) => {
updateSkillConfig("description", e.target.value);
}}
/>
}
/>
<ControlRowView
title="Secrets"
className="mt-4"
description="Environment variables"
value=""
control={
<SecretsEditor
secrets={skill?.secrets || []}
updateSkillConfig={updateSkillConfig}
/>
}
/>
</div>
</div>
</div>
</div>
)}
<div className="w-full mt-4 text-right">
{/* <Button
key="test"
type="primary"
loading={loading}
onClick={() => {
if (skill) {
testModel(skill);
}
}}
>
Test Model
</Button> */}
{!hasChanged && (
<Button
className="ml-2"
key="save"
type="primary"
onClick={() => {
if (skill) {
createSkill(skill);
setSkill(skill);
}
}}
>
{skill?.id ? "Update Skill" : "Save Skill"}
</Button>
)}
<Button
className="ml-2"
key="close"
type="default"
onClick={() => {
close();
}}
>
Close
</Button>
</div>
</div>
);
};

View File

@@ -1,279 +0,0 @@
import React from "react";
import { IWorkflow, IStatus, IChatSession } from "../../../types";
import { ControlRowView } from "../../../atoms";
import {
fetchJSON,
getRandomIntFromDateAndSalt,
getServerUrl,
} from "../../../utils";
import { Button, Drawer, Input, Select, Tabs, message, theme } from "antd";
import { appContext } from "../../../../hooks/provider";
import { BugAntIcon, UserGroupIcon } from "@heroicons/react/24/outline";
import { WorkflowAgentSelector, WorkflowTypeSelector } from "./selectors";
import ChatBox from "../../playground/chatbox";
export const WorkflowViewConfig = ({
workflow,
setWorkflow,
close,
}: {
workflow: IWorkflow;
setWorkflow: (newFlowConfig: IWorkflow) => void;
close: () => void;
}) => {
const [loading, setLoading] = React.useState<boolean>(false);
const [error, setError] = React.useState<IStatus | null>(null);
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const createWorkflowUrl = `${serverUrl}/workflows`;
const [controlChanged, setControlChanged] = React.useState<boolean>(false);
const [localWorkflow, setLocalWorkflow] = React.useState<IWorkflow>(workflow);
const updateFlowConfig = (key: string, value: string) => {
// When an updatedFlowConfig is created using localWorkflow, if the contents of FlowConfigViewer Modal are changed after the Agent Specification Modal is updated, the updated contents of the Agent Specification Modal are not saved. Fixed to localWorkflow->flowConfig. Fixed a bug.
const updatedFlowConfig = { ...workflow, [key]: value };
setLocalWorkflow(updatedFlowConfig);
setWorkflow(updatedFlowConfig);
setControlChanged(true);
};
const createWorkflow = (workflow: IWorkflow) => {
setError(null);
setLoading(true);
// const fetch;
workflow.user_id = user?.email;
const payLoad = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(workflow),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
const newWorkflow = data.data;
setWorkflow(newWorkflow);
} else {
message.error(data.message);
}
setLoading(false);
// setNewAgent(sampleAgent);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
const onFinal = () => {
setLoading(false);
setControlChanged(false);
};
fetchJSON(createWorkflowUrl, payLoad, onSuccess, onError, onFinal);
};
const hasChanged = !controlChanged && workflow.id !== undefined;
const [drawerOpen, setDrawerOpen] = React.useState<boolean>(false);
const openDrawer = () => {
setDrawerOpen(true);
};
const closeDrawer = () => {
setDrawerOpen(false);
};
const dummySession: IChatSession = {
user_id: user?.email || "test_session_user_id",
workflow_id: workflow?.id,
name: "test_session",
};
return (
<>
{/* <div className="mb-2">{flowConfig.name}</div> */}
<div>
<ControlRowView
title="Workflow Name"
className="mt-4 mb-2"
description="Name of the workflow"
value={localWorkflow.name}
control={
<Input
className="mt-2 w-full"
value={localWorkflow.name}
onChange={(e) => updateFlowConfig("name", e.target.value)}
/>
}
/>
<ControlRowView
title="Workflow Description"
className="mt-4 mb-2"
description="Description of the workflow"
value={localWorkflow.description}
control={
<Input
className="mt-2 w-full"
value={localWorkflow.description}
onChange={(e) => updateFlowConfig("description", e.target.value)}
/>
}
/>
<ControlRowView
title="Summary Method"
description="Defines the method to summarize the conversation"
value={localWorkflow.summary_method || "last"}
control={
<Select
className="mt-2 w-full"
defaultValue={localWorkflow.summary_method || "last"}
onChange={(value: any) =>
updateFlowConfig("summary_method", value)
}
options={
[
{ label: "last", value: "last" },
{ label: "none", value: "none" },
{ label: "llm", value: "llm" },
] as any
}
/>
}
/>
</div>
<div className="w-full mt-4 text-right">
{" "}
{!hasChanged && (
<Button
type="primary"
onClick={() => {
createWorkflow(localWorkflow);
}}
loading={loading}
>
{workflow.id ? "Update Workflow" : "Create Workflow"}
</Button>
)}
{workflow?.id && (
<Button
className="ml-2 text-primary"
type="primary"
onClick={() => {
setDrawerOpen(true);
}}
>
Test Workflow
</Button>
)}
<Button
className="ml-2"
key="close text-primary"
type="default"
onClick={() => {
close();
}}
>
Close
</Button>
</div>
<Drawer
title={<div>{workflow?.name || "Test Workflow"}</div>}
size="large"
onClose={closeDrawer}
open={drawerOpen}
>
<div className="h-full ">
{drawerOpen && (
<ChatBox
initMessages={[]}
session={dummySession}
heightOffset={100}
/>
)}
</div>
</Drawer>
</>
);
};
export const WorflowViewer = ({
workflow,
setWorkflow,
close,
}: {
workflow: IWorkflow;
setWorkflow: (workflow: IWorkflow) => void;
close: () => void;
}) => {
let items = [
{
label: (
<div className="w-full ">
{" "}
<BugAntIcon className="h-4 w-4 inline-block mr-1" />
Workflow Configuration
</div>
),
key: "1",
children: (
<div>
{!workflow?.type && (
<WorkflowTypeSelector
workflow={workflow}
setWorkflow={setWorkflow}
/>
)}
{workflow?.type && workflow && (
<WorkflowViewConfig
workflow={workflow}
setWorkflow={setWorkflow}
close={close}
/>
)}
</div>
),
},
];
if (workflow) {
if (workflow?.id) {
items.push({
label: (
<div className="w-full ">
{" "}
<UserGroupIcon className="h-4 w-4 inline-block mr-1" />
Agents
</div>
),
key: "2",
children: (
<>
<WorkflowAgentSelector workflow={workflow} />{" "}
</>
),
});
}
}
const { user } = React.useContext(appContext);
return (
<div className="text-primary">
<Tabs
tabBarStyle={{ paddingLeft: 0, marginLeft: 0 }}
defaultActiveKey="1"
items={items}
/>
</div>
);
};

View File

@@ -1,428 +0,0 @@
import {
ArrowDownTrayIcon,
ArrowUpTrayIcon,
CodeBracketSquareIcon,
DocumentDuplicateIcon,
InformationCircleIcon,
PlusIcon,
TrashIcon,
UserGroupIcon,
UsersIcon,
} from "@heroicons/react/24/outline";
import { Dropdown, MenuProps, Modal, message } from "antd";
import * as React from "react";
import { IWorkflow, IStatus } from "../../types";
import { appContext } from "../../../hooks/provider";
import {
fetchJSON,
getServerUrl,
sanitizeConfig,
timeAgo,
truncateText,
} from "../../utils";
import { BounceLoader, Card, CardHoverBar, LoadingOverlay } from "../../atoms";
import { WorflowViewer } from "./utils/workflowconfig";
import { ExportWorkflowModal } from "./utils/export";
const WorkflowView = ({}: any) => {
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const listWorkflowsUrl = `${serverUrl}/workflows?user_id=${user?.email}`;
const saveWorkflowsUrl = `${serverUrl}/workflows`;
const [workflows, setWorkflows] = React.useState<IWorkflow[] | null>([]);
const [selectedWorkflow, setSelectedWorkflow] =
React.useState<IWorkflow | null>(null);
const [selectedExportWorkflow, setSelectedExportWorkflow] =
React.useState<IWorkflow | null>(null);
const sampleWorkflow: IWorkflow = {
name: "Sample Agent Workflow",
description: "Sample Agent Workflow",
};
const [newWorkflow, setNewWorkflow] = React.useState<IWorkflow | null>(
sampleWorkflow
);
const [showWorkflowModal, setShowWorkflowModal] = React.useState(false);
const [showNewWorkflowModal, setShowNewWorkflowModal] = React.useState(false);
const fetchWorkFlow = () => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
setWorkflows(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(listWorkflowsUrl, payLoad, onSuccess, onError);
};
const deleteWorkFlow = (workflow: IWorkflow) => {
setError(null);
setLoading(true);
// const fetch;
const deleteWorkflowsUrl = `${serverUrl}/workflows/delete?user_id=${user?.email}&workflow_id=${workflow.id}`;
const payLoad = {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: user?.email,
workflow: workflow,
}),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
fetchWorkFlow();
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(deleteWorkflowsUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user) {
// console.log("fetching messages", messages);
fetchWorkFlow();
}
}, []);
React.useEffect(() => {
if (selectedWorkflow) {
setShowWorkflowModal(true);
}
}, [selectedWorkflow]);
const [showExportModal, setShowExportModal] = React.useState(false);
const workflowRows = (workflows || []).map(
(workflow: IWorkflow, i: number) => {
const cardItems = [
{
title: "Export",
icon: CodeBracketSquareIcon,
onClick: (e: any) => {
e.stopPropagation();
setSelectedExportWorkflow(workflow);
setShowExportModal(true);
},
hoverText: "Export",
},
{
title: "Download",
icon: ArrowDownTrayIcon,
onClick: (e: any) => {
e.stopPropagation();
// download workflow as workflow.name.json
const element = document.createElement("a");
const sanitizedWorkflow = sanitizeConfig(workflow);
const file = new Blob([JSON.stringify(sanitizedWorkflow)], {
type: "application/json",
});
element.href = URL.createObjectURL(file);
element.download = `workflow_${workflow.name}.json`;
document.body.appendChild(element); // Required for this to work in FireFox
element.click();
},
hoverText: "Download",
},
{
title: "Make a Copy",
icon: DocumentDuplicateIcon,
onClick: (e: any) => {
e.stopPropagation();
let newWorkflow = { ...sanitizeConfig(workflow) };
newWorkflow.name = `${workflow.name}_copy`;
setNewWorkflow(newWorkflow);
setShowNewWorkflowModal(true);
},
hoverText: "Make a Copy",
},
{
title: "Delete",
icon: TrashIcon,
onClick: (e: any) => {
e.stopPropagation();
deleteWorkFlow(workflow);
},
hoverText: "Delete",
},
];
return (
<li
key={"workflowrow" + i}
className="block h-full"
style={{ width: "200px" }}
>
<Card
className=" block p-2 cursor-pointer"
title={<div className=" ">{truncateText(workflow.name, 25)}</div>}
onClick={() => {
setSelectedWorkflow(workflow);
}}
>
<div
style={{ minHeight: "65px" }}
className="break-words my-2"
aria-hidden="true"
>
<div className="text-xs mb-2">{workflow.type}</div>{" "}
{truncateText(workflow.description, 70)}
</div>
<div
aria-label={`Updated ${timeAgo(workflow.updated_at || "")} ago`}
className="text-xs"
>
{timeAgo(workflow.updated_at || "")}
</div>
<CardHoverBar items={cardItems} />
</Card>
</li>
);
}
);
const WorkflowModal = ({
workflow,
setWorkflow,
showModal,
setShowModal,
handler,
}: {
workflow: IWorkflow | null;
setWorkflow?: (workflow: IWorkflow | null) => void;
showModal: boolean;
setShowModal: (show: boolean) => void;
handler?: (workflow: IWorkflow) => void;
}) => {
const [localWorkflow, setLocalWorkflow] = React.useState<IWorkflow | null>(
workflow
);
const closeModal = () => {
setShowModal(false);
if (handler) {
handler(localWorkflow as IWorkflow);
}
};
return (
<Modal
title={
<>
Workflow Specification{" "}
<span className="text-accent font-normal">
{localWorkflow?.name}
</span>{" "}
</>
}
width={800}
open={showModal}
onOk={() => {
closeModal();
}}
onCancel={() => {
closeModal();
}}
footer={[]}
>
<>
{localWorkflow && (
<WorflowViewer
workflow={localWorkflow}
setWorkflow={setLocalWorkflow}
close={closeModal}
/>
)}
</>
</Modal>
);
};
const uploadWorkflow = () => {
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
input.onchange = (e: any) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (e: any) => {
const contents = e.target.result;
if (contents) {
try {
const workflow = JSON.parse(contents);
// TBD validate that it is a valid workflow
setNewWorkflow(workflow);
setShowNewWorkflowModal(true);
} catch (err) {
message.error("Invalid workflow file");
}
}
};
reader.readAsText(file);
};
input.click();
};
const workflowTypes: MenuProps["items"] = [
// {
// key: "twoagents",
// label: (
// <div>
// {" "}
// <UsersIcon className="w-5 h-5 inline-block mr-2" />
// Two Agents
// </div>
// ),
// },
// {
// key: "groupchat",
// label: (
// <div>
// <UserGroupIcon className="w-5 h-5 inline-block mr-2" />
// Group Chat
// </div>
// ),
// },
// {
// type: "divider",
// },
{
key: "uploadworkflow",
label: (
<div>
<ArrowUpTrayIcon className="w-5 h-5 inline-block mr-2" />
Upload Workflow
</div>
),
},
];
const showWorkflow = (config: IWorkflow) => {
setSelectedWorkflow(config);
setShowWorkflowModal(true);
};
const workflowTypesOnClick: MenuProps["onClick"] = ({ key }) => {
if (key === "uploadworkflow") {
uploadWorkflow();
return;
}
showWorkflow(sampleWorkflow);
};
return (
<div className=" text-primary ">
<WorkflowModal
workflow={selectedWorkflow}
setWorkflow={setSelectedWorkflow}
showModal={showWorkflowModal}
setShowModal={setShowWorkflowModal}
handler={(workflow: IWorkflow) => {
fetchWorkFlow();
}}
/>
<WorkflowModal
workflow={newWorkflow}
showModal={showNewWorkflowModal}
setShowModal={setShowNewWorkflowModal}
handler={(workflow: IWorkflow) => {
fetchWorkFlow();
}}
/>
<ExportWorkflowModal
workflow={selectedExportWorkflow}
show={showExportModal}
setShow={setShowExportModal}
/>
<div className="mb-2 relative">
<div className=" rounded ">
<div className="flex mt-2 pb-2 mb-2 border-b">
<div className="flex-1 font-semibold mb-2 ">
{" "}
Workflows ({workflowRows.length}){" "}
</div>
<div className=" ">
<Dropdown.Button
type="primary"
menu={{ items: workflowTypes, onClick: workflowTypesOnClick }}
placement="bottomRight"
trigger={["click"]}
onClick={() => {
showWorkflow(sampleWorkflow);
}}
>
<PlusIcon className="w-5 h-5 inline-block mr-1" />
New Workflow
</Dropdown.Button>
</div>
</div>
<div className="text-xs mb-2 pb-1 ">
{" "}
Configure an agent workflow that can be used to handle tasks.
</div>
{workflows && workflows.length > 0 && (
<div
// style={{ minHeight: "500px" }}
className="w-full relative"
>
<LoadingOverlay loading={loading} />
<ul className="flex flex-wrap gap-3">{workflowRows}</ul>
</div>
)}
{workflows && workflows.length === 0 && !loading && (
<div className="text-sm border mt-4 rounded text-secondary p-2">
<InformationCircleIcon className="h-4 w-4 inline mr-1" />
No workflows found. Please create a new workflow.
</div>
)}
{loading && (
<div className=" w-full text-center">
{" "}
<BounceLoader />{" "}
<span className="inline-block"> loading .. </span>
</div>
)}
</div>
</div>
</div>
);
};
export default WorkflowView;

View File

@@ -1,207 +0,0 @@
import * as React from "react";
import { appContext } from "../../../hooks/provider";
import { fetchJSON, getServerUrl, timeAgo, truncateText } from "../../utils";
import { IGalleryItem, IStatus } from "../../types";
import { Button, message } from "antd";
import { BounceLoader, Card } from "../../atoms";
import {
ChevronLeftIcon,
InformationCircleIcon,
} from "@heroicons/react/24/outline";
import { navigate } from "gatsby";
import ChatBox from "../playground/chatbox";
const GalleryView = ({ location }: any) => {
const serverUrl = getServerUrl();
const { user } = React.useContext(appContext);
const [loading, setLoading] = React.useState(false);
const [gallery, setGallery] = React.useState<null | IGalleryItem[]>(null);
const [currentGallery, setCurrentGallery] =
React.useState<null | IGalleryItem>(null);
const listGalleryUrl = `${serverUrl}/gallery?user_id=${user?.email}`;
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const [currentGalleryId, setCurrentGalleryId] = React.useState<string | null>(
null
);
React.useEffect(() => {
// get gallery id from url
const urlParams = new URLSearchParams(location.search);
const galleryId = urlParams.get("id");
if (galleryId) {
// Fetch gallery details using the galleryId
fetchGallery(galleryId);
setCurrentGalleryId(galleryId);
} else {
// Redirect to an error page or home page if the id is not found
// navigate("/");
fetchGallery(null);
}
}, []);
const fetchGallery = (galleryId: string | null) => {
const fetchGalleryUrl = galleryId
? `${serverUrl}/gallery?gallery_id=${galleryId}`
: listGalleryUrl;
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
// message.success(data.message);
console.log("gallery", data);
if (galleryId) {
// Set the currently viewed gallery item
setCurrentGallery(data.data[0]);
} else {
setGallery(data.data);
}
// Set the list of gallery items
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(fetchGalleryUrl, payLoad, onSuccess, onError);
};
const GalleryContent = ({ item }: { item: IGalleryItem }) => {
return (
<div>
<div className="mb-4 text-sm">
This session contains {item.messages.length} messages and was created{" "}
{timeAgo(item.timestamp)}
</div>
<div className="">
<ChatBox initMessages={item.messages} editable={false} />
</div>
</div>
);
};
const TagsView = ({ tags }: { tags: string[] }) => {
const tagsView = tags.map((tag: string, index: number) => {
return (
<div key={"tag" + index} className="mr-2 inline-block">
<span className="text-xs bg-secondary border px-3 p-1 rounded">
{tag}
</span>
</div>
);
});
return <div className="flex flex-wrap">{tagsView}</div>;
};
const galleryRows = gallery?.map((item: IGalleryItem, index: number) => {
const isSelected = currentGallery?.id === item.id;
return (
<div key={"galleryrow" + index} className="">
<Card
active={isSelected}
onClick={() => {
setCurrentGallery(item);
// add to history
navigate(`/gallery?id=${item.id}`);
}}
className="h-full p-2 cursor-pointer"
title={truncateText(item.messages[0]?.content || "", 20)}
>
<div className="my-2">
{" "}
{truncateText(item.messages[0]?.content || "", 80)}
</div>
<div className="text-xs">
{" "}
{item.messages.length} message{item.messages.length > 1 && "s"}
</div>
<div className="my-2 border-t border-dashed w-full pt-2 inline-flex gap-2 ">
<TagsView tags={item.tags} />{" "}
</div>
<div className="text-xs">{timeAgo(item.timestamp)}</div>
</Card>
</div>
);
});
return (
<div className=" ">
<div className="mb-4 text-2xl">Gallery</div>
{/* back to gallery button */}
{currentGallery && (
<div className="mb-4 w-full">
<Button
type="primary"
onClick={() => {
setCurrentGallery(null);
// add to history
navigate(`/gallery?_=${Date.now()}`);
if (currentGalleryId) {
fetchGallery(null);
setCurrentGalleryId(null);
}
}}
className="bg-primary text-white px-2 py-1 rounded"
>
<ChevronLeftIcon className="h-4 w-4 inline mr-1" />
Back to gallery
</Button>
</div>
)}
{!currentGallery && (
<>
<div>
View a collection of AutoGen agent specifications and sessions{" "}
</div>
<div className="mt-4 grid gap-3 grid-cols-2 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6">
{galleryRows}
</div>
</>
)}
{gallery && gallery.length === 0 && (
<div className="text-sm border rounded text-secondary p-2">
<InformationCircleIcon className="h-4 w-4 inline mr-1" />
No gallery items found. Please create a chat session and publish to
gallery.
</div>
)}
{currentGallery && (
<div className="mt-4 border-t pt-2">
<GalleryContent item={currentGallery} />
</div>
)}
{loading && (
<div className="w-full text-center boder mt-4">
<div>
{" "}
<BounceLoader />
</div>
loading gallery
</div>
)}
</div>
);
};
export default GalleryView;

View File

@@ -0,0 +1,449 @@
import * as React from "react";
import { message } from "antd";
import { getServerUrl } from "../../../utils";
import { SessionManager } from "../../shared/session/manager";
import { IStatus } from "../../../types/app";
import { Message } from "../../../types/datamodel";
import { useConfigStore } from "../../../../hooks/store";
import { appContext } from "../../../../hooks/provider";
import ChatInput from "./chatinput";
import { ModelUsage, SocketMessage, ThreadState, ThreadStatus } from "./types";
import { MessageList } from "./messagelist";
import TeamManager from "../../shared/team/manager";
const logo = require("../../../../images/landing/welcome.svg").default;
export default function ChatView({
initMessages,
}: {
initMessages: Message[];
}) {
const serverUrl = getServerUrl();
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const [messages, setMessages] = React.useState<Message[]>(initMessages);
const [threadMessages, setThreadMessages] = React.useState<
Record<string, ThreadState>
>({});
const chatContainerRef = React.useRef<HTMLDivElement>(null);
const { user } = React.useContext(appContext);
const { session, sessions } = useConfigStore();
const [activeSockets, setActiveSockets] = React.useState<
Record<string, WebSocket>
>({});
React.useEffect(() => {
if (chatContainerRef.current) {
chatContainerRef.current.scrollTo({
top: chatContainerRef.current.scrollHeight,
behavior: "smooth",
});
}
}, [messages, threadMessages]);
React.useEffect(() => {
return () => {
Object.values(activeSockets).forEach((socket) => socket.close());
};
}, [activeSockets]);
const getBaseUrl = (url: string): string => {
try {
// Remove protocol (http:// or https://)
let baseUrl = url.replace(/(^\w+:|^)\/\//, "");
// Handle both localhost and production cases
if (baseUrl.startsWith("localhost")) {
// For localhost, keep the port if it exists
baseUrl = baseUrl.replace("/api", "");
} else if (baseUrl === "/api") {
// For production where url is just '/api'
baseUrl = window.location.host;
} else {
// For other cases, remove '/api' and trailing slash
baseUrl = baseUrl.replace("/api", "").replace(/\/$/, "");
}
return baseUrl;
} catch (error) {
console.error("Error processing server URL:", error);
throw new Error("Invalid server URL configuration");
}
};
const createRun = async (sessionId: number): Promise<string> => {
const payload = { session_id: sessionId, user_id: user?.email || "" };
const response = await fetch(`${serverUrl}/runs`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error("Failed to create run");
}
const data = await response.json();
return data.data.run_id;
};
const startRun = async (runId: string, query: string) => {
const messagePayload = {
user_id: user?.email,
session_id: session?.id,
config: {
content: query,
source: "user",
},
};
const response = await fetch(`${serverUrl}/runs/${runId}/start`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(messagePayload),
});
if (!response.ok) {
throw new Error("Failed to start run");
}
return await response.json();
};
interface RequestUsage {
prompt_tokens: number;
completion_tokens: number;
}
const connectWebSocket = (runId: string, query: string) => {
const baseUrl = getBaseUrl(serverUrl);
// Determine if we should use ws:// or wss:// based on current protocol
const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const wsUrl = `${wsProtocol}//${baseUrl}/api/ws/runs/${runId}`;
console.log("Connecting to WebSocket URL:", wsUrl); // For debugging
const socket = new WebSocket(wsUrl);
let isClosing = false;
const closeSocket = () => {
if (!isClosing && socket.readyState !== WebSocket.CLOSED) {
isClosing = true;
socket.close();
setActiveSockets((prev) => {
const newSockets = { ...prev };
delete newSockets[runId];
return newSockets;
});
}
};
socket.onopen = async () => {
try {
setActiveSockets((prev) => ({
...prev,
[runId]: socket,
}));
setThreadMessages((prev) => ({
...prev,
[runId]: {
messages: [],
status: "streaming",
isExpanded: true,
},
}));
setMessages((prev: Message[]) =>
prev.map((msg: Message) => {
if (msg.run_id === runId && msg.config.source === "bot") {
return {
...msg,
config: {
...msg.config,
content: "Starting...",
},
};
}
return msg;
})
);
// Start the run only after socket is connected
await startRun(runId, query);
} catch (error) {
console.error("Error starting run:", error);
message.error("Failed to start run");
closeSocket();
setThreadMessages((prev) => ({
...prev,
[runId]: {
...prev[runId],
status: "error",
isExpanded: true,
},
}));
}
};
socket.onmessage = (event) => {
const message: SocketMessage = JSON.parse(event.data);
switch (message.type) {
case "message":
setThreadMessages((prev) => {
const currentThread = prev[runId] || {
messages: [],
status: "streaming",
isExpanded: true,
};
const models_usage: ModelUsage | undefined = message.data
?.models_usage
? {
prompt_tokens: message.data.models_usage.prompt_tokens,
completion_tokens:
message.data.models_usage.completion_tokens,
}
: undefined;
const newMessage = {
source: message.data?.source || "",
content: message.data?.content || "",
models_usage,
};
return {
...prev,
[runId]: {
...currentThread,
messages: [...currentThread.messages, newMessage],
status: "streaming",
},
};
});
break;
case "result":
case "completion":
setThreadMessages((prev) => {
const currentThread = prev[runId];
if (!currentThread) return prev;
const finalMessage = message.data?.task_result?.messages
?.filter((msg: any) => msg.content !== "TERMINATE")
.pop();
const status: ThreadStatus = message.status || "complete";
// Capture completion reason from task_result
const reason =
message.data?.task_result?.stop_reason ||
(message.error ? `Error: ${message.error}` : undefined);
return {
...prev,
[runId]: {
...currentThread,
status: status,
reason: reason,
isExpanded: true,
finalResult: finalMessage,
messages: currentThread.messages,
},
};
});
closeSocket();
break;
}
};
socket.onclose = (event) => {
console.log(
`WebSocket closed for run ${runId}. Code: ${event.code}, Reason: ${event.reason}`
);
if (!isClosing) {
setActiveSockets((prev) => {
const newSockets = { ...prev };
delete newSockets[runId];
return newSockets;
});
setThreadMessages((prev) => {
const thread = prev[runId];
if (thread && thread.status === "streaming") {
return {
...prev,
[runId]: {
...thread,
status: "complete",
reason: event.reason || "Connection closed",
},
};
}
return prev;
});
}
};
socket.onerror = (error) => {
console.error("WebSocket error:", error);
message.error("WebSocket connection error");
setThreadMessages((prev) => {
const thread = prev[runId];
if (!thread) return prev;
return {
...prev,
[runId]: {
...thread,
status: "error",
reason: "WebSocket connection error occurred",
isExpanded: true,
},
};
});
closeSocket();
};
return socket;
};
const cancelRun = async (runId: string) => {
const socket = activeSockets[runId];
if (socket && socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({ type: "stop" }));
setThreadMessages((prev) => ({
...prev,
[runId]: {
...prev[runId],
status: "cancelled",
reason: "Cancelled by user",
isExpanded: true,
},
}));
}
};
const runTask = async (query: string) => {
setError(null);
setLoading(true);
if (!session?.id) {
setLoading(false);
return;
}
let runId: string | null = null;
try {
runId = (await createRun(session.id)) + "";
const userMessage: Message = {
config: {
content: query,
source: "user",
},
session_id: session.id,
run_id: runId,
};
const botMessage: Message = {
config: {
content: "Thinking...",
source: "bot",
},
session_id: session.id,
run_id: runId,
};
setMessages((prev) => [...prev, userMessage, botMessage]);
connectWebSocket(runId, query); // Now passing query to connectWebSocket
} catch (err) {
console.error("Error:", err);
message.error("Error during request processing");
if (runId) {
if (activeSockets[runId]) {
activeSockets[runId].close();
}
setThreadMessages((prev) => ({
...prev,
[runId!]: {
...prev[runId!],
status: "error",
isExpanded: true,
},
}));
}
setError({
status: false,
message: err instanceof Error ? err.message : "Unknown error occurred",
});
} finally {
setLoading(false);
}
};
React.useEffect(() => {
// session changed
if (session) {
setMessages([]);
setThreadMessages({});
}
}, [session]);
return (
<div className="text-primary h-[calc(100vh-195px)] bg-primary relative rounded flex-1 scroll">
<div className="flex gap-4 w-full">
<div className="flex-1">
<SessionManager />
</div>
<TeamManager />
</div>
<div className="flex flex-col h-full">
<div
className="flex-1 overflow-y-auto scroll relative min-h-0"
ref={chatContainerRef}
>
<MessageList
messages={messages}
threadMessages={threadMessages}
setThreadMessages={setThreadMessages}
onRetry={runTask}
onCancel={cancelRun}
loading={loading}
/>
</div>
{sessions?.length === 0 ? (
<div className="flex h-[calc(100%-100px)] flex-col items-center justify-center w-full">
<div className="mt-4 text-sm text-secondary text-center">
<img src={logo} alt="Welcome" className="w-72 h-72 mb-4" />
Welcome! Create a session to get started!
</div>
</div>
) : (
<>
{session && (
<div className="flex-shrink-0">
<ChatInput onSubmit={runTask} loading={loading} error={error} />
</div>
)}
</>
)}
</div>
</div>
);
}

View File

@@ -0,0 +1,128 @@
"use client";
import {
PaperAirplaneIcon,
Cog6ToothIcon,
ExclamationTriangleIcon,
} from "@heroicons/react/24/outline";
import * as React from "react";
import { IStatus } from "../../../types/app";
interface ChatInputProps {
onSubmit: (text: string) => void;
loading: boolean;
error: IStatus | null;
}
export default function ChatInput({
onSubmit,
loading,
error,
}: ChatInputProps) {
const textAreaRef = React.useRef<HTMLTextAreaElement>(null);
const [previousLoading, setPreviousLoading] = React.useState(loading);
const [text, setText] = React.useState("");
const textAreaDefaultHeight = "64px";
// Handle textarea auto-resize
React.useEffect(() => {
if (textAreaRef.current) {
textAreaRef.current.style.height = textAreaDefaultHeight;
const scrollHeight = textAreaRef.current.scrollHeight;
textAreaRef.current.style.height = `${scrollHeight}px`;
}
}, [text]);
// Clear input when loading changes from true to false (meaning the response is complete)
React.useEffect(() => {
if (previousLoading && !loading && !error) {
resetInput();
}
setPreviousLoading(loading);
}, [loading, error, previousLoading]);
const resetInput = () => {
if (textAreaRef.current) {
textAreaRef.current.value = "";
textAreaRef.current.style.height = textAreaDefaultHeight;
setText("");
}
};
const handleTextChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
setText(event.target.value);
};
const handleSubmit = () => {
if (textAreaRef.current?.value && !loading) {
const query = textAreaRef.current.value;
onSubmit(query);
// Don't reset immediately - wait for response to complete
}
};
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
handleSubmit();
}
};
return (
<div className="mt-2 w-full">
<div
className={`mt-2 rounded shadow-sm flex mb-1 ${
loading ? "opacity-50 pointer-events-none" : ""
}`}
>
<form
className="flex-1 relative"
onSubmit={(e) => {
e.preventDefault();
handleSubmit();
}}
>
<textarea
id="queryInput"
name="queryInput"
ref={textAreaRef}
defaultValue={"what is the height of the eiffel tower"}
onChange={handleTextChange}
onKeyDown={handleKeyDown}
className="flex items-center w-full resize-none text-gray-600 rounded border border-accent bg-white p-2 pl-5 pr-16"
style={{
maxHeight: "120px",
overflowY: "auto",
minHeight: "50px",
}}
placeholder="Type your message here..."
disabled={loading}
/>
<div
role="button"
onClick={handleSubmit}
style={{ width: "45px", height: "35px" }}
className="absolute right-3 bottom-2 bg-accent hover:brightness-75 transition duration-300 rounded cursor-pointer flex justify-center items-center"
>
{!loading ? (
<div className="inline-block">
<PaperAirplaneIcon className="h-6 w-6 text-white" />
</div>
) : (
<div className="inline-block">
<Cog6ToothIcon className="text-white animate-spin rounded-full h-6 w-6" />
</div>
)}
</div>
</form>
</div>
{error && !error.status && (
<div className="p-2 border rounded mt-4 text-orange-500 text-sm">
<ExclamationTriangleIcon className="h-5 text-orange-500 inline-block mr-2" />
{error.message}
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,139 @@
import React, { useEffect, useRef } from "react";
import { ChevronDown, ChevronRight, RotateCcw, User, Bot } from "lucide-react";
import { ThreadView } from "./thread";
import { MessageListProps } from "./types";
export const MessageList: React.FC<MessageListProps> = ({
messages,
threadMessages,
setThreadMessages,
onRetry,
onCancel,
loading,
}) => {
const messageListRef = useRef<HTMLDivElement>(null);
const messagePairs = React.useMemo(() => {
const pairs = [];
for (let i = 0; i < messages.length; i += 2) {
if (messages[i] && messages[i + 1]) {
pairs.push({
userMessage: messages[i],
botMessage: messages[i + 1],
});
}
}
return pairs;
}, [messages]);
const toggleThread = (runId: string) => {
setThreadMessages((prev) => ({
...prev,
[runId]: {
...prev[runId],
isExpanded: !prev[runId].isExpanded,
},
}));
};
return (
<div
ref={messageListRef}
className="flex flex-col space-y-4 p-4 overflow-y-hidden overflow-x-hidden"
>
{messagePairs.map(({ userMessage, botMessage }, idx) => {
const thread = threadMessages[botMessage.run_id];
const isStreaming = thread?.status === "streaming";
const isError = thread?.status === "error";
return (
<div key={idx} className="space-y-2 text-primary">
{/* User Message */}
<div className="flex items-start gap-2 ]">
<div className="p-1.5 rounded bg-light text-accent">
<User size={24} />
</div>
<div className="flex-1">
<div className="p-2 bg-accent rounded text-white">
{userMessage.config.content}
</div>
{userMessage.config.models_usage && (
<div className="text-xs text-secondary mt-1">
Tokens:{" "}
{userMessage.config.models_usage.prompt_tokens +
userMessage.config.models_usage.completion_tokens}
</div>
)}
</div>
</div>
{/* Bot Response */}
<div className="flex items-start gap-2 ml-auto ">
<div className="flex-1">
<div className="p-2 bg-secondary rounded text-primary">
<div className="flex items-center justify-between">
<div className="flex-1 text-sm">
{isStreaming ? (
<>Processing...</>
) : (
<>
{" "}
{thread?.finalResult?.content}
<div className="mt-2 mb-2 text-sm text-secondary">
{thread?.reason}
</div>
</>
)}
</div>
<div className="flex items-center gap-1">
{isError && (
<button
onClick={() => onRetry(userMessage.config.content)}
className="p-1 text-secondary hover:text-primary transition-colors"
>
<RotateCcw size={14} />
</button>
)}
{thread && thread.messages?.length > 0 && (
<button
onClick={() => toggleThread(botMessage.run_id)}
className="p-1 text-secondary hover:text-primary transition-colors"
>
{thread.isExpanded ? (
<ChevronDown size={14} />
) : (
<ChevronRight size={14} />
)}
</button>
)}
</div>
</div>
{botMessage.config.models_usage && (
<div className="text-sm text-secondary -mt-4">
{botMessage.config.models_usage.prompt_tokens +
botMessage.config.models_usage.completion_tokens}{" "}
tokens | {thread.messages.length} messages
</div>
)}
{/* Thread View */}
{thread && thread.isExpanded && (
<ThreadView
messages={thread.messages}
status={thread.status}
onCancel={onCancel}
runId={botMessage.run_id}
/>
)}
</div>
</div>
<div className="p-1.5 rounded bg-light text-primary">
<Bot size={24} />
</div>
</div>
</div>
);
})}
</div>
);
};

View File

@@ -0,0 +1,212 @@
import React, { useEffect, useRef } from "react";
import {
CheckCircle,
AlertCircle,
Loader2,
User,
Bot,
StopCircle,
} from "lucide-react";
import { Tooltip } from "antd";
import { MessageConfig } from "../../../types/datamodel";
import { ThreadState } from "./types";
interface ThreadViewProps {
messages: MessageConfig[];
status: ThreadState["status"];
reason?: string; // Add reason
onCancel: (runId: string) => void;
runId: string;
}
interface StatusBannerProps {
status: ThreadState["status"];
reason?: string; // Add reason prop
onCancel: (runId: string) => void;
runId: string;
}
const StatusBanner = ({
status,
reason,
onCancel,
runId,
}: StatusBannerProps) => {
// Define variants FIRST
const variants = {
streaming: {
wrapper: "bg-secondary border border-secondary",
text: "text-primary",
icon: "text-accent",
},
complete: {
wrapper: "bg-secondary p-2",
text: "text-accent",
icon: " ",
},
error: {
wrapper: "bg-secondary border border-secondary",
text: "text-primary",
icon: "text-red-500",
},
cancelled: {
wrapper: "bg-secondary border border-secondary",
text: "text-red-500",
icon: "text-red-500",
},
} as const;
const content = {
streaming: {
icon: <Loader2 className="animate-spin" size={16} />,
text: "Processing",
showStop: true,
},
complete: {
icon: <CheckCircle size={16} />,
text: reason || "Completed",
showStop: false,
},
error: {
icon: <AlertCircle size={16} />,
text: reason || "Error occurred",
showStop: false,
},
cancelled: {
icon: <StopCircle size={16} />,
text: reason || "Cancelled",
showStop: false,
},
} as const;
// THEN check valid status and use variants
const validStatus = status && status in variants ? status : "error";
const currentVariant = variants[validStatus];
const currentContent = content[validStatus];
// Rest of component remains the same...
return (
<div
className={`flex items-center mt-2 justify-between p-2 rounded transition-all duration-200 ${currentVariant.wrapper}`}
>
<div className={`flex items-center gap-2 ${currentVariant.text}`}>
<span className={currentVariant.icon}>{currentContent.icon}</span>
<span className="text-sm font-medium">{currentContent.text}</span>
{currentContent.showStop && (
<Tooltip title="Stop processing" placement="right">
<button
onClick={() => onCancel(runId)}
className="ml-2 flex items-center gap-1 px-2 py-1 rounded bg-red-500 hover:bg-red-600 text-white text-xs font-medium transition-colors duration-200"
>
<StopCircle size={12} />
<span>Stop</span>
</button>
</Tooltip>
)}
</div>
</div>
);
};
const Message = ({ msg, isLast }: { msg: MessageConfig; isLast: boolean }) => {
const isUser = msg.source === "user";
return (
<div className={`relative group ${!isLast ? "mb-2" : ""}`}>
<div
className={`
flex items-start gap-2 p-2 rounded
${isUser ? "bg-secondary" : "bg-tertiary"}
border border-secondary
transition-all duration-200
`}
>
<div
className={`
p-1.5 rounded bg-light
${isUser ? " text-accent" : "text-primary"}
`}
>
{isUser ? <User size={14} /> : <Bot size={14} />}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<span className="text-sm font-semibold text-primary">
{msg.source}
</span>
</div>
<div className="text-sm text-secondary whitespace-pre-wrap">
{msg.content || ""}
</div>
{msg.models_usage && (
<div className="text-xs text-secondary mt-1">
Tokens:{" "}
{(msg.models_usage.prompt_tokens || 0) +
(msg.models_usage.completion_tokens || 0)}
</div>
)}
</div>
</div>
</div>
);
};
export const ThreadView: React.FC<ThreadViewProps> = ({
messages = [],
status,
reason,
onCancel,
runId,
}) => {
const scrollRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (scrollRef.current && status === "streaming") {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
}, [messages, status]);
// const displayMessages = messages.filter(
// (msg): msg is MessageConfig =>
// msg && typeof msg.content === "string" && msg.content !== "TERMINATE"
// );
const displayMessages = messages;
return (
<div>
<div className="space-y-2">
<StatusBanner
status={status}
reason={reason}
onCancel={onCancel}
runId={runId}
/>
<div
ref={scrollRef}
style={{ maxHeight: "300px" }}
className="overflow-y-scroll scroll p-2 space-y-2 bg-primary rounded border border-secondary"
>
{displayMessages.map((msg, idx) => (
<Message
key={idx}
msg={msg}
isLast={idx === displayMessages.length - 1}
/>
))}
{displayMessages.length === 0 && status !== "streaming" && (
<div className="text-sm text-secondary text-center p-2">
No messages to display
</div>
)}
</div>
</div>
</div>
);
};
export default ThreadView;

View File

@@ -0,0 +1,47 @@
import { Message, TaskResult } from "../../../types/datamodel";
export type ThreadStatus = "streaming" | "complete" | "error" | "cancelled";
export interface ThreadState {
messages: any[];
finalResult?: any;
status: ThreadStatus;
isExpanded: boolean;
}
export interface ThreadState {
messages: any[];
finalResult?: any;
status: "streaming" | "complete" | "error" | "cancelled";
isExpanded: boolean;
reason?: string;
}
export interface MessageListProps {
messages: Message[];
threadMessages: Record<string, ThreadState>;
setThreadMessages: React.Dispatch<
React.SetStateAction<Record<string, ThreadState>>
>; // Add this
onRetry: (query: string) => void;
onCancel: (runId: string) => void;
loading: boolean;
}
export interface ModelUsage {
prompt_tokens: number;
completion_tokens: number;
}
export interface SocketMessage {
type: "message" | "result" | "completion";
data?: {
source?: string;
models_usage?: ModelUsage | null;
content?: string;
task_result?: TaskResult;
};
status?: ThreadStatus;
timestamp?: string;
error?: string;
}

View File

@@ -1,824 +0,0 @@
import {
ArrowPathIcon,
ChatBubbleLeftRightIcon,
Cog6ToothIcon,
DocumentDuplicateIcon,
ExclamationTriangleIcon,
InformationCircleIcon,
PaperAirplaneIcon,
SignalSlashIcon,
} from "@heroicons/react/24/outline";
import {
Button,
Dropdown,
MenuProps,
Tabs,
message as ToastMessage,
Tooltip,
message,
} from "antd";
import * as React from "react";
import {
IChatMessage,
IChatSession,
IMessage,
IStatus,
IWorkflow,
} from "../../types";
import { examplePrompts, fetchJSON, getServerUrl, guid } from "../../utils";
import { appContext } from "../../../hooks/provider";
import MetaDataView from "./metadata";
import {
AgentRow,
BounceLoader,
CollapseBox,
LoadingBar,
MarkdownView,
} from "../../atoms";
import { useConfigStore } from "../../../hooks/store";
import ProfilerView from "./utils/profiler";
let socketMsgs: any[] = [];
const ChatBox = ({
initMessages,
session,
editable = true,
heightOffset = 160,
}: {
initMessages: IMessage[] | null;
session: IChatSession | null;
editable?: boolean;
heightOffset?: number;
}) => {
// const session: IChatSession | null = useConfigStore((state) => state.session);
const textAreaInputRef = React.useRef<HTMLTextAreaElement>(null);
const messageBoxInputRef = React.useRef<HTMLDivElement>(null);
const { user } = React.useContext(appContext);
const wsClient = React.useRef<WebSocket | null>(null);
const wsMessages = React.useRef<IChatMessage[]>([]);
const [wsConnectionStatus, setWsConnectionStatus] =
React.useState<string>("disconnected");
const [workflow, setWorkflow] = React.useState<IWorkflow | null>(null);
const [socketMessages, setSocketMessages] = React.useState<any[]>([]);
const [awaitingUserInput, setAwaitingUserInput] = React.useState(false); // New state for tracking user input
const setAreSessionButtonsDisabled = useConfigStore(
(state) => state.setAreSessionButtonsDisabled
);
const MAX_RETRIES = 10;
const RETRY_INTERVAL = 2000;
const [retries, setRetries] = React.useState(0);
const serverUrl = getServerUrl();
let websocketUrl = serverUrl.replace("http", "ws") + "/ws/";
// check if there is a protocol in the serverUrl e.g. /api. if use the page url
if (!serverUrl.includes("http")) {
const pageUrl = window.location.href;
const url = new URL(pageUrl);
const protocol = url.protocol;
const host = url.host;
const baseUrl = protocol + "//" + host + serverUrl;
websocketUrl = baseUrl.replace("http", "ws") + "/ws/";
} else {
websocketUrl = serverUrl.replace("http", "ws") + "/ws/";
}
const [loading, setLoading] = React.useState(false);
const [text, setText] = React.useState("");
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const socketDivRef = React.useRef<HTMLDivElement>(null);
const connectionId = useConfigStore((state) => state.connectionId);
const messages = useConfigStore((state) => state.messages);
const setMessages = useConfigStore((state) => state.setMessages);
const parseMessage = (message: IMessage) => {
let meta;
try {
meta = JSON.parse(message.meta);
} catch (e) {
meta = message?.meta;
}
const msg: IChatMessage = {
text: message.content,
sender: message.role === "user" ? "user" : "bot",
meta: meta,
id: message.id,
};
return msg;
};
const parseMessages = (messages: any) => {
return messages?.map(parseMessage);
};
React.useEffect(() => {
// console.log("initMessages changed", initMessages);
const initMsgs: IChatMessage[] = parseMessages(initMessages);
setMessages(initMsgs);
wsMessages.current = initMsgs;
}, [initMessages]);
const promptButtons = examplePrompts.map((prompt, i) => {
return (
<Button
key={"prompt" + i}
type="primary"
className=""
onClick={() => {
runWorkflow(prompt.prompt);
}}
>
{" "}
{prompt.title}{" "}
</Button>
);
});
const messageListView = messages && messages?.map((message: IChatMessage, i: number) => {
const isUser = message.sender === "user";
const css = isUser ? "bg-accent text-white " : "bg-light";
// console.log("message", message);
let hasMeta = false;
if (message.meta) {
hasMeta =
message.meta.code !== null ||
message.meta.images?.length > 0 ||
message.meta.files?.length > 0 ||
message.meta.scripts?.length > 0;
}
let items: MenuProps["items"] = [];
if (isUser) {
items.push({
label: (
<div
onClick={() => {
console.log("retrying");
runWorkflow(message.text);
}}
>
<ArrowPathIcon
role={"button"}
title={"Retry"}
className="h-4 w-4 mr-1 inline-block"
/>
Retry
</div>
),
key: "retrymessage",
});
items.push({
label: (
<div
onClick={() => {
// copy to clipboard
navigator.clipboard.writeText(message.text);
ToastMessage.success("Message copied to clipboard");
}}
>
<DocumentDuplicateIcon
role={"button"}
title={"Copy"}
className="h-4 w-4 mr-1 inline-block"
/>
Copy
</div>
),
key: "copymessage",
});
}
const menu = (
<Dropdown menu={{ items }} trigger={["click"]} placement="bottomRight">
<div
role="button"
className="float-right ml-2 duration-100 hover:bg-secondary font-semibold px-2 pb-1 rounded"
>
<span className="block -mt-2 text-primary "> ...</span>
</div>
</Dropdown>
);
return (
<div
id={"message" + i} className={`align-right ${isUser ? "text-righpt" : ""} mb-2 border-b`}
key={"message" + i}
>
{" "}
<div className={` ${isUser ? "" : " w-full"} inline-flex gap-2`}>
<div className=""></div>
<div className="font-semibold text-secondary text-sm w-16">{`${
isUser ? "USER" : "AGENTS"
}`}</div>
<div
className={`inline-block group relative w-full p-2 rounded ${css}`}
>
{" "}
{items.length > 0 && editable && (
<div className=" group-hover:opacity-100 opacity-0 ">{menu}</div>
)}
{isUser && (
<>
<div className="inline-block">{message.text}</div>
</>
)}
{!isUser && (
<div
className={` w-full chatbox prose dark:prose-invert text-primary rounded `}
>
<MarkdownView
className="text-sm"
data={message.text}
showCode={false}
/>
</div>
)}
{message.meta && !isUser && (
<>
{" "}
<Tabs
defaultActiveKey="1"
items={[
{
label: (
<>
{" "}
<ChatBubbleLeftRightIcon className="h-4 w-4 inline-block mr-1" />
Agent Messages
</>
),
key: "1",
children: (
<div className="text-primary">
<MetaDataView metadata={message.meta} />
</div>
),
},
{
label: (
<div>
{" "}
<SignalSlashIcon className="h-4 w-4 inline-block mr-1" />{" "}
Profiler
</div>
),
key: "2",
children: (
<div className="text-primary">
<ProfilerView agentMessage={message} />
</div>
),
},
]}
/>
</>
)}
</div>
</div>
</div>
);
});
React.useEffect(() => {
// console.log("messages updated, scrolling");
setTimeout(() => {
scrollChatBox(messageBoxInputRef);
}, 500);
}, [messages]);
const textAreaDefaultHeight = "64px";
// clear text box if loading has just changed to false and there is no error
React.useEffect(() => {
if ((awaitingUserInput || loading === false) && textAreaInputRef.current) {
if (textAreaInputRef.current) {
if (error === null || (error && error.status === false)) {
textAreaInputRef.current.value = "";
textAreaInputRef.current.style.height = textAreaDefaultHeight;
}
}
}
}, [loading]);
React.useEffect(() => {
if (textAreaInputRef.current) {
textAreaInputRef.current.style.height = textAreaDefaultHeight; // Reset height to shrink if text is deleted
const scrollHeight = textAreaInputRef.current.scrollHeight;
textAreaInputRef.current.style.height = `${scrollHeight}px`;
}
}, [text]);
const [waitingToReconnect, setWaitingToReconnect] = React.useState<
boolean | null
>(null);
React.useEffect(() => {
if (waitingToReconnect) {
return;
}
// Only set up the websocket once
const socketUrl = websocketUrl + connectionId;
console.log("socketUrl", socketUrl);
if (!wsClient.current) {
const client = new WebSocket(socketUrl);
wsClient.current = client;
client.onerror = (e) => {
console.log("ws error", e);
};
client.onopen = () => {
setWsConnectionStatus("connected");
console.log("ws opened");
};
client.onclose = () => {
if (wsClient.current) {
// Connection failed
console.log("ws closed by server");
} else {
// Cleanup initiated from app side, can return here, to not attempt a reconnect
return;
}
if (waitingToReconnect) {
return;
}
setWsConnectionStatus("disconnected");
setWaitingToReconnect(true);
setWsConnectionStatus("reconnecting");
setTimeout(() => {
setWaitingToReconnect(null);
}, RETRY_INTERVAL);
};
client.onmessage = (message) => {
const data = JSON.parse(message.data);
console.log("received message", data);
if (data && data.type === "agent_message") {
// indicates an intermediate agent message update
const newsocketMessages = Object.assign([], socketMessages);
newsocketMessages.push(data.data);
setSocketMessages(newsocketMessages);
socketMsgs.push(data.data);
setTimeout(() => {
scrollChatBox(socketDivRef);
scrollChatBox(messageBoxInputRef);
}, 200);
// console.log("received message", data, socketMsgs.length);
} else if (data && data.type === "user_input_request") {
setAwaitingUserInput(true); // Set awaiting input state
textAreaInputRef.current.value = ""
textAreaInputRef.current.placeholder = data.data.message.content
const newsocketMessages = Object.assign([], socketMessages);
newsocketMessages.push(data.data);
setSocketMessages(newsocketMessages);
socketMsgs.push(data.data);
setTimeout(() => {
scrollChatBox(socketDivRef);
scrollChatBox(messageBoxInputRef);
}, 200);
ToastMessage.info(data.data.message)
} else if (data && data.type === "agent_status") {
// indicates a status message update
const agentStatusSpan = document.getElementById("agentstatusspan");
if (agentStatusSpan) {
agentStatusSpan.innerHTML = data.data.message;
}
} else if (data && data.type === "agent_response") {
// indicates a final agent response
setAwaitingUserInput(false); // Set awaiting input state
setAreSessionButtonsDisabled(false);
processAgentResponse(data.data);
}
};
return () => {
console.log("Cleanup");
// Dereference, so it will set up next time
wsClient.current = null;
client.close();
};
}
}, [waitingToReconnect]);
const scrollChatBox = (element: any) => {
element.current?.scroll({
top: element.current.scrollHeight,
behavior: "smooth",
});
};
const mainDivRef = React.useRef<HTMLDivElement>(null);
const processAgentResponse = (data: any) => {
if (data && data.status) {
const msg = parseMessage(data.data);
wsMessages.current.push(msg);
setMessages(wsMessages.current);
setLoading(false);
setAwaitingUserInput(false);
} else {
console.log("error", data);
// setError(data);
ToastMessage.error(data.message);
setLoading(false);
setAwaitingUserInput(false);
}
};
const fetchWorkFlow = (workflowId: number) => {
const fetchUrl = `${serverUrl}/workflows/${workflowId}?user_id=${user?.email}`;
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
if (data.data && data.data.length > 0) {
setWorkflow(data.data[0]);
}
} else {
message.error(data.message);
}
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
};
fetchJSON(fetchUrl, payLoad, onSuccess, onError);
};
const runWorkflow = (query: string) => {
setError(null);
setAreSessionButtonsDisabled(true);
socketMsgs = [];
let messageHolder = Object.assign([], messages);
const userMessage: IChatMessage = {
text: query,
sender: "user",
};
messageHolder.push(userMessage);
setMessages(messageHolder);
wsMessages.current.push(userMessage);
const messagePayload: IMessage = {
role: "user",
content: query,
user_id: user?.email || "",
session_id: session?.id,
workflow_id: session?.workflow_id,
connection_id: connectionId,
};
const runWorkflowUrl = `${serverUrl}/sessions/${session?.id}/workflow/${session?.workflow_id}/run`;
const postData = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(messagePayload),
};
setLoading(true);
// check if socket connected, send on socket
// else send on fetch
if (wsClient.current && wsClient.current.readyState === 1) {
wsClient.current.send(
JSON.stringify({
connection_id: connectionId,
data: messagePayload,
type: "user_message",
session_id: session?.id,
workflow_id: session?.workflow_id,
})
);
} else {
fetch(runWorkflowUrl, postData)
.then((res) => {
if (res.status === 200) {
res.json().then((data) => {
processAgentResponse(data);
});
} else {
res.json().then((data) => {
console.log("error", data);
ToastMessage.error(data.message);
setLoading(false);
});
ToastMessage.error(
"Connection error. Ensure server is up and running."
);
}
})
.catch(() => {
setLoading(false);
ToastMessage.error(
"Connection error. Ensure server is up and running."
);
})
.finally(() => {
setTimeout(() => {
scrollChatBox(messageBoxInputRef);
}, 500);
});
}
};
const sendUserResponse = (userResponse: string) => {
setAwaitingUserInput(false);
setError(null);
setLoading(true);
textAreaInputRef.current.placeholder = "Write message here..."
const userMessage: IChatMessage = {
text: userResponse,
sender: "system",
};
const messagePayload: IMessage = {
role: "user",
content: userResponse,
user_id: user?.email || "",
session_id: session?.id,
workflow_id: session?.workflow_id,
connection_id: connectionId,
};
// check if socket connected,
if (wsClient.current && wsClient.current.readyState === 1) {
wsClient.current.send(
JSON.stringify({
connection_id: connectionId,
data: messagePayload,
type: "user_message",
session_id: session?.id,
workflow_id: session?.workflow_id,
})
);
} else {
console.err("websocket client error")
}
};
const handleTextChange = (
event: React.ChangeEvent<HTMLTextAreaElement>
): void => {
setText(event.target.value);
};
const handleKeyDown = (
event: React.KeyboardEvent<HTMLTextAreaElement>
): void => {
if (event.key === "Enter" && !event.shiftKey) {
if (textAreaInputRef.current &&(awaitingUserInput || !loading)) {
event.preventDefault();
if (awaitingUserInput) {
sendUserResponse(textAreaInputRef.current.value); // New function call for sending user input
textAreaInputRef.current.value = "";
} else {
runWorkflow(textAreaInputRef.current.value);
}
}
}
};
const getConnectionColor = (status: string) => {
if (status === "connected") {
return "bg-green-500";
} else if (status === "reconnecting") {
return "bg-orange-500";
} else if (status === "disconnected") {
return "bg-red-500";
}
};
React.useEffect(() => {
if (session && session.workflow_id) {
fetchWorkFlow(session.workflow_id);
}
}, [session]);
const WorkflowView = ({ workflow }: { workflow: IWorkflow }) => {
return (
<div id="workflow-view" className="text-xs cursor-pointer inline-block">
{" "}
{workflow.name}
</div>
);
};
return (
<div
id="chatbox-main"
style={{ height: "calc(100vh - " + heightOffset + "px)" }}
className="text-primary relative rounded "
ref={mainDivRef}
>
<div
id="workflow-name"
style={{ zIndex: 100 }}
className=" absolute right-3 bg-primary rounded text-secondary -top-6 p-2"
>
{" "}
{workflow && <div className="text-xs"> {workflow.name}</div>}
</div>
<div
id="message-box"
ref={messageBoxInputRef}
className="flex h-full flex-col rounded scroll pr-2 overflow-auto "
style={{ minHeight: "30px", height: "calc(100vh - 310px)" }}
>
<div id="scroll-gradient" className="scroll-gradient h-10">
{" "}
<span className=" inline-block h-6"></span>{" "}
</div>
<div className="flex-1 boder mt-4"></div>
{!messages && messages !== null && (
<div id="loading-messages" className="w-full text-center boder mt-4">
<div>
{" "}
<BounceLoader />
</div>
loading messages
</div>
)}
{messages && messages?.length === 0 && (
<div id="no-messages" className="ml-2 text-sm text-secondary ">
<InformationCircleIcon className="inline-block h-6 mr-2" />
No messages in the current session. Start a conversation to begin.
</div>
)}
<div id="message-list" className="ml-2"> {messageListView}</div>
{(loading || awaitingUserInput) && (
<div id="loading-bar" className={` inline-flex gap-2 duration-300 `}>
<div className=""></div>
<div className="font-semibold text-secondary text-sm w-16">
AGENTS
</div>
<div className="relative w-full ">
<div className="mb-2 ">
<LoadingBar>
<div className="mb-1 inline-block ml-2 text-xs text-secondary">
<span className="innline-block text-sm ml-2">
{" "}
<span id="agentstatusspan">
{" "}
agents working on task ..
</span>
</span>{" "}
{socketMsgs.length > 0 && (
<span className="border-l inline-block text-right ml-2 pl-2">
{socketMsgs.length} agent message
{socketMsgs.length > 1 && "s"} sent/received.
</span>
)}
</div>
</LoadingBar>
</div>
{socketMsgs.length > 0 && (
<div
id="agent-messages"
ref={socketDivRef}
style={{
minHeight: "300px",
maxHeight: "400px",
overflowY: "auto",
}}
className={`inline-block scroll group relative p-2 rounded w-full bg-light `}
>
<CollapseBox
open={true}
title={`Agent Messages (${socketMsgs.length} message${
socketMsgs.length > 1 ? "s" : ""
}) `}
>
{socketMsgs?.map((message: any, i: number) => {
return (
<div key={i}>
<AgentRow message={message} />
</div>
);
})}
</CollapseBox>
</div>
)}
</div>
</div>
)}
</div>
{editable && (
<div id="input-area" className="mt-2 p-2 absolute bg-primary bottom-0 w-full">
<div
id="input-form"
className={`rounded p-2 shadow-lg flex mb-1 gap-2 ${
loading && !awaitingUserInput ? " opacity-50 pointer-events-none" : ""
}`}
>
{/* <input className="flex-1 p-2 ring-2" /> */}
<form
autoComplete="on"
className="flex-1 relative"
onSubmit={(e) => {
e.preventDefault();
}}
>
<textarea
id="queryInput"
name="queryInput"
autoComplete="on"
onKeyDown={handleKeyDown}
onChange={handleTextChange}
placeholder="Write message here..."
ref={textAreaInputRef}
className="flex items-center w-full resize-none text-gray-600 bg-white p-2 ring-2 rounded-sm pl-5 pr-16 h-64"
style={{
maxHeight: "120px",
overflowY: "auto",
minHeight: "50px",
}}
/>
<div
id="send-button"
role={"button"}
style={{ width: "45px", height: "35px" }}
title="Send message"
onClick={() => {
if (textAreaInputRef.current && (awaitingUserInput || !loading)) {
if (awaitingUserInput) {
sendUserResponse(textAreaInputRef.current.value); // Use the new function for user input
} else {
runWorkflow(textAreaInputRef.current.value);
}
}
}}
className="absolute right-3 bottom-2 bg-accent hover:brightness-75 transition duration-300 rounded cursor-pointer flex justify-center items-center"
>
{" "}
{(awaitingUserInput || !loading) && (
<div className="inline-block ">
<PaperAirplaneIcon className="h-6 w-6 text-white " />{" "}
</div>
)}
{loading && !awaitingUserInput && (
<div className="inline-block ">
<Cog6ToothIcon className="text-white animate-spin rounded-full h-6 w-6" />
</div>
)}
</div>
</form>
</div>{" "}
<div>
<div className="mt-2 text-xs text-secondary">
<Tooltip title={`Socket ${wsConnectionStatus}`}>
<div
className={`w-1 h-3 rounded inline-block mr-1 ${getConnectionColor(
wsConnectionStatus
)}`}
></div>{" "}
</Tooltip>
Blank slate? Try one of the example prompts below{" "}
</div>
<div
id="prompt-buttons"
className={`mt-2 inline-flex gap-2 flex-wrap ${
(loading && !awaitingUserInput) ? "brightness-75 pointer-events-none" : ""
}`}
>
{promptButtons}
</div>
</div>
{error && !error.status && (
<div id="error-message" className="p-2 rounded mt-4 text-orange-500 text-sm">
{" "}
<ExclamationTriangleIcon className="h-5 text-orange-500 inline-block mr-2" />{" "}
{error.message}
</div>
)}
</div>
)}
</div>
);
};
export default ChatBox;

View File

@@ -1,242 +0,0 @@
import {
DocumentTextIcon,
PhotoIcon,
VideoCameraIcon,
} from "@heroicons/react/24/outline";
import * as React from "react";
import {
CodeBlock,
CodeLoader,
CsvLoader,
CollapseBox,
ExpandView,
GroupView,
ImageLoader,
MarkdownView,
PdfViewer,
AgentRow,
} from "../../atoms";
import { formatDuration, getServerUrl } from "../../utils";
import { IMetadataFile } from "../../types";
import Icon from "../../icons";
const MetaDataView = ({ metadata }: { metadata: any | null }) => {
const serverUrl = getServerUrl();
const renderFileContent = (file: IMetadataFile, i: number) => {
const file_type = file.extension;
const is_image = ["image"].includes(file.type);
const is_code = ["code"].includes(file.type);
const is_csv = ["csv"].includes(file.type);
const is_pdf = ["pdf"].includes(file.type);
const is_video = ["video"].includes(file.type);
const file_name = file.name || "unknown";
const file_path = file.path || "unknown";
let fileView = null;
let fileTitle = (
<div>
{file.type === "image" ? (
<PhotoIcon className="h-4 mr-1 inline-block" />
) : (
<DocumentTextIcon className="h-4 mr-1 inline-block" />
)}{" "}
<span className="break-all ">{file_name}</span>{" "}
</div>
);
let icon = (
<div className="p-2 bg-secondary rounded flex items-center justify-center h-full">
<div className="">
<div style={{ fontSize: "2em" }} className=" text-center mb-2">
{file.extension}
</div>
<div>{fileTitle}</div>
</div>
</div>
);
if (is_image) {
fileView = (
<div>
<div className="mb-2">{fileTitle}</div>
<ImageLoader
src={`${serverUrl}/${file_path}`}
className="w-full rounded"
/>
</div>
);
icon = fileView;
} else if (is_video) {
fileView = (
<div className="mb-2">
<a href={`${serverUrl}/${file_path}`}>{fileTitle}</a>
<video controls className="w-full rounded">
<source
src={`${serverUrl}/${file_path}`}
type={`video/${file_type}`}
/>
Your browser does not support the video tag.
</video>
</div>
);
// Update icon to show a video-related icon
icon = (
<div className=" relative rounded h-full">
<div className="absolute rounded p-2 bg-secondary top-0 ">
{fileTitle}
</div>
<div
style={{ minHeight: "150px" }}
className="bg-secondary h-full w-full rounded flex items-center justify-center text-primary "
>
<VideoCameraIcon className="h-14 w-14" />
</div>
</div>
);
} else if (is_csv) {
fileView = (
<div className="h">
<a href={`${serverUrl}/${file_path}`}>
<div className="mb-4">{fileTitle}</div>
</a>
<CsvLoader
csvUrl={`${serverUrl}/${file_path}`}
className="w-full rounded"
/>
</div>
);
icon = (
<div className=" relative rounded h-full">
<div className="absolute rounded p-2 bg-secondary top-0 ">
{fileTitle}
</div>
<div
style={{ minHeight: "150px" }}
className="bg-secondary h-full w-full rounded flex items-center justify-center text-primary"
>
<Icon icon="csv" size={14} />
</div>
</div>
);
} else if (is_code) {
fileView = (
<div className="h">
<a
href={`${serverUrl}/${file_path}`}
target="_blank"
rel="noopener noreferrer"
>
<div className="mb-4">{fileTitle}</div>
</a>
<CodeLoader
url={`${serverUrl}/${file_path}`}
className="w-full rounded"
/>
</div>
);
icon = (
<div className=" relative rounded h-full">
<div className="absolute rounded p-2 bg-secondary top-0 ">
{fileTitle}
</div>
<div
style={{ minHeight: "150px" }}
className="bg-secondary h-full w-full rounded flex items-center justify-center text-primary"
>
<Icon icon="python" size={14} />
</div>
</div>
);
} else if (is_pdf) {
fileView = (
<div className="h-full">
<div className="mb-4">
<a
href={`${serverUrl}/${file_path}`}
target="_blank"
rel="noopener noreferrer"
>
{fileTitle}
</a>
</div>
<PdfViewer url={`${serverUrl}/${file_path}`} />
</div>
);
icon = (
<div className=" relative rounded h-full">
<div className="absolute rounded p-2 bg-secondary top-0 ">
{fileTitle}
</div>
<div
style={{ minHeight: "150px" }}
className="bg-secondary h-full w-full rounded flex items-center justify-center text-primary "
>
<Icon icon="pdf" size={14} />
</div>
</div>
);
} else {
fileView = <span>Unsupported file type.</span>;
}
return (
<div className=" h-full rounded">
<ExpandView className="mb-1" icon={icon} title={file_name}>
{fileView}
</ExpandView>
</div>
);
};
const renderFile = (file: IMetadataFile, i: number) => (
<div key={"metafilesrow" + i} className="text-primary ">
{renderFileContent(file, i)}
</div>
);
const files = (metadata.files || []).map(renderFile);
const messages = (metadata.messages || []).map((message: any, i: number) => {
return (
<div className=" mb-2 border-dashed" key={"messagerow" + i}>
<AgentRow message={message} />
</div>
);
});
const hasContent = files.length > 0;
const hasMessages = messages.length > 0;
return (
<div>
{hasMessages && (
<div className="rounded bg-primary ">
<CollapseBox
open={false}
title={`Agent Messages (${messages.length} message${
messages.length > 1 ? "s" : ""
}) | ${formatDuration(metadata?.time)}`}
>
{messages}
</CollapseBox>
</div>
)}
{hasContent && (
<div className="rounded mt-2">
<CollapseBox
open={true}
title={`Results (${files.length} file${
files.length > 1 ? "s" : ""
})`}
>
<div className="mt-2 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{files}
</div>
</CollapseBox>
</div>
)}
</div>
);
};
export default MetaDataView;

View File

@@ -1,94 +0,0 @@
import * as React from "react";
import { IChatSession, IMessage, IStatus } from "../../types";
import { fetchJSON, getServerUrl, setLocalStorage } from "../../utils";
import ChatBox from "./chatbox";
import { appContext } from "../../../hooks/provider";
import { message } from "antd";
import SideBarView from "./sidebar";
import { useConfigStore } from "../../../hooks/store";
import SessionsView from "./sessions";
const RAView = () => {
const session: IChatSession | null = useConfigStore((state) => state.session);
const [loading, setLoading] = React.useState(false);
const [messages, setMessages] = React.useState<IMessage[] | null>(null);
const [config, setConfig] = React.useState(null);
React.useEffect(() => {
setLocalStorage("ara_config", config);
}, [config]);
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const fetchMessagesUrl = `${serverUrl}/sessions/${session?.id}/messages?user_id=${user?.email}`;
const fetchMessages = () => {
setError(null);
setLoading(true);
setMessages(null);
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
setMessages(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(fetchMessagesUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user && session) {
fetchMessages();
}
}, [session]);
return (
<div className="h-full ">
<div className="flex h-full ">
<div className=" mr-2 rounded">
<SideBarView />
</div>
<div className=" flex-1 ">
{!session && (
<div className=" w-full h-full flex items-center justify-center">
<div className="w-2/3" id="middle">
<div className="w-full text-center">
{" "}
<img
src="/images/svgs/welcome.svg"
alt="welcome"
className="text-accent inline-block object-cover w-56"
/>
</div>
<SessionsView />
</div>
</div>
)}
{session !== null && (
<ChatBox initMessages={messages} session={session} />
)}
</div>
</div>
</div>
);
};
export default RAView;

View File

@@ -1,460 +0,0 @@
import {
ChatBubbleLeftRightIcon,
ExclamationTriangleIcon,
GlobeAltIcon,
PencilIcon,
PlusIcon,
Square3Stack3DIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import { Button, Dropdown, Input, MenuProps, Modal, message } from "antd";
import * as React from "react";
import { IChatSession, IWorkflow, IStatus } from "../../types";
import { appContext } from "../../../hooks/provider";
import { fetchJSON, getServerUrl, timeAgo } from "../../utils";
import { LaunchButton, LoadingOverlay } from "../../atoms";
import { useConfigStore } from "../../../hooks/store";
import WorkflowSelector from "./utils/selectors";
const SessionsView = ({}: any) => {
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const listSessionUrl = `${serverUrl}/sessions?user_id=${user?.email}`;
const createSessionUrl = `${serverUrl}/sessions`;
const publishSessionUrl = `${serverUrl}/sessions/publish`;
const sessions = useConfigStore((state) => state.sessions);
const setSessions = useConfigStore((state) => state.setSessions);
const sampleSession: IChatSession = {
user_id: user?.email || "",
name:
"New Session " +
new Date().toLocaleString("en-US", {
month: "short",
day: "numeric",
hour: "numeric",
minute: "numeric",
hour12: true,
}),
};
const [selectedSession, setSelectedSession] =
React.useState<IChatSession | null>(sampleSession);
// const [session, setSession] =
// React.useState<IChatSession | null>(null);
const session = useConfigStore((state) => state.session);
const setSession = useConfigStore((state) => state.setSession);
const isSessionButtonsDisabled = useConfigStore(
(state) => state.areSessionButtonsDisabled
);
const deleteSession = (session: IChatSession) => {
setError(null);
setLoading(true);
// const fetch;
const deleteSessionUrl = `${serverUrl}/sessions/delete?user_id=${user?.email}&session_id=${session.id}`;
const payLoad = {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: user?.email,
session: session,
}),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
fetchSessions();
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(deleteSessionUrl, payLoad, onSuccess, onError);
};
const [newSessionModalVisible, setNewSessionModalVisible] =
React.useState(false);
const fetchSessions = () => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
// message.success(data.message);
// console.log("sessions", data);
setSessions(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(listSessionUrl, payLoad, onSuccess, onError);
};
const publishSession = () => {
setError(null);
setLoading(true);
const body = {
user_id: user?.email,
session: session,
tags: ["published"],
};
// const fetch;
const payLoad = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
// setSessions(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(publishSessionUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (sessions && sessions.length > 0) {
const firstSession = sessions[0];
setSession(firstSession);
} else {
setSession(null);
}
}, [sessions]);
const createSession = (session: IChatSession) => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(session),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
fetchSessions();
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(createSessionUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user) {
fetchSessions();
}
}, []);
const sessionRows = sessions.map((data: IChatSession, index: number) => {
const isSelected = session?.id === data.id;
const rowClass = isSelected
? "bg-accent text-white"
: "bg-secondary text-primary";
let items: MenuProps["items"] = [
{
label: (
<div
onClick={() => {
console.log("deleting session");
deleteSession(data);
}}
>
<TrashIcon
role={"button"}
title={"Delete"}
className="h-4 w-4 mr-1 inline-block"
/>
Delete
</div>
),
key: "delete",
},
{
label: (
<div
onClick={() => {
// get current clicked session
setSelectedSession(data);
setNewSessionModalVisible(true);
}}
>
<PencilIcon
role={"button"}
title={"Edit"}
className="h-4 w-4 mr-1 inline-block"
/>
Edit
</div>
),
key: "edit",
},
// {
// label: (
// <div
// onClick={() => {
// console.log("publishing session");
// publishSession();
// }}
// >
// <GlobeAltIcon
// role={"button"}
// title={"Publish"}
// className="h-4 w-4 mr-1 inline-block"
// />
// Publish
// </div>
// ),
// key: "publish",
// },
];
items.push();
const menu = (
<Dropdown
menu={{ items: items }}
trigger={["click"]}
placement="bottomRight"
>
<div
role="button"
className={`float-right ml-2 duration-100 hover:bg-secondary font-semibold px-2 pb-1 rounded ${
isSelected ? "hover:text-accent" : ""
}`}
>
<span className={`block -mt-2 ${isSelected ? "text-white" : ""}`}>
{" "}
...
</span>
</div>
</Dropdown>
);
return (
<div
key={"sessionsrow" + index}
className={`group relative mb-2 pb-1 border-b border-dashed ${
isSessionButtonsDisabled ? "opacity-50 pointer-events-none" : ""
}`}
>
{items.length > 0 && (
<div className=" absolute right-2 top-2 group-hover:opacity-100 opacity-0 ">
{menu}
</div>
)}
<div
className={`rounded p-2 cursor-pointer ${rowClass}`}
role="button"
onClick={() => {
// setWorkflowConfig(data.flow_config);
if (!isSessionButtonsDisabled) {
setSession(data);
}
}}
>
<div className="text-xs mt-1">
<Square3Stack3DIcon className="h-4 w-4 inline-block mr-1" />
{data.name}
</div>
<div className="text-xs text-right ">
{timeAgo(data.created_at || "")}
</div>
</div>
</div>
);
});
let windowHeight, skillsMaxHeight;
if (typeof window !== "undefined") {
windowHeight = window.innerHeight;
skillsMaxHeight = windowHeight - 400 + "px";
}
const NewSessionModal = ({ session }: { session: IChatSession | null }) => {
const [workflow, setWorkflow] = React.useState<IWorkflow | null>(null);
const [localSession, setLocalSession] = React.useState<IChatSession | null>(
session
);
React.useEffect(() => {
if (workflow && workflow.id && localSession) {
setLocalSession({ ...localSession, workflow_id: workflow.id });
}
}, [workflow]);
const sessionExists =
localSession !== null && localSession.id !== undefined;
return (
<Modal
onCancel={() => {
setNewSessionModalVisible(false);
}}
title={
<div className="font-semibold mb-2 pb-1 border-b">
<Square3Stack3DIcon className="h-5 w-5 inline-block mr-1" />
New Session{" "}
</div>
}
open={newSessionModalVisible}
footer={[
<Button
key="back"
onClick={() => {
setNewSessionModalVisible(false);
}}
>
Cancel
</Button>,
<Button
key="submit"
type="primary"
disabled={!workflow}
onClick={() => {
setNewSessionModalVisible(false);
if (localSession) {
createSession(localSession);
}
}}
>
Create
</Button>,
]}
>
<WorkflowSelector
workflow={workflow}
setWorkflow={setWorkflow}
workflow_id={selectedSession?.workflow_id}
disabled={sessionExists}
/>
<div className="my-2 text-xs"> Session Name </div>
<Input
placeholder="Session Name"
value={localSession?.name || ""}
onChange={(event) => {
if (localSession) {
setLocalSession({ ...localSession, name: event.target.value });
}
}}
/>
<div className="text-xs mt-4">
{" "}
{timeAgo(localSession?.created_at || "", true)}
</div>
</Modal>
);
};
return (
<div className=" ">
<NewSessionModal session={selectedSession || sampleSession} />
<div className="mb-2 relative">
<div className="">
<div className="font-semibold mb-2 pb-1 border-b">
<ChatBubbleLeftRightIcon className="h-5 w-5 inline-block mr-1" />
Sessions{" "}
</div>
{sessions && sessions.length > 0 && (
<div className="text-xs hidden mb-2 pb-1 ">
{" "}
Create a new session or select an existing session to view chat.
</div>
)}
<div
style={{
maxHeight: skillsMaxHeight,
}}
className="mb-4 overflow-y-auto scroll rounded relative "
>
{sessionRows}
<LoadingOverlay loading={loading} />
</div>
{(!sessions || sessions.length == 0) && !loading && (
<div className="text-xs text-gray-500">
No sessions found. Create a new session to get started.
</div>
)}
</div>
<div className="flex gap-x-2">
<div className="flex-1"></div>
<LaunchButton
className={`text-sm p-2 px-3 ${isSessionButtonsDisabled ? 'opacity-50 cursor-not-allowed' : ''}`}
onClick={() => {
setSelectedSession(sampleSession);
setNewSessionModalVisible(true);
}}
>
{" "}
<PlusIcon className="w-5 h-5 inline-block mr-1" />
New
</LaunchButton>
</div>
</div>
{error && !error.status && (
<div className="p-2 border border-orange-500 text-secondary rounded mt-4 text-sm">
{" "}
<ExclamationTriangleIcon className="h-5 text-orange-500 inline-block mr-2" />{" "}
{error.message}
</div>
)}
</div>
);
};
export default SessionsView;

View File

@@ -1,49 +0,0 @@
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/24/outline";
import * as React from "react";
import SessionsView from "./sessions";
const SideBarView = () => {
const [isOpen, setIsOpen] = React.useState(true);
const minWidth = isOpen ? "270px" : "50px";
let windowHeight, sidebarMaxHeight;
if (typeof window !== "undefined") {
windowHeight = window.innerHeight;
sidebarMaxHeight = windowHeight - 180 + "px";
}
return (
<div
style={{
minWidth: minWidth,
maxWidth: minWidth,
height: "calc(100vh - 190px)",
}}
className=" "
>
<div className=" transition overflow-hidden duration-300 flex flex-col h-full p-2 overflow-y-scroll scroll rounded ">
<div className={`${isOpen ? "" : "hidden"} `}>
{/* <AgentsView /> */}
{<SessionsView />}
</div>
</div>
<div
onClick={() => setIsOpen(!isOpen)}
role="button"
className=" hover:text-accent duration-150 "
>
{isOpen ? (
<div className="mt-4 ">
{" "}
<ChevronLeftIcon className="w-6 h-6 inline-block rounded" />{" "}
<span className="text-xs "> close sidebar</span>
</div>
) : (
<ChevronRightIcon className="w-6 h-6 inline-block font-bold rounded " />
)}
</div>
</div>
);
};
export default SideBarView;

View File

@@ -1,58 +0,0 @@
import { Bar, Line } from "@ant-design/plots";
import * as React from "react";
import { IStatus } from "../../../../types";
const BarChartViewer = ({ data }: { data: any | null }) => {
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const [loading, setLoading] = React.useState(false);
const config = {
data: data.bar,
xField: "agent",
yField: "message",
colorField: "tool_call",
stack: true,
axis: {
y: { labelFormatter: "" },
x: {
labelSpacing: 4,
},
},
style: {
radiusTopLeft: 10,
radiusTopRight: 10,
},
height: 60 * data.agents.length,
};
const config_code_exec = Object.assign({}, config);
config_code_exec.colorField = "code_execution";
return (
<div className="bg-white rounded relative">
<div>
<div className="grid grid-cols-2">
<div>
<div className=" text-gray-700 border-b border-dashed p-4">
{" "}
Tool Call
</div>
<Bar {...config} />
</div>
<div className=" ">
<div className=" text-gray-700 border-b border-dashed p-4">
{" "}
Code Execution Status
</div>
<Bar {...config_code_exec} />
</div>
</div>
</div>
</div>
);
};
export default BarChartViewer;

View File

@@ -1,125 +0,0 @@
import { Tooltip, message } from "antd";
import * as React from "react";
import { IStatus, IChatMessage } from "../../../types";
import { fetchJSON, getServerUrl } from "../../../utils";
import { appContext } from "../../../../hooks/provider";
import { InformationCircleIcon } from "@heroicons/react/24/outline";
const BarChartViewer = React.lazy(() => import("./charts/bar"));
const ProfilerView = ({
agentMessage,
}: {
agentMessage: IChatMessage | null;
}) => {
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const [loading, setLoading] = React.useState(false);
const [profile, setProfile] = React.useState<any | null>(null);
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const fetchProfile = (messageId: number) => {
const profilerUrl = `${serverUrl}/profiler/${messageId}?user_id=${user?.email}`;
setError(null);
setLoading(true);
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
console.log(data);
if (data && data.status) {
setProfile(data.data);
setTimeout(() => {
// scroll parent to bottom
const parent = document.getElementById("chatbox");
if (parent) {
parent.scrollTop = parent.scrollHeight;
}
}, 4000);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(profilerUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user && agentMessage && agentMessage.id) {
fetchProfile(agentMessage.id);
}
}, []);
const UsageViewer = ({ usage }: { usage: any }) => {
const usageRows = usage.map((usage: any, index: number) => (
<div key={index} className=" borpder rounded">
{(usage.total_cost != 0 || usage.total_tokens != 0) && (
<>
<div className="bg-secondary p-2 text-xs rounded-t">
{usage.agent}
</div>
<div className="bg-tertiary p-3 rounded-b inline-flex gap-2 w-full">
{usage.total_tokens && usage.total_tokens != 0 && (
<div className="flex flex-col text-center w-full">
<div className="w-full px-2 text-2xl ">
{usage.total_tokens}
</div>
<div className="w-full text-xs">tokens</div>
</div>
)}
{usage.total_cost && usage.total_cost != 0 && (
<div className="flex flex-col text-center w-full">
<div className="w-full px-2 text-2xl ">
{usage.total_cost?.toFixed(3)}
</div>
<div className="w-full text-xs">USD</div>
</div>
)}
</div>
</>
)}
</div>
));
return (
<div className="inline-flex gap-3 flex-wrap">{usage && usageRows}</div>
);
};
return (
<div className=" relative">
<div className="text-sm ">
{/* {profile && <RadarMetrics profileData={profile} />} */}
{profile && <BarChartViewer data={profile} />}
<div className="mt-4">
<div className="mt-4 mb-4 txt">
LLM Costs
<Tooltip
title={
"LLM tokens below based on data returned by the model. Support for exact costs may vary."
}
>
<InformationCircleIcon className="ml-1 text-gray-400 inline-block w-4 h-4" />
</Tooltip>
</div>
{profile && profile.usage && <UsageViewer usage={profile.usage} />}
</div>
</div>
</div>
);
};
export default ProfilerView;

View File

@@ -1,122 +0,0 @@
import { Select, message } from "antd";
import * as React from "react";
import { LoadingOverlay } from "../../../atoms";
import { IWorkflow, IStatus } from "../../../types";
import { fetchJSON, getServerUrl } from "../../../utils";
import { appContext } from "../../../../hooks/provider";
import { Link } from "gatsby";
const WorkflowSelector = ({
workflow,
setWorkflow,
workflow_id,
disabled,
}: {
workflow: IWorkflow | null;
setWorkflow: (workflow: IWorkflow) => void;
workflow_id: number | undefined;
disabled?: boolean;
}) => {
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const [loading, setLoading] = React.useState(false);
const [workflows, setWorkflows] = React.useState<IWorkflow[]>([]);
const [selectedWorkflow, setSelectedWorkflow] = React.useState<number>(0);
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const listWorkflowsUrl = `${serverUrl}/workflows?user_id=${user?.email}`;
const fetchWorkFlow = () => {
setError(null);
setLoading(true);
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
// message.success(data.message);
setWorkflows(data.data);
if (data.data.length > 0) {
if (!disabled) {
setWorkflow(data.data[0]);
} else {
const index = data.data.findIndex((item:IWorkflow) => item.id === workflow_id);
if (index !== -1) {
setSelectedWorkflow(index);
setWorkflow(data.data[index]);
}
}
}
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(listWorkflowsUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user) {
fetchWorkFlow();
}
}, []);
return (
<div className=" mb-4 relative">
<div className="text-sm mt-2 mb-2 pb-1 ">
{" "}
Please select an agent workflow to begin.{" "}
</div>
<div className="relative mt-2 ">
<LoadingOverlay loading={loading} />
{workflows && workflows.length > 0 && (
<Select
disabled={disabled}
className="w-full"
value={workflows[selectedWorkflow].name}
onChange={(value: any) => {
setSelectedWorkflow(value);
setWorkflow(workflows[value]);
}}
options={
workflows.map((config, index) => {
return { label: config.name, value: index };
}) as any
}
/>
)}
<div className="mt-2 text-xs hidden">
{" "}
<div className="my-2 text-xs"> {workflow?.name}</div>
View all workflows{" "}
<span className="text-accent">
{" "}
<Link to="/build">here</Link>
</span>{" "}
</div>
</div>
{!workflows ||
(workflows && workflows.length === 0 && (
<div className="p-1 border rounded text-xs px-2 text-secondary">
{" "}
No agent workflows found.
</div>
))}
</div>
);
};
export default WorkflowSelector;

View File

@@ -0,0 +1,20 @@
import Markdown from "react-markdown";
import React from "react";
interface MarkdownViewProps {
children: string;
className?: string;
}
export const MarkdownView: React.FC<MarkdownViewProps> = ({
children,
className = "",
}) => {
return (
<div
className={`text-sm w-full prose dark:prose-invert text-primary rounded p-2 ${className}`}
>
<Markdown>{children}</Markdown>
</div>
);
};

View File

@@ -0,0 +1,48 @@
import React, { useState } from "react";
import Editor from "@monaco-editor/react";
export const MonacoEditor = ({
value,
editorRef,
language,
onChange,
minimap = true,
}: {
value: string;
onChange?: (value: string) => void;
editorRef: any;
language: string;
minimap?: boolean;
}) => {
const [isEditorReady, setIsEditorReady] = useState(false);
const onEditorDidMount = (editor: any, monaco: any) => {
editorRef.current = editor;
setIsEditorReady(true);
};
return (
<div id="monaco-editor" className="h-full rounded">
<Editor
height="100%"
className="h-full rounded"
defaultLanguage={language}
defaultValue={value}
value={value}
onChange={(value: string | undefined) => {
if (onChange && value) {
onChange(value);
}
}}
onMount={onEditorDidMount}
theme="vs-dark"
options={{
wordWrap: "on",
wrappingIndent: "indent",
wrappingStrategy: "advanced",
minimap: {
enabled: minimap,
},
}}
/>
</div>
);
};

View File

@@ -0,0 +1,115 @@
import { Session } from "../../../types/datamodel";
import { getServerUrl } from "../../../utils";
export class SessionAPI {
private getBaseUrl(): string {
return getServerUrl();
}
private getHeaders(): HeadersInit {
return {
"Content-Type": "application/json",
};
}
async listSessions(userId: string): Promise<Session[]> {
const response = await fetch(
`${this.getBaseUrl()}/sessions?user_id=${userId}`,
{
headers: this.getHeaders(),
}
);
const data = await response.json();
if (!data.status)
throw new Error(data.message || "Failed to fetch sessions");
return data.data;
}
async getSession(sessionId: number, userId: string): Promise<Session> {
const response = await fetch(
`${this.getBaseUrl()}/sessions/${sessionId}?user_id=${userId}`,
{
headers: this.getHeaders(),
}
);
const data = await response.json();
if (!data.status)
throw new Error(data.message || "Failed to fetch session");
return data.data;
}
async createSession(
sessionData: Partial<Session>,
userId: string
): Promise<Session> {
const session = {
...sessionData,
user_id: userId, // Ensure user_id is included
};
const response = await fetch(`${this.getBaseUrl()}/sessions`, {
method: "POST",
headers: this.getHeaders(),
body: JSON.stringify(session),
});
const data = await response.json();
if (!data.status)
throw new Error(data.message || "Failed to create session");
return data.data;
}
async updateSession(
sessionId: number,
sessionData: Partial<Session>,
userId: string
): Promise<Session> {
const session = {
...sessionData,
id: sessionId,
user_id: userId, // Ensure user_id is included
};
const response = await fetch(
`${this.getBaseUrl()}/sessions/${sessionId}?user_id=${userId}`,
{
method: "PUT",
headers: this.getHeaders(),
body: JSON.stringify(session),
}
);
const data = await response.json();
if (!data.status)
throw new Error(data.message || "Failed to update session");
return data.data;
}
async deleteSession(sessionId: number, userId: string): Promise<void> {
const response = await fetch(
`${this.getBaseUrl()}/sessions/${sessionId}?user_id=${userId}`,
{
method: "DELETE",
headers: this.getHeaders(),
}
);
const data = await response.json();
if (!data.status)
throw new Error(data.message || "Failed to delete session");
}
// Adding messages endpoint
async listSessionMessages(sessionId: number, userId: string): Promise<any[]> {
// Replace 'any' with proper message type
const response = await fetch(
`${this.getBaseUrl()}/sessions/${sessionId}/messages?user_id=${userId}`,
{
headers: this.getHeaders(),
}
);
const data = await response.json();
if (!data.status)
throw new Error(data.message || "Failed to fetch messages");
return data.data;
}
}
export const sessionAPI = new SessionAPI();

View File

@@ -0,0 +1,157 @@
import React, { useContext, useEffect, useState } from "react";
import { Modal, Form, Input, message, Button, Select, Spin } from "antd";
import { TriangleAlertIcon } from "lucide-react";
import type { FormProps } from "antd";
import { SessionEditorProps } from "./types";
import { Team } from "../../../types/datamodel";
import { teamAPI } from "../team/api";
import { appContext } from "../../../../hooks/provider";
type FieldType = {
name: string;
team_id?: string;
};
export const SessionEditor: React.FC<SessionEditorProps> = ({
session,
onSave,
onCancel,
isOpen,
}) => {
const [form] = Form.useForm();
const [teams, setTeams] = useState<Team[]>([]);
const [loading, setLoading] = useState(false);
const { user } = useContext(appContext);
// Fetch teams when modal opens
useEffect(() => {
const fetchTeams = async () => {
if (isOpen) {
try {
setLoading(true);
const userId = user?.email || "";
const teamsData = await teamAPI.listTeams(userId);
setTeams(teamsData);
} catch (error) {
message.error("Failed to load teams");
console.error("Error loading teams:", error);
} finally {
setLoading(false);
}
}
};
fetchTeams();
}, [isOpen, user?.email]);
// Set form values when modal opens or session changes
useEffect(() => {
if (isOpen) {
form.setFieldsValue({
name: session?.name || "",
team_id: session?.team_id || undefined,
});
} else {
form.resetFields();
}
}, [form, session, isOpen]);
const onFinish: FormProps<FieldType>["onFinish"] = async (values) => {
try {
await onSave({
...values,
id: session?.id,
});
message.success(
`Session ${session ? "updated" : "created"} successfully`
);
} catch (error) {
if (error instanceof Error) {
message.error(error.message);
}
}
};
const onFinishFailed: FormProps<FieldType>["onFinishFailed"] = (
errorInfo
) => {
message.error("Please check the form for errors");
console.error("Form validation failed:", errorInfo);
};
const hasNoTeams = !loading && teams.length === 0;
return (
<Modal
title={session ? "Edit Session" : "Create Session"}
open={isOpen}
onCancel={onCancel}
footer={null}
className="text-primary"
forceRender
>
<Form
form={form}
name="session-form"
layout="vertical"
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item<FieldType>
label="Session Name"
name="name"
rules={[
{ required: true, message: "Please enter a session name" },
{ max: 100, message: "Session name cannot exceed 100 characters" },
]}
>
<Input />
</Form.Item>
<div className="space-y-2">
<Form.Item<FieldType>
label="Team"
name="team_id"
rules={[{ required: true, message: "Please select a team" }]}
>
<Select
placeholder="Select a team"
loading={loading}
disabled={loading || hasNoTeams}
showSearch
optionFilterProp="children"
filterOption={(input, option) =>
(option?.label ?? "")
.toLowerCase()
.includes(input.toLowerCase())
}
options={teams.map((team) => ({
value: team.id,
label: `${team.config.name} (${team.config.team_type})`,
}))}
notFoundContent={loading ? <Spin size="small" /> : null}
/>
</Form.Item>
</div>
{hasNoTeams && (
<div className="flex border p-1 rounded -mt-2 mb-4 items-center gap-1.5 text-sm text-yellow-600">
<TriangleAlertIcon className="h-4 w-4" />
<span>No teams found. Please create a team first.</span>
</div>
)}
<Form.Item className="flex justify-end mb-0">
<div className="flex gap-2">
<Button onClick={onCancel}>Cancel</Button>
<Button type="primary" htmlType="submit" disabled={hasNoTeams}>
{session ? "Update" : "Create"}
</Button>
</div>
</Form.Item>
</Form>
</Modal>
);
};
export default SessionEditor;

View File

@@ -0,0 +1,76 @@
import React from "react";
import { Select, Button, Popconfirm } from "antd";
import { Edit, Trash2 } from "lucide-react";
import type { SessionListProps } from "./types";
import type { SelectProps } from "antd";
export const SessionList: React.FC<SessionListProps> = ({
sessions,
currentSession,
onSelect,
onEdit,
onDelete,
isLoading,
}) => {
const options: SelectProps["options"] = [
{
label: "Sessions",
options: sessions.map((session) => ({
label: (
<div className="flex items-center justify-between w-full pr-2">
<span className="flex-1 truncate">{session.name}</span>
<div className="flex gap-2 ml-2">
<Button
type="text"
size="small"
className="p-0 min-w-[24px] h-6 text-primary"
icon={<Edit className="w-4 h-4 text-primary" />}
onClick={(e) => {
e.stopPropagation();
onEdit(session);
}}
/>
<Popconfirm
title="Delete Session"
description="Are you sure you want to delete this session?"
onConfirm={(e) => {
e?.stopPropagation();
if (session.id) onDelete(session.id);
}}
onCancel={(e) => e?.stopPropagation()}
>
<Button
type="text"
size="small"
className="p-0 min-w-[24px] h-6"
danger
icon={<Trash2 className="w-4 h-4 text-primary" />}
onClick={(e) => e.stopPropagation()}
/>
</Popconfirm>
</div>
</div>
),
value: session.id,
})),
},
];
return (
<Select
className="w-64"
placeholder={isLoading ? "Loading sessions..." : "Select a session"}
loading={isLoading}
disabled={isLoading}
value={currentSession?.id}
onChange={(value) => {
const session = sessions.find((s) => s.id === value);
if (session) onSelect(session);
}}
options={options}
notFoundContent={sessions.length === 0 ? "No sessions found" : undefined}
dropdownStyle={{ minWidth: "256px" }}
listHeight={256}
/>
);
};

View File

@@ -0,0 +1,190 @@
import React, { useCallback, useEffect, useState, useContext } from "react";
import { Button, message, Collapse, Badge, CollapseProps } from "antd";
import { Plus, ChevronDown } from "lucide-react";
import { useConfigStore } from "../../../../hooks/store";
import { appContext } from "../../../../hooks/provider";
import { sessionAPI } from "./api";
import { SessionList } from "./list";
import { SessionEditor } from "./editor";
import type { Session } from "../../../types/datamodel";
export const SessionManager: React.FC = () => {
// UI State
const [isLoading, setIsLoading] = useState(false);
const [isEditorOpen, setIsEditorOpen] = useState(false);
const [editingSession, setEditingSession] = useState<Session | undefined>();
// Global context and store
const { user } = useContext(appContext);
const { session, setSession, sessions, setSessions } = useConfigStore();
// Fetch all sessions
const fetchSessions = useCallback(async () => {
if (!user?.email) return;
try {
setIsLoading(true);
const data = await sessionAPI.listSessions(user.email);
setSessions(data);
if (!session && data.length > 0) {
setSession(data[0]);
}
} catch (error) {
console.error("Error fetching sessions:", error);
message.error("Error loading sessions");
} finally {
setIsLoading(false);
}
}, [user?.email, setSessions]);
// Handle session operations
const handleSaveSession = async (sessionData: Partial<Session>) => {
if (!user?.email) return;
try {
if (sessionData.id) {
const updated = await sessionAPI.updateSession(
sessionData.id,
sessionData,
user.email
);
setSessions(
sessions.map((s) => (s.id && s.id === updated.id ? updated : s))
);
if (session?.id === updated.id) {
setSession(updated);
}
} else {
const created = await sessionAPI.createSession(sessionData, user.email);
setSession(created);
setSessions([...sessions, created]);
}
setIsEditorOpen(false);
setEditingSession(undefined);
} catch (error) {
throw error;
}
};
const handleDeleteSession = async (sessionId: number) => {
if (!user?.email) return;
try {
await sessionAPI.deleteSession(sessionId, user.email);
setSessions(sessions.filter((s) => s.id !== sessionId));
if (sessions.length > 0) {
setSession(sessions[0]);
}
if (session?.id === sessionId) {
setSession(null);
}
message.success("Session deleted");
} catch (error) {
console.error("Error deleting session:", error);
message.error("Error deleting session");
}
};
const handleSelectSession = async (selectedSession: Session) => {
if (!user?.email || !selectedSession.id) return;
try {
setIsLoading(true);
const data = await sessionAPI.getSession(selectedSession.id, user.email);
setSession(data);
} catch (error) {
console.error("Error loading session:", error);
message.error("Error loading session");
} finally {
setIsLoading(false);
}
};
// Load sessions on mount
useEffect(() => {
fetchSessions();
}, [fetchSessions]);
// Custom header with session count
const CollapsibleHeader = () => (
<div className="flex items-center justify-between w-full">
<div className="flex items-center gap-3">
<span className="font-medium">Sessions</span>
<Badge
count={sessions.length}
showZero
className="site-badge-count-4"
style={{ backgroundColor: "#52525b" }}
/>
</div>
{session && (
<span className="text-sm text-gray-500">Current: {session.name}</span>
)}
</div>
);
// Session management content
const SessionContent = () => (
<div className="flex gap-2 items-center">
{sessions && sessions.length > 0 && (
<SessionList
sessions={sessions}
currentSession={session}
onSelect={handleSelectSession}
onEdit={(session) => {
setEditingSession(session);
setIsEditorOpen(true);
}}
onDelete={handleDeleteSession}
isLoading={isLoading}
/>
)}
<div className="relative">
<Button
type="primary"
onClick={() => {
setEditingSession(undefined);
setIsEditorOpen(true);
}}
icon={<Plus className="w-4 h-4" />}
>
New Session{" "}
{sessions.length === 0 && (
<span className="relative flex h-3 w-3">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-secondary opacity-75"></span>
<span className="relative inline-flex rounded-full h-3 w-3 bg-secondary"></span>
</span>
)}
</Button>
</div>
</div>
);
const items: CollapseProps["items"] = [
{
key: "1",
label: <CollapsibleHeader />,
children: <SessionContent />,
},
];
return (
<>
<div className="bg-secondary rounded p-2">
<div className="text-xs pb-2">
Sessions <span className="px-1 text-accent">{sessions.length} </span>
</div>
<SessionContent />
</div>
<SessionEditor
session={editingSession}
isOpen={isEditorOpen}
onSave={handleSaveSession}
onCancel={() => {
setIsEditorOpen(false);
setEditingSession(undefined);
}}
/>
</>
);
};

View File

@@ -0,0 +1,24 @@
import type { Session } from "../../../types/datamodel";
export interface SessionEditorProps {
session?: Session;
onSave: (session: Partial<Session>) => Promise<void>;
onCancel: () => void;
isOpen: boolean;
}
export interface SessionListProps {
sessions: Session[];
currentSession?: Session | null;
onSelect: (session: Session) => void;
onEdit: (session: Session) => void;
onDelete: (sessionId: number) => void;
isLoading?: boolean;
}
export interface SessionFormState {
name: string;
team_id: string;
isSubmitting: boolean;
error?: string;
}

View File

@@ -0,0 +1,127 @@
import { Team, AgentConfig } from "../../../types/datamodel";
import { getServerUrl } from "../../../utils";
export class TeamAPI {
private getBaseUrl(): string {
return getServerUrl();
}
private getHeaders(): HeadersInit {
return {
"Content-Type": "application/json",
};
}
async listTeams(userId: string): Promise<Team[]> {
const response = await fetch(
`${this.getBaseUrl()}/teams?user_id=${userId}`,
{
headers: this.getHeaders(),
}
);
const data = await response.json();
if (!data.status) throw new Error(data.message || "Failed to fetch teams");
return data.data;
}
async getTeam(teamId: number, userId: string): Promise<Team> {
const response = await fetch(
`${this.getBaseUrl()}/teams/${teamId}?user_id=${userId}`,
{
headers: this.getHeaders(),
}
);
const data = await response.json();
if (!data.status) throw new Error(data.message || "Failed to fetch team");
return data.data;
}
async createTeam(teamData: Partial<Team>, userId: string): Promise<Team> {
const team = {
...teamData,
user_id: userId,
};
const response = await fetch(`${this.getBaseUrl()}/teams`, {
method: "POST",
headers: this.getHeaders(),
body: JSON.stringify(team),
});
const data = await response.json();
if (!data.status) throw new Error(data.message || "Failed to create team");
return data.data;
}
async deleteTeam(teamId: number, userId: string): Promise<void> {
const response = await fetch(
`${this.getBaseUrl()}/teams/${teamId}?user_id=${userId}`,
{
method: "DELETE",
headers: this.getHeaders(),
}
);
const data = await response.json();
if (!data.status) throw new Error(data.message || "Failed to delete team");
}
// Team-Agent Link Management
async linkAgent(teamId: number, agentId: number): Promise<void> {
const response = await fetch(
`${this.getBaseUrl()}/teams/${teamId}/agents/${agentId}`,
{
method: "POST",
headers: this.getHeaders(),
}
);
const data = await response.json();
if (!data.status)
throw new Error(data.message || "Failed to link agent to team");
}
async linkAgentWithSequence(
teamId: number,
agentId: number,
sequenceId: number
): Promise<void> {
const response = await fetch(
`${this.getBaseUrl()}/teams/${teamId}/agents/${agentId}/${sequenceId}`,
{
method: "POST",
headers: this.getHeaders(),
}
);
const data = await response.json();
if (!data.status)
throw new Error(
data.message || "Failed to link agent to team with sequence"
);
}
async unlinkAgent(teamId: number, agentId: number): Promise<void> {
const response = await fetch(
`${this.getBaseUrl()}/teams/${teamId}/agents/${agentId}`,
{
method: "DELETE",
headers: this.getHeaders(),
}
);
const data = await response.json();
if (!data.status)
throw new Error(data.message || "Failed to unlink agent from team");
}
async getTeamAgents(teamId: number): Promise<AgentConfig[]> {
const response = await fetch(
`${this.getBaseUrl()}/teams/${teamId}/agents`,
{
headers: this.getHeaders(),
}
);
const data = await response.json();
if (!data.status)
throw new Error(data.message || "Failed to fetch team agents");
return data.data;
}
}
export const teamAPI = new TeamAPI();

View File

@@ -0,0 +1,171 @@
import React, { useEffect, useState, useRef } from "react";
import { Modal, Form, message, Button } from "antd";
import { TriangleAlertIcon } from "lucide-react";
import { TeamEditorProps } from "./types";
import type { FormProps } from "antd";
import type { Team, TeamConfig } from "../../../types/datamodel";
import { MonacoEditor } from "../monaco";
const defaultTeamConfig: TeamConfig = {
name: "",
participants: [],
team_type: "RoundRobinGroupChat",
};
type FieldType = {
config: string;
};
export const TeamEditor: React.FC<TeamEditorProps> = ({
team,
onSave,
onCancel,
isOpen,
}) => {
const [form] = Form.useForm();
const [jsonError, setJsonError] = useState<string | null>(null);
const editorRef = useRef(null);
const [editorValue, setEditorValue] = useState<string>("");
// Set form values when modal opens or team changes
useEffect(() => {
if (isOpen) {
const configStr = team
? JSON.stringify(team.config, null, 2)
: JSON.stringify(defaultTeamConfig, null, 2);
setEditorValue(configStr);
form.setFieldsValue({
config: configStr,
});
} else {
form.resetFields();
setJsonError(null);
}
}, [form, team, isOpen]);
const validateJSON = (jsonString: string): TeamConfig | null => {
try {
const parsed = JSON.parse(jsonString);
// Basic validation of required fields
if (typeof parsed.name !== "string" || parsed.name.trim() === "") {
throw new Error("Team name is required");
}
if (!Array.isArray(parsed.participants)) {
throw new Error("Participants must be an array");
}
if (
!["RoundRobinGroupChat", "SelectorGroupChat"].includes(parsed.team_type)
) {
throw new Error("Invalid team_type");
}
return parsed;
} catch (error) {
if (error instanceof Error) {
setJsonError(error.message);
} else {
setJsonError("Invalid JSON format");
}
return null;
}
};
const onFinish: FormProps<FieldType>["onFinish"] = async () => {
const config = validateJSON(editorValue);
if (!config) {
return;
}
try {
// When updating, keep the existing id and dates
const teamData: Partial<Team> = team
? {
...team,
config,
// Remove date fields to let the backend handle them
created_at: undefined,
updated_at: undefined,
}
: { config };
await onSave(teamData);
message.success(`Team ${team ? "updated" : "created"} successfully`);
setJsonError(null);
} catch (error) {
if (error instanceof Error) {
message.error(error.message);
}
}
};
const handleEditorChange = (value: string) => {
setEditorValue(value);
form.setFieldsValue({ config: value });
// Clear error if JSON becomes valid
try {
JSON.parse(value);
setJsonError(null);
} catch (e) {
// Don't set error while typing - Monaco will show syntax errors
}
};
return (
<Modal
title={team ? "Edit Team" : "Create Team"}
open={isOpen}
onCancel={onCancel}
footer={null}
className="text-primary"
width={800}
forceRender
>
<Form
form={form}
name="team-form"
layout="vertical"
onFinish={onFinish}
autoComplete="off"
>
<div className="mb-2 text-xs text-gray-500">
Required fields: name (string), team_type ("RoundRobinGroupChat" |
"SelectorGroupChat"), participants (array)
</div>
<div className="h-[500px] mb-4">
<MonacoEditor
value={editorValue}
onChange={handleEditorChange}
editorRef={editorRef}
language="json"
minimap={false}
/>
</div>
{jsonError && (
<div className="flex items-center gap-1.5 text-sm text-red-500 mb-4">
<TriangleAlertIcon className="h-4 w-4" />
<span>{jsonError}</span>
</div>
)}
<Form.Item className="flex justify-end mb-0">
<div className="flex gap-2">
<Button onClick={onCancel}>Cancel</Button>
<Button
type="primary"
onClick={() => form.submit()}
disabled={!!jsonError}
>
{team ? "Update" : "Create"}
</Button>
</div>
</Form.Item>
</Form>
</Modal>
);
};
export default TeamEditor;

View File

@@ -0,0 +1,76 @@
import React from "react";
import { Select, Button, Popconfirm } from "antd";
import { Edit, Trash2 } from "lucide-react";
import type { TeamListProps } from "./types";
import type { SelectProps } from "antd";
export const TeamList: React.FC<TeamListProps> = ({
teams,
currentTeam,
onSelect,
onEdit,
onDelete,
isLoading,
}) => {
const options: SelectProps["options"] = [
{
label: "Teams",
options: teams.map((team) => ({
label: (
<div className="flex items-center justify-between w-full pr-2">
<span className="flex-1 truncate">{team.config.name}</span>
<div className="flex gap-2 ml-2">
<Button
type="text"
size="small"
className="p-0 min-w-[24px] h-6 text-primary"
icon={<Edit className="w-4 h-4 text-primary" />}
onClick={(e) => {
e.stopPropagation();
onEdit(team);
}}
/>
<Popconfirm
title="Delete Team"
description="Are you sure you want to delete this team?"
onConfirm={(e) => {
e?.stopPropagation();
if (team.id) onDelete(team.id);
}}
onCancel={(e) => e?.stopPropagation()}
>
<Button
type="text"
size="small"
className="p-0 min-w-[24px] h-6"
danger
icon={<Trash2 className="w-4 h-4 text-primary" />}
onClick={(e) => e.stopPropagation()}
/>
</Popconfirm>
</div>
</div>
),
value: team.id,
})),
},
];
return (
<Select
className="w-64"
placeholder={isLoading ? "Loading teams..." : "Select a team"}
loading={isLoading}
disabled={isLoading}
value={currentTeam?.id}
onChange={(value) => {
const team = teams.find((t) => t.id === value);
if (team) onSelect(team);
}}
options={options}
notFoundContent={teams.length === 0 ? "No teams found" : undefined}
dropdownStyle={{ minWidth: "256px" }}
listHeight={256}
/>
);
};

View File

@@ -0,0 +1,153 @@
import React, { useCallback, useEffect, useState, useContext } from "react";
import { Button, message, Badge } from "antd";
import { Plus } from "lucide-react";
import { appContext } from "../../../../hooks/provider";
import { teamAPI } from "./api";
import { TeamList } from "./list";
import { TeamEditor } from "./editor";
import type { Team } from "../../../types/datamodel";
export const TeamManager: React.FC = () => {
// UI State
const [isLoading, setIsLoading] = useState(false);
const [isEditorOpen, setIsEditorOpen] = useState(false);
const [editingTeam, setEditingTeam] = useState<Team | undefined>();
const [teams, setTeams] = useState<Team[]>([]);
const [currentTeam, setCurrentTeam] = useState<Team | null>(null);
// Global context
const { user } = useContext(appContext);
// Fetch all teams
const fetchTeams = useCallback(async () => {
if (!user?.email) return;
try {
setIsLoading(true);
const data = await teamAPI.listTeams(user.email);
setTeams(data);
if (!currentTeam && data.length > 0) {
setCurrentTeam(data[0]);
}
} catch (error) {
console.error("Error fetching teams:", error);
message.error("Error loading teams");
} finally {
setIsLoading(false);
}
}, [user?.email, currentTeam]);
// Handle team operations
const handleSaveTeam = async (teamData: Partial<Team>) => {
if (!user?.email) return;
try {
console.log("teamData", teamData);
const savedTeam = await teamAPI.createTeam(teamData, user.email);
// Update teams list
if (teamData.id) {
setTeams(teams.map((t) => (t.id === savedTeam.id ? savedTeam : t)));
if (currentTeam?.id === savedTeam.id) {
setCurrentTeam(savedTeam);
}
} else {
setTeams([...teams, savedTeam]);
}
setIsEditorOpen(false);
setEditingTeam(undefined);
} catch (error) {
throw error;
}
};
const handleDeleteTeam = async (teamId: number) => {
if (!user?.email) return;
try {
await teamAPI.deleteTeam(teamId, user.email);
setTeams(teams.filter((t) => t.id !== teamId));
if (currentTeam?.id === teamId) {
setCurrentTeam(null);
}
message.success("Team deleted");
} catch (error) {
console.error("Error deleting team:", error);
message.error("Error deleting team");
}
};
const handleSelectTeam = async (selectedTeam: Team) => {
if (!user?.email || !selectedTeam.id) return;
try {
setIsLoading(true);
const data = await teamAPI.getTeam(selectedTeam.id, user.email);
setCurrentTeam(data);
} catch (error) {
console.error("Error loading team:", error);
message.error("Error loading team");
} finally {
setIsLoading(false);
}
};
// Load teams on mount
useEffect(() => {
fetchTeams();
}, [fetchTeams]);
// Content component
const TeamContent = () => (
<div className="flex gap-2 items-center">
{teams && teams.length > 0 && (
<div className="flex items-center gap-3">
<TeamList
teams={teams}
currentTeam={currentTeam}
onSelect={handleSelectTeam}
onEdit={(team) => {
setEditingTeam(team);
setIsEditorOpen(true);
}}
onDelete={handleDeleteTeam}
isLoading={isLoading}
/>
</div>
)}
<Button
type="primary"
onClick={() => {
setEditingTeam(undefined);
setIsEditorOpen(true);
}}
icon={<Plus className="w-4 h-4" />}
>
New Team
</Button>
</div>
);
return (
<>
<div className="bg-secondary rounded p-2">
<div className="text-xs pb-2">
Teams <span className="px-1 text-accent">{teams.length} </span>
</div>
<TeamContent />
</div>
<TeamEditor
team={editingTeam}
isOpen={isEditorOpen}
onSave={handleSaveTeam}
onCancel={() => {
setIsEditorOpen(false);
setEditingTeam(undefined);
}}
/>
</>
);
};
export default TeamManager;

View File

@@ -0,0 +1,17 @@
import type { Team } from "../../../types/datamodel";
export interface TeamEditorProps {
team?: Team;
onSave: (team: Partial<Team>) => Promise<void>;
onCancel: () => void;
isOpen: boolean;
}
export interface TeamListProps {
teams: Team[];
currentTeam?: Team | null;
onSelect: (team: Team) => void;
onEdit: (team: Team) => void;
onDelete: (teamId: number) => void;
isLoading?: boolean;
}

View File

@@ -1,25 +1,33 @@
import { create } from "zustand";
import { v4 as uuidv4 } from "uuid";
import { Message, Session } from "../components/types/datamodel";
import { IChatMessage, IChatSession } from "../components/types";
interface ConfigState {
messages: IChatMessage[] | null;
setMessages: (messages: IChatMessage[]) => void;
session: IChatSession | null;
setSession: (session: IChatSession | null) => void;
sessions: IChatSession[];
setSessions: (sessions: IChatSession[]) => void;
version: string | null;
setVersion: (version: string) => void;
connectionId: string;
setConnectionId: (connectionId: string) => void;
areSessionButtonsDisabled: boolean;
setAreSessionButtonsDisabled: (disabled: boolean) => void;
interface ISidebarState {
isExpanded: boolean;
isPinned: boolean;
}
export const useConfigStore = create<ConfigState>()((set) => ({
messages: null,
export interface IConfigState {
messages: Message[];
setMessages: (messages: Message[]) => void;
session: Session | null;
setSession: (session: Session | null) => void;
sessions: Session[];
setSessions: (sessions: Session[]) => void;
version: string | null;
setVersion: (version: string | null) => void;
// Sidebar state
sidebar: ISidebarState;
setSidebarState: (state: Partial<ISidebarState>) => void;
collapseSidebar: () => void;
expandSidebar: () => void;
toggleSidebar: () => void;
}
export const useConfigStore = create<IConfigState>((set) => ({
// Existing state
messages: [],
setMessages: (messages) => set({ messages }),
session: null,
setSession: (session) => set({ session }),
@@ -28,7 +36,30 @@ export const useConfigStore = create<ConfigState>()((set) => ({
version: null,
setVersion: (version) => set({ version }),
connectionId: uuidv4(),
setConnectionId: (connectionId) => set({ connectionId }),
areSessionButtonsDisabled: false,
setAreSessionButtonsDisabled: (disabled) => set({ areSessionButtonsDisabled: disabled }),
// Sidebar state and actions
sidebar: {
isExpanded: true,
isPinned: false,
},
setSidebarState: (newState) =>
set((state) => ({
sidebar: { ...state.sidebar, ...newState },
})),
collapseSidebar: () =>
set((state) => ({
sidebar: { ...state.sidebar, isExpanded: false },
})),
expandSidebar: () =>
set((state) => ({
sidebar: { ...state.sidebar, isExpanded: true },
})),
toggleSidebar: () =>
set((state) => ({
sidebar: { ...state.sidebar, isExpanded: !state.sidebar.isExpanded },
})),
}));

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4f7307728cfd62bc0d07d8cc3ab3809e36d8859a5ad314d020b21648642cf574
size 12710
oid sha256:7ec19e710d8b38d99b5f1b170db18eb192cd3a50fe80f1c103f88cce68e057b4
size 33034

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" width="647.63626" height="632.17383" viewBox="0 0 647.63626 632.17383" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M687.3279,276.08691H512.81813a15.01828,15.01828,0,0,0-15,15v387.85l-2,.61005-42.81006,13.11a8.00676,8.00676,0,0,1-9.98974-5.31L315.678,271.39691a8.00313,8.00313,0,0,1,5.31006-9.99l65.97022-20.2,191.25-58.54,65.96972-20.2a7.98927,7.98927,0,0,1,9.99024,5.3l32.5498,106.32Z" transform="translate(-276.18187 -133.91309)" fill="#f2f2f2"/><path d="M725.408,274.08691l-39.23-128.14a16.99368,16.99368,0,0,0-21.23-11.28l-92.75,28.39L380.95827,221.60693l-92.75,28.4a17.0152,17.0152,0,0,0-11.28028,21.23l134.08008,437.93a17.02661,17.02661,0,0,0,16.26026,12.03,16.78926,16.78926,0,0,0,4.96972-.75l63.58008-19.46,2-.62v-2.09l-2,.61-64.16992,19.65a15.01489,15.01489,0,0,1-18.73-9.95l-134.06983-437.94a14.97935,14.97935,0,0,1,9.94971-18.73l92.75-28.4,191.24024-58.54,92.75-28.4a15.15551,15.15551,0,0,1,4.40966-.66,15.01461,15.01461,0,0,1,14.32032,10.61l39.0498,127.56.62012,2h2.08008Z" transform="translate(-276.18187 -133.91309)" fill="#3f3d56"/><path d="M398.86279,261.73389a9.0157,9.0157,0,0,1-8.61133-6.3667l-12.88037-42.07178a8.99884,8.99884,0,0,1,5.9712-11.24023l175.939-53.86377a9.00867,9.00867,0,0,1,11.24072,5.9707l12.88037,42.07227a9.01029,9.01029,0,0,1-5.9707,11.24072L401.49219,261.33887A8.976,8.976,0,0,1,398.86279,261.73389Z" transform="translate(-276.18187 -133.91309)" fill="#6c63ff"/><circle cx="190.15351" cy="24.95465" r="20" fill="#6c63ff"/><circle cx="190.15351" cy="24.95465" r="12.66462" fill="#fff"/><path d="M878.81836,716.08691h-338a8.50981,8.50981,0,0,1-8.5-8.5v-405a8.50951,8.50951,0,0,1,8.5-8.5h338a8.50982,8.50982,0,0,1,8.5,8.5v405A8.51013,8.51013,0,0,1,878.81836,716.08691Z" transform="translate(-276.18187 -133.91309)" fill="#e6e6e6"/><path d="M723.31813,274.08691h-210.5a17.02411,17.02411,0,0,0-17,17v407.8l2-.61v-407.19a15.01828,15.01828,0,0,1,15-15H723.93825Zm183.5,0h-394a17.02411,17.02411,0,0,0-17,17v458a17.0241,17.0241,0,0,0,17,17h394a17.0241,17.0241,0,0,0,17-17v-458A17.02411,17.02411,0,0,0,906.81813,274.08691Zm15,475a15.01828,15.01828,0,0,1-15,15h-394a15.01828,15.01828,0,0,1-15-15v-458a15.01828,15.01828,0,0,1,15-15h394a15.01828,15.01828,0,0,1,15,15Z" transform="translate(-276.18187 -133.91309)" fill="#3f3d56"/><path d="M801.81836,318.08691h-184a9.01015,9.01015,0,0,1-9-9v-44a9.01016,9.01016,0,0,1,9-9h184a9.01016,9.01016,0,0,1,9,9v44A9.01015,9.01015,0,0,1,801.81836,318.08691Z" transform="translate(-276.18187 -133.91309)" fill="#6c63ff"/><circle cx="433.63626" cy="105.17383" r="20" fill="#6c63ff"/><circle cx="433.63626" cy="105.17383" r="12.18187" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,3 @@
declare module "*.jpg";
declare module "*.png";
declare module "*.svg";

View File

@@ -1,7 +1,6 @@
import * as React from "react"
import { Link } from "gatsby"
import { Link, HeadFC, PageProps } from "gatsby"
// styles
const pageStyles = {
color: "#232129",
padding: "96px",
@@ -24,18 +23,12 @@ const codeStyles = {
borderRadius: 4,
}
// markup
const NotFoundPage = () => {
const NotFoundPage: React.FC<PageProps> = () => {
return (
<main style={pageStyles}>
<title>Not found</title>
<h1 style={headingStyles}>Page not found</h1>
<p style={paragraphStyles}>
Sorry{" "}
<span role="img" aria-label="Pensive emoji">
😔
</span>{" "}
we couldnt find what you were looking for.
Sorry 😔, we couldnt find what you were looking for.
<br />
{process.env.NODE_ENV === "development" ? (
<>
@@ -52,3 +45,5 @@ const NotFoundPage = () => {
}
export default NotFoundPage
export const Head: HeadFC = () => <title>Not found</title>

View File

@@ -1,14 +1,13 @@
import * as React from "react";
import Layout from "../components/layout";
import { graphql } from "gatsby";
import BuildView from "../components/views/builder/build";
// markup
const IndexPage = ({ data }: any) => {
return (
<Layout meta={data.site.siteMetadata} title="Home" link={"/build"}>
<main style={{ height: "100%" }} className=" h-full ">
<BuildView />
Build
</main>
</Layout>
);

View File

@@ -1,28 +0,0 @@
import * as React from "react";
import { graphql } from "gatsby";
import Layout from "../../components/layout";
import GalleryView from "../../components/views/gallery/gallery";
// markup
const GalleryPage = ({ location, data }: any) => {
return (
<Layout meta={data.site.siteMetadata} title="Gallery" link={"/gallery"}>
<main style={{ height: "100%" }} className=" h-full ">
<GalleryView location={location} />
</main>
</Layout>
);
};
export const query = graphql`
query HomePageQuery {
site {
siteMetadata {
description
title
}
}
}
`;
export default GalleryPage;

View File

@@ -1,14 +1,14 @@
import * as React from "react";
import Layout from "../components/layout";
import { graphql } from "gatsby";
import RAView from "../components/views/playground/ra";
import ChatView from "../components/views/playground/chat/chat";
// markup
const IndexPage = ({ data }: any) => {
return (
<Layout meta={data.site.siteMetadata} title="Home" link={"/"}>
<main style={{ height: "100%" }} className=" h-full ">
<RAView />
<ChatView initMessages={[]} />
</main>
</Layout>
);

View File

@@ -0,0 +1,35 @@
import * as React from "react";
import Layout from "../components/layout";
import { graphql } from "gatsby";
import { TriangleAlertIcon } from "lucide-react";
// markup
const SettingsPage = ({ data }: any) => {
return (
<Layout meta={data.site.siteMetadata} title="Home" link={"/build"}>
<main style={{ height: "100%" }} className=" h-full ">
<div className="mb-2"> Settings</div>
<div className="p-2 mt-4 bg-tertiary rounded ">
<TriangleAlertIcon
className="w-5 h-5 text-primary inline-block -mt-1"
strokeWidth={1.5}
/>{" "}
Work in progress ..
</div>
</main>
</Layout>
);
};
export const query = graphql`
query HomePageQuery {
site {
siteMetadata {
description
title
}
}
}
`;
export default SettingsPage;

View File

@@ -3,27 +3,27 @@
--color-bg-secondary: #1e293b;
--color-bg-light: #27354c;
--color-bg-tertiary: #374151;
--color-bg-accent: #22c55e;
--color-bg-accent: #464feb;
--color-text-primary: #f7fafc;
--color-text-secondary: #e2e8f0;
--color-text-accent: #22c55e;
--color-text-accent: #464feb;
--color-border-primary: #f7fafc;
--color-border-secondary: #e2e8f045;
--color-border-accent: #22c55e;
--color-border-accent: #464feb;
}
.light {
--color-bg-primary: #ffffff;
--color-bg-secondary: #edf2f7;
--color-bg-light: #f9fafb;
--color-bg-tertiary: #ffffff;
--color-bg-accent: #16a34a;
--color-bg-tertiary: #f3f6f9;
--color-bg-accent: #464feb;
--color-text-primary: #334155;
--color-text-secondary: #64748b;
--color-text-accent: #16a34a;
--color-text-accent: #464feb;
--color-border-primary: #2d3748c1;
--color-border-secondary: #edf2f7;
--color-border-accent: #16a34a;
--color-border-accent: #464feb;
}
@tailwind base;
@@ -31,28 +31,28 @@
@tailwind utilities;
/* @layer base {
body {
@apply dark:bg-slate-800 dark:text-white;
}
} */
body {
@apply dark:bg-slate-800 dark:text-white;
}
} */
html {
display: table;
/* display: table; */
margin: auto;
height: 100%;
}
body {
/* padding: 0px 64px 0px 64px; */
@apply px-4 md:px-8 lg:px-16 !important;
/* margin: 0px auto; */
padding: 0px 64px 0px 64px;
@apply px-4 md:px-8 lg:px-16 w-full !important;
margin: 0px auto;
min-width: 300px;
max-width: 1400px;
max-width: 1900px;
background: transparent;
height: 100%;
/* border: 2px solid green; */
display: table-cell;
vertical-align: middle;
/* border: 2px solid green; */
width: 100%;
height: 100%;
}
/* @import "antd/dist/antd.css"; */
@@ -123,9 +123,9 @@ a:hover {
@apply text-accent;
}
/* .iiz__img,
iiz__zoom-img {
@apply w-full;
} */
iiz__zoom-img {
@apply w-full;
} */
.ant-radio-checked .ant-radio-inner {
@apply border-accent !important;
@@ -259,6 +259,11 @@ iiz__zoom-img {
}
.ant-upload-drag-container,
.ant-upload-text,
.ant-btn,
.ant-select,
.ant-select > span,
.ant-select-selector,
.ant-form-item-required,
.ant-upload-hint {
@apply text-primary !important;
}
@@ -267,7 +272,9 @@ iiz__zoom-img {
.ant-upload-list-item-info:hover {
@apply bg-secondary text-accent !important;
}
.ant-pagination .ant-pagination-item {
.ant-pagination .ant-pagination-item,
.ant-select-selector,
.ant-btn-variant-outlined {
@apply bg-primary !important;
}
.ant-pagination .ant-pagination-item-active a {
@@ -290,9 +297,11 @@ iiz__zoom-img {
@apply border-secondary !important;
}
.ant-btn,
.ant-select-arrow,
.ant-btn:hover {
@apply text-primary !important;
}
:where(.ant-btn).ant-btn-compact-item.ant-btn-primary:not([disabled])
+ .ant-btn-compact-item.ant-btn-primary:not([disabled]):before {
@apply bg-secondary !important;
@@ -341,8 +350,8 @@ iiz__zoom-img {
}
/* .ant-radio-input::before {
@apply bg-primary !important;
} */
@apply bg-primary !important;
} */
.prose > pre {
padding: 0px !important;
@@ -356,16 +365,16 @@ iiz__zoom-img {
}
/* div.chatbox > ul {
list-style: disc !important;
}
div.chatbox > ul,
div.chatbox > ol {
padding-left: 20px !important;
margin: 0px !important;
}
div.chatbox > ol {
list-style: decimal !important;
} */
list-style: disc !important;
}
div.chatbox > ul,
div.chatbox > ol {
padding-left: 20px !important;
margin: 0px !important;
}
div.chatbox > ol {
list-style: decimal !important;
} */
div#___gatsby,
div#gatsby-focus-wrapper {