From 2f9f428a2fd9eebdf49d169fcc8646f09def03f5 Mon Sep 17 00:00:00 2001 From: Siddhant Rai Date: Wed, 16 Apr 2025 19:41:16 +0530 Subject: [PATCH] feat: add prompt management functionality with modal integration --- frontend/src/agents/NewAgent.tsx | 89 ++++++++++++- frontend/src/hooks/usePromptManager.ts | 168 +++++++++++++++++++++++++ frontend/src/models/misc.ts | 6 + 3 files changed, 258 insertions(+), 5 deletions(-) create mode 100644 frontend/src/hooks/usePromptManager.ts diff --git a/frontend/src/agents/NewAgent.tsx b/frontend/src/agents/NewAgent.tsx index 2f6c83c6..37466a86 100644 --- a/frontend/src/agents/NewAgent.tsx +++ b/frontend/src/agents/NewAgent.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef, useState } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { useNavigate, useParams } from 'react-router-dom'; import userService from '../api/services/userService'; @@ -9,13 +9,14 @@ import Dropdown from '../components/Dropdown'; import MultiSelectPopup, { OptionType } from '../components/MultiSelectPopup'; import AgentDetailsModal from '../modals/AgentDetailsModal'; import ConfirmationModal from '../modals/ConfirmationModal'; -import { ActiveState, Doc } from '../models/misc'; +import { ActiveState, Doc, Prompt } from '../models/misc'; import { + selectSelectedAgent, selectSourceDocs, selectToken, setSelectedAgent, - selectSelectedAgent, } from '../preferences/preferenceSlice'; +import PromptsModal from '../preferences/PromptsModal'; import { UserToolType } from '../settings/types'; import AgentPreview from './AgentPreview'; import { Agent } from './types'; @@ -62,6 +63,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) { const [deleteConfirmation, setDeleteConfirmation] = useState('INACTIVE'); const [agentDetails, setAgentDetails] = useState('INACTIVE'); + const [addPromptModal, setAddPromptModal] = useState('INACTIVE'); const sourceAnchorButtonRef = useRef(null); const toolAnchorButtonRef = useRef(null); @@ -423,7 +425,10 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) { contentSize="text-sm" /> - @@ -510,6 +515,15 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) { modalState={agentDetails} setModalState={setAgentDetails} /> + setAddPromptModal('INACTIVE')} + onSelect={(name: string, id: string, type: string) => { + setAgent({ ...agent, prompt_id: id }); + }} + /> ); } @@ -517,7 +531,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) { function AgentPreviewArea() { const selectedAgent = useSelector(selectSelectedAgent); return ( -
+
{selectedAgent?.id ? (
@@ -533,3 +547,68 @@ function AgentPreviewArea() {
); } + +function AddPromptModal({ + prompts, + setPrompts, + isOpen, + onClose, + onSelect, +}: { + prompts: Prompt[]; + setPrompts?: React.Dispatch>; + isOpen: ActiveState; + onClose: () => void; + onSelect?: (name: string, id: string, type: string) => void; +}) { + const token = useSelector(selectToken); + + const [newPromptName, setNewPromptName] = useState(''); + const [newPromptContent, setNewPromptContent] = useState(''); + + const handleAddPrompt = async () => { + try { + const response = await userService.createPrompt( + { + name: newPromptName, + content: newPromptContent, + }, + token, + ); + if (!response.ok) { + throw new Error('Failed to add prompt'); + } + const newPrompt = await response.json(); + if (setPrompts) { + setPrompts([ + ...prompts, + { name: newPromptName, id: newPrompt.id, type: 'private' }, + ]); + } + onClose(); + setNewPromptName(''); + setNewPromptContent(''); + onSelect?.(newPromptName, newPrompt.id, newPromptContent); + } catch (error) { + console.error(error); + } + }; + return ( + {}} + editPromptContent={''} + setEditPromptContent={() => {}} + currentPromptEdit={{ id: '', name: '', type: '' }} + handleAddPrompt={handleAddPrompt} + /> + ); +} diff --git a/frontend/src/hooks/usePromptManager.ts b/frontend/src/hooks/usePromptManager.ts new file mode 100644 index 00000000..28d1d6b5 --- /dev/null +++ b/frontend/src/hooks/usePromptManager.ts @@ -0,0 +1,168 @@ +import { useCallback, useState, useEffect } from 'react'; +import { useSelector } from 'react-redux'; + +import userService from '../api/services/userService'; +import { Prompt } from '../models/misc'; +import { selectToken } from '../preferences/preferenceSlice'; + +type UsePromptManagerProps = { + initialPrompts: Prompt[]; + onPromptSelect: (name: string, id: string, type: string) => void; + onPromptsUpdate: (updatedPrompts: Prompt[]) => void; +}; + +type PromptContentResponse = { + id: string; + name: string; + content: string; +}; + +type PromptCreateResponse = { + id: string; +}; + +export const usePromptManager = ({ + initialPrompts, + onPromptSelect, + onPromptsUpdate, +}: UsePromptManagerProps) => { + const token = useSelector(selectToken); + + const [prompts, setPrompts] = useState(initialPrompts); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + setPrompts(initialPrompts); + }, [initialPrompts]); + + const handleApiCall = async ( + apiCall: () => Promise, + errorMessage: string, + ): Promise => { + setIsLoading(true); + setError(null); + try { + const response = await apiCall(); + if (!response.ok) { + const errorData = await response.text(); + console.error(`${errorMessage}: ${response.status} ${errorData}`); + throw new Error(`${errorMessage} (Status: ${response.status})`); + } + const contentType = response.headers.get('content-type'); + if (contentType && contentType.includes('application/json')) { + return (await response.json()) as T; + } + return null; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + setError(message); + console.error(err); + return null; + } finally { + setIsLoading(false); + } + }; + + const addPrompt = useCallback( + async (name: string, content: string): Promise => { + const newPromptData = await handleApiCall( + () => userService.createPrompt({ name, content }, token), + 'Failed to add prompt', + ); + + if (newPromptData) { + const newPrompt: Prompt = { + name, + id: newPromptData.id, + type: 'private', + }; + const updatedPrompts = [...prompts, newPrompt]; + setPrompts(updatedPrompts); + onPromptsUpdate(updatedPrompts); + onPromptSelect(newPrompt.name, newPrompt.id, newPrompt.type); + return newPrompt; + } + return null; + }, + [token, prompts, onPromptsUpdate, onPromptSelect], + ); + + const deletePrompt = useCallback( + async (idToDelete: string): Promise => { + const originalPrompts = [...prompts]; + const updatedPrompts = prompts.filter( + (prompt) => prompt.id !== idToDelete, + ); + setPrompts(updatedPrompts); + onPromptsUpdate(updatedPrompts); + + const result = await handleApiCall( + () => userService.deletePrompt({ id: idToDelete }, token), + 'Failed to delete prompt', + ); + + if (result === null && error) { + setPrompts(originalPrompts); + onPromptsUpdate(originalPrompts); + } else { + if (updatedPrompts.length > 0) { + onPromptSelect( + updatedPrompts[0].name, + updatedPrompts[0].id, + updatedPrompts[0].type, + ); + } + } + }, + [token, prompts, onPromptsUpdate, onPromptSelect, error], + ); + + const fetchPromptContent = useCallback( + async (id: string): Promise => { + const promptDetails = await handleApiCall( + () => userService.getSinglePrompt(id, token), + 'Failed to fetch prompt content', + ); + return promptDetails ? promptDetails.content : null; + }, + [token], + ); + + const updatePrompt = useCallback( + async ( + id: string, + name: string, + content: string, + type: string, + ): Promise => { + const result = await handleApiCall<{ success: boolean }>( + () => userService.updatePrompt({ id, name, content }, token), + 'Failed to update prompt', + ); + + if (result?.success) { + const updatedPrompts = prompts.map((p) => + p.id === id ? { ...p, name, type } : p, + ); + setPrompts(updatedPrompts); + onPromptsUpdate(updatedPrompts); + onPromptSelect(name, id, type); + return true; + } + return false; + }, + [token, prompts, onPromptsUpdate, onPromptSelect], + ); + + return { + prompts, + isLoading, + error, + addPrompt, + deletePrompt, + fetchPromptContent, + updatePrompt, + setError, + }; +}; diff --git a/frontend/src/models/misc.ts b/frontend/src/models/misc.ts index 0d9c8931..57e100f3 100644 --- a/frontend/src/models/misc.ts +++ b/frontend/src/models/misc.ts @@ -21,6 +21,12 @@ export type GetDocsResponse = { nextCursor: string; }; +export type Prompt = { + name: string; + id: string; + type: string; +}; + export type PromptProps = { prompts: { name: string; id: string; type: string }[]; selectedPrompt: { name: string; id: string; type: string };