F: Implementing PromptEdit pt3
This commit is contained in:
parent
56652095af
commit
56f1bcacad
|
@ -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';
|
||||
|
|
|
@ -3,11 +3,26 @@ import { PromptVariableType } from './models/prompting';
|
|||
const describePromptVariableRecord: Record<PromptVariableType, string> = {
|
||||
[PromptVariableType.BLOCK]: 'Текущий блок операционной схемы',
|
||||
[PromptVariableType.OSS]: 'Текущая операционная схема',
|
||||
[PromptVariableType.SCHEMA]: 'Текущая концептуальный схема',
|
||||
[PromptVariableType.SCHEMA]: 'Текущая концептуальная схема',
|
||||
[PromptVariableType.CONSTITUENTA]: 'Текущая конституента'
|
||||
};
|
||||
|
||||
const mockPromptVariableRecord: Record<PromptVariableType, string> = {
|
||||
[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}`;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<string | null>(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 (
|
||||
<form
|
||||
id={globalIDs.prompt_editor}
|
||||
className={cn('flex flex-col gap-3 px-6', className)}
|
||||
className={cn('flex flex-col gap-3 px-6 py-2', className)}
|
||||
onSubmit={event => void handleSubmit(onSubmit)(event)}
|
||||
>
|
||||
<TextInput
|
||||
|
@ -101,26 +110,44 @@ export function FormPromptTemplate({ promptTemplate, className, isMutable, toggl
|
|||
<TextArea
|
||||
id='prompt_text'
|
||||
label='Содержание' //
|
||||
fitContent
|
||||
className='disabled:min-h-9 max-h-64'
|
||||
{...register('text')}
|
||||
error={errors.text}
|
||||
disabled={isProcessing || !isMutable}
|
||||
/>
|
||||
<div className='flex justify-between'>
|
||||
<Controller
|
||||
name='is_shared'
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
id='prompt_is_shared'
|
||||
label='Общий шаблон'
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
onBlur={field.onBlur}
|
||||
ref={field.ref}
|
||||
disabled={isProcessing || !isMutable || !user.is_staff}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<MiniButton
|
||||
title='Сгенерировать пример запроса'
|
||||
icon={<IconSample size='1.25rem' className='icon-primary' />}
|
||||
onClick={() => setSampleResult(!!sampleResult ? null : generateSample(text))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Controller
|
||||
name='is_shared'
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
id='prompt_is_shared'
|
||||
label='Общий шаблон'
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
onBlur={field.onBlur}
|
||||
ref={field.ref}
|
||||
disabled={isProcessing || !isMutable || !user.is_staff}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className={clsx('cc-prompt-result overflow-y-hidden', sampleResult !== null && 'open')}>
|
||||
<TextArea
|
||||
fitContent
|
||||
className='mt-3 max-h-64 min-h-12'
|
||||
label='Пример запроса'
|
||||
value={sampleResult ?? debouncedResult ?? ''}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ export function TabEditTemplate({ activeID }: TabEditTemplateProps) {
|
|||
/>
|
||||
) : null}
|
||||
<FormPromptTemplate
|
||||
className='mt-12 xs:mt-4 w-100 md:w-180 min-w-70'
|
||||
className='mt-8 xs:mt-0 w-100 md:w-180 min-w-70'
|
||||
isMutable={isMutable}
|
||||
promptTemplate={promptTemplate}
|
||||
toggleReset={toggleReset}
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||
import { describePromptVariable } from '../../labels';
|
||||
import { PromptVariableType } from '../../models/prompting';
|
||||
|
||||
/** Displays all prompt variable types with their descriptions. */
|
||||
export function TabViewVariables() {
|
||||
return <div className='pt-8 border rounded'>View all variables</div>;
|
||||
return (
|
||||
<div className='pt-8'>
|
||||
<ul className='space-y-1'>
|
||||
{Object.values(PromptVariableType).map(variableType => (
|
||||
<li key={variableType} className='flex flex-col'>
|
||||
<span className='font-math text-primary'>{`{{${variableType}}}`}</span>
|
||||
<span className='font-main text-muted-foreground'>{describePromptVariable(variableType)}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ export function SidePanel({ isMounted, className }: SidePanelProps) {
|
|||
selectedItems.length === 1 && selectedItems[0].nodeType === NodeType.BLOCK ? selectedItems[0] : null;
|
||||
const selectedSchema = selectedOperation?.result ?? null;
|
||||
|
||||
const debouncedMounted = useDebounce(isMounted, PARAMETER.moveDuration);
|
||||
const [debouncedMounted] = useDebounce(isMounted, PARAMETER.moveDuration);
|
||||
const closePanel = usePreferencesStore(state => state.toggleShowOssSidePanel);
|
||||
const sidePanelHeight = useMainHeight();
|
||||
|
||||
|
|
|
@ -221,6 +221,20 @@
|
|||
}
|
||||
}
|
||||
|
||||
@utility cc-prompt-result {
|
||||
transition-property: clip-path, height;
|
||||
transition-duration: var(--duration-move);
|
||||
transition-timing-function: var(--ease-in-out);
|
||||
|
||||
clip-path: inset(0% 0% 100% 0%);
|
||||
height: 0;
|
||||
|
||||
&.open {
|
||||
clip-path: inset(0% 0% 0% 0%);
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@utility cc-parsing-result {
|
||||
transition-property: clip-path, padding, margin, border, height;
|
||||
transition-duration: var(--duration-move);
|
||||
|
|
Loading…
Reference in New Issue
Block a user