Update Assistants Creation UI (#1714)

* slide up "Tools"

* rework assistants page

* update layout

* reorg complete

- pending: useful header text?

* add tooltips

* alter organizational structure

* rm shadcn

* rm dependencies

* revalidate dependencies

* restore

* update component structure

* [s] format

* rm package json

* add package-lock.json [s]

* collapsible

* naming + width

* formatting

* formatting

* updated user flow

- Fix error/detail messages
- Fix tooltip delay
- Fix icons

* 1 -> 2

* naming fixes

* ran pretty

* fix build issue?

* web build issues?
This commit is contained in:
pablodanswer 2024-07-03 10:11:14 -07:00 committed by GitHub
parent a7da07afc0
commit ae4e643266
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 736 additions and 531 deletions

BIN
web/public/Amazon.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

8
web/public/Anthropic.svg Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="256px" height="176px" viewBox="0 0 256 176" version="1.1" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
<title>Anthropic</title>
<g fill="#181818">
<path d="M147.486878,0 C147.486878,0 217.568251,175.780074 217.568251,175.780074 C217.568251,175.780074 256,175.780074 256,175.780074 C256,175.780074 185.918621,0 185.918621,0 C185.918621,0 147.486878,0 147.486878,0 C147.486878,0 147.486878,0 147.486878,0 Z"></path>
<path d="M66.1828124,106.221191 C66.1828124,106.221191 90.1624677,44.4471185 90.1624677,44.4471185 C90.1624677,44.4471185 114.142128,106.221191 114.142128,106.221191 C114.142128,106.221191 66.1828124,106.221191 66.1828124,106.221191 C66.1828124,106.221191 66.1828124,106.221191 66.1828124,106.221191 Z M70.0705318,0 C70.0705318,0 0,175.780074 0,175.780074 C0,175.780074 39.179211,175.780074 39.179211,175.780074 C39.179211,175.780074 53.5097704,138.86606 53.5097704,138.86606 C53.5097704,138.86606 126.817544,138.86606 126.817544,138.86606 C126.817544,138.86606 141.145724,175.780074 141.145724,175.780074 C141.145724,175.780074 180.324935,175.780074 180.324935,175.780074 C180.324935,175.780074 110.254409,0 110.254409,0 C110.254409,0 70.0705318,0 70.0705318,0 C70.0705318,0 70.0705318,0 70.0705318,0 Z"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
web/public/Azure.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

BIN
web/public/OpenSource.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

1
web/public/Openai.svg Normal file
View File

@ -0,0 +1 @@
<svg viewBox="0 0 320 320" xmlns="http://www.w3.org/2000/svg"><path d="m297.06 130.97c7.26-21.79 4.76-45.66-6.85-65.48-17.46-30.4-52.56-46.04-86.84-38.68-15.25-17.18-37.16-26.95-60.13-26.81-35.04-.08-66.13 22.48-76.91 55.82-22.51 4.61-41.94 18.7-53.31 38.67-17.59 30.32-13.58 68.54 9.92 94.54-7.26 21.79-4.76 45.66 6.85 65.48 17.46 30.4 52.56 46.04 86.84 38.68 15.24 17.18 37.16 26.95 60.13 26.8 35.06.09 66.16-22.49 76.94-55.86 22.51-4.61 41.94-18.7 53.31-38.67 17.57-30.32 13.55-68.51-9.94-94.51zm-120.28 168.11c-14.03.02-27.62-4.89-38.39-13.88.49-.26 1.34-.73 1.89-1.07l63.72-36.8c3.26-1.85 5.26-5.32 5.24-9.07v-89.83l26.93 15.55c.29.14.48.42.52.74v74.39c-.04 33.08-26.83 59.9-59.91 59.97zm-128.84-55.03c-7.03-12.14-9.56-26.37-7.15-40.18.47.28 1.3.79 1.89 1.13l63.72 36.8c3.23 1.89 7.23 1.89 10.47 0l77.79-44.92v31.1c.02.32-.13.63-.38.83l-64.41 37.19c-28.69 16.52-65.33 6.7-81.92-21.95zm-16.77-139.09c7-12.16 18.05-21.46 31.21-26.29 0 .55-.03 1.52-.03 2.2v73.61c-.02 3.74 1.98 7.21 5.23 9.06l77.79 44.91-26.93 15.55c-.27.18-.61.21-.91.08l-64.42-37.22c-28.63-16.58-38.45-53.21-21.95-81.89zm221.26 51.49-77.79-44.92 26.93-15.54c.27-.18.61-.21.91-.08l64.42 37.19c28.68 16.57 38.51 53.26 21.94 81.94-7.01 12.14-18.05 21.44-31.2 26.28v-75.81c.03-3.74-1.96-7.2-5.2-9.06zm26.8-40.34c-.47-.29-1.3-.79-1.89-1.13l-63.72-36.8c-3.23-1.89-7.23-1.89-10.47 0l-77.79 44.92v-31.1c-.02-.32.13-.63.38-.83l64.41-37.16c28.69-16.55 65.37-6.7 81.91 22 6.99 12.12 9.52 26.31 7.15 40.1zm-168.51 55.43-26.94-15.55c-.29-.14-.48-.42-.52-.74v-74.39c.02-33.12 26.89-59.96 60.01-59.94 14.01 0 27.57 4.92 38.34 13.88-.49.26-1.33.73-1.89 1.07l-63.72 36.8c-3.26 1.85-5.26 5.31-5.24 9.06l-.04 89.79zm14.63-31.54 34.65-20.01 34.65 20v40.01l-34.65 20-34.65-20z"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,55 @@
"use client";
import { Button } from "@tremor/react";
import React, { ReactNode, useState } from "react";
import { FiSettings } from "react-icons/fi";
interface CollapsibleSectionProps {
children: ReactNode;
prompt?: string;
className?: string;
}
const CollapsibleSection: React.FC<CollapsibleSectionProps> = ({
children,
prompt,
className = "",
}) => {
const [isCollapsed, setIsCollapsed] = useState<boolean>(false);
const toggleCollapse = (e?: React.MouseEvent<HTMLDivElement>) => {
// Only toggle if the click is on the border or plus sign
if (
!e ||
e.currentTarget === e.target ||
(e.target as HTMLElement).classList.contains("collapse-toggle")
) {
setIsCollapsed(!isCollapsed);
}
};
return (
<div
className={`relative ${isCollapsed ? "h-6" : ""} ${className}`}
style={{ transition: "height 0.3s ease-out" }}
>
<div
className={`
cursor-pointer
${isCollapsed ? "h-6" : "pl-4 border-l-2 border-border"}
`}
onClick={toggleCollapse}
>
{isCollapsed ? (
<span className="collapse-toggle text-lg absolute left-0 top-0 text-sm flex items-center gap-x-3 cursor-pointer">
<FiSettings className="pointer-events-none my-auto" size={16} />
{prompt}{" "}
</span>
) : (
<>{children}</>
)}
</div>
</div>
);
};
export default CollapsibleSection;

View File

@ -44,7 +44,7 @@ export function HidableSection({
</div>
</div>
{!isHidden && <div className="mx-2 mt-2">{children}</div>}
{!isHidden && <div className="mx-2 gap-y-2 mt-2">{children}</div>}
</div>
);
}

View File

@ -30,12 +30,10 @@ export default async function Page() {
return (
<div>
<BackButton />
<AdminPageTitle
title="Create a New Persona"
title="Create a New Assistant"
icon={<RobotIcon size={32} />}
/>
{body}
</div>
);

View File

@ -34,6 +34,7 @@ export interface FullLLMProvider extends LLMProvider {
id: number;
is_default_provider: boolean | null;
model_names: string[];
icon?: React.FC<{ size?: number; className?: string }>;
}
export interface LLMProviderDescriptor {

View File

@ -8,6 +8,7 @@ export interface Option<T> {
value: T;
description?: string;
metadata?: { [key: string]: any };
icon?: React.FC<{ size?: number; className?: string }>;
}
export type StringOrNumberOption = Option<string | number>;
@ -24,9 +25,7 @@ function StandardDropdownOption<T>({
return (
<button
onClick={() => handleSelect(option)}
className={`w-full text-left block px-4 py-2.5 text-sm hover:bg-gray-800 ${
index !== 0 ? " border-t-2 border-gray-600" : ""
}`}
className={`w-full text-left block px-4 py-2.5 text-sm hover:bg-gray-800 ${index !== 0 ? " border-t-2 border-gray-600" : ""}`}
role="menuitem"
>
<p className="font-medium">{option.name}</p>
@ -216,9 +215,7 @@ export const CustomDropdown = ({
{isOpen && (
<div
onClick={() => setIsOpen(!isOpen)}
className={`absolute ${
direction === "up" ? "bottom-full pb-2" : "pt-2 "
} w-full z-30 box-shadow`}
className={`absolute ${direction === "up" ? "bottom-full pb-2" : "pt-2"} w-full z-30 box-shadow`}
>
{dropdown}
</div>
@ -269,7 +266,7 @@ export function DefaultDropdownElement({
onChange={() => null}
/>
)}
{icon && icon({ size: 16, className: "mr-2 my-auto" })}
{icon && icon({ size: 16, className: "mr-2 h-4 w-4 my-auto" })}
{name}
</div>
{description && <div className="text-xs">{description}</div>}
@ -290,11 +287,13 @@ export function DefaultDropdown({
includeDefault = false,
side,
maxHeight,
defaultValue,
}: {
options: StringOrNumberOption[];
selected: string | null;
onSelect: (value: string | number | null) => void;
includeDefault?: boolean;
defaultValue?: string;
side?: "top" | "right" | "bottom" | "left";
maxHeight?: string;
}) {
@ -316,7 +315,7 @@ export function DefaultDropdown({
>
<p className="line-clamp-1">
{selectedOption?.name ||
(includeDefault ? "Default" : "Select an option...")}
(includeDefault ? defaultValue ?? "Default" : "Select an option...")}
</p>
<FiChevronDown className="my-auto ml-auto" />
</div>
@ -354,6 +353,7 @@ export function DefaultDropdown({
description={option.description}
onSelect={() => onSelect(option.value)}
isSelected={isSelected}
icon={option.icon}
/>
);
})}

View File

@ -10,7 +10,13 @@ import {
import * as Yup from "yup";
import { FormBodyBuilder } from "./types";
import { DefaultDropdown, StringOrNumberOption } from "@/components/Dropdown";
import { FiPlus, FiX } from "react-icons/fi";
import { FiInfo, FiPlus, FiX } from "react-icons/fi";
import {
TooltipProvider,
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@radix-ui/react-tooltip";
export function SectionHeader({
children,
@ -20,8 +26,20 @@ export function SectionHeader({
return <div className="mb-4 font-bold text-lg">{children}</div>;
}
export function Label({ children }: { children: string | JSX.Element }) {
return <div className="block font-medium text-base">{children}</div>;
export function Label({
children,
small,
}: {
children: string | JSX.Element;
small?: boolean;
}) {
return (
<div
className={`block font-medium base ${small ? "text-sm" : "text-base"}`}
>
{children}
</div>
);
}
export function SubLabel({ children }: { children: string | JSX.Element }) {
@ -29,7 +47,48 @@ export function SubLabel({ children }: { children: string | JSX.Element }) {
}
export function ManualErrorMessage({ children }: { children: string }) {
return <div className="text-error text-sm mt-1">{children}</div>;
return <div className="text-error text-sm">{children}</div>;
}
export function ExplanationText({
text,
link,
}: {
text: string;
link?: string;
}) {
return link ? (
<a
className="underline cursor-pointer text-sm font-medium"
target="_blank"
href={link}
>
{text}
</a>
) : (
<div className="text-sm font-semibold">{text}</div>
);
}
export function ToolTipDetails({
children,
}: {
children: string | JSX.Element;
}) {
return (
<TooltipProvider delayDuration={50}>
<Tooltip>
<TooltipTrigger>
<FiInfo size={12} />
</TooltipTrigger>
<TooltipContent side="top" align="center">
<p className="bg-background-dark max-w-[200px] mb-1 text-sm rounded-lg p-1.5 text-inverted">
{children}
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
export function TextFormField({
@ -47,6 +106,10 @@ export function TextFormField({
isCode = false,
fontSize,
hideError,
tooltip,
explanationText,
explanationLink,
small,
}: {
name: string;
label: string;
@ -62,6 +125,10 @@ export function TextFormField({
isCode?: boolean;
fontSize?: "text-sm" | "text-base" | "text-lg";
hideError?: boolean;
tooltip?: string;
explanationText?: string;
explanationLink?: string;
small?: boolean;
}) {
let heightString = defaultHeight || "";
if (isTextArea && !heightString) {
@ -69,8 +136,25 @@ export function TextFormField({
}
return (
<div className="mb-4">
<Label>{label}</Label>
<div className="mb-6">
<div className="flex gap-x-2 items-center">
<Label small={small}>{label}</Label>
{tooltip && <ToolTipDetails>{tooltip}</ToolTipDetails>}
{error ? (
<ManualErrorMessage>{error}</ManualErrorMessage>
) : (
!hideError && (
<ErrorMessage
name={name}
component="div"
className="text-error my-auto text-sm"
/>
)
)}
</div>
{subtext && <SubLabel>{subtext}</SubLabel>}
<Field
as={isTextArea ? "textarea" : "input"}
@ -78,6 +162,7 @@ export function TextFormField({
name={name}
id={name}
className={`
${small && "text-sm"}
border
border-border
rounded
@ -95,16 +180,8 @@ export function TextFormField({
autoComplete={autoCompleteDisabled ? "off" : undefined}
{...(onChange ? { onChange } : {})}
/>
{error ? (
<ManualErrorMessage>{error}</ManualErrorMessage>
) : (
!hideError && (
<ErrorMessage
name={name}
component="div"
className="text-red-500 text-sm mt-1"
/>
)
{explanationText && (
<ExplanationText link={explanationLink} text={explanationText} />
)}
</div>
);
@ -115,6 +192,9 @@ interface BooleanFormFieldProps {
label: string;
subtext?: string | JSX.Element;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
noPadding?: boolean;
small?: boolean;
alignTop?: boolean;
}
export const BooleanFormField = ({
@ -122,6 +202,9 @@ export const BooleanFormField = ({
label,
subtext,
onChange,
noPadding,
small,
alignTop,
}: BooleanFormFieldProps) => {
return (
<div className="mb-4">
@ -129,19 +212,18 @@ export const BooleanFormField = ({
<Field
name={name}
type="checkbox"
className="mx-3 px-5 w-3.5 h-3.5 my-auto"
className={`${noPadding ? "mr-3" : "mx-3"} px-5 w-3.5 h-3.5 ${alignTop ? "mt-1" : "my-auto"}`}
{...(onChange ? { onChange } : {})}
/>
<div>
<Label>{label}</Label>
<Label small={small}>{label}</Label>
{subtext && <SubLabel>{subtext}</SubLabel>}
</div>
</label>
<ErrorMessage
name={name}
component="div"
className="text-red-500 text-sm mt-1"
className="text-error text-sm mt-1"
/>
</div>
);
@ -252,6 +334,7 @@ interface SelectorFormFieldProps {
side?: "top" | "right" | "bottom" | "left";
maxHeight?: string;
onSelect?: (selected: string | number | null) => void;
defaultValue?: string;
}
export function SelectorFormField({
@ -263,6 +346,7 @@ export function SelectorFormField({
side = "bottom",
maxHeight,
onSelect,
defaultValue,
}: SelectorFormFieldProps) {
const [field] = useField<string>(name);
const { setFieldValue } = useFormikContext();
@ -280,13 +364,14 @@ export function SelectorFormField({
includeDefault={includeDefault}
side={side}
maxHeight={maxHeight}
defaultValue={defaultValue}
/>
</div>
<ErrorMessage
name={name}
component="div"
className="text-red-500 text-sm mt-1"
className="text-error text-sm mt-1"
/>
</div>
);

View File

@ -44,6 +44,14 @@ import { SiBookstack } from "react-icons/si";
import Image from "next/image";
import jiraSVG from "../../../public/Jira.svg";
import confluenceSVG from "../../../public/Confluence.svg";
import openAISVG from "../../../public/Openai.svg";
import openSourceIcon from "../../../public/OpenSource.png";
import awsWEBP from "../../../public/Amazon.webp";
import azureIcon from "../../../public/Azure.png";
import anthropicSVG from "../../../public/Anthropic.svg";
import OCIStorageSVG from "../../../public/OCI.svg";
import googleCloudStorageIcon from "../../../public/GoogleCloudStorage.png";
import guruIcon from "../../../public/Guru.svg";
@ -75,6 +83,48 @@ interface IconProps {
export const defaultTailwindCSS = "my-auto flex flex-shrink-0 text-default";
export const defaultTailwindCSSBlue = "my-auto flex flex-shrink-0 text-link";
export const OpenAIIcon = ({
size = 16,
className = defaultTailwindCSS,
}: IconProps) => {
return (
<div
style={{ width: `${size + 4}px`, height: `${size + 4}px` }}
className={`w-[${size + 4}px] h-[${size + 4}px] -m-0.5 ` + className}
>
<Image src={openAISVG} alt="Logo" width="96" height="96" />
</div>
);
};
export const OpenSourceIcon = ({
size = 16,
className = defaultTailwindCSS,
}: IconProps) => {
return (
<div
style={{ width: `${size + 4}px`, height: `${size + 4}px` }}
className={`w-[${size + 4}px] h-[${size + 4}px] -m-0.5 ` + className}
>
<Image src={openSourceIcon} alt="Logo" width="96" height="96" />
</div>
);
};
export const AnthropicIcon = ({
size = 16,
className = defaultTailwindCSS,
}: IconProps) => {
return (
<div
style={{ width: `${size + 4}px`, height: `${size + 4}px` }}
className={`w-[${size + 4}px] h-[${size + 4}px] -m-0.5 ` + className}
>
<Image src={anthropicSVG} alt="Logo" width="96" height="96" />
</div>
);
};
export const PlugIcon = ({
size = 16,
className = defaultTailwindCSS,
@ -498,6 +548,36 @@ export const ProductboardIcon = ({
);
};
export const AWSIcon = ({
size = 16,
className = defaultTailwindCSS,
}: IconProps) => {
return (
<div
// Linear Icon has a bit more surrounding whitespace than other icons, which is why we need to adjust it here
style={{ width: `${size + 4}px`, height: `${size + 4}px` }}
className={`w-[${size + 4}px] h-[${size + 4}px] -m-0.5 ` + className}
>
<Image src={awsWEBP} alt="Logo" width="96" height="96" />
</div>
);
};
export const AzureIcon = ({
size = 16,
className = defaultTailwindCSS,
}: IconProps) => {
return (
<div
// Linear Icon has a bit more surrounding whitespace than other icons, which is why we need to adjust it here
style={{ width: `${size + 4}px`, height: `${size + 4}px` }}
className={`w-[${size + 4}px] h-[${size + 4}px] -m-0.5 ` + className}
>
<Image src={azureIcon} alt="Logo" width="96" height="96" />
</div>
);
};
export const LinearIcon = ({
size = 16,
className = defaultTailwindCSS,

View File

@ -5,6 +5,14 @@ import { fetchSS } from "../utilsSS";
import { FullLLMProvider } from "@/app/admin/models/llm/interfaces";
import { ToolSnapshot } from "../tools/interfaces";
import { fetchToolsSS } from "../tools/fetchTools";
import { IconManifestType } from "react-icons/lib";
import {
OpenAIIcon,
AnthropicIcon,
AWSIcon,
AzureIcon,
OpenSourceIcon,
} from "@/components/icons/icons";
export async function fetchAssistantEditorInfoSS(
personaId?: number | string
@ -79,11 +87,27 @@ export async function fetchAssistantEditorInfoSS(
`Failed to fetch LLM providers - ${await llmProvidersResponse.text()}`,
];
}
const llmProviders = (await llmProvidersResponse.json()) as FullLLMProvider[];
if (personaId && personaResponse && !personaResponse.ok) {
return [null, `Failed to fetch Persona - ${await personaResponse.text()}`];
}
for (const provider of llmProviders) {
if (provider.provider == "openai") {
provider.icon = OpenAIIcon;
} else if (provider.provider == "anthropic") {
provider.icon = AnthropicIcon;
} else if (provider.provider == "bedrock") {
provider.icon = AzureIcon;
} else if (provider.provider == "azure") {
provider.icon = AWSIcon;
} else {
provider.icon = OpenSourceIcon;
}
}
const existingPersona = personaResponse
? ((await personaResponse.json()) as Persona)
: null;