mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-29 08:33:20 +00:00
775 lines
25 KiB
TypeScript
775 lines
25 KiB
TypeScript
import { ActiveState } from '../models/misc';
|
|
import Input from '../components/Input';
|
|
import { Link } from 'react-router-dom';
|
|
|
|
import React from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useSelector } from 'react-redux';
|
|
import WrapperModal from '../modals/WrapperModal';
|
|
import Dropdown from '../components/Dropdown';
|
|
import BookIcon from '../assets/book.svg';
|
|
import userService from '../api/services/userService';
|
|
import { selectToken } from '../preferences/preferenceSlice';
|
|
import { UserToolType } from '../settings/types';
|
|
|
|
const variablePattern = /(\{\{\s*[^{}]+\s*\}\}|\{(?!\{)[^{}]+\})/g;
|
|
|
|
const escapeHtml = (value: string) =>
|
|
value
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
|
|
const highlightPromptVariables = (text: string) => {
|
|
if (!text) {
|
|
return '​';
|
|
}
|
|
variablePattern.lastIndex = 0;
|
|
let result = '';
|
|
let lastIndex = 0;
|
|
let match: RegExpExecArray | null;
|
|
|
|
while ((match = variablePattern.exec(text)) !== null) {
|
|
const precedingText = text.slice(lastIndex, match.index);
|
|
if (precedingText) {
|
|
result += escapeHtml(precedingText);
|
|
}
|
|
result += `<span class="prompt-variable-highlight">${escapeHtml(match[0])}</span>`;
|
|
lastIndex = match.index + match[0].length;
|
|
}
|
|
|
|
const remainingText = text.slice(lastIndex);
|
|
if (remainingText) {
|
|
result += escapeHtml(remainingText);
|
|
}
|
|
|
|
return result || '​';
|
|
};
|
|
|
|
const systemVariableOptionDefinitions = [
|
|
{
|
|
labelKey: 'modals.prompts.systemVariableOptions.sourceContent',
|
|
value: 'source.content',
|
|
},
|
|
{
|
|
labelKey: 'modals.prompts.systemVariableOptions.sourceSummaries',
|
|
value: 'source.summaries',
|
|
},
|
|
{
|
|
labelKey: 'modals.prompts.systemVariableOptions.sourceDocuments',
|
|
value: 'source.documents',
|
|
},
|
|
{
|
|
labelKey: 'modals.prompts.systemVariableOptions.sourceCount',
|
|
value: 'source.count',
|
|
},
|
|
{
|
|
labelKey: 'modals.prompts.systemVariableOptions.systemDate',
|
|
value: 'system.date',
|
|
},
|
|
{
|
|
labelKey: 'modals.prompts.systemVariableOptions.systemTime',
|
|
value: 'system.time',
|
|
},
|
|
{
|
|
labelKey: 'modals.prompts.systemVariableOptions.systemTimestamp',
|
|
value: 'system.timestamp',
|
|
},
|
|
{
|
|
labelKey: 'modals.prompts.systemVariableOptions.systemRequestId',
|
|
value: 'system.request_id',
|
|
},
|
|
{
|
|
labelKey: 'modals.prompts.systemVariableOptions.systemUserId',
|
|
value: 'system.user_id',
|
|
},
|
|
];
|
|
|
|
const buildSystemVariableOptions = (translate: (key: string) => string) =>
|
|
systemVariableOptionDefinitions.map(({ value, labelKey }) => ({
|
|
value,
|
|
label: translate(labelKey),
|
|
}));
|
|
|
|
type PromptTextareaProps = {
|
|
id: string;
|
|
value: string;
|
|
onChange: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
|
ariaLabel: string;
|
|
};
|
|
|
|
function PromptTextarea({
|
|
id,
|
|
value,
|
|
onChange,
|
|
ariaLabel,
|
|
}: PromptTextareaProps) {
|
|
const [scrollOffsets, setScrollOffsets] = React.useState({ top: 0, left: 0 });
|
|
const highlightedValue = React.useMemo(
|
|
() => highlightPromptVariables(value),
|
|
[value],
|
|
);
|
|
|
|
const handleScroll = (event: React.UIEvent<HTMLTextAreaElement>) => {
|
|
const { scrollTop, scrollLeft } = event.currentTarget;
|
|
setScrollOffsets({
|
|
top: scrollTop,
|
|
left: scrollLeft,
|
|
});
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<div
|
|
className="pointer-events-none absolute inset-0 z-0 overflow-hidden rounded bg-white px-3 py-2 dark:bg-[#26272E]"
|
|
aria-hidden="true"
|
|
>
|
|
<div
|
|
className="min-h-full text-base leading-[1.5] break-words whitespace-pre-wrap text-transparent"
|
|
style={{
|
|
transform: `translate(${-scrollOffsets.left}px, ${-scrollOffsets.top}px)`,
|
|
}}
|
|
dangerouslySetInnerHTML={{ __html: highlightedValue }}
|
|
/>
|
|
</div>
|
|
<textarea
|
|
id={id}
|
|
className="peer border-silver dark:border-silver/40 relative z-10 h-48 w-full resize-none rounded border-2 bg-transparent px-3 py-2 text-base text-gray-800 outline-none dark:bg-transparent dark:text-white"
|
|
value={value}
|
|
onChange={onChange}
|
|
onScroll={handleScroll}
|
|
placeholder=" "
|
|
aria-label={ariaLabel}
|
|
/>
|
|
</>
|
|
);
|
|
}
|
|
|
|
// Custom hook for fetching tool variables
|
|
const useToolVariables = () => {
|
|
const token = useSelector(selectToken);
|
|
const [toolVariables, setToolVariables] = React.useState<
|
|
{ label: string; value: string }[]
|
|
>([]);
|
|
|
|
React.useEffect(() => {
|
|
const fetchToolVariables = async () => {
|
|
try {
|
|
const response = await userService.getUserTools(token);
|
|
const data = await response.json();
|
|
|
|
if (data.success && data.tools) {
|
|
const filteredActions: { label: string; value: string }[] = [];
|
|
|
|
data.tools.forEach((tool: UserToolType) => {
|
|
if (tool.actions && tool.status) {
|
|
// Only include active tools
|
|
tool.actions.forEach((action: any) => {
|
|
if (action.active) {
|
|
const canUseAction =
|
|
!action.parameters?.properties ||
|
|
Object.entries(action.parameters.properties).every(
|
|
([paramName, param]: [string, any]) => {
|
|
// Parameter is usable if:
|
|
// 1. It's filled by LLM (true) OR
|
|
// 2. It has a value in the tool config
|
|
return (
|
|
param.filled_by_llm === true ||
|
|
(tool.config &&
|
|
tool.config[paramName] &&
|
|
tool.config[paramName] !== '')
|
|
);
|
|
},
|
|
);
|
|
|
|
if (canUseAction) {
|
|
const toolIdentifier = tool.id ?? tool.name;
|
|
if (!toolIdentifier) {
|
|
return;
|
|
}
|
|
filteredActions.push({
|
|
label: `${action.name} (${tool.displayName || tool.name})`,
|
|
value: `tools.${toolIdentifier}.${action.name}`,
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
setToolVariables(filteredActions);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching tool variables:', error);
|
|
}
|
|
};
|
|
|
|
fetchToolVariables();
|
|
}, [token]);
|
|
|
|
return toolVariables;
|
|
};
|
|
|
|
function AddPrompt({
|
|
setModalState,
|
|
handleAddPrompt,
|
|
newPromptName,
|
|
setNewPromptName,
|
|
newPromptContent,
|
|
setNewPromptContent,
|
|
disableSave,
|
|
}: {
|
|
setModalState: (state: ActiveState) => void;
|
|
handleAddPrompt?: () => void;
|
|
newPromptName: string;
|
|
setNewPromptName: (name: string) => void;
|
|
newPromptContent: string;
|
|
setNewPromptContent: (content: string) => void;
|
|
disableSave: boolean;
|
|
}) {
|
|
const { t } = useTranslation();
|
|
const systemVariableOptions = React.useMemo(
|
|
() => buildSystemVariableOptions(t),
|
|
[t],
|
|
);
|
|
const toolVariables = useToolVariables();
|
|
|
|
return (
|
|
<div>
|
|
<p className="mb-1 text-xl font-semibold text-[#2B2B2B] dark:text-white">
|
|
{t('modals.prompts.addPrompt')}
|
|
</p>
|
|
<p className="mb-6 text-sm text-[#6B6B6B] dark:text-[#9A9AA0]">
|
|
{t('modals.prompts.addDescription')}
|
|
</p>
|
|
<div>
|
|
<Input
|
|
placeholder={t('modals.prompts.promptName')}
|
|
type="text"
|
|
className="mb-5"
|
|
edgeRoundness="rounded"
|
|
textSize="medium"
|
|
value={newPromptName}
|
|
onChange={(e) => setNewPromptName(e.target.value)}
|
|
labelBgClassName="bg-white dark:bg-[#26272E]"
|
|
borderVariant="thick"
|
|
/>
|
|
|
|
<div className="relative w-full">
|
|
<PromptTextarea
|
|
id="new-prompt-content"
|
|
value={newPromptContent}
|
|
onChange={(e) => setNewPromptContent(e.target.value)}
|
|
ariaLabel={t('prompts.textAriaLabel')}
|
|
/>
|
|
<label
|
|
htmlFor="new-prompt-content"
|
|
className={`absolute z-20 select-none ${
|
|
newPromptContent ? '-top-2.5 left-3 text-xs' : ''
|
|
} text-gray-4000 pointer-events-none max-w-[calc(100%-24px)] cursor-none overflow-hidden bg-white px-2 text-ellipsis whitespace-nowrap transition-all peer-placeholder-shown:top-2.5 peer-placeholder-shown:left-3 peer-placeholder-shown:text-base peer-focus:-top-2.5 peer-focus:left-3 peer-focus:text-xs dark:bg-[#26272E] dark:text-gray-400`}
|
|
>
|
|
{t('modals.prompts.promptText')}
|
|
</label>
|
|
</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-xs text-[10px] font-medium text-gray-500">
|
|
{t('modals.prompts.variablesDescription')}
|
|
</span>
|
|
</p>
|
|
|
|
<div className="flex flex-wrap items-center gap-2 sm:gap-3">
|
|
<Dropdown
|
|
options={systemVariableOptions}
|
|
selectedValue={t('modals.prompts.systemVariablesDropdownLabel')}
|
|
onSelect={(option) => {
|
|
const textarea = document.getElementById(
|
|
'new-prompt-content',
|
|
) as HTMLTextAreaElement;
|
|
if (textarea) {
|
|
const cursorPosition = textarea.selectionStart;
|
|
const textBefore = newPromptContent.slice(0, cursorPosition);
|
|
const textAfter = newPromptContent.slice(cursorPosition);
|
|
|
|
// Add leading space if needed
|
|
const needsSpace =
|
|
cursorPosition > 0 &&
|
|
newPromptContent.charAt(cursorPosition - 1) !== ' ';
|
|
|
|
const newText =
|
|
textBefore +
|
|
(needsSpace ? ' ' : '') +
|
|
`{{ ${option.value} }}` +
|
|
textAfter;
|
|
setNewPromptContent(newText);
|
|
|
|
setTimeout(() => {
|
|
textarea.focus();
|
|
textarea.setSelectionRange(
|
|
cursorPosition +
|
|
option.value.length +
|
|
6 +
|
|
(needsSpace ? 1 : 0),
|
|
cursorPosition +
|
|
option.value.length +
|
|
6 +
|
|
(needsSpace ? 1 : 0),
|
|
);
|
|
}, 0);
|
|
}
|
|
}}
|
|
placeholder={t('modals.prompts.systemVariablesDropdownLabel')}
|
|
size="w-[140px] sm:w-[185px]"
|
|
rounded="3xl"
|
|
border="border"
|
|
contentSize="text-[12px] sm:text-[14px]"
|
|
/>
|
|
|
|
<Dropdown
|
|
options={toolVariables}
|
|
selectedValue={'Tool Variables'}
|
|
onSelect={(option) => {
|
|
const textarea = document.getElementById(
|
|
'new-prompt-content',
|
|
) as HTMLTextAreaElement;
|
|
if (textarea) {
|
|
const cursorPosition = textarea.selectionStart;
|
|
const textBefore = newPromptContent.slice(0, cursorPosition);
|
|
const textAfter = newPromptContent.slice(cursorPosition);
|
|
|
|
// Add leading space if needed
|
|
const needsSpace =
|
|
cursorPosition > 0 &&
|
|
newPromptContent.charAt(cursorPosition - 1) !== ' ';
|
|
|
|
const newText =
|
|
textBefore +
|
|
(needsSpace ? ' ' : '') +
|
|
`{{ ${option.value} }}` +
|
|
textAfter;
|
|
setNewPromptContent(newText);
|
|
setTimeout(() => {
|
|
textarea.focus();
|
|
textarea.setSelectionRange(
|
|
cursorPosition +
|
|
option.value.length +
|
|
6 +
|
|
(needsSpace ? 1 : 0),
|
|
cursorPosition +
|
|
option.value.length +
|
|
6 +
|
|
(needsSpace ? 1 : 0),
|
|
);
|
|
}, 0);
|
|
}
|
|
}}
|
|
placeholder="Tool Variables"
|
|
size="w-[140px] sm:w-[171px]"
|
|
rounded="3xl"
|
|
border="border"
|
|
contentSize="text-[12px] sm:text-[14px]"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="mt-4 flex flex-col justify-between gap-4 text-[14px] sm:flex-row sm:gap-0">
|
|
<div className="flex justify-start">
|
|
<Link
|
|
to="https://docs.docsgpt.cloud/Guides/Customising-prompts"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="flex items-center gap-2 text-sm font-medium text-[#6A4DF4] hover:underline"
|
|
>
|
|
<img
|
|
src={BookIcon}
|
|
alt=""
|
|
className="flex h-4 w-3 flex-shrink-0 items-center justify-center"
|
|
aria-hidden="true"
|
|
/>
|
|
<span className="text-[14px] font-bold">
|
|
{t('modals.prompts.learnAboutPrompts')}
|
|
</span>
|
|
</Link>
|
|
</div>
|
|
|
|
<div className="flex justify-end gap-2 sm:gap-4">
|
|
<button
|
|
onClick={() => setModalState('INACTIVE')}
|
|
className="rounded-3xl border border-[#D9534F] px-5 py-2 text-sm font-medium text-[#D9534F] transition-all hover:bg-[#D9534F] hover:text-white"
|
|
>
|
|
{t('modals.prompts.cancel')}
|
|
</button>
|
|
|
|
<button
|
|
onClick={handleAddPrompt}
|
|
className="rounded-3xl bg-[#6A4DF4] px-6 py-2 text-sm font-medium text-white transition-all hover:bg-[#563DD1] disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-[#6A4DF4]"
|
|
disabled={disableSave}
|
|
>
|
|
{t('modals.prompts.save')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function EditPrompt({
|
|
setModalState,
|
|
handleEditPrompt,
|
|
editPromptName,
|
|
setEditPromptName,
|
|
editPromptContent,
|
|
setEditPromptContent,
|
|
currentPromptEdit,
|
|
disableSave,
|
|
}: {
|
|
setModalState: (state: ActiveState) => void;
|
|
handleEditPrompt?: (id: string, type: string) => void;
|
|
editPromptName: string;
|
|
setEditPromptName: (name: string) => void;
|
|
editPromptContent: string;
|
|
setEditPromptContent: (content: string) => void;
|
|
currentPromptEdit: { name: string; id: string; type: string };
|
|
disableSave: boolean;
|
|
}) {
|
|
const { t } = useTranslation();
|
|
const systemVariableOptions = React.useMemo(
|
|
() => buildSystemVariableOptions(t),
|
|
[t],
|
|
);
|
|
const toolVariables = useToolVariables();
|
|
|
|
return (
|
|
<div>
|
|
<p className="mb-1 text-xl font-semibold text-[#2B2B2B] dark:text-white">
|
|
{t('modals.prompts.editPrompt')}
|
|
</p>
|
|
<p className="mb-6 text-sm text-[#6B6B6B] dark:text-[#9A9AA0]">
|
|
{t('modals.prompts.editDescription')}
|
|
</p>
|
|
<div>
|
|
<Input
|
|
placeholder={t('modals.prompts.promptName')}
|
|
type="text"
|
|
className="mb-5"
|
|
edgeRoundness="rounded"
|
|
textSize="medium"
|
|
value={editPromptName}
|
|
onChange={(e) => setEditPromptName(e.target.value)}
|
|
labelBgClassName="bg-white dark:bg-[#26272E]"
|
|
borderVariant="thick"
|
|
/>
|
|
|
|
<div className="relative w-full">
|
|
<PromptTextarea
|
|
id="edit-prompt-content"
|
|
value={editPromptContent}
|
|
onChange={(e) => setEditPromptContent(e.target.value)}
|
|
ariaLabel={t('prompts.textAriaLabel')}
|
|
/>
|
|
<label
|
|
htmlFor="edit-prompt-content"
|
|
className={`absolute z-20 select-none ${
|
|
editPromptContent ? '-top-2.5 left-3 text-xs' : ''
|
|
} text-gray-4000 pointer-events-none max-w-[calc(100%-24px)] cursor-none overflow-hidden bg-white px-2 text-ellipsis whitespace-nowrap transition-all peer-placeholder-shown:top-2.5 peer-placeholder-shown:left-3 peer-placeholder-shown:text-base peer-focus:-top-2.5 peer-focus:left-3 peer-focus:text-xs dark:bg-[#26272E] dark:text-gray-400`}
|
|
>
|
|
{t('modals.prompts.promptText')}
|
|
</label>
|
|
</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-xs text-[10px] font-medium text-gray-500">
|
|
{t('modals.prompts.variablesDescription')}
|
|
</span>
|
|
</p>
|
|
|
|
<div className="flex flex-wrap items-center gap-2 sm:gap-3">
|
|
<Dropdown
|
|
options={systemVariableOptions}
|
|
selectedValue={t('modals.prompts.systemVariablesDropdownLabel')}
|
|
onSelect={(option) => {
|
|
const textarea = document.getElementById(
|
|
'edit-prompt-content',
|
|
) as HTMLTextAreaElement;
|
|
if (textarea) {
|
|
const cursorPosition = textarea.selectionStart;
|
|
const textBefore = editPromptContent.slice(0, cursorPosition);
|
|
const textAfter = editPromptContent.slice(cursorPosition);
|
|
|
|
// Add leading space if needed
|
|
const needsSpace =
|
|
cursorPosition > 0 &&
|
|
editPromptContent.charAt(cursorPosition - 1) !== ' ';
|
|
|
|
const newText =
|
|
textBefore +
|
|
(needsSpace ? ' ' : '') +
|
|
`{{ ${option.value} }}` +
|
|
textAfter;
|
|
setEditPromptContent(newText);
|
|
|
|
setTimeout(() => {
|
|
textarea.focus();
|
|
textarea.setSelectionRange(
|
|
cursorPosition +
|
|
option.value.length +
|
|
6 +
|
|
(needsSpace ? 1 : 0),
|
|
cursorPosition +
|
|
option.value.length +
|
|
6 +
|
|
(needsSpace ? 1 : 0),
|
|
);
|
|
}, 0);
|
|
}
|
|
}}
|
|
placeholder={t('modals.prompts.systemVariablesDropdownLabel')}
|
|
size="w-[140px] sm:w-[185px]"
|
|
rounded="3xl"
|
|
border="border"
|
|
contentSize="text-[12px] sm:text-[14px]"
|
|
/>
|
|
|
|
<Dropdown
|
|
options={toolVariables}
|
|
selectedValue={'Tool Variables'}
|
|
onSelect={(option) => {
|
|
const textarea = document.getElementById(
|
|
'edit-prompt-content',
|
|
) as HTMLTextAreaElement;
|
|
if (textarea) {
|
|
const cursorPosition = textarea.selectionStart;
|
|
const textBefore = editPromptContent.slice(0, cursorPosition);
|
|
const textAfter = editPromptContent.slice(cursorPosition);
|
|
|
|
// Add leading space if needed
|
|
const needsSpace =
|
|
cursorPosition > 0 &&
|
|
editPromptContent.charAt(cursorPosition - 1) !== ' ';
|
|
|
|
const newText =
|
|
textBefore +
|
|
(needsSpace ? ' ' : '') +
|
|
`{{ ${option.value} }}` +
|
|
textAfter;
|
|
setEditPromptContent(newText);
|
|
setTimeout(() => {
|
|
textarea.focus();
|
|
textarea.setSelectionRange(
|
|
cursorPosition +
|
|
option.value.length +
|
|
6 +
|
|
(needsSpace ? 1 : 0),
|
|
cursorPosition +
|
|
option.value.length +
|
|
6 +
|
|
(needsSpace ? 1 : 0),
|
|
);
|
|
}, 0);
|
|
}
|
|
}}
|
|
placeholder="Tool Variables"
|
|
size="w-[140px] sm:w-[171px]"
|
|
rounded="3xl"
|
|
border="border"
|
|
contentSize="text-[12px] sm:text-[14px]"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="mt-4 flex flex-col justify-between gap-4 text-[14px] sm:flex-row sm:gap-0">
|
|
<div className="flex justify-start">
|
|
<Link
|
|
to="https://docs.docsgpt.cloud/Guides/Customising-prompts"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="flex items-center gap-2 text-sm font-medium text-[#6A4DF4] hover:underline"
|
|
>
|
|
<img
|
|
src={BookIcon}
|
|
alt=""
|
|
className="flex h-4 w-3 flex-shrink-0 items-center justify-center"
|
|
aria-hidden="true"
|
|
/>
|
|
<span className="text-[14px] font-bold">
|
|
{t('modals.prompts.learnAboutPrompts')}
|
|
</span>
|
|
</Link>
|
|
</div>
|
|
|
|
<div className="flex justify-end gap-2 sm:gap-4">
|
|
<button
|
|
onClick={() => setModalState('INACTIVE')}
|
|
className="rounded-3xl border border-[#D9534F] px-5 py-2 text-sm font-medium text-[#D9534F] transition-all hover:bg-[#D9534F] hover:text-white"
|
|
>
|
|
{t('modals.prompts.cancel')}
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => {
|
|
handleEditPrompt &&
|
|
handleEditPrompt(currentPromptEdit.id, currentPromptEdit.type);
|
|
}}
|
|
className="rounded-3xl bg-[#6A4DF4] px-6 py-2 text-sm font-medium text-white transition-all hover:bg-[#563DD1] disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-[#6A4DF4]"
|
|
disabled={
|
|
currentPromptEdit.type === 'public' ||
|
|
disableSave ||
|
|
!editPromptName
|
|
}
|
|
title={
|
|
disableSave && editPromptName
|
|
? t('modals.prompts.nameExists')
|
|
: ''
|
|
}
|
|
>
|
|
{t('modals.prompts.save')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function PromptsModal({
|
|
existingPrompts,
|
|
modalState,
|
|
setModalState,
|
|
type,
|
|
newPromptName,
|
|
setNewPromptName,
|
|
newPromptContent,
|
|
setNewPromptContent,
|
|
editPromptName,
|
|
setEditPromptName,
|
|
editPromptContent,
|
|
setEditPromptContent,
|
|
currentPromptEdit,
|
|
handleAddPrompt,
|
|
handleEditPrompt,
|
|
}: {
|
|
existingPrompts: { name: string; id: string; type: string }[];
|
|
modalState: ActiveState;
|
|
setModalState: (state: ActiveState) => void;
|
|
type: 'ADD' | 'EDIT';
|
|
newPromptName: string;
|
|
setNewPromptName: (name: string) => void;
|
|
newPromptContent: string;
|
|
setNewPromptContent: (content: string) => void;
|
|
editPromptName: string;
|
|
setEditPromptName: (name: string) => void;
|
|
editPromptContent: string;
|
|
setEditPromptContent: (content: string) => void;
|
|
currentPromptEdit: {
|
|
name: string;
|
|
id: string;
|
|
type: string;
|
|
content?: string;
|
|
};
|
|
handleAddPrompt?: () => void;
|
|
handleEditPrompt?: (id: string, type: string) => void;
|
|
}) {
|
|
const [disableSave, setDisableSave] = React.useState(true);
|
|
const handlePromptNameChange = (edit: boolean, newName: string) => {
|
|
if (edit) {
|
|
const nameExists = existingPrompts.find(
|
|
(prompt) =>
|
|
newName === prompt.name && 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);
|
|
}
|
|
};
|
|
|
|
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);
|
|
}
|
|
};
|
|
|
|
let view;
|
|
|
|
if (type === 'ADD') {
|
|
view = (
|
|
<AddPrompt
|
|
setModalState={setModalState}
|
|
handleAddPrompt={handleAddPrompt}
|
|
newPromptName={newPromptName}
|
|
setNewPromptName={handlePromptNameChange.bind(null, false)}
|
|
newPromptContent={newPromptContent}
|
|
setNewPromptContent={handleContentChange.bind(null, false)}
|
|
disableSave={disableSave}
|
|
/>
|
|
);
|
|
} else if (type === 'EDIT') {
|
|
view = (
|
|
<EditPrompt
|
|
setModalState={setModalState}
|
|
handleEditPrompt={handleEditPrompt}
|
|
editPromptName={editPromptName}
|
|
setEditPromptName={handlePromptNameChange.bind(null, true)}
|
|
editPromptContent={editPromptContent}
|
|
setEditPromptContent={handleContentChange.bind(null, true)}
|
|
currentPromptEdit={currentPromptEdit}
|
|
disableSave={disableSave}
|
|
/>
|
|
);
|
|
} else {
|
|
view = <></>;
|
|
}
|
|
|
|
return modalState === 'ACTIVE' ? (
|
|
<WrapperModal
|
|
close={() => {
|
|
setModalState('INACTIVE');
|
|
if (type === 'ADD') {
|
|
setNewPromptName('');
|
|
setNewPromptContent('');
|
|
}
|
|
}}
|
|
className="mx-4 mt-16 w-[95vw] max-w-[650px] rounded-2xl bg-white px-4 py-4 sm:px-6 sm:py-6 md:px-8 md:py-6 dark:bg-[#1E1E2A]"
|
|
contentClassName="!overflow-visible"
|
|
>
|
|
{view}
|
|
</WrapperModal>
|
|
) : null;
|
|
}
|