diff --git a/rsconcept/frontend/src/app/navigation/menu-ai.tsx b/rsconcept/frontend/src/app/navigation/menu-ai.tsx index 3ddd5cf9..f41127b5 100644 --- a/rsconcept/frontend/src/app/navigation/menu-ai.tsx +++ b/rsconcept/frontend/src/app/navigation/menu-ai.tsx @@ -25,7 +25,7 @@ export function MenuAI() { event.preventDefault(); event.stopPropagation(); menu.hide(); - showAIPrompt({}); + showAIPrompt(); } return ( diff --git a/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt.tsx b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt.tsx deleted file mode 100644 index 3f02da5d..00000000 --- a/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt.tsx +++ /dev/null @@ -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(mockPrompts[0]); - - return ( - { - e.preventDefault(); - // Placeholder for generate logic - }} - className='w-120 px-6 cc-column' - > -
- - -
- {selectedPrompt && ( -
-
Label:
-
{selectedPrompt.label}
-
Description:
-
{selectedPrompt.description}
-
Template Text:
-
{selectedPrompt.text}
-
- )} -
- ); -} 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 new file mode 100644 index 00000000..4692b311 --- /dev/null +++ b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/dlg-ai-prompt.tsx @@ -0,0 +1,43 @@ +import { Suspense, useState } from 'react'; + +import { Loader } from '@/components/loader'; +import { ModalView } from '@/components/modal'; +import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs'; + +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.SELECT); + const [selectedPromptId, setSelectedPromptId] = useState(null); + + return ( + + setActiveTab(index as TabID)}> + + + + + + +
+ }> + + + + {selectedPromptId ? : null} + {selectedPromptId ? : null} + +
+
+
+ ); +} diff --git a/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/index.tsx b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/index.tsx new file mode 100644 index 00000000..153d3858 --- /dev/null +++ b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/index.tsx @@ -0,0 +1 @@ +export * from './dlg-ai-prompt'; diff --git a/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/tab-prompt-result.tsx b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/tab-prompt-result.tsx new file mode 100644 index 00000000..5341e549 --- /dev/null +++ b/rsconcept/frontend/src/features/ai/dialogs/dlg-ai-prompt/tab-prompt-result.tsx @@ -0,0 +1,66 @@ +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 ( +
+ } + onClick={handleCopyPrompt} + disabled={!generatedMessage} + /> +