F: Implementing prompt UI pt1

This commit is contained in:
Ivan 2025-07-14 19:05:50 +03:00
parent 1d11bd4ab5
commit c980ebab5a
11 changed files with 188 additions and 2 deletions

View File

@ -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: <NotFoundPage />

View File

@ -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) =>

View File

@ -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 };
}

View File

@ -0,0 +1 @@
export { PromptTemplatesPage as Component } from './prompt-templates-page';

View File

@ -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 (
<div className='flex border-r-2 px-2'>
<MiniButton
noHover
noPadding
title='Новый шаблон'
icon={<IconNewItem size='1.25rem' />}
className='h-full text-muted-foreground hover:text-constructive cc-animate-color bg-transparent'
onClick={handleNewTemplate}
/>
</div>
);
}

View File

@ -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 (
<RequireAuth>
<TemplatesTabs activeID={urlData.active} tab={urlData.tab} />
</RequireAuth>
);
}
export default PromptTemplatesPage;

View File

@ -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 <div className='p-4 border rounded'>Prompt Editor {promptTemplate.label}</div>;
}

View File

@ -0,0 +1 @@
export { TabListTemplates } from './tab-list-templates';

View File

@ -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<Element>) {
router.push({ path: urls.prompt_template(row.id, PromptTabID.EDIT), newTab: event.ctrlKey || event.metaKey });
}
return (
<DataTable
data={mockPrompts}
columns={[
{ accessorKey: 'label', header: 'Name' },
{ accessorKey: 'description', header: 'Description' }
]}
onRowDoubleClicked={handleRowDoubleClicked}
/>
);
}

View File

@ -0,0 +1,3 @@
export function TabViewVariables() {
return <div className='p-4 border rounded'>View all variables</div>;
}

View File

@ -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 (
<Tabs selectedIndex={tab} onSelect={onSelectTab} className='relative flex flex-col min-w-fit items-center'>
<TabList className='absolute z-sticky flex border-b-2 border-x-2 divide-x-2 bg-background'>
<MenuTemplates />
<TabLabel label='Список' />
<TabLabel label='Редактор' />
<TabLabel label='Переменные' />
</TabList>
<div className='overflow-x-hidden'>
<TabPanel>
<TabListTemplates activeID={activeID} />
</TabPanel>
<TabPanel>
{activeID ? (
<Suspense fallback={<Loader circular scale={1.5} />}>
<TabEditTemplate activeID={activeID} />
</Suspense>
) : null}
</TabPanel>
<TabPanel>
<TabViewVariables />
</TabPanel>
</div>
</Tabs>
);
}