F: Implementing prompt UI pt2
This commit is contained in:
parent
c980ebab5a
commit
2beed1c1c6
|
@ -143,6 +143,11 @@ const DlgImportSchema = React.lazy(() =>
|
||||||
const DlgAIPromptDialog = React.lazy(() =>
|
const DlgAIPromptDialog = React.lazy(() =>
|
||||||
import('@/features/ai/dialogs/dlg-ai-prompt').then(module => ({ default: module.DlgAIPromptDialog }))
|
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 = () => {
|
export const GlobalDialogs = () => {
|
||||||
const active = useDialogsStore(state => state.active);
|
const active = useDialogsStore(state => state.active);
|
||||||
|
@ -213,5 +218,7 @@ export const GlobalDialogs = () => {
|
||||||
return <DlgImportSchema />;
|
return <DlgImportSchema />;
|
||||||
case DialogType.AI_PROMPT:
|
case DialogType.AI_PROMPT:
|
||||||
return <DlgAIPromptDialog />;
|
return <DlgAIPromptDialog />;
|
||||||
|
case DialogType.CREATE_PROMPT_TEMPLATE:
|
||||||
|
return <DlgCreatePromptTemplate />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { createBrowserRouter } from 'react-router';
|
import { createBrowserRouter } from 'react-router';
|
||||||
|
|
||||||
|
import { prefetchAvailableTemplates } from '@/features/ai/backend/use-available-templates';
|
||||||
import { prefetchAuth } from '@/features/auth/backend/use-auth';
|
import { prefetchAuth } from '@/features/auth/backend/use-auth';
|
||||||
import { LoginPage } from '@/features/auth/pages/login-page';
|
import { LoginPage } from '@/features/auth/pages/login-page';
|
||||||
import { HomePage } from '@/features/home/home-page';
|
import { HomePage } from '@/features/home/home-page';
|
||||||
|
@ -87,6 +88,7 @@ export const Router = createBrowserRouter([
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: routes.prompt_templates,
|
path: routes.prompt_templates,
|
||||||
|
loader: prefetchAvailableTemplates,
|
||||||
lazy: () => import('@/features/ai/pages/prompt-templates-page')
|
lazy: () => import('@/features/ai/pages/prompt-templates-page')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,17 +1,23 @@
|
||||||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { queryClient } from '@/backend/query-client';
|
||||||
|
|
||||||
import { promptsApi } from './api';
|
import { promptsApi } from './api';
|
||||||
|
|
||||||
export function useAvailableTemplates() {
|
export function useAvailableTemplates() {
|
||||||
const { data, isLoading, error } = useQuery({
|
const { data, isLoading, error } = useQuery({
|
||||||
...promptsApi.getAvailableTemplatesQueryOptions()
|
...promptsApi.getAvailableTemplatesQueryOptions()
|
||||||
});
|
});
|
||||||
return { data, isLoading, error };
|
return { items: data, isLoading, error };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAvailableTemplatesSuspense() {
|
export function useAvailableTemplatesSuspense() {
|
||||||
const { data } = useSuspenseQuery({
|
const { data } = useSuspenseQuery({
|
||||||
...promptsApi.getAvailableTemplatesQueryOptions()
|
...promptsApi.getAvailableTemplatesQueryOptions()
|
||||||
});
|
});
|
||||||
return { data };
|
return { items: data };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prefetchAvailableTemplates() {
|
||||||
|
return queryClient.prefetchQuery(promptsApi.getAvailableTemplatesQueryOptions());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { queryClient } from '@/backend/query-client';
|
||||||
|
|
||||||
import { promptsApi } from './api';
|
import { promptsApi } from './api';
|
||||||
|
|
||||||
export function usePromptTemplate(id: number) {
|
export function usePromptTemplate(id: number) {
|
||||||
|
@ -15,3 +17,7 @@ export function usePromptTemplateSuspense(id: number) {
|
||||||
});
|
});
|
||||||
return { promptTemplate: data };
|
return { promptTemplate: data };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function prefetchPromptTemplate({ itemID }: { itemID: number }) {
|
||||||
|
return queryClient.prefetchQuery(promptsApi.getPromptTemplateQueryOptions(itemID));
|
||||||
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
export { DlgAIPromptDialog } from './dlg-ai-prompt';
|
|
|
@ -1,10 +1,19 @@
|
||||||
|
import { urls, useConceptNavigation } from '@/app';
|
||||||
|
|
||||||
import { MiniButton } from '@/components/control';
|
import { MiniButton } from '@/components/control';
|
||||||
import { IconNewItem } from '@/components/icons';
|
import { IconNewItem } from '@/components/icons';
|
||||||
import { notImplemented } from '@/utils/utils';
|
import { useDialogsStore } from '@/stores/dialogs';
|
||||||
|
|
||||||
|
import { PromptTabID } from './templates-tabs';
|
||||||
|
|
||||||
export function MenuTemplates() {
|
export function MenuTemplates() {
|
||||||
|
const router = useConceptNavigation();
|
||||||
|
const showCreatePromptTemplate = useDialogsStore(state => state.showCreatePromptTemplate);
|
||||||
|
|
||||||
function handleNewTemplate() {
|
function handleNewTemplate() {
|
||||||
notImplemented();
|
showCreatePromptTemplate({
|
||||||
|
onCreate: data => router.push({ path: urls.prompt_template(data.id, PromptTabID.EDIT) })
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -7,5 +7,5 @@ interface TabEditTemplateProps {
|
||||||
export function TabEditTemplate({ activeID }: TabEditTemplateProps) {
|
export function TabEditTemplate({ activeID }: TabEditTemplateProps) {
|
||||||
const { promptTemplate } = usePromptTemplateSuspense(activeID);
|
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>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,11 @@ import { urls, useConceptNavigation } from '@/app';
|
||||||
import { type IPromptTemplate } from '@/features/ai/backend/types';
|
import { type IPromptTemplate } from '@/features/ai/backend/types';
|
||||||
|
|
||||||
import { DataTable } from '@/components/data-table';
|
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
|
import { PromptTabID } from './templates-tabs';
|
||||||
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 {
|
interface TabListTemplatesProps {
|
||||||
activeID: number | null;
|
activeID: number | null;
|
||||||
|
@ -17,19 +14,21 @@ interface TabListTemplatesProps {
|
||||||
|
|
||||||
export function TabListTemplates({ activeID }: TabListTemplatesProps) {
|
export function TabListTemplates({ activeID }: TabListTemplatesProps) {
|
||||||
const router = useConceptNavigation();
|
const router = useConceptNavigation();
|
||||||
|
const { items } = useAvailableTemplatesSuspense();
|
||||||
console.log(activeID);
|
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 });
|
router.push({ path: urls.prompt_template(row.id, PromptTabID.EDIT), newTab: event.ctrlKey || event.metaKey });
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable
|
<DataTable
|
||||||
data={mockPrompts}
|
data={items as IPromptTemplate[]}
|
||||||
columns={[
|
columns={[
|
||||||
{ accessorKey: 'label', header: 'Name' },
|
{ accessorKey: 'label', header: 'Name' },
|
||||||
{ accessorKey: 'description', header: 'Description' }
|
{ accessorKey: 'description', header: 'Description' }
|
||||||
]}
|
]}
|
||||||
|
className='pt-8'
|
||||||
onRowDoubleClicked={handleRowDoubleClicked}
|
onRowDoubleClicked={handleRowDoubleClicked}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
|
@ -1 +0,0 @@
|
||||||
export { TabListTemplates } from './tab-list-templates';
|
|
|
@ -1,3 +1,3 @@
|
||||||
export function TabViewVariables() {
|
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>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,14 @@ interface TemplatesTabsProps {
|
||||||
tab: PromptTabID;
|
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) {
|
export function TemplatesTabs({ activeID, tab }: TemplatesTabsProps) {
|
||||||
const router = useConceptNavigation();
|
const router = useConceptNavigation();
|
||||||
|
|
||||||
|
@ -54,12 +62,15 @@ export function TemplatesTabs({ activeID, tab }: TemplatesTabsProps) {
|
||||||
</TabList>
|
</TabList>
|
||||||
<div className='overflow-x-hidden'>
|
<div className='overflow-x-hidden'>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
|
<Suspense fallback={<TabLoader />}>
|
||||||
<TabListTemplates activeID={activeID} />
|
<TabListTemplates activeID={activeID} />
|
||||||
|
</Suspense>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
{activeID ? (
|
{activeID ? (
|
||||||
<Suspense fallback={<Loader circular scale={1.5} />}>
|
<Suspense fallback={<TabLoader />}>
|
||||||
<TabEditTemplate activeID={activeID} />
|
{' '}
|
||||||
|
<TabEditTemplate activeID={activeID} />{' '}
|
||||||
</Suspense>
|
</Suspense>
|
||||||
) : null}
|
) : null}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
|
|
||||||
import { type DlgAIPromptDialogProps } from '@/features/ai/dialogs/dlg-ai-prompt';
|
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 DlgChangeLocationProps } from '@/features/library/dialogs/dlg-change-location';
|
||||||
import { type DlgCloneLibraryItemProps } from '@/features/library/dialogs/dlg-clone-library-item';
|
import { type DlgCloneLibraryItemProps } from '@/features/library/dialogs/dlg-clone-library-item';
|
||||||
import { type DlgCreateVersionProps } from '@/features/library/dialogs/dlg-create-version';
|
import { type DlgCreateVersionProps } from '@/features/library/dialogs/dlg-create-version';
|
||||||
|
@ -69,7 +70,9 @@ export const DialogType = {
|
||||||
SHOW_TERM_GRAPH: 28,
|
SHOW_TERM_GRAPH: 28,
|
||||||
CREATE_SCHEMA: 29,
|
CREATE_SCHEMA: 29,
|
||||||
IMPORT_SCHEMA: 30,
|
IMPORT_SCHEMA: 30,
|
||||||
AI_PROMPT: 31
|
|
||||||
|
AI_PROMPT: 31,
|
||||||
|
CREATE_PROMPT_TEMPLATE: 32
|
||||||
} as const;
|
} as const;
|
||||||
export type DialogType = (typeof DialogType)[keyof typeof DialogType];
|
export type DialogType = (typeof DialogType)[keyof typeof DialogType];
|
||||||
|
|
||||||
|
@ -113,6 +116,7 @@ interface DialogsStore {
|
||||||
showCreateSchema: (props: DlgCreateSchemaProps) => void;
|
showCreateSchema: (props: DlgCreateSchemaProps) => void;
|
||||||
showImportSchema: (props: DlgImportSchemaProps) => void;
|
showImportSchema: (props: DlgImportSchemaProps) => void;
|
||||||
showAIPrompt: (props: DlgAIPromptDialogProps) => void;
|
showAIPrompt: (props: DlgAIPromptDialogProps) => void;
|
||||||
|
showCreatePromptTemplate: (props: DlgCreatePromptTemplateProps) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useDialogsStore = create<DialogsStore>()(set => ({
|
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 }),
|
showEditCst: props => set({ active: DialogType.EDIT_CONSTITUENTA, props: props }),
|
||||||
showCreateSchema: props => set({ active: DialogType.CREATE_SCHEMA, props: props }),
|
showCreateSchema: props => set({ active: DialogType.CREATE_SCHEMA, props: props }),
|
||||||
showImportSchema: props => set({ active: DialogType.IMPORT_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 })
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
/* React Tooltip */
|
/* React Tooltip */
|
||||||
--rt-color-white: var(--color-input);
|
--rt-color-white: var(--color-input);
|
||||||
--rt-color-dark: var(--color-foreground);
|
--rt-color-dark: var(--color-foreground);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark Theme */
|
/* Dark Theme */
|
||||||
|
|
Loading…
Reference in New Issue
Block a user