F: Implementing generator pt1
This commit is contained in:
parent
56f1bcacad
commit
2a3f413315
|
@ -25,7 +25,7 @@ export function MenuAI() {
|
|||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
menu.hide();
|
||||
showAIPrompt({});
|
||||
showAIPrompt();
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
import { useState } from 'react';
|
||||
|
||||
import { ModalForm } from '@/components/modal';
|
||||
|
||||
import { type IPromptTemplate } from '../backend/types';
|
||||
|
||||
export interface DlgAIPromptDialogProps {
|
||||
onPromptSelected?: (prompt: IPromptTemplate) => void;
|
||||
}
|
||||
|
||||
const mockPrompts: IPromptTemplate[] = [
|
||||
{
|
||||
id: 1,
|
||||
owner: null,
|
||||
label: 'Greeting',
|
||||
is_shared: true,
|
||||
description: 'A simple greeting prompt.',
|
||||
text: 'Hello, ${name}! How can I assist you today?'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
owner: null,
|
||||
is_shared: true,
|
||||
label: 'Summary',
|
||||
description: 'Summarize the following text.',
|
||||
text: 'Please summarize the following: ${text}'
|
||||
}
|
||||
];
|
||||
|
||||
export function DlgAIPromptDialog() {
|
||||
const [selectedPrompt, setSelectedPrompt] = useState<IPromptTemplate | null>(mockPrompts[0]);
|
||||
|
||||
return (
|
||||
<ModalForm
|
||||
header='AI Prompt Generator'
|
||||
submitText='Generate Prompt'
|
||||
canSubmit={!!selectedPrompt}
|
||||
onSubmit={e => {
|
||||
e.preventDefault();
|
||||
// Placeholder for generate logic
|
||||
}}
|
||||
className='w-120 px-6 cc-column'
|
||||
>
|
||||
<div className='mb-4'>
|
||||
<label htmlFor='prompt-select' className='block mb-2 font-semibold'>
|
||||
Select a prompt:
|
||||
</label>
|
||||
<select
|
||||
id='prompt-select'
|
||||
className='w-full border rounded p-2'
|
||||
value={selectedPrompt?.id}
|
||||
onChange={e => {
|
||||
const prompt = mockPrompts.find(p => String(p.id) === e.target.value) || null;
|
||||
setSelectedPrompt(prompt);
|
||||
}}
|
||||
>
|
||||
{mockPrompts.map(prompt => (
|
||||
<option key={prompt.id} value={prompt.id}>
|
||||
{prompt.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{selectedPrompt && (
|
||||
<div className='mb-4'>
|
||||
<div className='font-semibold'>Label:</div>
|
||||
<div className='mb-2'>{selectedPrompt.label}</div>
|
||||
<div className='font-semibold'>Description:</div>
|
||||
<div className='mb-2'>{selectedPrompt.description}</div>
|
||||
<div className='font-semibold'>Template Text:</div>
|
||||
<pre className='bg-gray-100 p-2 rounded'>{selectedPrompt.text}</pre>
|
||||
</div>
|
||||
)}
|
||||
</ModalForm>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
import { Suspense, useState } from 'react';
|
||||
|
||||
import { ComboBox } from '@/components/input/combo-box';
|
||||
import { Loader } from '@/components/loader';
|
||||
import { ModalView } from '@/components/modal';
|
||||
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs';
|
||||
|
||||
import { useAvailableTemplatesSuspense } from '../../backend/use-available-templates';
|
||||
|
||||
import { TabPromptResult } from './tab-prompt-result';
|
||||
import { TabPromptSelect } from './tab-prompt-select';
|
||||
import { TabPromptVariables } from './tab-prompt-variables';
|
||||
|
||||
export const TabID = {
|
||||
SELECT: 0,
|
||||
RESULT: 1,
|
||||
VARIABLES: 2
|
||||
} as const;
|
||||
type TabID = (typeof TabID)[keyof typeof TabID];
|
||||
|
||||
export function DlgAIPromptDialog() {
|
||||
const [activeTab, setActiveTab] = useState<TabID>(TabID.SELECT);
|
||||
const [selected, setSelected] = useState<number | null>(null);
|
||||
const { items: prompts } = useAvailableTemplatesSuspense();
|
||||
|
||||
return (
|
||||
<ModalView header='Генератор запросом LLM' className='w-100 sm:w-160 px-6'>
|
||||
<Tabs selectedIndex={activeTab} onSelect={index => setActiveTab(index as TabID)}>
|
||||
<TabList className='mb-3 mx-auto w-fit flex border divide-x rounded-none'>
|
||||
<TabLabel label='Шаблон' />
|
||||
<TabLabel label='Результат' disabled={!selected} />
|
||||
<TabLabel label='Переменные' disabled={!selected} />
|
||||
</TabList>
|
||||
|
||||
<div className='h-120 flex flex-col gap-2'>
|
||||
<ComboBox
|
||||
id='prompt-select'
|
||||
items={prompts}
|
||||
value={prompts?.find(p => p.id === selected) ?? null}
|
||||
onChange={item => setSelected(item?.id ?? 0)}
|
||||
idFunc={item => String(item.id)}
|
||||
labelValueFunc={item => item.label}
|
||||
labelOptionFunc={item => item.label}
|
||||
placeholder='Выберите шаблон'
|
||||
className='w-full'
|
||||
/>
|
||||
<Suspense fallback={<Loader />}>
|
||||
<TabPanel>{selected ? <TabPromptSelect promptID={selected} /> : null}</TabPanel>
|
||||
<TabPanel>{selected ? <TabPromptResult promptID={selected} /> : null}</TabPanel>
|
||||
<TabPanel>{selected ? <TabPromptVariables promptID={selected} /> : null}</TabPanel>
|
||||
</Suspense>
|
||||
</div>
|
||||
</Tabs>
|
||||
</ModalView>
|
||||
);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './dlg-ai-prompt';
|
|
@ -0,0 +1,65 @@
|
|||
import { useMemo } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import { MiniButton } from '@/components/control';
|
||||
import { IconClone } from '@/components/icons';
|
||||
import { TextArea } from '@/components/input';
|
||||
import { infoMsg } from '@/utils/labels';
|
||||
|
||||
import { usePromptTemplateSuspense } from '../../backend/use-prompt-template';
|
||||
import { PromptVariableType } from '../../models/prompting';
|
||||
import { extractPromptVariables } from '../../models/prompting-api';
|
||||
import { evaluatePromptVariable, useAIStore } from '../../stores/ai-context';
|
||||
|
||||
interface TabPromptResultProps {
|
||||
promptID: number;
|
||||
}
|
||||
|
||||
export function TabPromptResult({ promptID }: TabPromptResultProps) {
|
||||
const { promptTemplate } = usePromptTemplateSuspense(promptID);
|
||||
const context = useAIStore();
|
||||
const variables = useMemo(() => {
|
||||
return promptTemplate ? extractPromptVariables(promptTemplate.text) : [];
|
||||
}, [promptTemplate]);
|
||||
|
||||
const generatedMessage = (() => {
|
||||
if (!promptTemplate) {
|
||||
return '';
|
||||
}
|
||||
let result = promptTemplate.text;
|
||||
for (const variable of variables) {
|
||||
const type = Object.values(PromptVariableType).find(t => t === variable);
|
||||
let value = '';
|
||||
if (type) {
|
||||
value = evaluatePromptVariable(type, context) ?? '';
|
||||
}
|
||||
result = result.replace(new RegExp(`\{\{${variable}\}\}`, 'g'), value || `${variable}`);
|
||||
}
|
||||
return result;
|
||||
})();
|
||||
|
||||
function handleCopyPrompt() {
|
||||
void navigator.clipboard.writeText(generatedMessage);
|
||||
toast.success(infoMsg.promptReady);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
<MiniButton
|
||||
title='Скопировать в буфер обмена'
|
||||
className='absolute -top-23 left-0'
|
||||
icon={<IconClone size='1.25rem' className='icon-green' />}
|
||||
onClick={handleCopyPrompt}
|
||||
disabled={!generatedMessage}
|
||||
/>
|
||||
<TextArea
|
||||
aria-label='Сгенерированное сообщение'
|
||||
value={generatedMessage}
|
||||
placeholder='Текст шаблона пуст'
|
||||
disabled
|
||||
fitContent
|
||||
className='w-full max-h-100 min-h-12'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
import { TextArea } from '@/components/input';
|
||||
|
||||
import { usePromptTemplateSuspense } from '../../backend/use-prompt-template';
|
||||
|
||||
interface TabPromptSelectProps {
|
||||
promptID: number;
|
||||
}
|
||||
|
||||
export function TabPromptSelect({ promptID }: TabPromptSelectProps) {
|
||||
const { promptTemplate } = usePromptTemplateSuspense(promptID);
|
||||
|
||||
return (
|
||||
<div className='cc-column'>
|
||||
{promptTemplate && (
|
||||
<div className='flex flex-col gap-2'>
|
||||
<TextArea
|
||||
id='prompt-label'
|
||||
label='Название' //
|
||||
value={promptTemplate.label}
|
||||
disabled
|
||||
noResize
|
||||
rows={1}
|
||||
/>
|
||||
<TextArea
|
||||
id='prompt-description'
|
||||
label='Описание'
|
||||
value={promptTemplate.description}
|
||||
disabled
|
||||
noResize
|
||||
rows={3}
|
||||
/>
|
||||
<TextArea
|
||||
id='prompt-text' //
|
||||
label='Текст шаблона'
|
||||
value={promptTemplate.text}
|
||||
disabled
|
||||
noResize
|
||||
rows={6}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
'use client';
|
||||
|
||||
import { usePromptTemplateSuspense } from '../../backend/use-prompt-template';
|
||||
import { describePromptVariable } from '../../labels';
|
||||
import { PromptVariableType } from '../../models/prompting';
|
||||
import { extractPromptVariables } from '../../models/prompting-api';
|
||||
import { useAvailableVariables } from '../../stores/use-available-variables';
|
||||
|
||||
interface TabPromptVariablesProps {
|
||||
promptID: number;
|
||||
}
|
||||
|
||||
export function TabPromptVariables({ promptID }: TabPromptVariablesProps) {
|
||||
const { promptTemplate } = usePromptTemplateSuspense(promptID);
|
||||
const variables = extractPromptVariables(promptTemplate.text);
|
||||
const availableTypes = useAvailableVariables();
|
||||
return (
|
||||
<ul>
|
||||
{variables.length === 0 && <li>Нет переменных</li>}
|
||||
{variables.map(variable => {
|
||||
const type = Object.values(PromptVariableType).find(t => t === variable);
|
||||
const isAvailable = !!type && availableTypes.includes(type);
|
||||
return (
|
||||
<li key={variable} className={isAvailable ? 'text-green-700 font-bold' : 'text-gray-500'}>
|
||||
{variable} — {type ? describePromptVariable(type) : 'Неизвестная переменная'}
|
||||
{isAvailable ? ' (доступна)' : ' (нет в контексте)'}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
|
@ -2,6 +2,7 @@ import { create } from 'zustand';
|
|||
|
||||
import { type IBlock, type IOperationSchema } from '@/features/oss/models/oss';
|
||||
import { type IConstituenta, type IRSForm } from '@/features/rsform';
|
||||
import { labelCstTypification } from '@/features/rsform/labels';
|
||||
|
||||
import { PromptVariableType } from '../models/prompting';
|
||||
|
||||
|
@ -55,10 +56,23 @@ export function evaluatePromptVariable(variableType: PromptVariableType, context
|
|||
case PromptVariableType.OSS:
|
||||
return context.currentOSS?.title ?? '';
|
||||
case PromptVariableType.SCHEMA:
|
||||
return context.currentSchema?.title ?? '';
|
||||
return context.currentSchema ? generateSchemaPrompt(context.currentSchema) : '';
|
||||
case PromptVariableType.BLOCK:
|
||||
return context.currentBlock?.title ?? '';
|
||||
case PromptVariableType.CONSTITUENTA:
|
||||
return context.currentConstituenta?.alias ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
// ====== Internals =========
|
||||
function generateSchemaPrompt(schema: IRSForm): string {
|
||||
let body = `Название концептуальной схемы: ${schema.title}\n`;
|
||||
body += `[${schema.alias}] Описание: "${schema.description}"\n\n`;
|
||||
body += 'Понятия:\n';
|
||||
schema.items.forEach(item => {
|
||||
body += `${item.alias} - "${labelCstTypification(item)}" - "${item.term_resolved}" - "${
|
||||
item.definition_formal
|
||||
}" - "${item.definition_resolved}" - "${item.convention}"\n`;
|
||||
});
|
||||
return body;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { urls, useConceptNavigation } from '@/app';
|
||||
import { useAIStore } from '@/features/ai/stores/ai-context';
|
||||
import { useAuthSuspense } from '@/features/auth';
|
||||
import { useLibrarySearchStore } from '@/features/library';
|
||||
import { useDeleteItem } from '@/features/library/backend/use-delete-item';
|
||||
|
@ -30,6 +31,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
|
|||
const role = useRoleStore(state => state.role);
|
||||
const setSearchLocation = useLibrarySearchStore(state => state.setLocation);
|
||||
const searchLocation = useLibrarySearchStore(state => state.location);
|
||||
const setCurrentOSS = useAIStore(state => state.setCurrentOSS);
|
||||
|
||||
const { user } = useAuthSuspense();
|
||||
const { schema } = useOssSuspense({ itemID: itemID });
|
||||
|
@ -50,6 +52,11 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
|
|||
adminMode: adminMode
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentOSS(schema);
|
||||
return () => setCurrentOSS(null);
|
||||
}, [schema, setCurrentOSS]);
|
||||
|
||||
function navigateTab(tab: OssTabID) {
|
||||
const url = urls.oss_props({
|
||||
id: schema.id,
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { urls, useConceptNavigation } from '@/app';
|
||||
import { useAIStore } from '@/features/ai/stores/ai-context';
|
||||
import { useAuthSuspense } from '@/features/auth';
|
||||
import { useLibrarySearchStore } from '@/features/library';
|
||||
import { useDeleteItem } from '@/features/library/backend/use-delete-item';
|
||||
|
@ -68,6 +69,8 @@ export const RSEditState = ({
|
|||
const showCreateCst = useDialogsStore(state => state.showCreateCst);
|
||||
const showDeleteCst = useDialogsStore(state => state.showDeleteCst);
|
||||
const showCstTemplate = useDialogsStore(state => state.showCstTemplate);
|
||||
const setCurrentSchema = useAIStore(state => state.setCurrentSchema);
|
||||
const setCurrentConstituenta = useAIStore(state => state.setCurrentConstituenta);
|
||||
|
||||
useAdjustRole({
|
||||
isOwner: isOwned,
|
||||
|
@ -76,6 +79,16 @@ export const RSEditState = ({
|
|||
adminMode: adminMode
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentSchema(schema);
|
||||
return () => setCurrentSchema(null);
|
||||
}, [schema, setCurrentSchema]);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentConstituenta(activeCst);
|
||||
return () => setCurrentConstituenta(null);
|
||||
}, [activeCst, setCurrentConstituenta]);
|
||||
|
||||
function handleSetFocus(newValue: IConstituenta | null) {
|
||||
setFocusCst(newValue);
|
||||
setSelected([]);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { create } from 'zustand';
|
||||
|
||||
import { type DlgAIPromptDialogProps } from '@/features/ai/dialogs/dlg-ai-prompt';
|
||||
import { type DlgCreatePromptTemplateProps } from '@/features/ai/dialogs/dlg-create-prompt-template';
|
||||
import { type DlgChangeLocationProps } from '@/features/library/dialogs/dlg-change-location';
|
||||
import { type DlgCloneLibraryItemProps } from '@/features/library/dialogs/dlg-clone-library-item';
|
||||
|
@ -115,7 +114,7 @@ interface DialogsStore {
|
|||
showEditCst: (props: DlgEditCstProps) => void;
|
||||
showCreateSchema: (props: DlgCreateSchemaProps) => void;
|
||||
showImportSchema: (props: DlgImportSchemaProps) => void;
|
||||
showAIPrompt: (props: DlgAIPromptDialogProps) => void;
|
||||
showAIPrompt: () => void;
|
||||
showCreatePromptTemplate: (props: DlgCreatePromptTemplateProps) => void;
|
||||
}
|
||||
|
||||
|
@ -159,6 +158,6 @@ export const useDialogsStore = create<DialogsStore>()(set => ({
|
|||
showEditCst: props => set({ active: DialogType.EDIT_CONSTITUENTA, props: props }),
|
||||
showCreateSchema: props => set({ active: DialogType.CREATE_SCHEMA, props: props }),
|
||||
showImportSchema: props => set({ active: DialogType.IMPORT_SCHEMA, props: props }),
|
||||
showAIPrompt: (props: DlgAIPromptDialogProps) => set({ active: DialogType.AI_PROMPT, props: props }),
|
||||
showAIPrompt: () => set({ active: DialogType.AI_PROMPT, props: null }),
|
||||
showCreatePromptTemplate: props => set({ active: DialogType.CREATE_PROMPT_TEMPLATE, props: props })
|
||||
}));
|
||||
|
|
Loading…
Reference in New Issue
Block a user