Compare commits

...

1 Commits

9 changed files with 252 additions and 26 deletions

View File

@@ -56,9 +56,10 @@ class GetTools(Resource):
tools = user_tools_collection.find({"user": user}) tools = user_tools_collection.find({"user": user})
user_tools = [] user_tools = []
for tool in tools: for tool in tools:
tool["id"] = str(tool["_id"]) tool_copy = {**tool}
tool.pop("_id") tool_copy["id"] = str(tool["_id"])
user_tools.append(tool) tool_copy.pop("_id", None)
user_tools.append(tool_copy)
except Exception as err: except Exception as err:
current_app.logger.error(f"Error getting user tools: {err}", exc_info=True) current_app.logger.error(f"Error getting user tools: {err}", exc_info=True)
return make_response(jsonify({"success": False}), 400) return make_response(jsonify({"success": False}), 400)

View File

@@ -225,6 +225,16 @@ layer(base);
} }
@layer base { @layer base {
.prompt-variable-highlight {
background-color: rgba(106, 77, 244, 0.18);
border-radius: 0.375rem;
padding: 0 0.25rem;
}
.dark .prompt-variable-highlight {
background-color: rgba(106, 77, 244, 0.32);
}
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* Document /* Document

View File

@@ -396,6 +396,18 @@
"variablesDescription": "Click to insert into prompt", "variablesDescription": "Click to insert into prompt",
"systemVariables": "Click to insert into prompt", "systemVariables": "Click to insert into prompt",
"toolVariables": "Tool Variables", "toolVariables": "Tool Variables",
"systemVariablesDropdownLabel": "System Variables",
"systemVariableOptions": {
"sourceContent": "Sources content",
"sourceSummaries": "Alias for content (backward compatible)",
"sourceDocuments": "Document objects list",
"sourceCount": "Number of retrieved documents",
"systemDate": "Current date (YYYY-MM-DD)",
"systemTime": "Current time (HH:MM:SS)",
"systemTimestamp": "ISO 8601 timestamp",
"systemRequestId": "Unique request identifier",
"systemUserId": "Current user ID"
},
"learnAboutPrompts": "Learn about Prompts →", "learnAboutPrompts": "Learn about Prompts →",
"publicPromptEditDisabled": "Public prompts cannot be edited", "publicPromptEditDisabled": "Public prompts cannot be edited",
"promptTypePublic": "public", "promptTypePublic": "public",

View File

@@ -396,6 +396,18 @@
"variablesDescription": "Haz clic para insertar en el prompt", "variablesDescription": "Haz clic para insertar en el prompt",
"systemVariables": "Variables del sistema", "systemVariables": "Variables del sistema",
"toolVariables": "Variables de herramientas", "toolVariables": "Variables de herramientas",
"systemVariablesDropdownLabel": "Variables del sistema",
"systemVariableOptions": {
"sourceContent": "Contenido de las fuentes",
"sourceSummaries": "Alias del contenido (compatibilidad retroactiva)",
"sourceDocuments": "Lista de objetos de documentos",
"sourceCount": "Número de documentos recuperados",
"systemDate": "Fecha actual (YYYY-MM-DD)",
"systemTime": "Hora actual (HH:MM:SS)",
"systemTimestamp": "Marca de tiempo ISO 8601",
"systemRequestId": "Identificador único de solicitud",
"systemUserId": "ID del usuario actual"
},
"learnAboutPrompts": "Aprende sobre los Prompts →", "learnAboutPrompts": "Aprende sobre los Prompts →",
"publicPromptEditDisabled": "Los prompts públicos no se pueden editar", "publicPromptEditDisabled": "Los prompts públicos no se pueden editar",
"promptTypePublic": "público", "promptTypePublic": "público",

View File

@@ -396,6 +396,18 @@
"variablesDescription": "クリックしてプロンプトに挿入", "variablesDescription": "クリックしてプロンプトに挿入",
"systemVariables": "システム変数", "systemVariables": "システム変数",
"toolVariables": "ツール変数", "toolVariables": "ツール変数",
"systemVariablesDropdownLabel": "System Variables",
"systemVariableOptions": {
"sourceContent": "Sources content",
"sourceSummaries": "Alias for content (backward compatible)",
"sourceDocuments": "Document objects list",
"sourceCount": "Number of retrieved documents",
"systemDate": "Current date (YYYY-MM-DD)",
"systemTime": "Current time (HH:MM:SS)",
"systemTimestamp": "ISO 8601 timestamp",
"systemRequestId": "Unique request identifier",
"systemUserId": "Current user ID"
},
"learnAboutPrompts": "プロンプトについて学ぶ →", "learnAboutPrompts": "プロンプトについて学ぶ →",
"publicPromptEditDisabled": "公開プロンプトは編集できません", "publicPromptEditDisabled": "公開プロンプトは編集できません",
"promptTypePublic": "公開", "promptTypePublic": "公開",

View File

@@ -396,6 +396,18 @@
"variablesDescription": "Нажмите, чтобы вставить в промпт", "variablesDescription": "Нажмите, чтобы вставить в промпт",
"systemVariables": "Системные переменные", "systemVariables": "Системные переменные",
"toolVariables": "Переменные инструментов", "toolVariables": "Переменные инструментов",
"systemVariablesDropdownLabel": "Системные переменные",
"systemVariableOptions": {
"sourceContent": "Содержимое источников",
"sourceSummaries": "Псевдоним содержимого (обратная совместимость)",
"sourceDocuments": "Список объектов документов",
"sourceCount": "Количество полученных документов",
"systemDate": "Текущая дата (ГГГГ-ММ-ДД)",
"systemTime": "Текущее время (ЧЧ:ММ:СС)",
"systemTimestamp": "Отметка времени ISO 8601",
"systemRequestId": "Уникальный идентификатор запроса",
"systemUserId": "Идентификатор текущего пользователя"
},
"learnAboutPrompts": "Узнать о промптах →", "learnAboutPrompts": "Узнать о промптах →",
"publicPromptEditDisabled": "Публичные промпты нельзя редактировать", "publicPromptEditDisabled": "Публичные промпты нельзя редактировать",
"promptTypePublic": "публичный", "promptTypePublic": "публичный",

View File

@@ -396,6 +396,18 @@
"variablesDescription": "點擊以插入到提示中", "variablesDescription": "點擊以插入到提示中",
"systemVariables": "點擊以插入提示中", "systemVariables": "點擊以插入提示中",
"toolVariables": "工具變數", "toolVariables": "工具變數",
"systemVariablesDropdownLabel": "系統變數",
"systemVariableOptions": {
"sourceContent": "來源內容",
"sourceSummaries": "內容別名(向後相容)",
"sourceDocuments": "文件物件列表",
"sourceCount": "擷取的文件數量",
"systemDate": "目前日期 (YYYY-MM-DD)",
"systemTime": "目前時間 (HH:MM:SS)",
"systemTimestamp": "ISO 8601 時間戳記",
"systemRequestId": "唯一請求識別碼",
"systemUserId": "目前使用者 ID"
},
"learnAboutPrompts": "了解提示 →", "learnAboutPrompts": "了解提示 →",
"publicPromptEditDisabled": "公共提示無法編輯", "publicPromptEditDisabled": "公共提示無法編輯",
"promptTypePublic": "公共", "promptTypePublic": "公共",

View File

@@ -396,6 +396,18 @@
"variablesDescription": "點擊以插入到提示中", "variablesDescription": "點擊以插入到提示中",
"systemVariables": "點擊以插入提示中", "systemVariables": "點擊以插入提示中",
"toolVariables": "工具變數", "toolVariables": "工具變數",
"systemVariablesDropdownLabel": "系統變數",
"systemVariableOptions": {
"sourceContent": "來源內容",
"sourceSummaries": "內容別名(向後相容)",
"sourceDocuments": "文件物件列表",
"sourceCount": "擷取的文件數量",
"systemDate": "目前日期 (YYYY-MM-DD)",
"systemTime": "目前時間 (HH:MM:SS)",
"systemTimestamp": "ISO 8601 時間戳記",
"systemRequestId": "唯一請求識別碼",
"systemUserId": "目前使用者 ID"
},
"learnAboutPrompts": "了解提示 →", "learnAboutPrompts": "了解提示 →",
"publicPromptEditDisabled": "公共提示無法編輯", "publicPromptEditDisabled": "公共提示無法編輯",
"promptTypePublic": "公共", "promptTypePublic": "公共",

View File

@@ -12,6 +12,141 @@ import userService from '../api/services/userService';
import { selectToken } from '../preferences/preferenceSlice'; import { selectToken } from '../preferences/preferenceSlice';
import { UserToolType } from '../settings/types'; import { UserToolType } from '../settings/types';
const variablePattern = /(\{\{\s*[^{}]+\s*\}\}|\{(?!\{)[^{}]+\})/g;
const escapeHtml = (value: string) =>
value
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
const highlightPromptVariables = (text: string) => {
if (!text) {
return '&#8203;';
}
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 || '&#8203;';
};
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 // Custom hook for fetching tool variables
const useToolVariables = () => { const useToolVariables = () => {
const token = useSelector(selectToken); const token = useSelector(selectToken);
@@ -50,9 +185,13 @@ const useToolVariables = () => {
); );
if (canUseAction) { if (canUseAction) {
const toolIdentifier = tool.id ?? tool.name;
if (!toolIdentifier) {
return;
}
filteredActions.push({ filteredActions.push({
label: `${action.name} (${tool.displayName || tool.name})`, label: `${action.name} (${tool.displayName || tool.name})`,
value: `tools.${tool.name}.${action.name}`, value: `tools.${toolIdentifier}.${action.name}`,
}); });
} }
} }
@@ -91,6 +230,10 @@ function AddPrompt({
disableSave: boolean; disableSave: boolean;
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const systemVariableOptions = React.useMemo(
() => buildSystemVariableOptions(t),
[t],
);
const toolVariables = useToolVariables(); const toolVariables = useToolVariables();
return ( return (
@@ -115,17 +258,15 @@ function AddPrompt({
/> />
<div className="relative w-full"> <div className="relative w-full">
<textarea <PromptTextarea
id="new-prompt-content" id="new-prompt-content"
className="peer border-silver dark:border-silver/40 h-48 w-full resize-none rounded border-2 bg-white px-3 py-2 text-base text-gray-800 outline-none dark:bg-[#26272E] dark:text-white"
value={newPromptContent} value={newPromptContent}
onChange={(e) => setNewPromptContent(e.target.value)} onChange={(e) => setNewPromptContent(e.target.value)}
placeholder=" " ariaLabel={t('prompts.textAriaLabel')}
aria-label={t('prompts.textAriaLabel')}
/> />
<label <label
htmlFor="new-prompt-content" htmlFor="new-prompt-content"
className={`absolute select-none ${ className={`absolute z-20 select-none ${
newPromptContent ? '-top-2.5 left-3 text-xs' : '' 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`} } 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`}
> >
@@ -146,8 +287,8 @@ function AddPrompt({
<div className="flex flex-wrap items-center gap-2 sm:gap-3"> <div className="flex flex-wrap items-center gap-2 sm:gap-3">
<Dropdown <Dropdown
options={[{ label: 'Summaries', value: 'summaries' }]} options={systemVariableOptions}
selectedValue={'System Variables'} selectedValue={t('modals.prompts.systemVariablesDropdownLabel')}
onSelect={(option) => { onSelect={(option) => {
const textarea = document.getElementById( const textarea = document.getElementById(
'new-prompt-content', 'new-prompt-content',
@@ -165,7 +306,7 @@ function AddPrompt({
const newText = const newText =
textBefore + textBefore +
(needsSpace ? ' ' : '') + (needsSpace ? ' ' : '') +
`{${option.value}}` + `{{ ${option.value} }}` +
textAfter; textAfter;
setNewPromptContent(newText); setNewPromptContent(newText);
@@ -174,17 +315,17 @@ function AddPrompt({
textarea.setSelectionRange( textarea.setSelectionRange(
cursorPosition + cursorPosition +
option.value.length + option.value.length +
2 + 6 +
(needsSpace ? 1 : 0), (needsSpace ? 1 : 0),
cursorPosition + cursorPosition +
option.value.length + option.value.length +
2 + 6 +
(needsSpace ? 1 : 0), (needsSpace ? 1 : 0),
); );
}, 0); }, 0);
} }
}} }}
placeholder="System Variables" placeholder={t('modals.prompts.systemVariablesDropdownLabel')}
size="w-[140px] sm:w-[185px]" size="w-[140px] sm:w-[185px]"
rounded="3xl" rounded="3xl"
border="border" border="border"
@@ -298,6 +439,10 @@ function EditPrompt({
disableSave: boolean; disableSave: boolean;
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const systemVariableOptions = React.useMemo(
() => buildSystemVariableOptions(t),
[t],
);
const toolVariables = useToolVariables(); const toolVariables = useToolVariables();
return ( return (
@@ -322,17 +467,15 @@ function EditPrompt({
/> />
<div className="relative w-full"> <div className="relative w-full">
<textarea <PromptTextarea
id="edit-prompt-content" id="edit-prompt-content"
className="peer border-silver dark:border-silver/40 h-48 w-full resize-none rounded border-2 bg-white px-3 py-2 text-base text-gray-800 outline-none dark:bg-[#26272E] dark:text-white"
value={editPromptContent} value={editPromptContent}
onChange={(e) => setEditPromptContent(e.target.value)} onChange={(e) => setEditPromptContent(e.target.value)}
placeholder=" " ariaLabel={t('prompts.textAriaLabel')}
aria-label={t('prompts.textAriaLabel')}
/> />
<label <label
htmlFor="edit-prompt-content" htmlFor="edit-prompt-content"
className={`absolute select-none ${ className={`absolute z-20 select-none ${
editPromptContent ? '-top-2.5 left-3 text-xs' : '' 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`} } 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`}
> >
@@ -353,8 +496,8 @@ function EditPrompt({
<div className="flex flex-wrap items-center gap-2 sm:gap-3"> <div className="flex flex-wrap items-center gap-2 sm:gap-3">
<Dropdown <Dropdown
options={[{ label: 'Summaries', value: 'summaries' }]} options={systemVariableOptions}
selectedValue={'System Variables'} selectedValue={t('modals.prompts.systemVariablesDropdownLabel')}
onSelect={(option) => { onSelect={(option) => {
const textarea = document.getElementById( const textarea = document.getElementById(
'edit-prompt-content', 'edit-prompt-content',
@@ -372,7 +515,7 @@ function EditPrompt({
const newText = const newText =
textBefore + textBefore +
(needsSpace ? ' ' : '') + (needsSpace ? ' ' : '') +
`{${option.value}}` + `{{ ${option.value} }}` +
textAfter; textAfter;
setEditPromptContent(newText); setEditPromptContent(newText);
@@ -381,17 +524,17 @@ function EditPrompt({
textarea.setSelectionRange( textarea.setSelectionRange(
cursorPosition + cursorPosition +
option.value.length + option.value.length +
2 + 6 +
(needsSpace ? 1 : 0), (needsSpace ? 1 : 0),
cursorPosition + cursorPosition +
option.value.length + option.value.length +
2 + 6 +
(needsSpace ? 1 : 0), (needsSpace ? 1 : 0),
); );
}, 0); }, 0);
} }
}} }}
placeholder="System Variables" placeholder={t('modals.prompts.systemVariablesDropdownLabel')}
size="w-[140px] sm:w-[185px]" size="w-[140px] sm:w-[185px]"
rounded="3xl" rounded="3xl"
border="border" border="border"