diff --git a/documentation/docs/advanced/admin.md b/documentation/docs/advanced/admin.md index 305741a8..c1d641e1 100644 --- a/documentation/docs/advanced/admin.md +++ b/documentation/docs/advanced/admin.md @@ -1,6 +1,8 @@ # Admin Panel > Describes the Khoj settings configurable via the admin panel +By default, you admin panel is available at `http://localhost:42110/server/admin/`. You can access the admin panel by logging in with your admin credentials (this would be your `KHOJ_ADMIN_EMAIL` and `KHOJ_ADMIN_PASSWORD`). The admin panel allows you to configure various settings for your Khoj server. + ## App Settings ### Agents Add all the agents you want to use for your different use-cases like Writer, Researcher, Therapist etc. diff --git a/documentation/docs/contributing/development.mdx b/documentation/docs/contributing/development.mdx index caefa3a9..b73f3948 100644 --- a/documentation/docs/contributing/development.mdx +++ b/documentation/docs/contributing/development.mdx @@ -102,7 +102,19 @@ sudo -u postgres createdb khoj --password -#### 3. Run +#### 3. Build the front-end assets + +```shell +cd src/interface/web/ +yarn install +yarn export +``` + +You can optionally use `yarn dev` to start a development server for the front-end which will be available at http://localhost:3000. This is especially useful if you're making changes to the front-end code, but not necessary for running Khoj. Note that streaming does not work on the dev server due to how it is handled with SSR in Next.js. + +Always run `yarn export` to test your front-end changes on http://localhost:42110 before creating a PR. + +#### 4. Run 1. Start Khoj ```bash khoj -vv diff --git a/manifest.json b/manifest.json index 9ba9a6e4..979c67c7 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "khoj", "name": "Khoj", - "version": "1.26.3", + "version": "1.26.4", "minAppVersion": "0.15.0", "description": "Your Second Brain", "author": "Khoj Inc.", diff --git a/src/interface/desktop/chat.html b/src/interface/desktop/chat.html index a6ae9a15..4c2258cc 100644 --- a/src/interface/desktop/chat.html +++ b/src/interface/desktop/chat.html @@ -326,7 +326,7 @@ entries.forEach(entry => { // If the element is in the viewport, fetch the remaining message and unobserve the element if (entry.isIntersecting) { - fetchRemainingChatMessages(chatHistoryUrl, headers); + fetchRemainingChatMessages(chatHistoryUrl, headers, chatBody.dataset.conversation_id, hostURL); observer.unobserve(entry.target); } }); @@ -342,7 +342,11 @@ new Date(chat_log.created), chat_log.onlineContext, chat_log.intent?.type, - chat_log.intent?.["inferred-queries"]); + chat_log.intent?.["inferred-queries"], + chatBody.dataset.conversationId ?? "", + hostURL, + ); + chatBody.appendChild(messageElement); // When the 4th oldest message is within viewing distance (~60% scrolled up) @@ -421,7 +425,7 @@ } } - function fetchRemainingChatMessages(chatHistoryUrl, headers) { + function fetchRemainingChatMessages(chatHistoryUrl, headers, conversationId, hostURL) { // Create a new IntersectionObserver let observer = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { @@ -435,7 +439,9 @@ new Date(chat_log.created), chat_log.onlineContext, chat_log.intent?.type, - chat_log.intent?.["inferred-queries"] + chat_log.intent?.["inferred-queries"], + chatBody.dataset.conversationId ?? "", + hostURL, ); entry.target.replaceWith(messageElement); diff --git a/src/interface/desktop/chatutils.js b/src/interface/desktop/chatutils.js index 5213979f..48fb72c3 100644 --- a/src/interface/desktop/chatutils.js +++ b/src/interface/desktop/chatutils.js @@ -189,11 +189,19 @@ function processOnlineReferences(referenceSection, onlineContext) { //same return numOnlineReferences; } -function renderMessageWithReference(message, by, context=null, dt=null, onlineContext=null, intentType=null, inferredQueries=null) { //same +function renderMessageWithReference(message, by, context=null, dt=null, onlineContext=null, intentType=null, inferredQueries=null, conversationId=null, hostURL=null) { let chatEl; if (intentType?.includes("text-to-image")) { let imageMarkdown = generateImageMarkdown(message, intentType, inferredQueries); chatEl = renderMessage(imageMarkdown, by, dt, null, false, "return"); + } else if (intentType === "excalidraw") { + let domain = hostURL ?? "https://app.khoj.dev/"; + + if (!domain.endsWith("/")) domain += "/"; + + let excalidrawMessage = `Hey, I'm not ready to show you diagrams yet here. But you can view it in the web app at ${domain}chat?conversationId=${conversationId}`; + + chatEl = renderMessage(excalidrawMessage, by, dt, null, false, "return"); } else { chatEl = renderMessage(message, by, dt, null, false, "return"); } @@ -312,7 +320,6 @@ function formatHTMLMessage(message, raw=false, willReplace=true) { //same } function createReferenceSection(references, createLinkerSection=false) { - console.log("linker data: ", createLinkerSection); let referenceSection = document.createElement('div'); referenceSection.classList.add("reference-section"); referenceSection.classList.add("collapsed"); @@ -417,7 +424,11 @@ function handleImageResponse(imageJson, rawResponse) { rawResponse += `![generated_image](${imageJson.image})`; } else if (imageJson.intentType === "text-to-image-v3") { rawResponse = `![](data:image/webp;base64,${imageJson.image})`; + } else if (imageJson.intentType === "excalidraw") { + const redirectMessage = `Hey, I'm not ready to show you diagrams yet here. But you can view it in the web app`; + rawResponse += redirectMessage; } + if (inferredQuery) { rawResponse += `\n\n**Inferred Query**:\n\n${inferredQuery}`; } diff --git a/src/interface/desktop/package.json b/src/interface/desktop/package.json index 4452f0aa..bbae8d2b 100644 --- a/src/interface/desktop/package.json +++ b/src/interface/desktop/package.json @@ -1,6 +1,6 @@ { "name": "Khoj", - "version": "1.26.3", + "version": "1.26.4", "description": "Your Second Brain", "author": "Khoj Inc. ", "license": "GPL-3.0-or-later", diff --git a/src/interface/emacs/khoj.el b/src/interface/emacs/khoj.el index 92fe9f77..cad27196 100644 --- a/src/interface/emacs/khoj.el +++ b/src/interface/emacs/khoj.el @@ -6,7 +6,7 @@ ;; Saba Imran ;; Description: Your Second Brain ;; Keywords: search, chat, ai, org-mode, outlines, markdown, pdf, image -;; Version: 1.26.3 +;; Version: 1.26.4 ;; Package-Requires: ((emacs "27.1") (transient "0.3.0") (dash "2.19.1")) ;; URL: https://github.com/khoj-ai/khoj/tree/master/src/interface/emacs diff --git a/src/interface/obsidian/manifest.json b/src/interface/obsidian/manifest.json index 9ba9a6e4..979c67c7 100644 --- a/src/interface/obsidian/manifest.json +++ b/src/interface/obsidian/manifest.json @@ -1,7 +1,7 @@ { "id": "khoj", "name": "Khoj", - "version": "1.26.3", + "version": "1.26.4", "minAppVersion": "0.15.0", "description": "Your Second Brain", "author": "Khoj Inc.", diff --git a/src/interface/obsidian/package.json b/src/interface/obsidian/package.json index 2714d8fb..46b74922 100644 --- a/src/interface/obsidian/package.json +++ b/src/interface/obsidian/package.json @@ -1,6 +1,6 @@ { "name": "Khoj", - "version": "1.26.3", + "version": "1.26.4", "description": "Your Second Brain", "author": "Debanjum Singh Solanky, Saba Imran ", "license": "GPL-3.0-or-later", diff --git a/src/interface/obsidian/src/chat_view.ts b/src/interface/obsidian/src/chat_view.ts index ed23bff0..408ce3a1 100644 --- a/src/interface/obsidian/src/chat_view.ts +++ b/src/interface/obsidian/src/chat_view.ts @@ -484,12 +484,13 @@ export class KhojChatView extends KhojPaneView { dt?: Date, intentType?: string, inferredQueries?: string[], + conversationId?: string, ) { if (!message) return; let chatMessageEl; - if (intentType?.includes("text-to-image")) { - let imageMarkdown = this.generateImageMarkdown(message, intentType, inferredQueries); + if (intentType?.includes("text-to-image") || intentType === "excalidraw") { + let imageMarkdown = this.generateImageMarkdown(message, intentType, inferredQueries, conversationId); chatMessageEl = this.renderMessage(chatEl, imageMarkdown, sender, dt); } else { chatMessageEl = this.renderMessage(chatEl, message, sender, dt); @@ -509,7 +510,7 @@ export class KhojChatView extends KhojPaneView { chatMessageBodyEl.appendChild(this.createReferenceSection(references)); } - generateImageMarkdown(message: string, intentType: string, inferredQueries?: string[]) { + generateImageMarkdown(message: string, intentType: string, inferredQueries?: string[], conversationId?: string): string { let imageMarkdown = ""; if (intentType === "text-to-image") { imageMarkdown = `![](data:image/png;base64,${message})`; @@ -517,6 +518,10 @@ export class KhojChatView extends KhojPaneView { imageMarkdown = `![](${message})`; } else if (intentType === "text-to-image-v3") { imageMarkdown = `![](data:image/webp;base64,${message})`; + } else if (intentType === "excalidraw") { + const domain = this.setting.khojUrl.endsWith("/") ? this.setting.khojUrl : `${this.setting.khojUrl}/`; + const redirectMessage = `Hey, I'm not ready to show you diagrams yet here. But you can view it in ${domain}chat?conversationId=${conversationId}`; + imageMarkdown = redirectMessage; } if (inferredQueries) { imageMarkdown += "\n\n**Inferred Query**:"; @@ -884,6 +889,7 @@ export class KhojChatView extends KhojPaneView { new Date(chatLog.created), chatLog.intent?.type, chatLog.intent?.["inferred-queries"], + chatBodyEl.dataset.conversationId ?? "", ); // push the user messages to the chat history if(chatLog.by === "you"){ @@ -1354,6 +1360,10 @@ export class KhojChatView extends KhojPaneView { rawResponse += `![generated_image](${imageJson.image})`; } else if (imageJson.intentType === "text-to-image-v3") { rawResponse = `![](data:image/webp;base64,${imageJson.image})`; + } else if (imageJson.intentType === "excalidraw") { + const domain = this.setting.khojUrl.endsWith("/") ? this.setting.khojUrl : `${this.setting.khojUrl}/`; + const redirectMessage = `Hey, I'm not ready to show you diagrams yet here. But you can view it in ${domain}`; + rawResponse += redirectMessage; } if (inferredQuery) { rawResponse += `\n\n**Inferred Query**:\n\n${inferredQuery}`; diff --git a/src/interface/obsidian/versions.json b/src/interface/obsidian/versions.json index 285cc0f4..4ed50e51 100644 --- a/src/interface/obsidian/versions.json +++ b/src/interface/obsidian/versions.json @@ -81,5 +81,6 @@ "1.26.0": "0.15.0", "1.26.1": "0.15.0", "1.26.2": "0.15.0", - "1.26.3": "0.15.0" + "1.26.3": "0.15.0", + "1.26.4": "0.15.0" } diff --git a/src/interface/web/app/agents/page.tsx b/src/interface/web/app/agents/page.tsx index 45b485fb..4a6c7619 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, + isUserSubscribed, } 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. - - -