From a6bfdbdbfe84f15eb3e08ba5c730d63a5ef2cf9d Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Sun, 20 Oct 2024 01:30:15 -0700 Subject: [PATCH 1/9] Show all agents in carousel on home screen agent pane of web app This change wraps the agent pane in a scroll area with all agents shown. It allows selecting an agent to chat with directly from the home screen without breaking flow and having to jump to the agents page. The previous flow was not convenient to quickly and consistently start chat with one of your standard agents. This was because a random subet of agents were shown on the home page. To start chat with an agent not shown on home screen load you had to open the agents page and initiate the conversation from there. --- src/interface/web/app/page.tsx | 93 +++++++++++++++------------------- 1 file changed, 41 insertions(+), 52 deletions(-) diff --git a/src/interface/web/app/page.tsx b/src/interface/web/app/page.tsx index 158b6fb7..45b91ad2 100644 --- a/src/interface/web/app/page.tsx +++ b/src/interface/web/app/page.tsx @@ -23,6 +23,7 @@ import { AgentData } from "@/app/agents/page"; import { createNewConversation } from "./common/chatFunctions"; import { useIsMobileWidth } from "./common/utils"; import { useSearchParams } from "next/navigation"; +import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; interface ChatBodyDataProps { chatOptionsData: ChatOptions | null; @@ -112,7 +113,7 @@ function ChatBodyData(props: ChatBodyDataProps) { const shuffledAgents = agentsData ? [...agentsData].sort(() => 0.5 - Math.random()) : []; const agents = agentsData ? [agentsData[0]] : []; // Always add the first/default agent. - shuffledAgents.slice(0, nSlice - 1).forEach((agent) => { + shuffledAgents.forEach((agent) => { if (!agents.find((a) => a.slug === agent.slug)) { agents.push(agent); } @@ -194,34 +195,29 @@ function ChatBodyData(props: ChatBodyDataProps) { {!props.isMobileWidth && ( -
- {agentIcons.map((icon, index) => ( - - setSelectedAgent(agents[index].slug)} + +
+ {agentIcons.map((icon, index) => ( + - {icon} {agents[index].name} - - - ))} - (window.location.href = "/agents")} - > - - See All → - - -
+ setSelectedAgent(agents[index].slug)} + > + {icon} {agents[index].name} + +
+ ))} +
+ + )}
@@ -285,31 +281,24 @@ function ChatBodyData(props: ChatBodyDataProps) {
-
- {agentIcons.map((icon, index) => ( - - setSelectedAgent(agents[index].slug)} + +
+ {agentIcons.map((icon, index) => ( + - {icon} {agents[index].name} - - - ))} - (window.location.href = "/agents")} - > - - See All → - - -
+ setSelectedAgent(agents[index].slug)} + > + {icon} {agents[index].name} + +
+ ))} +
+ + setMessage(message)} From 5fca41cc29ad121915f8c486421e8bcaa3d88539 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Sun, 20 Oct 2024 10:52:14 -0700 Subject: [PATCH 2/9] Show agents sorted by mru, Select mru agent by default on web app Have get agents API return agents ordered intelligently - Put the default agent first - Sort used agents by most recently chatted with agent for ease of access - Randomly shuffle the remaining unused agents for discoverability --- src/interface/web/app/page.tsx | 19 +++-------- src/khoj/routers/api_agents.py | 60 ++++++++++++++++++++++------------ 2 files changed, 44 insertions(+), 35 deletions(-) diff --git a/src/interface/web/app/page.tsx b/src/interface/web/app/page.tsx index 45b91ad2..3976a6b6 100644 --- a/src/interface/web/app/page.tsx +++ b/src/interface/web/app/page.tsx @@ -109,22 +109,13 @@ function ChatBodyData(props: ChatBodyDataProps) { }, [props.chatOptionsData]); useEffect(() => { - const nSlice = props.isMobileWidth ? 2 : 4; - const shuffledAgents = agentsData ? [...agentsData].sort(() => 0.5 - Math.random()) : []; - const agents = agentsData ? [agentsData[0]] : []; // Always add the first/default agent. - - shuffledAgents.forEach((agent) => { - if (!agents.find((a) => a.slug === agent.slug)) { - agents.push(agent); - } - }); - + const agents = (agentsData || []).filter((agent) => agent !== null && agent !== undefined); setAgents(agents); + // set the selected agent to the most recently used agent, first agent is always khoj + setSelectedAgent(agents.length > 1 ? agents[1].slug : "khoj"); - //generate colored icons for the selected agents - const agentIcons = agents - .filter((agent) => agent !== null && agent !== undefined) - .map((agent) => getIconFromIconName(agent.icon, agent.color)!); + // generate colored icons for the available agents + const agentIcons = agents.map((agent) => getIconFromIconName(agent.icon, agent.color)!); setAgentIcons(agentIcons); }, [agentsData, props.isMobileWidth]); diff --git a/src/khoj/routers/api_agents.py b/src/khoj/routers/api_agents.py index 9aaf6d05..b47bb039 100644 --- a/src/khoj/routers/api_agents.py +++ b/src/khoj/routers/api_agents.py @@ -1,5 +1,7 @@ import json import logging +import random +from datetime import datetime, timezone from typing import Dict, List, Optional from asgiref.sync import sync_to_async @@ -9,8 +11,8 @@ from fastapi.responses import Response from pydantic import BaseModel from starlette.authentication import requires -from khoj.database.adapters import AgentAdapters -from khoj.database.models import Agent, KhojUser +from khoj.database.adapters import AgentAdapters, ConversationAdapters +from khoj.database.models import Agent, Conversation, KhojUser from khoj.routers.helpers import CommonQueryParams, acheck_if_safe_prompt from khoj.utils.helpers import ( ConversationCommand, @@ -45,30 +47,46 @@ async def all_agents( ) -> Response: user: KhojUser = request.user.object if request.user.is_authenticated else None agents = await AgentAdapters.aget_all_accessible_agents(user) + default_agent = await AgentAdapters.aget_default_agent() + default_agent_packet = None agents_packet = list() for agent in agents: files = agent.fileobject_set.all() file_names = [file.file_name for file in files] - agents_packet.append( - { - "slug": agent.slug, - "name": agent.name, - "persona": agent.personality, - "creator": agent.creator.username if agent.creator else None, - "managed_by_admin": agent.managed_by_admin, - "color": agent.style_color, - "icon": agent.style_icon, - "privacy_level": agent.privacy_level, - "chat_model": agent.chat_model.chat_model, - "files": file_names, - "input_tools": agent.input_tools, - "output_modes": agent.output_modes, - } - ) + agent_packet = { + "slug": agent.slug, + "name": agent.name, + "persona": agent.personality, + "creator": agent.creator.username if agent.creator else None, + "managed_by_admin": agent.managed_by_admin, + "color": agent.style_color, + "icon": agent.style_icon, + "privacy_level": agent.privacy_level, + "chat_model": agent.chat_model.chat_model, + "files": file_names, + "input_tools": agent.input_tools, + "output_modes": agent.output_modes, + } + if agent.slug == default_agent.slug: + default_agent_packet = agent_packet + else: + agents_packet.append(agent_packet) + + # Load Conversation Sessions + min_date = datetime.min.replace(tzinfo=timezone.utc) + conversations = [] + if user: + conversations = await sync_to_async(list[Conversation])( + ConversationAdapters.get_conversation_sessions(user, request.user.client_app).reverse() + ) + conversation_times = {conv.agent.slug: conv.updated_at for conv in conversations} + + # Put default agent first, then sort by mru and finally shuffle unused randomly + random.shuffle(agents_packet) + agents_packet.sort(key=lambda x: conversation_times.get(x["slug"]) or min_date, reverse=True) + if default_agent_packet: + agents_packet.insert(0, default_agent_packet) - # Make sure that the agent named 'khoj' is first in the list. Everything else is sorted by name. - agents_packet.sort(key=lambda x: x["name"]) - agents_packet.sort(key=lambda x: x["slug"] == "khoj", reverse=True) return Response(content=json.dumps(agents_packet), media_type="application/json", status_code=200) From bdbe8f003e3ac32580b3e523093475a9e1f3d9f7 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Sun, 20 Oct 2024 20:32:19 -0700 Subject: [PATCH 3/9] Move agent details and edit card out into reusable components on web app --- src/interface/web/app/agents/page.tsx | 1282 +--------------- .../components/agentCard/agentCard.module.css | 20 + .../app/components/agentCard/agentCard.tsx | 1297 +++++++++++++++++ 3 files changed, 1327 insertions(+), 1272 deletions(-) create mode 100644 src/interface/web/app/components/agentCard/agentCard.module.css create mode 100644 src/interface/web/app/components/agentCard/agentCard.tsx diff --git a/src/interface/web/app/agents/page.tsx b/src/interface/web/app/agents/page.tsx index 45b485fb..697d4f80 100644 --- a/src/interface/web/app/agents/page.tsx +++ b/src/interface/web/app/agents/page.tsx @@ -4,107 +4,32 @@ import styles from "./agents.module.css"; import useSWR from "swr"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useState } from "react"; import { useAuthenticatedData, UserProfile, ModelOptions, useUserConfig, - UserConfig, SubscriptionStates, } from "../common/auth"; -import { Button } from "@/components/ui/button"; -import { - PaperPlaneTilt, - Lightning, - Plus, - Circle, - Info, - Check, - ShieldWarning, - Lock, - Book, - Brain, - Waveform, - CaretUpDown, - Globe, - LockOpen, - FloppyDisk, - DotsThreeVertical, - Pencil, - Trash, - ArrowRight, - ArrowLeft, -} from "@phosphor-icons/react"; -import { set, z, ZodError } from "zod"; -import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; -import { - Dialog, - DialogContent, - DialogFooter, - DialogHeader, - DialogTrigger, -} from "@/components/ui/dialog"; +import { Lightning, Plus } from "@phosphor-icons/react"; +import { z } from "zod"; +import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "@/components/ui/dialog"; import LoginPrompt from "../components/loginPrompt/loginPrompt"; import { InlineLoading } from "../components/loading/loading"; import SidePanel from "../components/sidePanel/chatHistorySidePanel"; -import { - getAvailableIcons, - getIconForSlashCommand, - getIconFromIconName, -} from "../common/iconUtils"; -import { convertColorToTextClass, tailwindColors } from "../common/colorUtils"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { useIsMobileWidth } from "../common/utils"; +import { + AgentCard, + EditAgentSchema, + AgentModificationForm, +} from "@/app/components/agentCard/agentCard"; -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { useForm, UseFormReturn } from "react-hook-form"; -import { Input } from "@/components/ui/input"; +import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; -import { Textarea } from "@/components/ui/textarea"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { DialogTitle } from "@radix-ui/react-dialog"; -import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; -import { cn } from "@/lib/utils"; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, -} from "@/components/ui/command"; -import { uploadDataForIndexing } from "../common/chatFunctions"; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from "@/components/ui/alert-dialog"; -import { Progress } from "@/components/ui/progress"; -import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; -import ShareLink from "../components/shareLink/shareLink"; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; export interface AgentData { slug: string; @@ -121,49 +46,6 @@ export interface AgentData { output_modes: string[]; } -async function openChat(slug: string, userData: UserProfile | null) { - const unauthenticatedRedirectUrl = `/login?next=/agents?agent=${slug}`; - if (!userData) { - window.location.href = unauthenticatedRedirectUrl; - return; - } - - const response = await fetch(`/api/chat/sessions?agent_slug=${encodeURIComponent(slug)}`, { - method: "POST", - }); - const data = await response.json(); - if (response.status == 200) { - window.location.href = `/chat?conversationId=${data.conversation_id}`; - } else if (response.status == 403 || response.status == 401) { - window.location.href = unauthenticatedRedirectUrl; - } else { - alert("Failed to start chat session"); - } -} - -function Badge(props: { icon: JSX.Element; text?: string; hoverText?: string }) { - // Always convert text to proper case (e.g., "public" -> "Public") - const displayBadgeText = props.text?.replace(/^\w/, (c) => c.toUpperCase()) || ""; - - return ( - - - -
{props.hoverText || displayBadgeText}
-
- -
-
{props.icon}
- {displayBadgeText && displayBadgeText.length > 0 && ( -
{displayBadgeText}
- )} -
-
-
-
- ); -} - const agentsFetcher = () => window .fetch("/api/agents") @@ -173,1150 +55,6 @@ const agentsFetcher = () => // A generic fetcher function that uses the fetch API to make a request to a given URL and returns the response as JSON. const fetcher = (url: string) => fetch(url).then((res) => res.json()); -interface AgentCardProps { - data: AgentData; - userProfile: UserProfile | null; - isMobileWidth: boolean; - editCard: boolean; - filesOptions: string[]; - modelOptions: ModelOptions[]; - selectedChatModelOption: string; - isSubscribed: boolean; - setAgentChangeTriggered: (value: boolean) => void; - agentSlug: string; - inputToolOptions: { [key: string]: string }; - outputModeOptions: { [key: string]: string }; -} - -function AgentCard(props: AgentCardProps) { - const [showModal, setShowModal] = useState(props.agentSlug === props.data.slug); - const [showLoginPrompt, setShowLoginPrompt] = useState(false); - const [errors, setErrors] = useState(null); - - let lockIcon = ; - let privacyHoverText = "Private agents are only visible to you."; - - if (props.data.privacy_level === "public") { - lockIcon = ; - privacyHoverText = "Public agents are visible to everyone."; - } else if (props.data.privacy_level === "protected") { - lockIcon = ; - privacyHoverText = "Protected agents are visible to anyone with a direct link."; - } - - const userData = props.userProfile; - - const form = useForm>({ - resolver: zodResolver(EditAgentSchema), - defaultValues: { - name: props.data.name, - persona: props.data.persona, - color: props.data.color, - icon: props.data.icon, - privacy_level: props.data.privacy_level, - chat_model: props.data.chat_model, - files: props.data.files, - input_tools: props.data.input_tools, - output_modes: props.data.output_modes, - }, - }); - - useEffect(() => { - form.reset({ - name: props.data.name, - persona: props.data.persona, - color: props.data.color, - icon: props.data.icon, - privacy_level: props.data.privacy_level, - chat_model: props.data.chat_model, - files: props.data.files, - input_tools: props.data.input_tools, - output_modes: props.data.output_modes, - }); - }, [props.data]); - - if (showModal) { - window.history.pushState( - {}, - `Khoj AI - Agent ${props.data.slug}`, - `/agents?agent=${props.data.slug}`, - ); - } - - const onSubmit = (values: z.infer) => { - let agentsApiUrl = `/api/agents`; - let method = props.editCard ? "PATCH" : "POST"; - - let valuesToSend: any = values; - - if (props.editCard) { - valuesToSend = { ...values, slug: props.data.slug }; - } - - fetch(agentsApiUrl, { - method: method, - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(valuesToSend), - }) - .then((response) => { - if (response.status === 200) { - form.reset(); - setShowModal(false); - setErrors(null); - props.setAgentChangeTriggered(true); - } else { - response.json().then((data) => { - console.error(data); - form.clearErrors(); - if (data.error) { - setErrors(data.error); - } - }); - } - }) - .catch((error) => { - console.error("Error:", error); - setErrors(error); - form.clearErrors(); - }); - }; - - const stylingString = convertColorToTextClass(props.data.color); - - function makeBadgeFooter() { - return ( -
- {props.editCard && ( - - )} - {props.data.files && props.data.files.length > 0 && ( - } - text={`knowledge`} - hoverText={`The agent has a custom knowledge base with ${props.data.files.length} documents. It can use them to give you answers.`} - /> - )} - } - text={props.data.chat_model} - hoverText={`The agent uses the ${props.data.chat_model} model to chat with you.`} - /> - {props.data.output_modes.map((outputMode) => ( - - ))} - {props.data.input_tools.map((inputTool) => ( - - ))} -
- ); - } - - return ( - - {showLoginPrompt && ( - - )} - - - { - setShowModal(!showModal); - window.history.pushState({}, `Khoj AI - Agents`, `/agents`); - }} - > - -
- {getIconFromIconName(props.data.icon, props.data.color)} - {props.data.name} -
-
-
- {props.editCard && ( -
- - - - - - - {props.editCard && - props.data.privacy_level !== "private" && ( - - )} - {props.data.creator === userData?.username && ( - - )} - - -
- )} -
- {props.userProfile ? ( - - ) : ( - - )} -
-
- {props.editCard ? ( - - - Edit {props.data.name} - - - - ) : ( - - -
- {getIconFromIconName(props.data.icon, props.data.color)} -

{props.data.name}

-
-
-
- {props.data.persona} -
-
- {makeBadgeFooter()} -
- - - -
- )} -
-
-
- -
- -
-
- -
{makeBadgeFooter()}
-
-
- ); -} - -const EditAgentSchema = z.object({ - name: z.string({ required_error: "Name is required" }).min(1, "Name is required"), - persona: z - .string({ required_error: "Personality is required" }) - .min(1, "Personality is required"), - color: z.string({ required_error: "Color is required" }).min(1, "Color is required"), - icon: z.string({ required_error: "Icon is required" }).min(1, "Icon is required"), - privacy_level: z - .string({ required_error: "Privacy level is required" }) - .min(1, "Privacy level is required"), - chat_model: z - .string({ required_error: "Chat model is required" }) - .min(1, "Chat model is required"), - files: z.array(z.string()).default([]).optional(), - input_tools: z.array(z.string()).default([]).optional(), - output_modes: z.array(z.string()).default([]).optional(), -}); - -interface AgentModificationFormProps { - form: UseFormReturn>; - onSubmit: (values: z.infer) => void; - userConfig?: UserConfig; - create?: boolean; - errors?: string | null; - modelOptions: ModelOptions[]; - filesOptions: string[]; - inputToolOptions: { [key: string]: string }; - outputModeOptions: { [key: string]: string }; - slug?: string; - isSubscribed: boolean; -} - -function AgentModificationForm(props: AgentModificationFormProps) { - const [isSaving, setIsSaving] = useState(false); - - const iconOptions = getAvailableIcons(); - const colorOptions = tailwindColors; - const colorOptionClassName = convertColorToTextClass(props.form.getValues("color")); - - const [isDragAndDropping, setIsDragAndDropping] = useState(false); - const [warning, setWarning] = useState(null); - const [error, setError] = useState(null); - const [uploading, setUploading] = useState(false); - const [progressValue, setProgressValue] = useState(0); - const [uploadedFiles, setUploadedFiles] = useState([]); - const [allFileOptions, setAllFileOptions] = useState([]); - const [currentStep, setCurrentStep] = useState(0); - - const [showSubscribeDialog, setShowSubscribeDialog] = useState(true); - - const privacyOptions = ["public", "private", "protected"]; - - const basicFields = [ - { name: "name", label: "Name" }, - { name: "persona", label: "Personality" }, - ]; - - const advancedFields = [ - { name: "files", label: "Knowledge Base" }, - { name: "input_tools", label: "Input Tools" }, - { name: "output_modes", label: "Output Modes" }, - ]; - - const customizationFields = [ - { name: "color", label: "Color" }, - { name: "icon", label: "Icon" }, - { name: "chat_model", label: "Chat Model" }, - { name: "privacy_level", label: "Privacy Level" }, - ]; - - const formGroups = [ - { fields: basicFields, label: "Basic Settings" }, - { fields: customizationFields, label: "Customization & Access" }, - { fields: advancedFields, label: "Advanced Settings" }, - ]; - - const fileInputRef = useRef(null); - - useEffect(() => { - if (!uploading) { - setProgressValue(0); - } - - if (uploading) { - const interval = setInterval(() => { - setProgressValue((prev) => { - const increment = Math.floor(Math.random() * 5) + 1; // Generates a random number between 1 and 5 - const nextValue = prev + increment; - return nextValue < 100 ? nextValue : 100; // Ensures progress does not exceed 100 - }); - }, 800); - return () => clearInterval(interval); - } - }, [uploading]); - - useEffect(() => { - const currentFiles = props.form.getValues("files") || []; - const fileOptions = props.filesOptions || []; - const concatenatedFiles = [...currentFiles, ...fileOptions]; - const fullAllFileOptions = [...allFileOptions, ...concatenatedFiles]; - const dedupedAllFileOptions = Array.from(new Set(fullAllFileOptions)); - setAllFileOptions(dedupedAllFileOptions); - }, []); - - useEffect(() => { - if (uploadedFiles.length > 0) { - handleAgentFileChange(uploadedFiles); - setAllFileOptions((prev) => [...prev, ...uploadedFiles]); - } - }, [uploadedFiles]); - - useEffect(() => { - if (props.errors) { - setIsSaving(false); - } - }, [props.errors]); - - function handleDragOver(event: React.DragEvent) { - event.preventDefault(); - setIsDragAndDropping(true); - } - - function handleDragLeave(event: React.DragEvent) { - event.preventDefault(); - setIsDragAndDropping(false); - } - - function handleDragAndDropFiles(event: React.DragEvent) { - event.preventDefault(); - setIsDragAndDropping(false); - - if (!event.dataTransfer.files) return; - - uploadFiles(event.dataTransfer.files); - } - - function uploadFiles(files: FileList) { - uploadDataForIndexing(files, setWarning, setUploading, setError, setUploadedFiles); - } - - function openFileInput() { - if (fileInputRef && fileInputRef.current) { - fileInputRef.current.click(); - } - } - - function handleFileChange(event: React.ChangeEvent) { - if (!event.target.files) return; - - uploadFiles(event.target.files); - } - - const handleNext = (event: React.MouseEvent) => { - event.preventDefault(); - if (currentStep < formGroups.length - 1) { - setCurrentStep(currentStep + 1); - } - }; - - const handlePrevious = (event: React.MouseEvent) => { - event.preventDefault(); - if (currentStep > 0) { - setCurrentStep(currentStep - 1); - } - }; - - const handleSubmit = (values: any) => { - props.onSubmit(values); - setIsSaving(true); - }; - - const handleAgentFileChange = (files: string[]) => { - for (const file of files) { - const currentFiles = props.form.getValues("files") || []; - const newFiles = currentFiles.includes(file) - ? currentFiles.filter((item) => item !== file) - : [...currentFiles, file]; - props.form.setValue("files", newFiles); - } - }; - - const areRequiredFieldsCompletedForCurrentStep = (formGroup: { - fields: { name: string }[]; - }) => { - try { - EditAgentSchema.parse(props.form.getValues()); - return true; - } catch (error) { - const errors: { [key: string]: string } = (error as ZodError).errors.reduce( - (acc: any, curr: any) => { - acc[curr.path[0]] = curr.message; - return acc; - }, - {}, - ); - - for (const field of formGroup.fields) { - if (errors[field.name]) { - return false; - } - } - - return true; - } - }; - - if (!props.isSubscribed && showSubscribeDialog) { - return ( - - - - Upgrade to Futurist - - - You need to be a Futurist subscriber to create more agents.{" "} - Upgrade now. - - - { - setShowSubscribeDialog(false); - }} - > - Cancel - - { - window.location.href = "/settings"; - }} - > - Continue - - - - - ); - } - - const renderFormField = (fieldName: string) => { - switch (fieldName) { - case "name": - return ( - ( - - Name - - What should this agent be called? Pick something descriptive & - memorable. - - - - - - - )} - /> - ); - case "chat_model": - return ( - ( - - Chat Model - - Which large language model should this agent use? - - - - - )} - /> - ); - case "privacy_level": - return ( - ( - - -
Privacy Level
-
- - - - - - - Private: only visible to you. -
- Protected: visible to anyone with a link. -
- Public: visible to everyone. -
- All public agents will be reviewed by us before they are - launched. -
-
-
- - -
- )} - /> - ); - case "color": - return ( - ( - - Color - Choose a color for your agent. - - - - )} - /> - ); - case "icon": - return ( - ( - - Icon - Choose an icon for your agent. - - - - )} - /> - ); - case "persona": - return ( - ( - - Personality - - What is the personality, thought process, or tuning of this - agent? Get creative; this is how you can influence the agent - constitution. - - -