diff --git a/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/ai-prompt-tabs.tsx b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/ai-prompt-tabs.tsx new file mode 100644 index 00000000..1b183ab9 --- /dev/null +++ b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/ai-prompt-tabs.tsx @@ -0,0 +1,81 @@ +'use client'; + +import { useEffect, useState } from 'react'; + +import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs'; + +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'; + +import { MenuAIPrompt } from './menu-ai-prompt'; +import { TabPromptEdit } from './tab-prompt-edit'; +import { TabPromptResult } from './tab-prompt-result'; +import { TabPromptVariables } from './tab-prompt-variables'; + +interface AIPromptTabsProps { + promptID: number; + activeTab: number; + setActiveTab: (value: TabID) => void; +} + +export const TabID = { + TEMPLATE: 0, + RESULT: 1, + VARIABLES: 2 +} as const; +type TabID = (typeof TabID)[keyof typeof TabID]; + +export function AIPromptTabs({ promptID, activeTab, setActiveTab }: AIPromptTabsProps) { + const context = useAIStore(); + const { promptTemplate } = usePromptTemplateSuspense(promptID); + const [text, setText] = useState(promptTemplate.text); + const variables = extractPromptVariables(text); + + const generatedPrompt = (() => { + let result = 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; + })(); + + useEffect(() => { + setText(promptTemplate.text); + }, [promptTemplate]); + + return ( + setActiveTab(index as TabID)}> + + + + + + + + +
+ + + + + + + + + +
+
+ ); +} diff --git a/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/dlg-ai-prompt.tsx b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/dlg-ai-prompt.tsx index eb26e538..86e1fbba 100644 --- a/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/dlg-ai-prompt.tsx +++ b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/dlg-ai-prompt.tsx @@ -3,54 +3,32 @@ 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]; +import { AIPromptTabs, TabID } from './ai-prompt-tabs'; export function DlgAIPromptDialog() { - const [activeTab, setActiveTab] = useState(TabID.SELECT); + const [activeTab, setActiveTab] = useState(TabID.TEMPLATE); const [selected, setSelected] = useState(null); const { items: prompts } = useAvailableTemplatesSuspense(); return ( - - setActiveTab(index as TabID)}> - - - - - - -
- 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' - /> - }> - {selected ? : null} - {selected ? : null} - {selected ? : null} - -
-
+ + 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' + /> + }> + {selected ? : null} + ); } diff --git a/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/menu-ai-prompt.tsx b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/menu-ai-prompt.tsx new file mode 100644 index 00000000..fff32b09 --- /dev/null +++ b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/menu-ai-prompt.tsx @@ -0,0 +1,54 @@ +'use client'; + +import { toast } from 'react-toastify'; + +import { urls, useConceptNavigation } from '@/app'; + +import { MiniButton } from '@/components/control'; +import { IconClone, IconEdit2 } from '@/components/icons'; +import { useDialogsStore } from '@/stores/dialogs'; +import { infoMsg } from '@/utils/labels'; + +import { PromptTabID } from '../../pages/prompt-templates-page/templates-tabs'; + +interface MenuAIPromptProps { + promptID: number; + generatedPrompt: string; +} + +export function MenuAIPrompt({ promptID, generatedPrompt }: MenuAIPromptProps) { + const router = useConceptNavigation(); + const hideDialog = useDialogsStore(state => state.hideDialog); + + function navigatePrompt() { + hideDialog(); + router.push({ path: urls.prompt_template(promptID, PromptTabID.EDIT) }); + } + + function handleCopyPrompt() { + void navigator.clipboard.writeText(generatedPrompt); + toast.success(infoMsg.promptReady); + } + + return ( +
+ } + className='h-full pl-2 text-muted-foreground hover:text-primary cc-animate-color bg-transparent' + onClick={navigatePrompt} + /> + } + className='h-full pl-2 text-muted-foreground hover:text-constructive cc-animate-color bg-transparent' + onClick={handleCopyPrompt} + disabled={!generatedPrompt} + /> +
+ ); +} diff --git a/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/tab-prompt-edit.tsx b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/tab-prompt-edit.tsx new file mode 100644 index 00000000..0a35906c --- /dev/null +++ b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/tab-prompt-edit.tsx @@ -0,0 +1,34 @@ +import { TextArea } from '@/components/input'; + +interface TabPromptEditProps { + label: string; + description: string; + text: string; + setText: (value: string) => void; +} + +export function TabPromptEdit({ label, description, text, setText }: TabPromptEditProps) { + return ( +
+
+