From ca150d3bd324165196838b76429bd668893dd01d Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Tue, 15 Jul 2025 21:59:59 +0300 Subject: [PATCH] F: Implement prompt editing pt2 --- rsconcept/frontend/src/components/icons.tsx | 1 + rsconcept/frontend/src/features/ai/labels.ts | 17 ++++- .../src/features/ai/models/prompting-api.ts | 17 +++++ .../form-prompt-template.tsx | 65 +++++++++++++------ .../tab-edit-template/tab-edit-template.tsx | 2 +- .../tab-view-variables.tsx | 17 ++++- .../side-panel/side-panel.tsx | 2 +- rsconcept/frontend/src/styling/components.css | 14 ++++ 8 files changed, 112 insertions(+), 23 deletions(-) diff --git a/rsconcept/frontend/src/components/icons.tsx b/rsconcept/frontend/src/components/icons.tsx index b19cbcbf..fc87fd99 100644 --- a/rsconcept/frontend/src/components/icons.tsx +++ b/rsconcept/frontend/src/components/icons.tsx @@ -48,6 +48,7 @@ export { LuFolderOpen as IconFolderOpened } from 'react-icons/lu'; export { LuFolderClosed as IconFolderClosed } from 'react-icons/lu'; export { LuFolderDot as IconFolderEmpty } from 'react-icons/lu'; export { TbHelpOctagon as IconHelp } from 'react-icons/tb'; +export { LuPresentation as IconSample } from 'react-icons/lu'; export { LuLightbulbOff as IconHelpOff } from 'react-icons/lu'; export { RiPushpinFill as IconPin } from 'react-icons/ri'; export { RiUnpinLine as IconUnpin } from 'react-icons/ri'; diff --git a/rsconcept/frontend/src/features/ai/labels.ts b/rsconcept/frontend/src/features/ai/labels.ts index 3ea42e9c..88eb21cc 100644 --- a/rsconcept/frontend/src/features/ai/labels.ts +++ b/rsconcept/frontend/src/features/ai/labels.ts @@ -3,11 +3,26 @@ import { PromptVariableType } from './models/prompting'; const describePromptVariableRecord: Record = { [PromptVariableType.BLOCK]: 'Текущий блок операционной схемы', [PromptVariableType.OSS]: 'Текущая операционная схема', - [PromptVariableType.SCHEMA]: 'Текущая концептуальный схема', + [PromptVariableType.SCHEMA]: 'Текущая концептуальная схема', [PromptVariableType.CONSTITUENTA]: 'Текущая конституента' }; +const mockPromptVariableRecord: Record = { + [PromptVariableType.BLOCK]: 'Пример: Текущий блок операционной схемы', + [PromptVariableType.OSS]: 'Пример: Текущая операционная схема', + [PromptVariableType.SCHEMA]: 'Пример: Текущая концептуальная схема', + [PromptVariableType.CONSTITUENTA]: 'Пример: Текущая конституента' +}; + /** Retrieves description for {@link PromptVariableType}. */ export function describePromptVariable(itemType: PromptVariableType): string { return describePromptVariableRecord[itemType] ?? `UNKNOWN VARIABLE TYPE: ${itemType}`; } + +/** Retrieves mock text for {@link PromptVariableType}. */ +export function mockPromptVariable(variable: string): string { + if (!Object.values(PromptVariableType).includes(variable as PromptVariableType)) { + return variable; + } + return mockPromptVariableRecord[variable as PromptVariableType] ?? `UNKNOWN VARIABLE: ${variable}`; +} diff --git a/rsconcept/frontend/src/features/ai/models/prompting-api.ts b/rsconcept/frontend/src/features/ai/models/prompting-api.ts index 7a7143b6..0f9c5c4b 100644 --- a/rsconcept/frontend/src/features/ai/models/prompting-api.ts +++ b/rsconcept/frontend/src/features/ai/models/prompting-api.ts @@ -1,3 +1,5 @@ +import { mockPromptVariable } from '../labels'; + /** Extracts a list of variables (as string[]) from a target string. * Note: Variables are wrapped in {{...}} and can include a-zA-Z, hyphen, and dot inside curly braces. * */ @@ -10,3 +12,18 @@ export function extractPromptVariables(target: string): string[] { } return result; } + +/** Generates a sample text from a target templates. */ +export function generateSample(target: string): string { + const variables = extractPromptVariables(target); + if (variables.length === 0) { + return target; + } + let result = target; + for (const variable of variables) { + const mockText = mockPromptVariable(variable); + const escapedVar = variable.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + result = result.replace(new RegExp(`\\{\\{${escapedVar}\\}\\}`, 'g'), mockText); + } + return result; +} diff --git a/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/tab-edit-template/form-prompt-template.tsx b/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/tab-edit-template/form-prompt-template.tsx index 22961121..75a42bde 100644 --- a/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/tab-edit-template/form-prompt-template.tsx +++ b/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/tab-edit-template/form-prompt-template.tsx @@ -1,18 +1,23 @@ 'use no memo'; // TODO: remove when react hook forms are compliant with react compiler 'use client'; -import { useRef } from 'react'; -import { Controller, useForm } from 'react-hook-form'; +import { useRef, useState } from 'react'; +import { Controller, useForm, useWatch } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; +import clsx from 'clsx'; +import { useDebounce } from 'use-debounce'; import { useMutatingPrompts } from '@/features/ai/backend/use-mutating-prompts'; import { useUpdatePromptTemplate } from '@/features/ai/backend/use-update-prompt-template'; +import { generateSample } from '@/features/ai/models/prompting-api'; import { useAuthSuspense } from '@/features/auth'; +import { MiniButton } from '@/components/control'; +import { IconSample } from '@/components/icons'; import { Checkbox, TextArea, TextInput } from '@/components/input'; import { cn } from '@/components/utils'; import { useModificationStore } from '@/stores/modification'; -import { globalIDs } from '@/utils/constants'; +import { globalIDs, PARAMETER } from '@/utils/constants'; import { type IPromptTemplate, @@ -33,6 +38,8 @@ export function FormPromptTemplate({ promptTemplate, className, isMutable, toggl const isProcessing = useMutatingPrompts(); const setIsModified = useModificationStore(state => state.setIsModified); const { updatePromptTemplate } = useUpdatePromptTemplate(); + const [sampleResult, setSampleResult] = useState(null); + const [debouncedResult] = useDebounce(sampleResult, PARAMETER.moveDuration); const { control, @@ -50,6 +57,7 @@ export function FormPromptTemplate({ promptTemplate, className, isMutable, toggl is_shared: promptTemplate.is_shared } }); + const text = useWatch({ control, name: 'text' }); const prevReset = useRef(toggleReset); const prevTemplate = useRef(promptTemplate); @@ -63,6 +71,7 @@ export function FormPromptTemplate({ promptTemplate, className, isMutable, toggl text: promptTemplate.text, is_shared: promptTemplate.is_shared }); + setSampleResult(null); } const prevDirty = useRef(isDirty); @@ -81,7 +90,7 @@ export function FormPromptTemplate({ promptTemplate, className, isMutable, toggl return (
void handleSubmit(onSubmit)(event)} > +
+ ( + + )} + /> + } + onClick={() => setSampleResult(!!sampleResult ? null : generateSample(text))} + /> +
- ( - - )} - /> +
+