diff --git a/ui/src/App.tsx b/ui/src/App.tsx
index 894f16c..6e417c3 100644
--- a/ui/src/App.tsx
+++ b/ui/src/App.tsx
@@ -19,10 +19,13 @@ import { ChatHistoriesTablePage } from 'pages/ChatHistoriesPage'
import { ChatPage } from 'pages/ChatPage'
import { DataSourceTablePage } from 'pages/DataSourcesTablePage'
import { DatasetsTablePage } from 'pages/DatasetsTablePage'
+import { DocumentsTablePage } from 'pages/DocumentsTablePage'
import { LoginPage } from 'pages/LoginPage'
import { ModelsTablePage } from 'pages/ModelsTablePage'
import { ProjectsTablePage } from 'pages/ProjectsTablePage'
+import { PromptTemplatesTablePage } from 'pages/PromptTemplatesTablePage'
import { UsersTablePage } from 'pages/UsersTablePage'
+import { WorkflowsTablePage } from 'pages/WorkflowsTablePage'
import { RouterProvider, createBrowserRouter } from 'react-router-dom'
function App() {
@@ -41,6 +44,9 @@ function App() {
{ path: '/admin/data-sources', element: },
{ path: '/admin/datasets', element: },
{ path: '/admin/models', element: },
+ { path: '/admin/documents', element: },
+ { path: '/admin/prompt-templates', element: },
+ { path: '/admin/workflows', element: },
{
path: '/admin/histories',
element:
diff --git a/ui/src/atoms/documents.ts b/ui/src/atoms/documents.ts
new file mode 100644
index 0000000..e47f465
--- /dev/null
+++ b/ui/src/atoms/documents.ts
@@ -0,0 +1,47 @@
+// Copyright 2024 Iguazio
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import Client from '@services/Api';
+import { Document } from '@shared/types/document';
+import { atom } from 'jotai';
+
+export const documentsAtom = atom([]);
+
+export const documentsLoadingAtom = atom(false);
+
+export const documentsErrorAtom = atom(null);
+
+
+export const documentsWithFetchAtom = atom(
+ (get) => get(documentsAtom),
+ async (_get, set, username) => {
+ set(documentsLoadingAtom, true);
+ set(documentsErrorAtom, null);
+ try {
+ const documents = await Client.getDocuments(username as string);
+ const sortedDocuments = documents.data.sort((a: Document, b: Document) => {
+ const dateA = new Date(a.created as string);
+ const dateB = new Date(b.created as string);
+ return dateA.getTime() - dateB.getTime();
+ });
+ set(documentsAtom, sortedDocuments);
+ } catch (error) {
+ set(documentsErrorAtom, 'Failed to fetch documents');
+ } finally {
+ set(documentsLoadingAtom, false);
+ }
+ }
+);
+
+export const selectedDocumentAtom = atom({ name: '', description: '', labels: {}, owner_id: '', project_id: '', path: '' });
diff --git a/ui/src/atoms/promptTemplates.ts b/ui/src/atoms/promptTemplates.ts
new file mode 100644
index 0000000..a331670
--- /dev/null
+++ b/ui/src/atoms/promptTemplates.ts
@@ -0,0 +1,47 @@
+// Copyright 2024 Iguazio
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import Client from '@services/Api';
+import { PromptTemplate } from '@shared/types/promptTemplate';
+import { atom } from 'jotai';
+
+export const promptTemplatesAtom = atom([]);
+
+export const promptTemplatesLoadingAtom = atom(false);
+
+export const promptTemplatesErrorAtom = atom(null);
+
+
+export const promptTemplatesWithFetchAtom = atom(
+ (get) => get(promptTemplatesAtom),
+ async (_get, set, username) => {
+ set(promptTemplatesLoadingAtom, true);
+ set(promptTemplatesErrorAtom, null);
+ try {
+ const promptTemplates = await Client.getPromptTemplates(username as string);
+ const sortedPromptTemplates = promptTemplates.data.sort((a: PromptTemplate, b: PromptTemplate) => {
+ const dateA = new Date(a.created as string);
+ const dateB = new Date(b.created as string);
+ return dateA.getTime() - dateB.getTime();
+ });
+ set(promptTemplatesAtom, sortedPromptTemplates);
+ } catch (error) {
+ set(promptTemplatesErrorAtom, 'Failed to fetch promptTemplates');
+ } finally {
+ set(promptTemplatesLoadingAtom, false);
+ }
+ }
+);
+
+export const selectedPromptTemplateAtom = atom({ name: '', description: '', labels: {}, owner_id: '', project_id: '', text: '', arguments: [] });
diff --git a/ui/src/atoms/workflows.ts b/ui/src/atoms/workflows.ts
new file mode 100644
index 0000000..f1dd9a1
--- /dev/null
+++ b/ui/src/atoms/workflows.ts
@@ -0,0 +1,47 @@
+// Copyright 2024 Iguazio
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import Client from '@services/Api';
+import { Workflow, WorkflowType } from '@shared/types/workflow';
+import { atom } from 'jotai';
+
+export const workflowsAtom = atom([]);
+
+export const workflowsLoadingAtom = atom(false);
+
+export const workflowsErrorAtom = atom(null);
+
+
+export const workflowsWithFetchAtom = atom(
+ (get) => get(workflowsAtom),
+ async (_get, set, username) => {
+ set(workflowsLoadingAtom, true);
+ set(workflowsErrorAtom, null);
+ try {
+ const workflows = await Client.getWorkflows(username as string);
+ const sortedWorkflows = workflows.data.sort((a: Workflow, b: Workflow) => {
+ const dateA = new Date(a.created as string);
+ const dateB = new Date(b.created as string);
+ return dateA.getTime() - dateB.getTime();
+ });
+ set(workflowsAtom, sortedWorkflows);
+ } catch (error) {
+ set(workflowsErrorAtom, 'Failed to fetch workflows');
+ } finally {
+ set(workflowsLoadingAtom, false);
+ }
+ }
+);
+
+export const selectedWorkflowAtom = atom({ name: '', description: '', labels: {}, owner_id: '', project_id: '', workflow_type: WorkflowType.APPLICATION, deployment: '' });
diff --git a/ui/src/components/feature/AddEditDocumentModal.tsx b/ui/src/components/feature/AddEditDocumentModal.tsx
new file mode 100644
index 0000000..ec1fdcb
--- /dev/null
+++ b/ui/src/components/feature/AddEditDocumentModal.tsx
@@ -0,0 +1,102 @@
+// Copyright 2024 Iguazio
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { publicUserAtom } from '@atoms/index'
+import {
+ Button,
+ FormControl,
+ FormLabel,
+ Input,
+ Modal,
+ ModalBody,
+ ModalCloseButton,
+ ModalContent,
+ ModalFooter,
+ ModalHeader,
+ ModalOverlay
+} from '@chakra-ui/react'
+import { Document } from '@shared/types/document'
+import { useAtom } from 'jotai'
+import React, { useEffect, useState } from 'react'
+
+type DocumentModalProps = {
+ isOpen: boolean
+ onClose: () => void
+ onSave: (document: Document) => void
+ document?: Document
+}
+
+const AddEditDocumentModal: React.FC = ({ isOpen, onClose, onSave, document }) => {
+ const [publicUser] = useAtom(publicUserAtom)
+ const [formData, setFormData] = useState(
+ document || {
+ name: '',
+ description: '',
+ owner_id: publicUser.uid as string,
+ project_id: '',
+ path: ''
+ }
+ )
+ useEffect(() => {
+ if (document) {
+ setFormData(document)
+ }
+ }, [document])
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target
+ setFormData({ ...formData, [name]: value })
+ }
+
+ const handleSubmit = () => {
+ onSave(formData)
+ onClose()
+ }
+
+ return (
+
+
+
+
+ {document?.uid ? 'Edit' : 'Add New'} {' Document'}
+
+
+
+
+ Document Name
+
+
+
+ Description
+
+
+
+ Path
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default AddEditDocumentModal
diff --git a/ui/src/components/feature/AddEditPromptTemplateModal.tsx b/ui/src/components/feature/AddEditPromptTemplateModal.tsx
new file mode 100644
index 0000000..84de6f6
--- /dev/null
+++ b/ui/src/components/feature/AddEditPromptTemplateModal.tsx
@@ -0,0 +1,108 @@
+// Copyright 2024 Iguazio
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { publicUserAtom } from '@atoms/index'
+import {
+ Button,
+ FormControl,
+ FormLabel,
+ Input,
+ Modal,
+ ModalBody,
+ ModalCloseButton,
+ ModalContent,
+ ModalFooter,
+ ModalHeader,
+ ModalOverlay
+} from '@chakra-ui/react'
+import { PromptTemplate } from '@shared/types/promptTemplate'
+import { useAtom } from 'jotai'
+import React, { useEffect, useState } from 'react'
+
+type PromptTemplateModalProps = {
+ isOpen: boolean
+ onClose: () => void
+ onSave: (promptTemplate: PromptTemplate) => void
+ promptTemplate?: PromptTemplate
+}
+
+const AddEditPromptTemplateModal: React.FC = ({
+ isOpen,
+ onClose,
+ onSave,
+ promptTemplate
+}) => {
+ const [publicUser] = useAtom(publicUserAtom)
+ const [formData, setFormData] = useState(
+ promptTemplate || {
+ name: '',
+ description: '',
+ owner_id: publicUser.uid as string,
+ project_id: '',
+ text: '',
+ arguments: ['prompt']
+ }
+ )
+ useEffect(() => {
+ if (promptTemplate) {
+ setFormData(promptTemplate)
+ }
+ }, [promptTemplate])
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target
+ setFormData({ ...formData, [name]: value })
+ }
+
+ const handleSubmit = () => {
+ onSave(formData)
+ onClose()
+ }
+
+ return (
+
+
+
+
+ {promptTemplate?.uid ? 'Edit' : 'Add New'} {' PromptTemplate'}
+
+
+
+
+ PromptTemplate Name
+
+
+
+ Description
+
+
+
+ Text
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default AddEditPromptTemplateModal
diff --git a/ui/src/components/feature/AddEditWorkflowModal.tsx b/ui/src/components/feature/AddEditWorkflowModal.tsx
new file mode 100644
index 0000000..9167336
--- /dev/null
+++ b/ui/src/components/feature/AddEditWorkflowModal.tsx
@@ -0,0 +1,103 @@
+// Copyright 2024 Iguazio
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { publicUserAtom } from '@atoms/index'
+import {
+ Button,
+ FormControl,
+ FormLabel,
+ Input,
+ Modal,
+ ModalBody,
+ ModalCloseButton,
+ ModalContent,
+ ModalFooter,
+ ModalHeader,
+ ModalOverlay
+} from '@chakra-ui/react'
+import { Workflow, WorkflowType } from '@shared/types/workflow'
+import { useAtom } from 'jotai'
+import React, { useEffect, useState } from 'react'
+
+type WorkflowModalProps = {
+ isOpen: boolean
+ onClose: () => void
+ onSave: (workflow: Workflow) => void
+ workflow?: Workflow
+}
+
+const AddEditWorkflowModal: React.FC = ({ isOpen, onClose, onSave, workflow }) => {
+ const [publicUser] = useAtom(publicUserAtom)
+ const [formData, setFormData] = useState(
+ workflow || {
+ name: '',
+ description: '',
+ owner_id: publicUser.uid as string,
+ project_id: '',
+ workflow_type: WorkflowType.APPLICATION,
+ deployment: ''
+ }
+ )
+ useEffect(() => {
+ if (workflow) {
+ setFormData(workflow)
+ }
+ }, [workflow])
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target
+ setFormData({ ...formData, [name]: value })
+ }
+
+ const handleSubmit = () => {
+ onSave(formData)
+ onClose()
+ }
+
+ return (
+
+
+
+
+ {workflow?.uid ? 'Edit' : 'Add New'} {' Workflow'}
+
+
+
+
+ Workflow Name
+
+
+
+ Description
+
+
+
+ Deployment
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default AddEditWorkflowModal
diff --git a/ui/src/components/feature/DocumentsTable.tsx b/ui/src/components/feature/DocumentsTable.tsx
new file mode 100644
index 0000000..072f634
--- /dev/null
+++ b/ui/src/components/feature/DocumentsTable.tsx
@@ -0,0 +1,222 @@
+// Copyright 2024 Iguazio
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { documentsAtom, documentsWithFetchAtom } from '@atoms/documents'
+import { selectedRowAtom } from '@atoms/index'
+import { AddIcon, DeleteIcon } from '@chakra-ui/icons'
+import {
+ Button,
+ Drawer,
+ DrawerBody,
+ DrawerContent,
+ DrawerHeader,
+ Flex,
+ FormControl,
+ FormLabel,
+ Input,
+ useDisclosure,
+ useToast
+} from '@chakra-ui/react'
+import Breadcrumbs from '@components/shared/Breadcrumbs'
+import DataTableComponent from '@components/shared/Datatable'
+import FilterComponent from '@components/shared/Filter'
+import Client from '@services/Api'
+import { Document } from '@shared/types/document'
+import { useAtom } from 'jotai'
+import React, { useCallback, useEffect, useMemo, useState } from 'react'
+import { TableColumn } from 'react-data-table-component'
+import AddEditDocumentModal from './AddEditDocumentModal'
+
+const DocumentsTable: React.FC = () => {
+ const [selectedRow, setSelectedRow] = useAtom(selectedRowAtom)
+ const [documents] = useAtom(documentsAtom)
+
+ const [selectedRows, setSelectedRows] = useState([])
+ const [editRow, setEditRow] = useState({
+ name: '',
+ description: '',
+ owner_id: '',
+ project_id: '',
+ path: ''
+ })
+ const [filterText, setFilterText] = useState('')
+ const [toggledClearRows, setToggleClearRows] = useState(false)
+
+ const [columns] = useState([
+ { name: 'name', selector: (row: Partial) => row.name ?? '', sortable: true },
+ { name: 'description', selector: (row: Partial) => row.description ?? '', sortable: true },
+ { name: 'version', selector: (row: Partial) => row.version ?? '', sortable: true }
+ ])
+
+ const [, fetchDocuments] = useAtom(documentsWithFetchAtom)
+
+ const { isOpen: isModalOpen, onOpen: onModalOpen, onClose: onModalClose } = useDisclosure()
+ const { isOpen: isDrawerOpen, onOpen: onDrawerOpen, onClose: onDrawerClose } = useDisclosure()
+
+ const toast = useToast()
+
+ useEffect(() => {
+ fetchDocuments('default')
+ }, [fetchDocuments])
+
+ const handleSave = async (document: Document) => {
+ try {
+ if (document.uid) {
+ await Client.updateDocument('default', document)
+ toast({ title: 'Document updated.', status: 'success', duration: 3000, isClosable: true })
+ } else {
+ await Client.createDocument('default', document)
+ toast({ title: 'Document added successfully.', status: 'success', duration: 3000, isClosable: true })
+ }
+ await fetchDocuments('default')
+ onDrawerClose()
+ } catch (error) {
+ console.error('Error saving document:', error)
+ toast({ title: 'Error saving document.', status: 'error', duration: 3000, isClosable: true })
+ }
+ }
+
+ const handleClearRows = useCallback(() => {
+ setToggleClearRows(!toggledClearRows)
+ }, [toggledClearRows])
+
+ const handleDelete = useCallback(async () => {
+ try {
+ await Promise.all(selectedRows.map(row => Client.deleteDocument('default', row.name as string)))
+ setSelectedRows([])
+ await fetchDocuments('default')
+ toast({ title: 'Documents deleted.', status: 'success', duration: 3000, isClosable: true })
+ } catch (error) {
+ console.error('Error deleting documents:', error)
+ toast({ title: 'Error deleting documents.', status: 'error', duration: 3000, isClosable: true })
+ }
+ handleClearRows()
+ }, [fetchDocuments, selectedRows, toast, handleClearRows])
+
+ const handleUpdate = async () => {
+ try {
+ await Client.updateDocument('default', selectedRow)
+ toast({
+ title: 'Document updated.',
+ description: 'The document has been updated successfully.',
+ status: 'success',
+ duration: 3000,
+ isClosable: true
+ })
+ await fetchDocuments('default')
+ onDrawerClose()
+ } catch (error) {
+ toast({
+ title: 'Error updating document.',
+ description: 'There was an error updating the document.',
+ status: 'error',
+ duration: 3000,
+ isClosable: true
+ })
+ }
+ }
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target
+ setSelectedRow({ ...selectedRow, [name]: value })
+ }
+
+ const contextActions = useMemo(
+ () => (
+ } onClick={handleDelete}>
+ Delete
+
+ ),
+ [handleDelete]
+ )
+
+ const subHeaderComponentMemo = useMemo(
+ () => (
+
+ setFilterText(e.target.value)} filterText={filterText} />
+ }
+ onClick={() => {
+ setEditRow({
+ name: '',
+ description: '',
+ owner_id: '',
+ project_id: '',
+ path: ''
+ })
+ onModalOpen()
+ }}
+ >
+ New
+
+
+ ),
+ [filterText]
+ )
+
+ return (
+
+
+ >[]}
+ contextActions={contextActions}
+ onSelectedRowChange={e => {
+ setSelectedRows(e.selectedRows)
+ }}
+ subheaderComponent={subHeaderComponentMemo}
+ filterText={filterText}
+ onOpenDrawer={() => {
+ onDrawerOpen()
+ }}
+ toggleClearRows={toggledClearRows}
+ />
+
+
+
+ {selectedRow?.name}
+
+
+
+
+ Name
+
+
+
+ Description
+
+
+
+ Path
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default DocumentsTable
diff --git a/ui/src/components/feature/Layout.tsx b/ui/src/components/feature/Layout.tsx
index 2de57c4..29214e8 100644
--- a/ui/src/components/feature/Layout.tsx
+++ b/ui/src/components/feature/Layout.tsx
@@ -60,6 +60,9 @@ const Layout: React.FC = ({ children }) => {
+
+
+
)}
diff --git a/ui/src/components/feature/PromptTemplatesTable.tsx b/ui/src/components/feature/PromptTemplatesTable.tsx
new file mode 100644
index 0000000..d72db8c
--- /dev/null
+++ b/ui/src/components/feature/PromptTemplatesTable.tsx
@@ -0,0 +1,229 @@
+// Copyright 2024 Iguazio
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { selectedRowAtom } from '@atoms/index'
+import { promptTemplatesAtom, promptTemplatesWithFetchAtom } from '@atoms/promptTemplates'
+import { AddIcon, DeleteIcon } from '@chakra-ui/icons'
+import {
+ Button,
+ Drawer,
+ DrawerBody,
+ DrawerContent,
+ DrawerHeader,
+ Flex,
+ FormControl,
+ FormLabel,
+ Input,
+ useDisclosure,
+ useToast
+} from '@chakra-ui/react'
+import Breadcrumbs from '@components/shared/Breadcrumbs'
+import DataTableComponent from '@components/shared/Datatable'
+import FilterComponent from '@components/shared/Filter'
+import Client from '@services/Api'
+import { PromptTemplate } from '@shared/types/promptTemplate'
+import { useAtom } from 'jotai'
+import React, { useCallback, useEffect, useMemo, useState } from 'react'
+import { TableColumn } from 'react-data-table-component'
+import AddEditPromptTemplateModal from './AddEditPromptTemplateModal'
+
+const PromptTemplatesTable: React.FC = () => {
+ const [selectedRow, setSelectedRow] = useAtom(selectedRowAtom)
+ const [promptTemplates] = useAtom(promptTemplatesAtom)
+
+ const [selectedRows, setSelectedRows] = useState([])
+ const [editRow, setEditRow] = useState({
+ name: '',
+ description: '',
+ owner_id: '',
+ project_id: '',
+ text: '',
+ arguments: []
+ })
+ const [filterText, setFilterText] = useState('')
+ const [toggledClearRows, setToggleClearRows] = useState(false)
+
+ const [columns] = useState([
+ { name: 'name', selector: (row: Partial) => row.name ?? '', sortable: true },
+ { name: 'description', selector: (row: Partial) => row.description ?? '', sortable: true },
+ { name: 'version', selector: (row: Partial) => row.version ?? '', sortable: true }
+ ])
+
+ const [, fetchPromptTemplates] = useAtom(promptTemplatesWithFetchAtom)
+
+ const { isOpen: isModalOpen, onOpen: onModalOpen, onClose: onModalClose } = useDisclosure()
+ const { isOpen: isDrawerOpen, onOpen: onDrawerOpen, onClose: onDrawerClose } = useDisclosure()
+
+ const toast = useToast()
+
+ useEffect(() => {
+ fetchPromptTemplates('default')
+ }, [fetchPromptTemplates])
+
+ const handleSave = async (promptTemplate: PromptTemplate) => {
+ try {
+ if (promptTemplate.uid) {
+ await Client.updatePromptTemplate('default', promptTemplate)
+ toast({ title: 'PromptTemplate updated.', status: 'success', duration: 3000, isClosable: true })
+ } else {
+ await Client.createPromptTemplate('default', promptTemplate)
+ toast({ title: 'PromptTemplate added successfully.', status: 'success', duration: 3000, isClosable: true })
+ }
+ await fetchPromptTemplates('default')
+ onDrawerClose()
+ } catch (error) {
+ console.error('Error saving promptTemplate:', error)
+ toast({ title: 'Error saving promptTemplate.', status: 'error', duration: 3000, isClosable: true })
+ }
+ }
+
+ const handleClearRows = useCallback(() => {
+ setToggleClearRows(!toggledClearRows)
+ }, [toggledClearRows])
+
+ const handleDelete = useCallback(async () => {
+ try {
+ await Promise.all(selectedRows.map(row => Client.deletePromptTemplate('default', row.name as string)))
+ setSelectedRows([])
+ await fetchPromptTemplates('default')
+ toast({ title: 'PromptTemplates deleted.', status: 'success', duration: 3000, isClosable: true })
+ } catch (error) {
+ console.error('Error deleting promptTemplates:', error)
+ toast({ title: 'Error deleting promptTemplates.', status: 'error', duration: 3000, isClosable: true })
+ }
+ handleClearRows()
+ }, [fetchPromptTemplates, selectedRows, toast, handleClearRows])
+
+ const handleUpdate = async () => {
+ try {
+ await Client.updatePromptTemplate('default', selectedRow)
+ toast({
+ title: 'PromptTemplate updated.',
+ description: 'The promptTemplate has been updated successfully.',
+ status: 'success',
+ duration: 3000,
+ isClosable: true
+ })
+ await fetchPromptTemplates('default')
+ onDrawerClose()
+ } catch (error) {
+ toast({
+ title: 'Error updating promptTemplate.',
+ description: 'There was an error updating the promptTemplate.',
+ status: 'error',
+ duration: 3000,
+ isClosable: true
+ })
+ }
+ }
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target
+ setSelectedRow({ ...selectedRow, [name]: value })
+ }
+
+ const contextActions = useMemo(
+ () => (
+ } onClick={handleDelete}>
+ Delete
+
+ ),
+ [handleDelete]
+ )
+
+ const subHeaderComponentMemo = useMemo(
+ () => (
+
+ setFilterText(e.target.value)} filterText={filterText} />
+ }
+ onClick={() => {
+ setEditRow({
+ name: '',
+ description: '',
+ owner_id: '',
+ project_id: '',
+ text: '',
+ arguments: []
+ })
+ onModalOpen()
+ }}
+ >
+ New
+
+
+ ),
+ [filterText]
+ )
+
+ return (
+
+
+ >[]}
+ contextActions={contextActions}
+ onSelectedRowChange={e => {
+ setSelectedRows(e.selectedRows)
+ }}
+ subheaderComponent={subHeaderComponentMemo}
+ filterText={filterText}
+ onOpenDrawer={() => {
+ onDrawerOpen()
+ }}
+ toggleClearRows={toggledClearRows}
+ />
+
+
+
+ {selectedRow?.name}
+
+
+
+
+ Name
+
+
+
+ Description
+
+
+
+ Path
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default PromptTemplatesTable
diff --git a/ui/src/components/feature/WorkflowsTable.tsx b/ui/src/components/feature/WorkflowsTable.tsx
new file mode 100644
index 0000000..c94369b
--- /dev/null
+++ b/ui/src/components/feature/WorkflowsTable.tsx
@@ -0,0 +1,224 @@
+// Copyright 2024 Iguazio
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { selectedRowAtom } from '@atoms/index'
+import { workflowsAtom, workflowsWithFetchAtom } from '@atoms/workflows'
+import { AddIcon, DeleteIcon } from '@chakra-ui/icons'
+import {
+ Button,
+ Drawer,
+ DrawerBody,
+ DrawerContent,
+ DrawerHeader,
+ Flex,
+ FormControl,
+ FormLabel,
+ Input,
+ useDisclosure,
+ useToast
+} from '@chakra-ui/react'
+import Breadcrumbs from '@components/shared/Breadcrumbs'
+import DataTableComponent from '@components/shared/Datatable'
+import FilterComponent from '@components/shared/Filter'
+import Client from '@services/Api'
+import { Workflow, WorkflowType } from '@shared/types/workflow'
+import { useAtom } from 'jotai'
+import React, { useCallback, useEffect, useMemo, useState } from 'react'
+import { TableColumn } from 'react-data-table-component'
+import AddEditWorkflowModal from './AddEditWorkflowModal'
+
+const WorkflowsTable: React.FC = () => {
+ const [selectedRow, setSelectedRow] = useAtom(selectedRowAtom)
+ const [workflows] = useAtom(workflowsAtom)
+
+ const [selectedRows, setSelectedRows] = useState([])
+ const [editRow, setEditRow] = useState({
+ name: '',
+ description: '',
+ owner_id: '',
+ project_id: '',
+ workflow_type: WorkflowType.DEPLOYMENT,
+ deployment: ''
+ })
+ const [filterText, setFilterText] = useState('')
+ const [toggledClearRows, setToggleClearRows] = useState(false)
+
+ const [columns] = useState([
+ { name: 'name', selector: (row: Partial) => row.name ?? '', sortable: true },
+ { name: 'description', selector: (row: Partial) => row.description ?? '', sortable: true },
+ { name: 'version', selector: (row: Partial) => row.version ?? '', sortable: true }
+ ])
+
+ const [, fetchWorkflows] = useAtom(workflowsWithFetchAtom)
+
+ const { isOpen: isModalOpen, onOpen: onModalOpen, onClose: onModalClose } = useDisclosure()
+ const { isOpen: isDrawerOpen, onOpen: onDrawerOpen, onClose: onDrawerClose } = useDisclosure()
+
+ const toast = useToast()
+
+ useEffect(() => {
+ fetchWorkflows('default')
+ }, [fetchWorkflows])
+
+ const handleSave = async (workflow: Workflow) => {
+ try {
+ if (workflow.uid) {
+ await Client.updateWorkflow('default', workflow)
+ toast({ title: 'Workflow updated.', status: 'success', duration: 3000, isClosable: true })
+ } else {
+ await Client.createWorkflow('default', workflow)
+ toast({ title: 'Workflow added successfully.', status: 'success', duration: 3000, isClosable: true })
+ }
+ await fetchWorkflows('default')
+ onDrawerClose()
+ } catch (error) {
+ console.error('Error saving workflow:', error)
+ toast({ title: 'Error saving workflow.', status: 'error', duration: 3000, isClosable: true })
+ }
+ }
+
+ const handleClearRows = useCallback(() => {
+ setToggleClearRows(!toggledClearRows)
+ }, [toggledClearRows])
+
+ const handleDelete = useCallback(async () => {
+ try {
+ await Promise.all(selectedRows.map(row => Client.deleteWorkflow('default', row.name as string)))
+ setSelectedRows([])
+ await fetchWorkflows('default')
+ toast({ title: 'Workflows deleted.', status: 'success', duration: 3000, isClosable: true })
+ } catch (error) {
+ console.error('Error deleting workflows:', error)
+ toast({ title: 'Error deleting workflows.', status: 'error', duration: 3000, isClosable: true })
+ }
+ handleClearRows()
+ }, [fetchWorkflows, selectedRows, toast, handleClearRows])
+
+ const handleUpdate = async () => {
+ try {
+ await Client.updateWorkflow('default', selectedRow)
+ toast({
+ title: 'Workflow updated.',
+ description: 'The workflow has been updated successfully.',
+ status: 'success',
+ duration: 3000,
+ isClosable: true
+ })
+ await fetchWorkflows('default')
+ onDrawerClose()
+ } catch (error) {
+ toast({
+ title: 'Error updating workflow.',
+ description: 'There was an error updating the workflow.',
+ status: 'error',
+ duration: 3000,
+ isClosable: true
+ })
+ }
+ }
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target
+ setSelectedRow({ ...selectedRow, [name]: value })
+ }
+
+ const contextActions = useMemo(
+ () => (
+ } onClick={handleDelete}>
+ Delete
+
+ ),
+ [handleDelete]
+ )
+
+ const subHeaderComponentMemo = useMemo(
+ () => (
+
+ setFilterText(e.target.value)} filterText={filterText} />
+ }
+ onClick={() => {
+ setEditRow({
+ name: '',
+ description: '',
+ owner_id: '',
+ project_id: '',
+ workflow_type: WorkflowType.DEPLOYMENT,
+ deployment: ''
+ })
+ onModalOpen()
+ }}
+ >
+ New
+
+
+ ),
+ [filterText]
+ )
+
+ return (
+
+
+ >[]}
+ contextActions={contextActions}
+ onSelectedRowChange={e => {
+ setSelectedRows(e.selectedRows)
+ }}
+ subheaderComponent={subHeaderComponentMemo}
+ filterText={filterText}
+ onOpenDrawer={() => {
+ onDrawerOpen()
+ }}
+ toggleClearRows={toggledClearRows}
+ />
+
+
+
+ {selectedRow?.name}
+
+
+
+
+ Name
+
+
+
+ Description
+
+
+
+ Path
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default WorkflowsTable
diff --git a/ui/src/pages/DocumentsTablePage.tsx b/ui/src/pages/DocumentsTablePage.tsx
new file mode 100644
index 0000000..6cc03e9
--- /dev/null
+++ b/ui/src/pages/DocumentsTablePage.tsx
@@ -0,0 +1,24 @@
+// Copyright 2024 Iguazio
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import DocumentsTable from '@components/feature/DocumentsTable'
+import Layout from '@components/feature/Layout'
+
+export const DocumentsTablePage = () => {
+ return (
+
+
+
+ )
+}
diff --git a/ui/src/pages/PromptTemplatesTablePage.tsx b/ui/src/pages/PromptTemplatesTablePage.tsx
new file mode 100644
index 0000000..1fc8b4d
--- /dev/null
+++ b/ui/src/pages/PromptTemplatesTablePage.tsx
@@ -0,0 +1,24 @@
+// Copyright 2024 Iguazio
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import Layout from '@components/feature/Layout'
+import PromptTemplatesTable from '@components/feature/PromptTemplatesTable'
+
+export const PromptTemplatesTablePage = () => {
+ return (
+
+
+
+ )
+}
diff --git a/ui/src/pages/WorkflowsTablePage.tsx b/ui/src/pages/WorkflowsTablePage.tsx
new file mode 100644
index 0000000..ea398eb
--- /dev/null
+++ b/ui/src/pages/WorkflowsTablePage.tsx
@@ -0,0 +1,24 @@
+// Copyright 2024 Iguazio
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import Layout from '@components/feature/Layout'
+import WorkflowsTable from '@components/feature/WorkflowsTable'
+
+export const WorkflowsTablePage = () => {
+ return (
+
+
+
+ )
+}
diff --git a/ui/src/services/Api.ts b/ui/src/services/Api.ts
index 4ba2115..ffb3e2e 100644
--- a/ui/src/services/Api.ts
+++ b/ui/src/services/Api.ts
@@ -15,10 +15,12 @@
import { User } from '@shared/types';
import { DataSource } from '@shared/types/dataSource';
import { Dataset } from '@shared/types/dataset';
+import { Document } from '@shared/types/document';
import { Model } from '@shared/types/model';
import { Project } from '@shared/types/project';
+import { PromptTemplate } from '@shared/types/promptTemplate';
import { Session } from '@shared/types/session';
-import { Query } from '@shared/types/workflow';
+import { Query, Workflow } from '@shared/types/workflow';
import axios, { AxiosResponse } from 'axios';
@@ -146,15 +148,49 @@ class ApiClient {
// WORKFLOWS
- async getWorkflow(projectId: string, workflowId: string, query: Query) {
+
+ async getWorkflows(projectName: string, params?: { name?: string; version?: string; workflow_type?: string; labels?: string[]; mode?: string }) {
try {
- const response = await this.client.post(
- `/projects/${projectId}/workflows/${workflowId}`, // is it project ID or name?
- query
- )
- return this.handleResponse(response)
+ const response = await this.client.get(`/projects/${projectName}/workflows`, { params });
+ return this.handleResponse(response);
} catch (error) {
- return this.handleError(error as Error)
+ return this.handleError(error);
+ }
+ }
+
+ async getWorkflow(projectName: string, uid: string) {
+ try {
+ const response = await this.client.get(`/projects/${projectName}/workflows/${uid}`);
+ return this.handleResponse(response);
+ } catch (error) {
+ return this.handleError(error);
+ }
+ }
+
+ async createWorkflow(projectName: string, workflow: Workflow) {
+ try {
+ const response = await this.client.post(`/projects/${projectName}/workflows`, workflow);
+ return this.handleResponse(response);
+ } catch (error) {
+ return this.handleError(error);
+ }
+ }
+
+ async updateWorkflow(projectName: string, workflow: Workflow) {
+ try {
+ const response = await this.client.put(`/projects/${projectName}/workflows/${workflow.name}`, workflow);
+ return this.handleResponse(response);
+ } catch (error) {
+ return this.handleError(error);
+ }
+ }
+
+ async deleteWorkflow(projectName: string, uid: string) {
+ try {
+ const response = await this.client.delete(`/projects/${projectName}/workflows/${uid}`);
+ return this.handleResponse(response);
+ } catch (error) {
+ return this.handleError(error);
}
}
@@ -369,8 +405,102 @@ class ApiClient {
return this.handleError(error);
}
}
-}
+ // DOCUMENTS
+
+ async getDocuments(projectName: string, params?: { name?: string; version?: string; labels?: string[]; mode?: string }) {
+ try {
+ const response = await this.client.get(`/projects/${projectName}/documents`, { params });
+ return this.handleResponse(response);
+ } catch (error) {
+ return this.handleError(error);
+ }
+ }
+ async getDocument(projectName: string, uid: string) {
+ try {
+ const response = await this.client.get(`/projects/${projectName}/documents/${uid}`);
+ return this.handleResponse(response);
+ } catch (error) {
+ return this.handleError(error);
+ }
+ }
+
+ async createDocument(projectName: string, document: Document) {
+ try {
+ const response = await this.client.post(`/projects/${projectName}/documents`, document);
+ return this.handleResponse(response);
+ } catch (error) {
+ return this.handleError(error);
+ }
+ }
+
+ async updateDocument(projectName: string, document: Document) {
+ try {
+ const response = await this.client.put(`/projects/${projectName}/documents/${document.name}`, document);
+ return this.handleResponse(response);
+ } catch (error) {
+ return this.handleError(error);
+ }
+ }
+
+ async deleteDocument(projectName: string, uid: string) {
+ try {
+ const response = await this.client.delete(`/projects/${projectName}/documents/${uid}`);
+ return this.handleResponse(response);
+ } catch (error) {
+ return this.handleError(error);
+ }
+ }
+
+ // PROMPT TEMPLATES
+
+ async getPromptTemplates(projectName: string, params?: { name?: string; version?: string; labels?: string[]; mode?: string }) {
+ try {
+ const response = await this.client.get(`/projects/${projectName}/prompt_templates`, { params });
+ return this.handleResponse(response);
+ } catch (error) {
+ return this.handleError(error);
+ }
+ }
+
+ async getPromptTemplate(projectName: string, uid: string) {
+ try {
+ const response = await this.client.get(`/projects/${projectName}/prompt_templates/${uid}`);
+ return this.handleResponse(response);
+ } catch (error) {
+ return this.handleError(error);
+ }
+ }
+
+ async createPromptTemplate(projectName: string, promptTemplate: PromptTemplate) {
+ try {
+ const response = await this.client.post(`/projects/${projectName}/prompt_templates`, promptTemplate);
+ return this.handleResponse(response);
+ } catch (error) {
+ return this.handleError(error);
+ }
+ }
+
+ async updatePromptTemplate(projectName: string, promptTemplate: PromptTemplate) {
+ try {
+ const response = await this.client.put(`/projects/${projectName}/prompt_templates/${promptTemplate.name}`, promptTemplate);
+ return this.handleResponse(response);
+ } catch (error) {
+ return this.handleError(error);
+ }
+ }
+
+ async deletePromptTemplate(projectName: string, uid: string) {
+ try {
+ const response = await this.client.delete(`/projects/${projectName}/prompt_templates/${uid}`);
+ return this.handleResponse(response);
+ } catch (error) {
+ return this.handleError(error);
+ }
+ }
+
+
+}
function getClient() {
return new ApiClient()
diff --git a/ui/src/shared/types/document.ts b/ui/src/shared/types/document.ts
new file mode 100644
index 0000000..467e456
--- /dev/null
+++ b/ui/src/shared/types/document.ts
@@ -0,0 +1,26 @@
+// Copyright 2024 Iguazio
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+export type Document = {
+ name: string
+ uid?: string
+ description?: string
+ labels?: { [key: string]: string }
+ owner_id: string
+ version?: string
+ project_id: string
+ path: string
+ origin?: string
+ created?: string
+}
diff --git a/ui/src/shared/types/promptTemplate.ts b/ui/src/shared/types/promptTemplate.ts
new file mode 100644
index 0000000..64fd015
--- /dev/null
+++ b/ui/src/shared/types/promptTemplate.ts
@@ -0,0 +1,29 @@
+// Copyright 2024 Iguazio
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+
+
+export type PromptTemplate = {
+ name: string
+ uid?: string
+ description?: string
+ labels?: { [key: string]: string }
+ owner_id: string
+ version?: string
+ project_id: string
+ text: string
+ arguments: string[]
+ created?: string
+}
diff --git a/ui/src/shared/types/workflow.ts b/ui/src/shared/types/workflow.ts
index 0ec9cbc..55def0e 100644
--- a/ui/src/shared/types/workflow.ts
+++ b/ui/src/shared/types/workflow.ts
@@ -14,17 +14,18 @@
export type Workflow = {
name: string
- uid: string
- description: string
- labels: { [key: string]: string }
+ uid?: string
+ description?: string
+ labels?: { [key: string]: string }
owner_id: string
- version: string
+ version?: string
project_id: string
workflow_type: WorkflowType
deployment: string
- workflow_function: string
- configuration: { [key: string]: string }
- graph: { [key: string]: string }
+ workflow_function?: string
+ configuration?: { [key: string]: string }
+ graph?: { [key: string]: string }
+ created?: string
}