diff --git a/application/api/user/tools/routes.py b/application/api/user/tools/routes.py index 6a9fee14..0d4bc6f8 100644 --- a/application/api/user/tools/routes.py +++ b/application/api/user/tools/routes.py @@ -56,9 +56,10 @@ class GetTools(Resource): tools = user_tools_collection.find({"user": user}) user_tools = [] for tool in tools: - tool["id"] = str(tool["_id"]) - tool.pop("_id") - user_tools.append(tool) + tool_copy = {**tool} + tool_copy["id"] = str(tool["_id"]) + tool_copy.pop("_id", None) + user_tools.append(tool_copy) except Exception as err: current_app.logger.error(f"Error getting user tools: {err}", exc_info=True) return make_response(jsonify({"success": False}), 400) diff --git a/frontend/src/index.css b/frontend/src/index.css index 6c61399f..61224d71 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -225,6 +225,16 @@ 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 */ /* Document diff --git a/frontend/src/locale/en.json b/frontend/src/locale/en.json index a4ec2bde..9237e5c9 100644 --- a/frontend/src/locale/en.json +++ b/frontend/src/locale/en.json @@ -396,6 +396,18 @@ "variablesDescription": "Click to insert into prompt", "systemVariables": "Click to insert into prompt", "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 →", "publicPromptEditDisabled": "Public prompts cannot be edited", "promptTypePublic": "public", diff --git a/frontend/src/locale/es.json b/frontend/src/locale/es.json index 8bdc9b32..abac6a0d 100644 --- a/frontend/src/locale/es.json +++ b/frontend/src/locale/es.json @@ -396,6 +396,18 @@ "variablesDescription": "Haz clic para insertar en el prompt", "systemVariables": "Variables del sistema", "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 →", "publicPromptEditDisabled": "Los prompts públicos no se pueden editar", "promptTypePublic": "público", diff --git a/frontend/src/locale/jp.json b/frontend/src/locale/jp.json index b3f885f7..e506d190 100644 --- a/frontend/src/locale/jp.json +++ b/frontend/src/locale/jp.json @@ -396,6 +396,18 @@ "variablesDescription": "クリックしてプロンプトに挿入", "systemVariables": "システム変数", "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": "プロンプトについて学ぶ →", "publicPromptEditDisabled": "公開プロンプトは編集できません", "promptTypePublic": "公開", diff --git a/frontend/src/locale/ru.json b/frontend/src/locale/ru.json index aba8332f..4f8a2f84 100644 --- a/frontend/src/locale/ru.json +++ b/frontend/src/locale/ru.json @@ -396,6 +396,18 @@ "variablesDescription": "Нажмите, чтобы вставить в промпт", "systemVariables": "Системные переменные", "toolVariables": "Переменные инструментов", + "systemVariablesDropdownLabel": "Системные переменные", + "systemVariableOptions": { + "sourceContent": "Содержимое источников", + "sourceSummaries": "Псевдоним содержимого (обратная совместимость)", + "sourceDocuments": "Список объектов документов", + "sourceCount": "Количество полученных документов", + "systemDate": "Текущая дата (ГГГГ-ММ-ДД)", + "systemTime": "Текущее время (ЧЧ:ММ:СС)", + "systemTimestamp": "Отметка времени ISO 8601", + "systemRequestId": "Уникальный идентификатор запроса", + "systemUserId": "Идентификатор текущего пользователя" + }, "learnAboutPrompts": "Узнать о промптах →", "publicPromptEditDisabled": "Публичные промпты нельзя редактировать", "promptTypePublic": "публичный", diff --git a/frontend/src/locale/zh-TW.json b/frontend/src/locale/zh-TW.json index 64f24074..d7b98e03 100644 --- a/frontend/src/locale/zh-TW.json +++ b/frontend/src/locale/zh-TW.json @@ -396,6 +396,18 @@ "variablesDescription": "點擊以插入到提示中", "systemVariables": "點擊以插入提示中", "toolVariables": "工具變數", + "systemVariablesDropdownLabel": "系統變數", + "systemVariableOptions": { + "sourceContent": "來源內容", + "sourceSummaries": "內容別名(向後相容)", + "sourceDocuments": "文件物件列表", + "sourceCount": "擷取的文件數量", + "systemDate": "目前日期 (YYYY-MM-DD)", + "systemTime": "目前時間 (HH:MM:SS)", + "systemTimestamp": "ISO 8601 時間戳記", + "systemRequestId": "唯一請求識別碼", + "systemUserId": "目前使用者 ID" + }, "learnAboutPrompts": "了解提示 →", "publicPromptEditDisabled": "公共提示無法編輯", "promptTypePublic": "公共", diff --git a/frontend/src/locale/zh.json b/frontend/src/locale/zh.json index d1eeb144..2aece1f0 100644 --- a/frontend/src/locale/zh.json +++ b/frontend/src/locale/zh.json @@ -396,6 +396,18 @@ "variablesDescription": "點擊以插入到提示中", "systemVariables": "點擊以插入提示中", "toolVariables": "工具變數", + "systemVariablesDropdownLabel": "系統變數", + "systemVariableOptions": { + "sourceContent": "來源內容", + "sourceSummaries": "內容別名(向後相容)", + "sourceDocuments": "文件物件列表", + "sourceCount": "擷取的文件數量", + "systemDate": "目前日期 (YYYY-MM-DD)", + "systemTime": "目前時間 (HH:MM:SS)", + "systemTimestamp": "ISO 8601 時間戳記", + "systemRequestId": "唯一請求識別碼", + "systemUserId": "目前使用者 ID" + }, "learnAboutPrompts": "了解提示 →", "publicPromptEditDisabled": "公共提示無法編輯", "promptTypePublic": "公共", diff --git a/frontend/src/preferences/PromptsModal.tsx b/frontend/src/preferences/PromptsModal.tsx index 72193852..d6071687 100644 --- a/frontend/src/preferences/PromptsModal.tsx +++ b/frontend/src/preferences/PromptsModal.tsx @@ -12,6 +12,141 @@ 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, '''); + +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 += `${escapeHtml(match[0])}`; + 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) => 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) => { + const { scrollTop, scrollLeft } = event.currentTarget; + setScrollOffsets({ + top: scrollTop, + left: scrollLeft, + }); + }; + + return ( + <> + + + + + > + ); +} + // Custom hook for fetching tool variables const useToolVariables = () => { const token = useSelector(selectToken); @@ -50,9 +185,13 @@ const useToolVariables = () => { ); if (canUseAction) { + const toolIdentifier = tool.id ?? tool.name; + if (!toolIdentifier) { + return; + } filteredActions.push({ 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; }) { const { t } = useTranslation(); + const systemVariableOptions = React.useMemo( + () => buildSystemVariableOptions(t), + [t], + ); const toolVariables = useToolVariables(); return ( @@ -115,17 +258,15 @@ function AddPrompt({ /> - setNewPromptContent(e.target.value)} - placeholder=" " - aria-label={t('prompts.textAriaLabel')} + ariaLabel={t('prompts.textAriaLabel')} /> @@ -146,8 +287,8 @@ function AddPrompt({ { const textarea = document.getElementById( 'new-prompt-content', @@ -165,7 +306,7 @@ function AddPrompt({ const newText = textBefore + (needsSpace ? ' ' : '') + - `{${option.value}}` + + `{{ ${option.value} }}` + textAfter; setNewPromptContent(newText); @@ -174,17 +315,17 @@ function AddPrompt({ textarea.setSelectionRange( cursorPosition + option.value.length + - 2 + + 6 + (needsSpace ? 1 : 0), cursorPosition + option.value.length + - 2 + + 6 + (needsSpace ? 1 : 0), ); }, 0); } }} - placeholder="System Variables" + placeholder={t('modals.prompts.systemVariablesDropdownLabel')} size="w-[140px] sm:w-[185px]" rounded="3xl" border="border" @@ -298,6 +439,10 @@ function EditPrompt({ disableSave: boolean; }) { const { t } = useTranslation(); + const systemVariableOptions = React.useMemo( + () => buildSystemVariableOptions(t), + [t], + ); const toolVariables = useToolVariables(); return ( @@ -322,17 +467,15 @@ function EditPrompt({ /> - setEditPromptContent(e.target.value)} - placeholder=" " - aria-label={t('prompts.textAriaLabel')} + ariaLabel={t('prompts.textAriaLabel')} /> @@ -353,8 +496,8 @@ function EditPrompt({ { const textarea = document.getElementById( 'edit-prompt-content', @@ -372,7 +515,7 @@ function EditPrompt({ const newText = textBefore + (needsSpace ? ' ' : '') + - `{${option.value}}` + + `{{ ${option.value} }}` + textAfter; setEditPromptContent(newText); @@ -381,17 +524,17 @@ function EditPrompt({ textarea.setSelectionRange( cursorPosition + option.value.length + - 2 + + 6 + (needsSpace ? 1 : 0), cursorPosition + option.value.length + - 2 + + 6 + (needsSpace ? 1 : 0), ); }, 0); } }} - placeholder="System Variables" + placeholder={t('modals.prompts.systemVariablesDropdownLabel')} size="w-[140px] sm:w-[185px]" rounded="3xl" border="border"