feat: add prompt management functionality with modal integration

This commit is contained in:
Siddhant Rai
2025-04-16 19:41:16 +05:30
parent da15cde49c
commit 2f9f428a2f
3 changed files with 258 additions and 5 deletions

View File

@@ -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<ActiveState>('INACTIVE');
const [agentDetails, setAgentDetails] = useState<ActiveState>('INACTIVE');
const [addPromptModal, setAddPromptModal] = useState<ActiveState>('INACTIVE');
const sourceAnchorButtonRef = useRef<HTMLButtonElement>(null);
const toolAnchorButtonRef = useRef<HTMLButtonElement>(null);
@@ -423,7 +425,10 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
contentSize="text-sm"
/>
</div>
<button className="w-20 flex-shrink-0 basis-full rounded-3xl border-2 border-solid border-violets-are-blue px-5 py-[11px] text-sm text-violets-are-blue transition-colors hover:bg-violets-are-blue hover:text-white sm:basis-auto">
<button
className="w-20 flex-shrink-0 basis-full rounded-3xl border-2 border-solid border-violets-are-blue px-5 py-[11px] text-sm text-violets-are-blue transition-colors hover:bg-violets-are-blue hover:text-white sm:basis-auto"
onClick={() => setAddPromptModal('ACTIVE')}
>
Add
</button>
</div>
@@ -510,6 +515,15 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
modalState={agentDetails}
setModalState={setAgentDetails}
/>
<AddPromptModal
prompts={prompts}
setPrompts={setPrompts}
isOpen={addPromptModal}
onClose={() => setAddPromptModal('INACTIVE')}
onSelect={(name: string, id: string, type: string) => {
setAgent({ ...agent, prompt_id: id });
}}
/>
</div>
);
}
@@ -517,7 +531,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
function AgentPreviewArea() {
const selectedAgent = useSelector(selectSelectedAgent);
return (
<div className="h-full w-full rounded-[30px] border border-[#F6F6F6] bg-white dark:border-[#7E7E7E] dark:bg-[#222327] max-[1024px]:min-h-[48rem]">
<div className="h-full w-full rounded-[30px] border border-[#F6F6F6] bg-white dark:border-[#7E7E7E] dark:bg-[#222327] max-[1180px]:h-[48rem]">
{selectedAgent?.id ? (
<div className="flex h-full w-full flex-col justify-end overflow-auto rounded-[30px]">
<AgentPreview />
@@ -533,3 +547,68 @@ function AgentPreviewArea() {
</div>
);
}
function AddPromptModal({
prompts,
setPrompts,
isOpen,
onClose,
onSelect,
}: {
prompts: Prompt[];
setPrompts?: React.Dispatch<React.SetStateAction<Prompt[]>>;
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 (
<PromptsModal
modalState={isOpen}
setModalState={onClose}
type="ADD"
existingPrompts={prompts}
newPromptName={newPromptName}
setNewPromptName={setNewPromptName}
newPromptContent={newPromptContent}
setNewPromptContent={setNewPromptContent}
editPromptName={''}
setEditPromptName={() => {}}
editPromptContent={''}
setEditPromptContent={() => {}}
currentPromptEdit={{ id: '', name: '', type: '' }}
handleAddPrompt={handleAddPrompt}
/>
);
}

View File

@@ -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<Prompt[]>(initialPrompts);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
setPrompts(initialPrompts);
}, [initialPrompts]);
const handleApiCall = async <T>(
apiCall: () => Promise<Response>,
errorMessage: string,
): Promise<T | null> => {
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<Prompt | null> => {
const newPromptData = await handleApiCall<PromptCreateResponse>(
() => 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<void> => {
const originalPrompts = [...prompts];
const updatedPrompts = prompts.filter(
(prompt) => prompt.id !== idToDelete,
);
setPrompts(updatedPrompts);
onPromptsUpdate(updatedPrompts);
const result = await handleApiCall<null>(
() => 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<string | null> => {
const promptDetails = await handleApiCall<PromptContentResponse>(
() => 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<boolean> => {
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,
};
};

View File

@@ -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 };