From c980ebab5a3d004ee1d6d6f25e4240f5511c4887 Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Mon, 14 Jul 2025 19:05:50 +0300 Subject: [PATCH] F: Implementing prompt UI pt1 --- rsconcept/frontend/src/app/router.tsx | 4 ++ rsconcept/frontend/src/app/urls.ts | 2 + .../ai/backend/use-prompt-template.tsx | 4 +- .../ai/pages/prompt-templates-page/index.tsx | 1 + .../prompt-templates-page/menu-templates.tsx | 22 ++++++ .../prompt-templates-page.tsx | 34 +++++++++ .../tab-edit-template.tsx | 11 +++ .../tab-list-templates/index.tsx | 1 + .../tab-list-templates/tab-list-templates.tsx | 36 ++++++++++ .../tab-view-variables.tsx | 3 + .../prompt-templates-page/templates-tabs.tsx | 72 +++++++++++++++++++ 11 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 rsconcept/frontend/src/features/ai/pages/prompt-templates-page/index.tsx create mode 100644 rsconcept/frontend/src/features/ai/pages/prompt-templates-page/menu-templates.tsx create mode 100644 rsconcept/frontend/src/features/ai/pages/prompt-templates-page/prompt-templates-page.tsx create mode 100644 rsconcept/frontend/src/features/ai/pages/prompt-templates-page/tab-edit-template.tsx create mode 100644 rsconcept/frontend/src/features/ai/pages/prompt-templates-page/tab-list-templates/index.tsx create mode 100644 rsconcept/frontend/src/features/ai/pages/prompt-templates-page/tab-list-templates/tab-list-templates.tsx create mode 100644 rsconcept/frontend/src/features/ai/pages/prompt-templates-page/tab-view-variables.tsx create mode 100644 rsconcept/frontend/src/features/ai/pages/prompt-templates-page/templates-tabs.tsx diff --git a/rsconcept/frontend/src/app/router.tsx b/rsconcept/frontend/src/app/router.tsx index b4e408ed..cf655d5a 100644 --- a/rsconcept/frontend/src/app/router.tsx +++ b/rsconcept/frontend/src/app/router.tsx @@ -85,6 +85,10 @@ export const Router = createBrowserRouter([ path: `${routes.database_schema}`, lazy: () => import('@/features/home/database-schema-page') }, + { + path: routes.prompt_templates, + lazy: () => import('@/features/ai/pages/prompt-templates-page') + }, { path: '*', element: diff --git a/rsconcept/frontend/src/app/urls.ts b/rsconcept/frontend/src/app/urls.ts index 52452fc7..20198fbc 100644 --- a/rsconcept/frontend/src/app/urls.ts +++ b/rsconcept/frontend/src/app/urls.ts @@ -39,6 +39,8 @@ export const urls = { library_filter: (strategy: string) => `/library?filter=${strategy}`, create_schema: `/${routes.create_schema}`, prompt_templates: `/${routes.prompt_templates}`, + prompt_template: (active: number | null, tab: number) => + `/prompt-templates?tab=${tab}${active ? `&active=${active}` : ''}`, manuals: `/${routes.manuals}`, help_topic: (topic: string) => `/manuals?topic=${topic}`, schema: (id: number | string, version?: number | string) => diff --git a/rsconcept/frontend/src/features/ai/backend/use-prompt-template.tsx b/rsconcept/frontend/src/features/ai/backend/use-prompt-template.tsx index 73f18b29..e83efbf2 100644 --- a/rsconcept/frontend/src/features/ai/backend/use-prompt-template.tsx +++ b/rsconcept/frontend/src/features/ai/backend/use-prompt-template.tsx @@ -6,12 +6,12 @@ export function usePromptTemplate(id: number) { const { data, isLoading, error } = useQuery({ ...promptsApi.getPromptTemplateQueryOptions(id) }); - return { data, isLoading, error }; + return { promptTemplate: data, isLoading, error }; } export function usePromptTemplateSuspense(id: number) { const { data } = useSuspenseQuery({ ...promptsApi.getPromptTemplateQueryOptions(id) }); - return { data }; + return { promptTemplate: data }; } diff --git a/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/index.tsx b/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/index.tsx new file mode 100644 index 00000000..7a9efcbe --- /dev/null +++ b/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/index.tsx @@ -0,0 +1 @@ +export { PromptTemplatesPage as Component } from './prompt-templates-page'; diff --git a/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/menu-templates.tsx b/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/menu-templates.tsx new file mode 100644 index 00000000..81961603 --- /dev/null +++ b/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/menu-templates.tsx @@ -0,0 +1,22 @@ +import { MiniButton } from '@/components/control'; +import { IconNewItem } from '@/components/icons'; +import { notImplemented } from '@/utils/utils'; + +export function MenuTemplates() { + function handleNewTemplate() { + notImplemented(); + } + + return ( +
+ } + className='h-full text-muted-foreground hover:text-constructive cc-animate-color bg-transparent' + onClick={handleNewTemplate} + /> +
+ ); +} diff --git a/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/prompt-templates-page.tsx b/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/prompt-templates-page.tsx new file mode 100644 index 00000000..3fe1e820 --- /dev/null +++ b/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/prompt-templates-page.tsx @@ -0,0 +1,34 @@ +import { z } from 'zod'; + +import { useBlockNavigation } from '@/app'; +import { RequireAuth } from '@/features/auth/components/require-auth'; + +import { useQueryStrings } from '@/hooks/use-query-strings'; +import { useModificationStore } from '@/stores/modification'; + +import { PromptTabID, TemplatesTabs } from './templates-tabs'; + +const paramsSchema = z.strictObject({ + tab: z.preprocess(v => (v ? Number(v) : undefined), z.nativeEnum(PromptTabID).default(PromptTabID.LIST)), + active: z.preprocess(v => (v ? Number(v) : undefined), z.number().nullable().default(null)) +}); + +export function PromptTemplatesPage() { + const query = useQueryStrings(); + + const urlData = paramsSchema.parse({ + tab: query.get('tab'), + active: query.get('active') + }); + + const { isModified } = useModificationStore(); + useBlockNavigation(isModified); + + return ( + + + + ); +} + +export default PromptTemplatesPage; diff --git a/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/tab-edit-template.tsx b/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/tab-edit-template.tsx new file mode 100644 index 00000000..267453e9 --- /dev/null +++ b/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/tab-edit-template.tsx @@ -0,0 +1,11 @@ +import { usePromptTemplateSuspense } from '../../backend/use-prompt-template'; + +interface TabEditTemplateProps { + activeID: number; +} + +export function TabEditTemplate({ activeID }: TabEditTemplateProps) { + const { promptTemplate } = usePromptTemplateSuspense(activeID); + + return
Prompt Editor {promptTemplate.label}
; +} diff --git a/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/tab-list-templates/index.tsx b/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/tab-list-templates/index.tsx new file mode 100644 index 00000000..6b614e60 --- /dev/null +++ b/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/tab-list-templates/index.tsx @@ -0,0 +1 @@ +export { TabListTemplates } from './tab-list-templates'; diff --git a/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/tab-list-templates/tab-list-templates.tsx b/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/tab-list-templates/tab-list-templates.tsx new file mode 100644 index 00000000..a1dd6939 --- /dev/null +++ b/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/tab-list-templates/tab-list-templates.tsx @@ -0,0 +1,36 @@ +import { urls, useConceptNavigation } from '@/app'; +import { type IPromptTemplate } from '@/features/ai/backend/types'; + +import { DataTable } from '@/components/data-table'; + +import { PromptTabID } from '../templates-tabs'; + +// Placeholder for prompt list data +const mockPrompts: IPromptTemplate[] = [ + { id: 1, owner: null, label: 'Prompt 1', description: 'Description 1', text: '', is_shared: false }, + { id: 2, owner: null, label: 'Prompt 2', description: 'Description 2', text: '', is_shared: false } +]; + +interface TabListTemplatesProps { + activeID: number | null; +} + +export function TabListTemplates({ activeID }: TabListTemplatesProps) { + const router = useConceptNavigation(); + console.log(activeID); + + function handleRowDoubleClicked(row: IPromptTemplate, event: React.MouseEvent) { + router.push({ path: urls.prompt_template(row.id, PromptTabID.EDIT), newTab: event.ctrlKey || event.metaKey }); + } + + return ( + + ); +} diff --git a/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/tab-view-variables.tsx b/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/tab-view-variables.tsx new file mode 100644 index 00000000..87dba601 --- /dev/null +++ b/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/tab-view-variables.tsx @@ -0,0 +1,3 @@ +export function TabViewVariables() { + return
View all variables
; +} diff --git a/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/templates-tabs.tsx b/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/templates-tabs.tsx new file mode 100644 index 00000000..f9d32a80 --- /dev/null +++ b/rsconcept/frontend/src/features/ai/pages/prompt-templates-page/templates-tabs.tsx @@ -0,0 +1,72 @@ +import { Suspense } from 'react'; + +import { urls, useConceptNavigation } from '@/app'; + +import { Loader } from '@/components/loader'; +import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs'; + +import { MenuTemplates } from './menu-templates'; +import { TabEditTemplate } from './tab-edit-template'; +import { TabListTemplates } from './tab-list-templates'; +import { TabViewVariables } from './tab-view-variables'; + +export const PromptTabID = { + LIST: 0, + EDIT: 1, + VARIABLES: 2 +} as const; +export type PromptTabID = (typeof PromptTabID)[keyof typeof PromptTabID]; + +interface TemplatesTabsProps { + activeID: number | null; + tab: PromptTabID; +} + +export function TemplatesTabs({ activeID, tab }: TemplatesTabsProps) { + const router = useConceptNavigation(); + + function onSelectTab(index: number, last: number, event: Event) { + if (last === index) { + return; + } + if (event.type == 'keydown') { + const kbEvent = event as KeyboardEvent; + if (kbEvent.altKey) { + if (kbEvent.code === 'ArrowLeft') { + router.back(); + return; + } else if (kbEvent.code === 'ArrowRight') { + router.forward(); + return; + } + } + } + router.replace({ path: urls.prompt_template(activeID, index as PromptTabID) }); + } + + return ( + + + + + + + +
+ + + + + {activeID ? ( + }> + + + ) : null} + + + + +
+
+ ); +}