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
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

View File

@ -20,11 +20,12 @@ import Link from "next/link";
import { useEffect, useState } from "react";
import {
BooleanFormField,
Label,
SelectorFormField,
TextFormField,
} from "@/components/admin/connectors/Field";
import { HidableSection } from "./HidableSection";
import { FiPlus, FiX } from "react-icons/fi";
import CollapsibleSection from "./CollapsibleSection";
import { FiInfo, FiPlus, FiX } from "react-icons/fi";
import { useUserGroups } from "@/lib/hooks";
import { Bubble } from "@/components/Bubble";
import { GroupsIcon } from "@/components/icons/icons";
@ -36,8 +37,13 @@ import { ToolSnapshot } from "@/lib/tools/interfaces";
import { checkUserIsNoAuthUser } from "@/lib/user";
import { addAssistantToList } from "@/lib/assistants/updateAssistantPreferences";
import { checkLLMSupportsImageInput } from "@/lib/llm/utils";
import { SettingsContext } from "@/components/settings/SettingsProvider";
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
import {
TooltipProvider,
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@radix-ui/react-tooltip";
function findSearchTool(tools: ToolSnapshot[]) {
return tools.find((tool) => tool.in_code_tool_id === "SearchTool");
@ -47,12 +53,6 @@ function findImageGenerationTool(tools: ToolSnapshot[]) {
return tools.find((tool) => tool.in_code_tool_id === "ImageGenerationTool");
}
function Label({ children }: { children: string | JSX.Element }) {
return (
<div className="block font-medium text-base text-emphasis">{children}</div>
);
}
function SubLabel({ children }: { children: string | JSX.Element }) {
return <div className="text-sm text-subtle mb-2">{children}</div>;
}
@ -202,9 +202,11 @@ export function AssistantEditor({
initialValues={initialValues}
validationSchema={Yup.object()
.shape({
name: Yup.string().required("Must give the Assistant a name!"),
name: Yup.string().required(
"Must provide a name for the Assistant"
),
description: Yup.string().required(
"Must give the Assistant a description!"
"Must provide a description for the Assistant"
),
system_prompt: Yup.string(),
task_prompt: Yup.string(),
@ -227,29 +229,29 @@ export function AssistantEditor({
})
.test(
"system-prompt-or-task-prompt",
"Must provide at least one of System Prompt or Task Prompt",
(values) => {
const systemPromptSpecified = values.system_prompt
? values.system_prompt.length > 0
: false;
const taskPromptSpecified = values.task_prompt
? values.task_prompt.length > 0
: false;
if (systemPromptSpecified || taskPromptSpecified) {
setFinalPromptError("");
return true;
} // Return true if at least one field has a value
"Must provide either System Prompt or Additional Instructions",
function (values) {
const systemPromptSpecified =
values.system_prompt && values.system_prompt.trim().length > 0;
const taskPromptSpecified =
values.task_prompt && values.task_prompt.trim().length > 0;
setFinalPromptError(
"Must provide at least one of System Prompt or Task Prompt"
);
if (systemPromptSpecified || taskPromptSpecified) {
return true;
}
return this.createError({
path: "system_prompt",
message:
"Must provide either System Prompt or Additional Instructions",
});
}
)}
onSubmit={async (values, formikHelpers) => {
if (finalPromptError) {
setPopup({
type: "error",
message: "Cannot submit while there are errors in the form!",
message: "Cannot submit while there are errors in the form",
});
return;
}
@ -392,28 +394,26 @@ export function AssistantEditor({
return (
<Form>
<div className="pb-6">
<HidableSection sectionTitle="Basics">
<>
<TextFormField
name="name"
tooltip="Used to identify the Assistant in the UI."
label="Name"
disabled={isUpdate}
subtext="Users will be able to select this Assistant based on this name."
placeholder="e.g. 'Email Assistant'"
/>
<TextFormField
tooltip="Used for identifying assistants and their use cases."
name="description"
label="Description"
subtext="Provide a short descriptions which gives users a hint as to what they should use this Assistant for."
placeholder="e.g. 'Use this Assistant to help draft professional emails'"
/>
<TextFormField
tooltip="Gives your assistant a prime directive"
name="system_prompt"
label="System Prompt"
isTextArea={true}
subtext={
'Give general info about what the Assistant is about. For example, "You are an assistant for On-Call engineers. Your goal is to read the provided context documents and give recommendations as to how to resolve the issue."'
}
placeholder="e.g. 'You are a professional email writing assistant that always uses a polite enthusiastic tone, emphasizes action items, and leaves blanks for the human to fill in when you have unknowns'"
//
onChange={(e) => {
setFieldValue("system_prompt", e.target.value);
triggerFinalPromptUpdate(
@ -425,46 +425,91 @@ export function AssistantEditor({
error={finalPromptError}
/>
<TextFormField
name="task_prompt"
label="Task Prompt (Optional)"
isTextArea={true}
subtext={`Give specific instructions as to what to do with the user query.
For example, "Find any relevant sections from the provided documents that can
help the user resolve their issue and explain how they are relevant."`}
onChange={(e) => {
setFieldValue("task_prompt", e.target.value);
triggerFinalPromptUpdate(
values.system_prompt,
e.target.value,
searchToolEnabled()
<div className="mb-6">
<div className="flex gap-x-2 items-center">
<div className="block font-medium text-base">
LLM Provider{" "}
</div>
<TooltipProvider delayDuration={50}>
<Tooltip>
<TooltipTrigger>
<FiInfo size={12} />
</TooltipTrigger>
<TooltipContent side="top" align="center">
<p className="bg-neutral-900 max-w-[200px] mb-1 text-sm rounded-lg p-1.5 text-white">
Select a Large Language Model (Generative AI model)
to power this Assistant
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div className="mb-2 flex items-starts">
<div className="w-96">
<SelectorFormField
defaultValue={`Default (${defaultModelName})`}
name="llm_model_provider_override"
options={llmProviders.map((llmProvider) => ({
name: llmProvider.name,
value: llmProvider.name,
icon: llmProvider.icon,
}))}
includeDefault={true}
onSelect={(selected) => {
if (selected !== values.llm_model_provider_override) {
setFieldValue("llm_model_version_override", null);
}
setFieldValue(
"llm_model_provider_override",
selected
);
}}
error={finalPromptError}
/>
</div>
<Label>Final Prompt</Label>
{finalPrompt ? (
<pre className="text-sm mt-2 whitespace-pre-wrap">
{finalPrompt}
</pre>
) : (
"-"
{values.llm_model_provider_override && (
<div className="w-96 ml-4">
<SelectorFormField
name="llm_model_version_override"
options={
modelOptionsByProvider.get(
values.llm_model_provider_override
) || []
}
maxHeight="max-h-72"
/>
</div>
)}
</>
</HidableSection>
</div>
<Divider />
<div className="ml-1">
{imageGenerationTool &&
checkLLMSupportsImageInput(
providerDisplayNameToProviderName.get(
values.llm_model_provider_override || ""
) ||
defaultProviderName ||
"",
values.llm_model_version_override ||
defaultModelName ||
""
) && (
<BooleanFormField
noPadding
name={`enabled_tools_map.${imageGenerationTool.id}`}
label="Image Generation Tool"
onChange={() => {
toggleToolInValues(imageGenerationTool.id);
}}
/>
)}
<HidableSection sectionTitle="Tools">
<>
{ccPairs.length > 0 && searchTool && (
<>
<BooleanFormField
name={`enabled_tools_map.${searchTool.id}`}
label="Search Tool"
subtext={`The Search Tool allows the Assistant to search through connected knowledge to help build an answer.`}
noPadding
onChange={() => {
setFieldValue("num_chunks", null);
toggleToolInValues(searchTool.id);
@ -472,10 +517,11 @@ export function AssistantEditor({
/>
{searchToolEnabled() && (
<div className="pl-4 border-l-2 ml-4 border-border">
<CollapsibleSection prompt="Configure Search">
<div className=" ">
{ccPairs.length > 0 && (
<>
<Label>Document Sets</Label>
<Label small>Document Sets</Label>
<div>
<SubLabel>
@ -492,10 +538,11 @@ export function AssistantEditor({
) : (
"Document Sets"
)}{" "}
that this Assistant should search through.
If none are specified, the Assistant will
search through all available documents in
order to try and respond to queries.
that this Assistant should search
through. If none are specified, the
Assistant will search through all
available documents in order to try and
respond to queries.
</>
</SubLabel>
</div>
@ -546,21 +593,15 @@ export function AssistantEditor({
</Italic>
)}
<>
<div className="mt-6">
<TextFormField
small={true}
name="num_chunks"
label="Number of Chunks"
placeholder="If unspecified, will use 10 chunks."
subtext={
<div>
How many chunks should we feed into the
LLM when generating the final response?
Each chunk is ~400 words long.
</div>
}
tooltip="How many chunks to feed the LLM"
placeholder="Defaults to 10 chunks."
onChange={(e) => {
const value = e.target.value;
// Allow only integer values
if (
value === "" ||
/^[0-9]+$/.test(value)
@ -570,9 +611,10 @@ export function AssistantEditor({
}}
/>
<Label>Misc</Label>
<BooleanFormField
small
noPadding
alignTop
name="llm_relevance_filter"
label="Apply LLM Relevance Filter"
subtext={
@ -581,6 +623,9 @@ export function AssistantEditor({
/>
<BooleanFormField
small
noPadding
alignTop
name="include_citations"
label="Include Citations"
subtext={`
@ -589,35 +634,15 @@ export function AssistantEditor({
the same technique used by the default Assistants. In general, we recommend
to leave this enabled in order to increase trust in the LLM answer.`}
/>
</>
</div>
</>
)}
</div>
</CollapsibleSection>
)}
</>
)}
{imageGenerationTool &&
checkLLMSupportsImageInput(
providerDisplayNameToProviderName.get(
values.llm_model_provider_override || ""
) ||
defaultProviderName ||
"",
values.llm_model_version_override ||
defaultModelName ||
""
) && (
<BooleanFormField
name={`enabled_tools_map.${imageGenerationTool.id}`}
label="Image Generation Tool"
subtext="The Image Generation Tool allows the assistant to use DALL-E 3 to generate images. The tool will be used when the user asks the assistant to generate an image."
onChange={() => {
toggleToolInValues(imageGenerationTool.id);
}}
/>
)}
{customTools.length > 0 && (
<>
{customTools.map((tool) => (
@ -633,106 +658,40 @@ export function AssistantEditor({
))}
</>
)}
</>
</HidableSection>
<Divider />
</div>
</div>
{llmProviders.length > 0 && (
<>
<HidableSection
sectionTitle="[Advanced] Model Selection"
defaultHidden
>
<>
<Text>
Pick which LLM to use for this Assistant. If left as
Default, will use{" "}
<b className="italic">{defaultModelName}</b>
.
<br />
<br />
For more information on the different LLMs, checkout
the{" "}
<a
href="https://platform.openai.com/docs/models"
target="_blank"
className="text-blue-500"
>
OpenAI docs
</a>
.
</Text>
<Divider />
<div className="flex mt-6">
<div className="w-96">
<SubLabel>LLM Provider</SubLabel>
<SelectorFormField
name="llm_model_provider_override"
options={llmProviders.map((llmProvider) => ({
name: llmProvider.name,
value: llmProvider.name,
}))}
includeDefault={true}
onSelect={(selected) => {
if (
selected !==
values.llm_model_provider_override
) {
setFieldValue(
"llm_model_version_override",
null
);
}
setFieldValue(
"llm_model_provider_override",
selected
<TextFormField
name="task_prompt"
label="Additional instructions (Optional)"
isTextArea={true}
placeholder="e.g. 'Remember to reference all of the points mentioned in my message to you and focus on identifying action items that can move things forward'"
onChange={(e) => {
setFieldValue("task_prompt", e.target.value);
triggerFinalPromptUpdate(
values.system_prompt,
e.target.value,
searchToolEnabled()
);
}}
explanationText="Learn about prompting in our docs!"
explanationLink="https://docs.danswer.dev/guides/assistants"
/>
</div>
{values.llm_model_provider_override && (
<div className="w-96 ml-4">
<SubLabel>Model</SubLabel>
<SelectorFormField
name="llm_model_version_override"
options={
modelOptionsByProvider.get(
values.llm_model_provider_override
) || []
}
maxHeight="max-h-72"
/>
</div>
)}
</div>
</>
</HidableSection>
<Divider />
</>
)}
<HidableSection
sectionTitle="[Advanced] Starter Messages"
defaultHidden
>
<>
<div className="mb-4">
<SubLabel>
Starter Messages help guide users to use this Assistant.
They are shown to the user as clickable options when
they select this Assistant. When selected, the specified
message is sent to the LLM as the initial user message.
</SubLabel>
<div className="mb-6">
<div className="flex gap-x-2 items-center">
<div className="block font-medium text-base">
Add Starter Messages (Optional){" "}
</div>
</div>
<FieldArray
name="starter_messages"
render={(
arrayHelpers: ArrayHelpers<StarterMessage[]>
) => (
render={(arrayHelpers: ArrayHelpers<StarterMessage[]>) => (
<div>
{values.starter_messages &&
values.starter_messages.length > 0 &&
@ -745,7 +704,7 @@ export function AssistantEditor({
<div className="flex">
<div className="w-full mr-6 border border-border p-3 rounded">
<div>
<Label>Name</Label>
<Label small>Name</Label>
<SubLabel>
Shows up as the &quot;title&quot; for
this Starter Message. For example,
@ -773,13 +732,12 @@ export function AssistantEditor({
</div>
<div className="mt-3">
<Label>Description</Label>
<Label small>Description</Label>
<SubLabel>
A description which tells the user
what they might want to use this
Starter Message for. For example
&quot;to a client about a new
feature&quot;
A description which tells the user what
they might want to use this Starter
Message for. For example &quot;to a
client about a new feature&quot;
</SubLabel>
<Field
name={`starter_messages.${index}.description`}
@ -803,7 +761,7 @@ export function AssistantEditor({
</div>
<div className="mt-3">
<Label>Message</Label>
<Label small>Message</Label>
<SubLabel>
The actual message to be sent as the
initial user message if a user selects
@ -837,9 +795,7 @@ export function AssistantEditor({
<div className="my-auto">
<FiX
className="my-auto w-10 h-10 cursor-pointer hover:bg-hover rounded p-2"
onClick={() =>
arrayHelpers.remove(index)
}
onClick={() => arrayHelpers.remove(index)}
/>
</div>
</div>
@ -866,18 +822,18 @@ export function AssistantEditor({
</div>
)}
/>
</>
</HidableSection>
<Divider />
</div>
{isPaidEnterpriseFeaturesEnabled &&
userGroups &&
(!user || user.role === "admin") && (
<>
<HidableSection sectionTitle="Access">
<>
<Divider />
<BooleanFormField
small
noPadding
alignTop
name="is_public"
label="Is Public?"
subtext="If set, this Assistant will be available to all users. If not, only the specified User Groups will be able to access it."
@ -929,9 +885,6 @@ export function AssistantEditor({
</div>
)}
</>
</HidableSection>
<Divider />
</>
)}
<div className="flex">

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;