mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-29 08:33:20 +00:00
feat: add prompt management functionality with modal integration
This commit is contained in:
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
168
frontend/src/hooks/usePromptManager.ts
Normal file
168
frontend/src/hooks/usePromptManager.ts
Normal 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,
|
||||
};
|
||||
};
|
||||
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user