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}
+
+
+
+
+
+
+ );
+}