F: Implementing prompt UI pt2

This commit is contained in:
Ivan 2025-07-14 22:31:20 +03:00
parent c980ebab5a
commit 2beed1c1c6
14 changed files with 133 additions and 22 deletions

View File

@ -143,6 +143,11 @@ const DlgImportSchema = React.lazy(() =>
const DlgAIPromptDialog = React.lazy(() =>
import('@/features/ai/dialogs/dlg-ai-prompt').then(module => ({ default: module.DlgAIPromptDialog }))
);
const DlgCreatePromptTemplate = React.lazy(() =>
import('@/features/ai/dialogs/dlg-create-prompt-template').then(module => ({
default: module.DlgCreatePromptTemplate
}))
);
export const GlobalDialogs = () => {
const active = useDialogsStore(state => state.active);
@ -213,5 +218,7 @@ export const GlobalDialogs = () => {
return <DlgImportSchema />;
case DialogType.AI_PROMPT:
return <DlgAIPromptDialog />;
case DialogType.CREATE_PROMPT_TEMPLATE:
return <DlgCreatePromptTemplate />;
}
};

View File

@ -1,5 +1,6 @@
import { createBrowserRouter } from 'react-router';
import { prefetchAvailableTemplates } from '@/features/ai/backend/use-available-templates';
import { prefetchAuth } from '@/features/auth/backend/use-auth';
import { LoginPage } from '@/features/auth/pages/login-page';
import { HomePage } from '@/features/home/home-page';
@ -87,6 +88,7 @@ export const Router = createBrowserRouter([
},
{
path: routes.prompt_templates,
loader: prefetchAvailableTemplates,
lazy: () => import('@/features/ai/pages/prompt-templates-page')
},
{

View File

@ -1,17 +1,23 @@
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
import { queryClient } from '@/backend/query-client';
import { promptsApi } from './api';
export function useAvailableTemplates() {
const { data, isLoading, error } = useQuery({
...promptsApi.getAvailableTemplatesQueryOptions()
});
return { data, isLoading, error };
return { items: data, isLoading, error };
}
export function useAvailableTemplatesSuspense() {
const { data } = useSuspenseQuery({
...promptsApi.getAvailableTemplatesQueryOptions()
});
return { data };
return { items: data };
}
export function prefetchAvailableTemplates() {
return queryClient.prefetchQuery(promptsApi.getAvailableTemplatesQueryOptions());
}

View File

@ -1,5 +1,7 @@
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
import { queryClient } from '@/backend/query-client';
import { promptsApi } from './api';
export function usePromptTemplate(id: number) {
@ -15,3 +17,7 @@ export function usePromptTemplateSuspense(id: number) {
});
return { promptTemplate: data };
}
export function prefetchPromptTemplate({ itemID }: { itemID: number }) {
return queryClient.prefetchQuery(promptsApi.getPromptTemplateQueryOptions(itemID));
}

View File

@ -0,0 +1,69 @@
import { Controller, useForm, useWatch } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { useAuthSuspense } from '@/features/auth';
import { Checkbox, TextArea, TextInput } from '@/components/input';
import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs';
import { type RO } from '@/utils/meta';
import { type ICreatePromptTemplateDTO, type IPromptTemplateDTO, schemaCreatePromptTemplate } from '../backend/types';
import { useAvailableTemplatesSuspense } from '../backend/use-available-templates';
import { useCreatePromptTemplate } from '../backend/use-create-prompt-template';
export interface DlgCreatePromptTemplateProps {
onCreate: (data: RO<IPromptTemplateDTO>) => void;
}
export function DlgCreatePromptTemplate() {
const { onCreate } = useDialogsStore(state => state.props as DlgCreatePromptTemplateProps);
const { createPromptTemplate } = useCreatePromptTemplate();
const { items: templates } = useAvailableTemplatesSuspense();
const { user } = useAuthSuspense();
const { handleSubmit, control, register } = useForm<ICreatePromptTemplateDTO>({
resolver: zodResolver(schemaCreatePromptTemplate),
defaultValues: {
label: '',
description: '',
text: '',
is_shared: false
}
});
const label = useWatch({ control, name: 'label' });
const isValid = label !== '' && !templates.find(template => template.label === label);
function onSubmit(data: ICreatePromptTemplateDTO) {
void createPromptTemplate(data).then(onCreate);
}
return (
<ModalForm
header='Создание шаблона'
submitText='Создать'
canSubmit={isValid}
onSubmit={event => void handleSubmit(onSubmit)(event)}
className='cc-column w-140 max-h-120 py-2 px-6'
>
<TextInput id='dlg_prompt_label' {...register('label')} label='Название шаблона' />
<TextArea id='dlg_prompt_description' {...register('description')} label='Описание' />
{user.is_staff ? (
<Controller
name='is_shared'
control={control}
render={({ field }) => (
<Checkbox
id='dlg_prompt_is_shared'
label='Общий шаблон'
value={field.value}
onChange={field.onChange}
onBlur={field.onBlur}
ref={field.ref}
/>
)}
/>
) : null}
</ModalForm>
);
}

View File

@ -1 +0,0 @@
export { DlgAIPromptDialog } from './dlg-ai-prompt';

View File

@ -1,10 +1,19 @@
import { urls, useConceptNavigation } from '@/app';
import { MiniButton } from '@/components/control';
import { IconNewItem } from '@/components/icons';
import { notImplemented } from '@/utils/utils';
import { useDialogsStore } from '@/stores/dialogs';
import { PromptTabID } from './templates-tabs';
export function MenuTemplates() {
const router = useConceptNavigation();
const showCreatePromptTemplate = useDialogsStore(state => state.showCreatePromptTemplate);
function handleNewTemplate() {
notImplemented();
showCreatePromptTemplate({
onCreate: data => router.push({ path: urls.prompt_template(data.id, PromptTabID.EDIT) })
});
}
return (

View File

@ -7,5 +7,5 @@ interface TabEditTemplateProps {
export function TabEditTemplate({ activeID }: TabEditTemplateProps) {
const { promptTemplate } = usePromptTemplateSuspense(activeID);
return <div className='p-4 border rounded'>Prompt Editor {promptTemplate.label}</div>;
return <div className='pt-8 border rounded'>Prompt Editor {promptTemplate.label}</div>;
}

View File

@ -2,14 +2,11 @@ import { urls, useConceptNavigation } from '@/app';
import { type IPromptTemplate } from '@/features/ai/backend/types';
import { DataTable } from '@/components/data-table';
import { type RO } from '@/utils/meta';
import { PromptTabID } from '../templates-tabs';
import { useAvailableTemplatesSuspense } from '../../backend/use-available-templates';
// 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 }
];
import { PromptTabID } from './templates-tabs';
interface TabListTemplatesProps {
activeID: number | null;
@ -17,19 +14,21 @@ interface TabListTemplatesProps {
export function TabListTemplates({ activeID }: TabListTemplatesProps) {
const router = useConceptNavigation();
const { items } = useAvailableTemplatesSuspense();
console.log(activeID);
function handleRowDoubleClicked(row: IPromptTemplate, event: React.MouseEvent<Element>) {
function handleRowDoubleClicked(row: RO<IPromptTemplate>, event: React.MouseEvent<Element>) {
router.push({ path: urls.prompt_template(row.id, PromptTabID.EDIT), newTab: event.ctrlKey || event.metaKey });
}
return (
<DataTable
data={mockPrompts}
data={items as IPromptTemplate[]}
columns={[
{ accessorKey: 'label', header: 'Name' },
{ accessorKey: 'description', header: 'Description' }
]}
className='pt-8'
onRowDoubleClicked={handleRowDoubleClicked}
/>
);

View File

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

View File

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

View File

@ -22,6 +22,14 @@ interface TemplatesTabsProps {
tab: PromptTabID;
}
function TabLoader() {
return (
<div className='h-20 mt-8 w-full flex items-center'>
<Loader scale={4} />
</div>
);
}
export function TemplatesTabs({ activeID, tab }: TemplatesTabsProps) {
const router = useConceptNavigation();
@ -54,12 +62,15 @@ export function TemplatesTabs({ activeID, tab }: TemplatesTabsProps) {
</TabList>
<div className='overflow-x-hidden'>
<TabPanel>
<TabListTemplates activeID={activeID} />
<Suspense fallback={<TabLoader />}>
<TabListTemplates activeID={activeID} />
</Suspense>
</TabPanel>
<TabPanel>
{activeID ? (
<Suspense fallback={<Loader circular scale={1.5} />}>
<TabEditTemplate activeID={activeID} />
<Suspense fallback={<TabLoader />}>
{' '}
<TabEditTemplate activeID={activeID} />{' '}
</Suspense>
) : null}
</TabPanel>

View File

@ -1,6 +1,7 @@
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';
import { type DlgCreateVersionProps } from '@/features/library/dialogs/dlg-create-version';
@ -69,7 +70,9 @@ export const DialogType = {
SHOW_TERM_GRAPH: 28,
CREATE_SCHEMA: 29,
IMPORT_SCHEMA: 30,
AI_PROMPT: 31
AI_PROMPT: 31,
CREATE_PROMPT_TEMPLATE: 32
} as const;
export type DialogType = (typeof DialogType)[keyof typeof DialogType];
@ -113,6 +116,7 @@ interface DialogsStore {
showCreateSchema: (props: DlgCreateSchemaProps) => void;
showImportSchema: (props: DlgImportSchemaProps) => void;
showAIPrompt: (props: DlgAIPromptDialogProps) => void;
showCreatePromptTemplate: (props: DlgCreatePromptTemplateProps) => void;
}
export const useDialogsStore = create<DialogsStore>()(set => ({
@ -155,5 +159,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: (props: DlgAIPromptDialogProps) => set({ active: DialogType.AI_PROMPT, props: props }),
showCreatePromptTemplate: props => set({ active: DialogType.CREATE_PROMPT_TEMPLATE, props: props })
}));

View File

@ -23,7 +23,6 @@
/* React Tooltip */
--rt-color-white: var(--color-input);
--rt-color-dark: var(--color-foreground);
}
/* Dark Theme */