mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-12 16:15:14 +00:00
Merge pull request #2536 from arc53/better-dropdown-prompt
feat: better prompt dropdown
This commit is contained in:
10 files changed
+311
-153
No files matched your search
@@ -1523,7 +1523,7 @@ function AddPromptModal({
|
||||
onClose();
|
||||
setNewPromptName('');
|
||||
setNewPromptContent('');
|
||||
onSelect?.(newPromptName, newPrompt.id, newPromptContent);
|
||||
onSelect?.(newPromptName, newPrompt.id, 'private');
|
||||
} catch (error) {
|
||||
console.error('Error adding prompt:', error);
|
||||
}
|
||||
|
||||
@@ -481,6 +481,11 @@
|
||||
"addDescription": "Füge deinen benutzerdefinierten Prompt hinzu und speichere ihn in DocsGPT",
|
||||
"editPrompt": "Prompt bearbeiten",
|
||||
"editDescription": "Bearbeite deinen benutzerdefinierten Prompt und speichere ihn in DocsGPT",
|
||||
"viewPrompt": "Prompt ansehen",
|
||||
"viewDescription": "Integrierte Prompts können nicht bearbeitet werden. Dupliziere ihn, um eine eigene bearbeitbare Kopie zu erstellen.",
|
||||
"duplicate": "Duplizieren",
|
||||
"duplicatePrompt": "Prompt duplizieren",
|
||||
"duplicateDescription": "Du bearbeitest eine Kopie von „{{name}}“ – speichere sie als eigenen Prompt",
|
||||
"promptName": "Prompt-Name",
|
||||
"promptText": "Prompt-Text",
|
||||
"save": "Speichern",
|
||||
|
||||
@@ -574,6 +574,11 @@
|
||||
"addDescription": "Add your custom prompt and save it to DocsGPT",
|
||||
"editPrompt": "Edit Prompt",
|
||||
"editDescription": "Edit your custom prompt and save it to DocsGPT",
|
||||
"viewPrompt": "View Prompt",
|
||||
"viewDescription": "Built-in prompts can't be edited. Duplicate to create your own editable copy.",
|
||||
"duplicate": "Duplicate",
|
||||
"duplicatePrompt": "Duplicate Prompt",
|
||||
"duplicateDescription": "You're editing a copy of \"{{name}}\" — save it as your own prompt",
|
||||
"promptName": "Prompt Name",
|
||||
"promptText": "Prompt Text",
|
||||
"save": "Save",
|
||||
|
||||
@@ -499,6 +499,11 @@
|
||||
"addDescription": "Agrega tu prompt personalizado y guárdalo en DocsGPT",
|
||||
"editPrompt": "Editar Prompt",
|
||||
"editDescription": "Edita tu prompt personalizado y guárdalo en DocsGPT",
|
||||
"viewPrompt": "Ver Prompt",
|
||||
"viewDescription": "Los prompts integrados no se pueden editar. Duplícalo para crear tu propia copia editable.",
|
||||
"duplicate": "Duplicar",
|
||||
"duplicatePrompt": "Duplicar Prompt",
|
||||
"duplicateDescription": "Estás editando una copia de \"{{name}}\"; guárdala como tu propio prompt",
|
||||
"promptName": "Nombre del Prompt",
|
||||
"promptText": "Texto del Prompt",
|
||||
"save": "Guardar",
|
||||
|
||||
@@ -499,6 +499,11 @@
|
||||
"addDescription": "カスタムプロンプトを追加して DocsGPT に保存します",
|
||||
"editPrompt": "プロンプトを編集",
|
||||
"editDescription": "カスタムプロンプトを編集して DocsGPT に保存します",
|
||||
"viewPrompt": "プロンプトを表示",
|
||||
"viewDescription": "組み込みプロンプトは編集できません。複製して編集可能なコピーを作成してください。",
|
||||
"duplicate": "複製",
|
||||
"duplicatePrompt": "プロンプトを複製",
|
||||
"duplicateDescription": "「{{name}}」のコピーを編集中です。自分のプロンプトとして保存してください",
|
||||
"promptName": "プロンプト名",
|
||||
"promptText": "プロンプトのテキスト",
|
||||
"save": "保存",
|
||||
|
||||
@@ -499,6 +499,11 @@
|
||||
"addDescription": "Добавьте свой собственный промпт и сохраните его в DocsGPT",
|
||||
"editPrompt": "Редактировать промпт",
|
||||
"editDescription": "Отредактируйте свой промпт и сохраните его в DocsGPT",
|
||||
"viewPrompt": "Просмотр промпта",
|
||||
"viewDescription": "Встроенные промпты нельзя редактировать. Дублируйте, чтобы создать свою редактируемую копию.",
|
||||
"duplicate": "Дублировать",
|
||||
"duplicatePrompt": "Дублировать промпт",
|
||||
"duplicateDescription": "Вы редактируете копию «{{name}}» — сохраните её как свой промпт",
|
||||
"promptName": "Название промпта",
|
||||
"promptText": "Текст промпта",
|
||||
"save": "Сохранить",
|
||||
|
||||
@@ -499,6 +499,11 @@
|
||||
"addDescription": "新增自定義提示並儲存到 DocsGPT",
|
||||
"editPrompt": "編輯提示",
|
||||
"editDescription": "編輯自定義提示並儲存到 DocsGPT",
|
||||
"viewPrompt": "檢視提示",
|
||||
"viewDescription": "內建提示無法編輯。建立副本後即可自行編輯。",
|
||||
"duplicate": "建立副本",
|
||||
"duplicatePrompt": "建立提示副本",
|
||||
"duplicateDescription": "正在編輯「{{name}}」的副本,儲存後即為您自己的提示",
|
||||
"promptName": "提示名稱",
|
||||
"promptText": "提示文字",
|
||||
"save": "儲存",
|
||||
|
||||
@@ -499,6 +499,11 @@
|
||||
"addDescription": "添加自定义提示并保存到 DocsGPT",
|
||||
"editPrompt": "编辑提示",
|
||||
"editDescription": "编辑自定义提示并保存到 DocsGPT",
|
||||
"viewPrompt": "查看提示",
|
||||
"viewDescription": "内置提示无法编辑。复制后可创建您自己的可编辑副本。",
|
||||
"duplicate": "创建副本",
|
||||
"duplicatePrompt": "创建提示副本",
|
||||
"duplicateDescription": "正在编辑“{{name}}”的副本,保存后即为您自己的提示",
|
||||
"promptName": "提示名称",
|
||||
"promptText": "提示文本",
|
||||
"save": "保存",
|
||||
|
||||
@@ -175,6 +175,7 @@ type PromptTextareaProps = {
|
||||
value: string;
|
||||
onChange: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
||||
ariaLabel: string;
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
function PromptTextarea({
|
||||
@@ -182,6 +183,7 @@ function PromptTextarea({
|
||||
value,
|
||||
onChange,
|
||||
ariaLabel,
|
||||
readOnly = false,
|
||||
}: PromptTextareaProps) {
|
||||
const [scrollOffsets, setScrollOffsets] = React.useState({ top: 0, left: 0 });
|
||||
const highlightedValue = React.useMemo(
|
||||
@@ -220,6 +222,7 @@ function PromptTextarea({
|
||||
onScroll={handleScroll}
|
||||
placeholder=" "
|
||||
aria-label={ariaLabel}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -298,6 +301,7 @@ function AddPrompt({
|
||||
newPromptContent,
|
||||
setNewPromptContent,
|
||||
disableSave,
|
||||
duplicateSourceName,
|
||||
}: {
|
||||
setModalState: (state: ActiveState) => void;
|
||||
handleAddPrompt?: () => void;
|
||||
@@ -306,6 +310,7 @@ function AddPrompt({
|
||||
newPromptContent: string;
|
||||
setNewPromptContent: (content: string) => void;
|
||||
disableSave: boolean;
|
||||
duplicateSourceName?: string | null;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const systemVariableOptions = React.useMemo(
|
||||
@@ -317,10 +322,16 @@ function AddPrompt({
|
||||
return (
|
||||
<div>
|
||||
<p className="mb-1 text-xl font-semibold text-[#2B2B2B] dark:text-white">
|
||||
{t('modals.prompts.addPrompt')}
|
||||
{duplicateSourceName
|
||||
? t('modals.prompts.duplicatePrompt')
|
||||
: t('modals.prompts.addPrompt')}
|
||||
</p>
|
||||
<p className="dark:text-muted-foreground mb-6 text-sm text-[#6B6B6B]">
|
||||
{t('modals.prompts.addDescription')}
|
||||
{duplicateSourceName
|
||||
? t('modals.prompts.duplicateDescription', {
|
||||
name: duplicateSourceName,
|
||||
})
|
||||
: t('modals.prompts.addDescription')}
|
||||
</p>
|
||||
<div>
|
||||
<Input
|
||||
@@ -433,6 +444,7 @@ function EditPrompt({
|
||||
setEditPromptContent,
|
||||
currentPromptEdit,
|
||||
disableSave,
|
||||
onDuplicate,
|
||||
}: {
|
||||
setModalState: (state: ActiveState) => void;
|
||||
handleEditPrompt?: (id: string, type: string) => void;
|
||||
@@ -442,6 +454,7 @@ function EditPrompt({
|
||||
setEditPromptContent: (content: string) => void;
|
||||
currentPromptEdit: { name: string; id: string; type: string };
|
||||
disableSave: boolean;
|
||||
onDuplicate?: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const systemVariableOptions = React.useMemo(
|
||||
@@ -449,14 +462,23 @@ function EditPrompt({
|
||||
[t],
|
||||
);
|
||||
const toolVariables = useToolVariables();
|
||||
const isReadOnly = currentPromptEdit.type === 'public';
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p className="mb-1 text-xl font-semibold text-[#2B2B2B] dark:text-white">
|
||||
{t('modals.prompts.editPrompt')}
|
||||
{t(
|
||||
isReadOnly
|
||||
? 'modals.prompts.viewPrompt'
|
||||
: 'modals.prompts.editPrompt',
|
||||
)}
|
||||
</p>
|
||||
<p className="dark:text-muted-foreground mb-6 text-sm text-[#6B6B6B]">
|
||||
{t('modals.prompts.editDescription')}
|
||||
{t(
|
||||
isReadOnly
|
||||
? 'modals.prompts.viewDescription'
|
||||
: 'modals.prompts.editDescription',
|
||||
)}
|
||||
</p>
|
||||
<div>
|
||||
<Input
|
||||
@@ -466,6 +488,7 @@ function EditPrompt({
|
||||
value={editPromptName}
|
||||
onChange={(e) => setEditPromptName(e.target.value)}
|
||||
labelBgClassName="bg-card"
|
||||
disabled={isReadOnly}
|
||||
/>
|
||||
|
||||
<div className="relative w-full">
|
||||
@@ -474,6 +497,7 @@ function EditPrompt({
|
||||
value={editPromptContent}
|
||||
onChange={(e) => setEditPromptContent(e.target.value)}
|
||||
ariaLabel={t('prompts.textAriaLabel')}
|
||||
readOnly={isReadOnly}
|
||||
/>
|
||||
<label
|
||||
htmlFor="edit-prompt-content"
|
||||
@@ -486,36 +510,38 @@ function EditPrompt({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center sm:gap-4">
|
||||
<p className="flex flex-col text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<span className="font-bold">
|
||||
{t('modals.prompts.variablesLabel')}
|
||||
</span>
|
||||
<span className="text-muted-foreground text-xs font-medium">
|
||||
{t('modals.prompts.variablesDescription')}
|
||||
</span>
|
||||
</p>
|
||||
{!isReadOnly && (
|
||||
<div className="mt-6 flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center sm:gap-4">
|
||||
<p className="flex flex-col text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<span className="font-bold">
|
||||
{t('modals.prompts.variablesLabel')}
|
||||
</span>
|
||||
<span className="text-muted-foreground text-xs font-medium">
|
||||
{t('modals.prompts.variablesDescription')}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2 sm:gap-3">
|
||||
<VariableMenu
|
||||
options={systemVariableOptions}
|
||||
label={t('modals.prompts.systemVariablesDropdownLabel')}
|
||||
textareaId="edit-prompt-content"
|
||||
content={editPromptContent}
|
||||
setContent={setEditPromptContent}
|
||||
triggerClassName="w-[140px] sm:w-[185px]"
|
||||
/>
|
||||
<div className="flex flex-wrap items-center gap-2 sm:gap-3">
|
||||
<VariableMenu
|
||||
options={systemVariableOptions}
|
||||
label={t('modals.prompts.systemVariablesDropdownLabel')}
|
||||
textareaId="edit-prompt-content"
|
||||
content={editPromptContent}
|
||||
setContent={setEditPromptContent}
|
||||
triggerClassName="w-[140px] sm:w-[185px]"
|
||||
/>
|
||||
|
||||
<VariableMenu
|
||||
options={toolVariables}
|
||||
label="Tool Variables"
|
||||
textareaId="edit-prompt-content"
|
||||
content={editPromptContent}
|
||||
setContent={setEditPromptContent}
|
||||
triggerClassName="w-[140px] sm:w-[171px]"
|
||||
/>
|
||||
<VariableMenu
|
||||
options={toolVariables}
|
||||
label="Tool Variables"
|
||||
textareaId="edit-prompt-content"
|
||||
content={editPromptContent}
|
||||
setContent={setEditPromptContent}
|
||||
triggerClassName="w-[140px] sm:w-[171px]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-4 flex flex-col justify-between gap-4 text-sm sm:flex-row sm:gap-0">
|
||||
<div className="flex justify-start">
|
||||
<Link
|
||||
@@ -546,26 +572,37 @@ function EditPrompt({
|
||||
{t('modals.prompts.cancel')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
handleEditPrompt &&
|
||||
handleEditPrompt(currentPromptEdit.id, currentPromptEdit.type);
|
||||
}}
|
||||
className="h-auto rounded-3xl px-6 py-2 text-sm font-medium text-white"
|
||||
disabled={
|
||||
currentPromptEdit.type === 'public' ||
|
||||
disableSave ||
|
||||
!editPromptName
|
||||
}
|
||||
title={
|
||||
disableSave && editPromptName
|
||||
? t('modals.prompts.nameExists')
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{t('modals.prompts.save')}
|
||||
</Button>
|
||||
{isReadOnly ? (
|
||||
onDuplicate && (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={onDuplicate}
|
||||
className="h-auto rounded-3xl px-6 py-2 text-sm font-medium text-white"
|
||||
>
|
||||
{t('modals.prompts.duplicate')}
|
||||
</Button>
|
||||
)
|
||||
) : (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
handleEditPrompt &&
|
||||
handleEditPrompt(
|
||||
currentPromptEdit.id,
|
||||
currentPromptEdit.type,
|
||||
);
|
||||
}}
|
||||
className="h-auto rounded-3xl px-6 py-2 text-sm font-medium text-white"
|
||||
disabled={disableSave || !editPromptName}
|
||||
title={
|
||||
disableSave && editPromptName
|
||||
? t('modals.prompts.nameExists')
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{t('modals.prompts.save')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -588,6 +625,8 @@ export default function PromptsModal({
|
||||
currentPromptEdit,
|
||||
handleAddPrompt,
|
||||
handleEditPrompt,
|
||||
onDuplicate,
|
||||
duplicateSourceName,
|
||||
}: {
|
||||
existingPrompts: { name: string; id: string; type: string }[];
|
||||
modalState: ActiveState;
|
||||
@@ -609,53 +648,30 @@ export default function PromptsModal({
|
||||
};
|
||||
handleAddPrompt?: () => void;
|
||||
handleEditPrompt?: (id: string, type: string) => void;
|
||||
onDuplicate?: () => void;
|
||||
duplicateSourceName?: string | null;
|
||||
}) {
|
||||
const [disableSave, setDisableSave] = React.useState(true);
|
||||
const handlePromptNameChange = (edit: boolean, newName: string) => {
|
||||
if (edit) {
|
||||
const nameExists = existingPrompts.find(
|
||||
const disableSave = React.useMemo(() => {
|
||||
if (type === 'EDIT') {
|
||||
const nameExists = existingPrompts.some(
|
||||
(prompt) =>
|
||||
newName === prompt.name && prompt.id !== currentPromptEdit.id,
|
||||
prompt.name === editPromptName && prompt.id !== currentPromptEdit.id,
|
||||
);
|
||||
setDisableSave(
|
||||
!(
|
||||
newName &&
|
||||
!nameExists &&
|
||||
editPromptName &&
|
||||
editPromptContent.trim() !== ''
|
||||
),
|
||||
);
|
||||
setEditPromptName(newName);
|
||||
} else {
|
||||
const nameExists = existingPrompts.find(
|
||||
(prompt) => newName === prompt.name,
|
||||
);
|
||||
setDisableSave(
|
||||
!(newName && !nameExists && newPromptContent.trim() !== ''),
|
||||
);
|
||||
setNewPromptName(newName);
|
||||
return !editPromptName || nameExists || editPromptContent.trim() === '';
|
||||
}
|
||||
};
|
||||
|
||||
const handleContentChange = (edit: boolean, newContent: string) => {
|
||||
if (edit) {
|
||||
const nameValid =
|
||||
editPromptName &&
|
||||
!existingPrompts.find(
|
||||
(prompt) =>
|
||||
editPromptName === prompt.name &&
|
||||
prompt.id !== currentPromptEdit.id,
|
||||
);
|
||||
setDisableSave(!(nameValid && newContent.trim() !== ''));
|
||||
setEditPromptContent(newContent);
|
||||
} else {
|
||||
const nameValid =
|
||||
newPromptName &&
|
||||
!existingPrompts.find((prompt) => newPromptName === prompt.name);
|
||||
setDisableSave(!(nameValid && newContent.trim() !== ''));
|
||||
setNewPromptContent(newContent);
|
||||
}
|
||||
};
|
||||
const nameExists = existingPrompts.some(
|
||||
(prompt) => prompt.name === newPromptName,
|
||||
);
|
||||
return !newPromptName || nameExists || newPromptContent.trim() === '';
|
||||
}, [
|
||||
type,
|
||||
existingPrompts,
|
||||
editPromptName,
|
||||
editPromptContent,
|
||||
currentPromptEdit.id,
|
||||
newPromptName,
|
||||
newPromptContent,
|
||||
]);
|
||||
|
||||
let view;
|
||||
|
||||
@@ -665,10 +681,11 @@ export default function PromptsModal({
|
||||
setModalState={setModalState}
|
||||
handleAddPrompt={handleAddPrompt}
|
||||
newPromptName={newPromptName}
|
||||
setNewPromptName={handlePromptNameChange.bind(null, false)}
|
||||
setNewPromptName={setNewPromptName}
|
||||
newPromptContent={newPromptContent}
|
||||
setNewPromptContent={handleContentChange.bind(null, false)}
|
||||
setNewPromptContent={setNewPromptContent}
|
||||
disableSave={disableSave}
|
||||
duplicateSourceName={duplicateSourceName}
|
||||
/>
|
||||
);
|
||||
} else if (type === 'EDIT') {
|
||||
@@ -677,11 +694,12 @@ export default function PromptsModal({
|
||||
setModalState={setModalState}
|
||||
handleEditPrompt={handleEditPrompt}
|
||||
editPromptName={editPromptName}
|
||||
setEditPromptName={handlePromptNameChange.bind(null, true)}
|
||||
setEditPromptName={setEditPromptName}
|
||||
editPromptContent={editPromptContent}
|
||||
setEditPromptContent={handleContentChange.bind(null, true)}
|
||||
setEditPromptContent={setEditPromptContent}
|
||||
currentPromptEdit={currentPromptEdit}
|
||||
disableSave={disableSave}
|
||||
onDuplicate={onDuplicate}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
@@ -701,7 +719,15 @@ export default function PromptsModal({
|
||||
}
|
||||
}}
|
||||
hideTitle
|
||||
title={type === 'ADD' ? 'Add Prompt' : 'Edit Prompt'}
|
||||
title={
|
||||
type === 'ADD'
|
||||
? duplicateSourceName
|
||||
? 'Duplicate Prompt'
|
||||
: 'Add Prompt'
|
||||
: currentPromptEdit.type === 'public'
|
||||
? 'View Prompt'
|
||||
: 'Edit Prompt'
|
||||
}
|
||||
size="lg"
|
||||
mobileVariant="sheet"
|
||||
className="bg-card dark:bg-card w-[95vw] max-w-[650px] rounded-2xl px-4 py-4 sm:px-6 sm:py-6 md:max-w-[860px] md:px-8 md:py-6 lg:max-w-[980px]"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChevronDown, Pencil, Trash2 } from 'lucide-react';
|
||||
import { ChevronDown, Copy, Eye, Pencil, Trash2 } from 'lucide-react';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import { Button } from '../components/ui/button';
|
||||
import {
|
||||
Popover,
|
||||
PopoverAnchor,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '../components/ui/popover';
|
||||
@@ -57,6 +58,9 @@ export default function Prompts({
|
||||
type: '',
|
||||
});
|
||||
const [modalType, setModalType] = React.useState<'ADD' | 'EDIT'>('ADD');
|
||||
const [duplicateSource, setDuplicateSource] = React.useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [modalState, setModalState] = React.useState<ActiveState>('INACTIVE');
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
@@ -95,7 +99,7 @@ export default function Prompts({
|
||||
]);
|
||||
}
|
||||
setModalState('INACTIVE');
|
||||
onSelectPrompt(newPromptName, newPrompt.id, newPromptContent);
|
||||
onSelectPrompt(newPromptName, newPrompt.id, 'private');
|
||||
setNewPromptName('');
|
||||
setNewPromptContent('');
|
||||
} catch (error) {
|
||||
@@ -155,6 +159,62 @@ export default function Prompts({
|
||||
}
|
||||
};
|
||||
|
||||
const openEditModal = (prompt: {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}) => {
|
||||
setModalType('EDIT');
|
||||
setEditPromptName(prompt.name);
|
||||
setEditPromptContent('');
|
||||
handleFetchPromptContent(prompt.id);
|
||||
setCurrentPromptEdit({
|
||||
id: prompt.id,
|
||||
name: prompt.name,
|
||||
type: prompt.type,
|
||||
});
|
||||
setModalState('ACTIVE');
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const generateCopyName = (baseName: string) => {
|
||||
let candidate = `${baseName} copy`;
|
||||
let counter = 2;
|
||||
while (prompts.some((prompt) => prompt.name === candidate)) {
|
||||
candidate = `${baseName} copy ${counter}`;
|
||||
counter += 1;
|
||||
}
|
||||
return candidate;
|
||||
};
|
||||
|
||||
const handleDuplicatePrompt = async (prompt: {
|
||||
id: string;
|
||||
name: string;
|
||||
}) => {
|
||||
try {
|
||||
const response = await userService.getSinglePrompt(prompt.id, token);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch prompt content');
|
||||
}
|
||||
const promptContent = await response.json();
|
||||
setModalType('ADD');
|
||||
setDuplicateSource(prompt.name);
|
||||
setNewPromptName(generateCopyName(prompt.name));
|
||||
setNewPromptContent(promptContent.content);
|
||||
setModalState('ACTIVE');
|
||||
setOpen(false);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDuplicateFromModal = () => {
|
||||
setDuplicateSource(currentPromptEdit.name);
|
||||
setNewPromptName(generateCopyName(currentPromptEdit.name));
|
||||
setNewPromptContent(editPromptContent);
|
||||
setModalType('ADD');
|
||||
};
|
||||
|
||||
const handleSaveChanges = (id: string, type: string) => {
|
||||
userService
|
||||
.updatePrompt(
|
||||
@@ -196,8 +256,8 @@ export default function Prompts({
|
||||
});
|
||||
};
|
||||
|
||||
const triggerClassName = cn(
|
||||
'border-border bg-card text-foreground hover:bg-accent flex w-56 items-center justify-between rounded-3xl border px-5 py-3 text-sm',
|
||||
const pillClassName = cn(
|
||||
'border-border bg-card text-foreground hover:bg-accent flex w-56 items-stretch rounded-3xl border text-sm transition-colors',
|
||||
dropdownProps.className,
|
||||
);
|
||||
|
||||
@@ -210,28 +270,54 @@ export default function Prompts({
|
||||
</p>
|
||||
<div className="flex flex-row flex-wrap items-end justify-start gap-6">
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className={cn('h-auto justify-between', triggerClassName)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
'truncate',
|
||||
!selectedPrompt?.name && 'text-muted-foreground',
|
||||
)}
|
||||
<PopoverAnchor asChild>
|
||||
<div className={pillClassName}>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="focus-visible:ring-ring/50 flex min-w-0 flex-1 items-center rounded-l-3xl py-3 pl-5 text-left outline-none focus-visible:ring-[3px]"
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
'truncate',
|
||||
!selectedPrompt?.name && 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{selectedPrompt?.name || 'Select a prompt'}
|
||||
</span>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
{selectedPrompt?.id && selectedPrompt.type !== 'public' && (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openEditModal(selectedPrompt)}
|
||||
className="text-muted-foreground hover:bg-foreground/15 hover:text-foreground dark:hover:bg-foreground/20 focus-visible:ring-ring/50 mx-1 my-auto shrink-0 rounded-full p-1.5 transition-colors outline-none focus-visible:ring-[3px]"
|
||||
aria-label="Edit prompt"
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
</button>
|
||||
<div
|
||||
className="bg-border my-2.5 w-px shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen(!open)}
|
||||
className="text-muted-foreground hover:bg-foreground/15 hover:text-foreground dark:hover:bg-foreground/20 focus-visible:ring-ring/50 my-auto mr-2.5 ml-1 shrink-0 rounded-full p-1.5 transition-colors outline-none focus-visible:ring-[3px]"
|
||||
aria-label="Toggle prompt list"
|
||||
>
|
||||
{selectedPrompt?.name || 'Select a prompt'}
|
||||
</span>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
'text-muted-foreground ml-2 h-4 w-4 shrink-0 transition-transform',
|
||||
open && 'rotate-180',
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
'h-4 w-4 transition-transform',
|
||||
open && 'rotate-180',
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</PopoverAnchor>
|
||||
<PopoverContent
|
||||
align="start"
|
||||
className={cn(
|
||||
@@ -245,7 +331,7 @@ export default function Prompts({
|
||||
<CommandEmpty>No results found</CommandEmpty>
|
||||
{prompts.map((prompt) => {
|
||||
const isActive = selectedPrompt?.id === prompt.id;
|
||||
const canDelete = prompt.type !== 'public';
|
||||
const canModify = prompt.type !== 'public';
|
||||
return (
|
||||
<CommandItem
|
||||
key={prompt.id}
|
||||
@@ -258,31 +344,39 @@ export default function Prompts({
|
||||
>
|
||||
<span className="truncate">{prompt.name}</span>
|
||||
<div className="flex shrink-0 items-center gap-1">
|
||||
{prompt.type !== 'public' && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setModalType('EDIT');
|
||||
setEditPromptName(prompt.name);
|
||||
handleFetchPromptContent(prompt.id);
|
||||
setCurrentPromptEdit({
|
||||
id: prompt.id,
|
||||
name: prompt.name,
|
||||
type: prompt.type,
|
||||
});
|
||||
setModalState('ACTIVE');
|
||||
setOpen(false);
|
||||
}}
|
||||
className="h-auto w-auto rounded p-1"
|
||||
aria-label="Edit prompt"
|
||||
>
|
||||
<Pencil className="text-muted-foreground h-3.5 w-3.5" />
|
||||
</Button>
|
||||
)}
|
||||
{canDelete && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
openEditModal(prompt);
|
||||
}}
|
||||
className="group/btn hover:bg-foreground/15 dark:hover:bg-foreground/20 h-auto w-auto rounded p-1"
|
||||
aria-label={
|
||||
canModify ? 'Edit prompt' : 'View prompt'
|
||||
}
|
||||
>
|
||||
{canModify ? (
|
||||
<Pencil className="text-muted-foreground group-hover/btn:text-foreground h-3.5 w-3.5" />
|
||||
) : (
|
||||
<Eye className="text-muted-foreground group-hover/btn:text-foreground h-3.5 w-3.5" />
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDuplicatePrompt(prompt);
|
||||
}}
|
||||
className="group/btn hover:bg-foreground/15 dark:hover:bg-foreground/20 h-auto w-auto rounded p-1"
|
||||
aria-label="Duplicate prompt"
|
||||
>
|
||||
<Copy className="text-muted-foreground group-hover/btn:text-foreground h-3.5 w-3.5" />
|
||||
</Button>
|
||||
{canModify && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
@@ -291,10 +385,10 @@ export default function Prompts({
|
||||
e.stopPropagation();
|
||||
handleDeletePrompt(prompt.id);
|
||||
}}
|
||||
className="h-auto w-auto rounded p-1"
|
||||
className="group/btn hover:bg-destructive/15 dark:hover:bg-destructive/25 h-auto w-auto rounded p-1"
|
||||
aria-label="Delete prompt"
|
||||
>
|
||||
<Trash2 className="text-muted-foreground h-3.5 w-3.5" />
|
||||
<Trash2 className="text-muted-foreground group-hover/btn:text-destructive h-3.5 w-3.5" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -311,6 +405,7 @@ export default function Prompts({
|
||||
className="h-auto w-20 rounded-3xl border border-transparent py-3"
|
||||
onClick={() => {
|
||||
setModalType('ADD');
|
||||
setDuplicateSource(null);
|
||||
setModalState('ACTIVE');
|
||||
}}
|
||||
>
|
||||
@@ -336,6 +431,8 @@ export default function Prompts({
|
||||
currentPromptEdit={currentPromptEdit}
|
||||
handleAddPrompt={handleAddPrompt}
|
||||
handleEditPrompt={handleSaveChanges}
|
||||
onDuplicate={handleDuplicateFromModal}
|
||||
duplicateSourceName={duplicateSource}
|
||||
/>
|
||||
{promptToDelete && (
|
||||
<ConfirmationModal
|
||||
|
||||
Reference in new issue
Block a user