681 lines
18 KiB
TypeScript
681 lines
18 KiB
TypeScript
import { Button } from "@/components/ui/button"
|
|
import { Label } from "@/components/ui/label"
|
|
import {
|
|
Sheet,
|
|
SheetContent,
|
|
SheetFooter,
|
|
SheetHeader,
|
|
SheetTitle,
|
|
SheetTrigger
|
|
} from "@/components/ui/sheet"
|
|
import { AssignWorkspaces } from "@/components/workspace/assign-workspaces"
|
|
import { ChatbotUIContext } from "@/context/context"
|
|
import {
|
|
createAssistantCollection,
|
|
deleteAssistantCollection,
|
|
getAssistantCollectionsByAssistantId
|
|
} from "@/db/assistant-collections"
|
|
import {
|
|
createAssistantFile,
|
|
deleteAssistantFile,
|
|
getAssistantFilesByAssistantId
|
|
} from "@/db/assistant-files"
|
|
import {
|
|
createAssistantTool,
|
|
deleteAssistantTool,
|
|
getAssistantToolsByAssistantId
|
|
} from "@/db/assistant-tools"
|
|
import {
|
|
createAssistantWorkspaces,
|
|
deleteAssistantWorkspace,
|
|
getAssistantWorkspacesByAssistantId,
|
|
updateAssistant
|
|
} from "@/db/assistants"
|
|
import { updateChat } from "@/db/chats"
|
|
import {
|
|
createCollectionFile,
|
|
deleteCollectionFile,
|
|
getCollectionFilesByCollectionId
|
|
} from "@/db/collection-files"
|
|
import {
|
|
createCollectionWorkspaces,
|
|
deleteCollectionWorkspace,
|
|
getCollectionWorkspacesByCollectionId,
|
|
updateCollection
|
|
} from "@/db/collections"
|
|
import {
|
|
createFileWorkspaces,
|
|
deleteFileWorkspace,
|
|
getFileWorkspacesByFileId,
|
|
updateFile
|
|
} from "@/db/files"
|
|
import {
|
|
createModelWorkspaces,
|
|
deleteModelWorkspace,
|
|
getModelWorkspacesByModelId,
|
|
updateModel
|
|
} from "@/db/models"
|
|
import {
|
|
createPresetWorkspaces,
|
|
deletePresetWorkspace,
|
|
getPresetWorkspacesByPresetId,
|
|
updatePreset
|
|
} from "@/db/presets"
|
|
import {
|
|
createPromptWorkspaces,
|
|
deletePromptWorkspace,
|
|
getPromptWorkspacesByPromptId,
|
|
updatePrompt
|
|
} from "@/db/prompts"
|
|
import {
|
|
getAssistantImageFromStorage,
|
|
uploadAssistantImage
|
|
} from "@/db/storage/assistant-images"
|
|
import {
|
|
createToolWorkspaces,
|
|
deleteToolWorkspace,
|
|
getToolWorkspacesByToolId,
|
|
updateTool
|
|
} from "@/db/tools"
|
|
import { convertBlobToBase64 } from "@/lib/blob-to-b64"
|
|
import { Tables, TablesUpdate } from "@/supabase/types"
|
|
import { CollectionFile, ContentType, DataItemType } from "@/types"
|
|
import { FC, useContext, useEffect, useRef, useState } from "react"
|
|
import profile from "react-syntax-highlighter/dist/esm/languages/hljs/profile"
|
|
import { toast } from "sonner"
|
|
import { SidebarDeleteItem } from "./sidebar-delete-item"
|
|
|
|
interface SidebarUpdateItemProps {
|
|
isTyping: boolean
|
|
item: DataItemType
|
|
contentType: ContentType
|
|
children: React.ReactNode
|
|
renderInputs: (renderState: any) => JSX.Element
|
|
updateState: any
|
|
}
|
|
|
|
export const SidebarUpdateItem: FC<SidebarUpdateItemProps> = ({
|
|
item,
|
|
contentType,
|
|
children,
|
|
renderInputs,
|
|
updateState,
|
|
isTyping
|
|
}) => {
|
|
const {
|
|
workspaces,
|
|
selectedWorkspace,
|
|
setChats,
|
|
setPresets,
|
|
setPrompts,
|
|
setFiles,
|
|
setCollections,
|
|
setAssistants,
|
|
setTools,
|
|
setModels,
|
|
setAssistantImages
|
|
} = useContext(ChatbotUIContext)
|
|
|
|
const buttonRef = useRef<HTMLButtonElement>(null)
|
|
|
|
const [isOpen, setIsOpen] = useState(false)
|
|
const [startingWorkspaces, setStartingWorkspaces] = useState<
|
|
Tables<"workspaces">[]
|
|
>([])
|
|
const [selectedWorkspaces, setSelectedWorkspaces] = useState<
|
|
Tables<"workspaces">[]
|
|
>([])
|
|
|
|
// Collections Render State
|
|
const [startingCollectionFiles, setStartingCollectionFiles] = useState<
|
|
CollectionFile[]
|
|
>([])
|
|
const [selectedCollectionFiles, setSelectedCollectionFiles] = useState<
|
|
CollectionFile[]
|
|
>([])
|
|
|
|
// Assistants Render State
|
|
const [startingAssistantFiles, setStartingAssistantFiles] = useState<
|
|
Tables<"files">[]
|
|
>([])
|
|
const [startingAssistantCollections, setStartingAssistantCollections] =
|
|
useState<Tables<"collections">[]>([])
|
|
const [startingAssistantTools, setStartingAssistantTools] = useState<
|
|
Tables<"tools">[]
|
|
>([])
|
|
const [selectedAssistantFiles, setSelectedAssistantFiles] = useState<
|
|
Tables<"files">[]
|
|
>([])
|
|
const [selectedAssistantCollections, setSelectedAssistantCollections] =
|
|
useState<Tables<"collections">[]>([])
|
|
const [selectedAssistantTools, setSelectedAssistantTools] = useState<
|
|
Tables<"tools">[]
|
|
>([])
|
|
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
const fetchData = async () => {
|
|
if (workspaces.length > 1) {
|
|
const workspaces = await fetchSelectedWorkspaces()
|
|
setStartingWorkspaces(workspaces)
|
|
setSelectedWorkspaces(workspaces)
|
|
}
|
|
|
|
const fetchDataFunction = fetchDataFunctions[contentType]
|
|
if (!fetchDataFunction) return
|
|
await fetchDataFunction(item.id)
|
|
}
|
|
|
|
fetchData()
|
|
}
|
|
}, [isOpen])
|
|
|
|
const renderState = {
|
|
chats: null,
|
|
presets: null,
|
|
prompts: null,
|
|
files: null,
|
|
collections: {
|
|
startingCollectionFiles,
|
|
setStartingCollectionFiles,
|
|
selectedCollectionFiles,
|
|
setSelectedCollectionFiles
|
|
},
|
|
assistants: {
|
|
startingAssistantFiles,
|
|
setStartingAssistantFiles,
|
|
startingAssistantCollections,
|
|
setStartingAssistantCollections,
|
|
startingAssistantTools,
|
|
setStartingAssistantTools,
|
|
selectedAssistantFiles,
|
|
setSelectedAssistantFiles,
|
|
selectedAssistantCollections,
|
|
setSelectedAssistantCollections,
|
|
selectedAssistantTools,
|
|
setSelectedAssistantTools
|
|
},
|
|
tools: null,
|
|
models: null
|
|
}
|
|
|
|
const fetchDataFunctions = {
|
|
chats: null,
|
|
presets: null,
|
|
prompts: null,
|
|
files: null,
|
|
collections: async (collectionId: string) => {
|
|
const collectionFiles =
|
|
await getCollectionFilesByCollectionId(collectionId)
|
|
setStartingCollectionFiles(collectionFiles.files)
|
|
setSelectedCollectionFiles([])
|
|
},
|
|
assistants: async (assistantId: string) => {
|
|
const assistantFiles = await getAssistantFilesByAssistantId(assistantId)
|
|
setStartingAssistantFiles(assistantFiles.files)
|
|
|
|
const assistantCollections =
|
|
await getAssistantCollectionsByAssistantId(assistantId)
|
|
setStartingAssistantCollections(assistantCollections.collections)
|
|
|
|
const assistantTools = await getAssistantToolsByAssistantId(assistantId)
|
|
setStartingAssistantTools(assistantTools.tools)
|
|
|
|
setSelectedAssistantFiles([])
|
|
setSelectedAssistantCollections([])
|
|
setSelectedAssistantTools([])
|
|
},
|
|
tools: null,
|
|
models: null
|
|
}
|
|
|
|
const fetchWorkpaceFunctions = {
|
|
chats: null,
|
|
presets: async (presetId: string) => {
|
|
const item = await getPresetWorkspacesByPresetId(presetId)
|
|
return item.workspaces
|
|
},
|
|
prompts: async (promptId: string) => {
|
|
const item = await getPromptWorkspacesByPromptId(promptId)
|
|
return item.workspaces
|
|
},
|
|
files: async (fileId: string) => {
|
|
const item = await getFileWorkspacesByFileId(fileId)
|
|
return item.workspaces
|
|
},
|
|
collections: async (collectionId: string) => {
|
|
const item = await getCollectionWorkspacesByCollectionId(collectionId)
|
|
return item.workspaces
|
|
},
|
|
assistants: async (assistantId: string) => {
|
|
const item = await getAssistantWorkspacesByAssistantId(assistantId)
|
|
return item.workspaces
|
|
},
|
|
tools: async (toolId: string) => {
|
|
const item = await getToolWorkspacesByToolId(toolId)
|
|
return item.workspaces
|
|
},
|
|
models: async (modelId: string) => {
|
|
const item = await getModelWorkspacesByModelId(modelId)
|
|
return item.workspaces
|
|
}
|
|
}
|
|
|
|
const fetchSelectedWorkspaces = async () => {
|
|
const fetchFunction = fetchWorkpaceFunctions[contentType]
|
|
|
|
if (!fetchFunction) return []
|
|
|
|
const workspaces = await fetchFunction(item.id)
|
|
|
|
return workspaces
|
|
}
|
|
|
|
const handleWorkspaceUpdates = async (
|
|
startingWorkspaces: Tables<"workspaces">[],
|
|
selectedWorkspaces: Tables<"workspaces">[],
|
|
itemId: string,
|
|
deleteWorkspaceFn: (
|
|
itemId: string,
|
|
workspaceId: string
|
|
) => Promise<boolean>,
|
|
createWorkspaceFn: (
|
|
workspaces: { user_id: string; item_id: string; workspace_id: string }[]
|
|
) => Promise<void>,
|
|
itemIdKey: string
|
|
) => {
|
|
if (!selectedWorkspace) return
|
|
|
|
const deleteList = startingWorkspaces.filter(
|
|
startingWorkspace =>
|
|
!selectedWorkspaces.some(
|
|
selectedWorkspace => selectedWorkspace.id === startingWorkspace.id
|
|
)
|
|
)
|
|
|
|
for (const workspace of deleteList) {
|
|
await deleteWorkspaceFn(itemId, workspace.id)
|
|
}
|
|
|
|
if (deleteList.map(w => w.id).includes(selectedWorkspace.id)) {
|
|
const setStateFunction = stateUpdateFunctions[contentType]
|
|
|
|
if (setStateFunction) {
|
|
setStateFunction((prevItems: any) =>
|
|
prevItems.filter((prevItem: any) => prevItem.id !== item.id)
|
|
)
|
|
}
|
|
}
|
|
|
|
const createList = selectedWorkspaces.filter(
|
|
selectedWorkspace =>
|
|
!startingWorkspaces.some(
|
|
startingWorkspace => startingWorkspace.id === selectedWorkspace.id
|
|
)
|
|
)
|
|
|
|
await createWorkspaceFn(
|
|
createList.map(workspace => {
|
|
return {
|
|
user_id: workspace.user_id,
|
|
[itemIdKey]: itemId,
|
|
workspace_id: workspace.id
|
|
} as any
|
|
})
|
|
)
|
|
}
|
|
|
|
const updateFunctions = {
|
|
chats: updateChat,
|
|
presets: async (presetId: string, updateState: TablesUpdate<"presets">) => {
|
|
const updatedPreset = await updatePreset(presetId, updateState)
|
|
|
|
await handleWorkspaceUpdates(
|
|
startingWorkspaces,
|
|
selectedWorkspaces,
|
|
presetId,
|
|
deletePresetWorkspace,
|
|
createPresetWorkspaces as any,
|
|
"preset_id"
|
|
)
|
|
|
|
return updatedPreset
|
|
},
|
|
prompts: async (promptId: string, updateState: TablesUpdate<"prompts">) => {
|
|
const updatedPrompt = await updatePrompt(promptId, updateState)
|
|
|
|
await handleWorkspaceUpdates(
|
|
startingWorkspaces,
|
|
selectedWorkspaces,
|
|
promptId,
|
|
deletePromptWorkspace,
|
|
createPromptWorkspaces as any,
|
|
"prompt_id"
|
|
)
|
|
|
|
return updatedPrompt
|
|
},
|
|
files: async (fileId: string, updateState: TablesUpdate<"files">) => {
|
|
const updatedFile = await updateFile(fileId, updateState)
|
|
|
|
await handleWorkspaceUpdates(
|
|
startingWorkspaces,
|
|
selectedWorkspaces,
|
|
fileId,
|
|
deleteFileWorkspace,
|
|
createFileWorkspaces as any,
|
|
"file_id"
|
|
)
|
|
|
|
return updatedFile
|
|
},
|
|
collections: async (
|
|
collectionId: string,
|
|
updateState: TablesUpdate<"assistants">
|
|
) => {
|
|
if (!profile) return
|
|
|
|
const { ...rest } = updateState
|
|
|
|
const filesToAdd = selectedCollectionFiles.filter(
|
|
selectedFile =>
|
|
!startingCollectionFiles.some(
|
|
startingFile => startingFile.id === selectedFile.id
|
|
)
|
|
)
|
|
|
|
const filesToRemove = startingCollectionFiles.filter(startingFile =>
|
|
selectedCollectionFiles.some(
|
|
selectedFile => selectedFile.id === startingFile.id
|
|
)
|
|
)
|
|
|
|
for (const file of filesToAdd) {
|
|
await createCollectionFile({
|
|
user_id: item.user_id,
|
|
collection_id: collectionId,
|
|
file_id: file.id
|
|
})
|
|
}
|
|
|
|
for (const file of filesToRemove) {
|
|
await deleteCollectionFile(collectionId, file.id)
|
|
}
|
|
|
|
const updatedCollection = await updateCollection(collectionId, rest)
|
|
|
|
await handleWorkspaceUpdates(
|
|
startingWorkspaces,
|
|
selectedWorkspaces,
|
|
collectionId,
|
|
deleteCollectionWorkspace,
|
|
createCollectionWorkspaces as any,
|
|
"collection_id"
|
|
)
|
|
|
|
return updatedCollection
|
|
},
|
|
assistants: async (
|
|
assistantId: string,
|
|
updateState: {
|
|
assistantId: string
|
|
image: File
|
|
} & TablesUpdate<"assistants">
|
|
) => {
|
|
const { image, ...rest } = updateState
|
|
|
|
const filesToAdd = selectedAssistantFiles.filter(
|
|
selectedFile =>
|
|
!startingAssistantFiles.some(
|
|
startingFile => startingFile.id === selectedFile.id
|
|
)
|
|
)
|
|
|
|
const filesToRemove = startingAssistantFiles.filter(startingFile =>
|
|
selectedAssistantFiles.some(
|
|
selectedFile => selectedFile.id === startingFile.id
|
|
)
|
|
)
|
|
|
|
for (const file of filesToAdd) {
|
|
await createAssistantFile({
|
|
user_id: item.user_id,
|
|
assistant_id: assistantId,
|
|
file_id: file.id
|
|
})
|
|
}
|
|
|
|
for (const file of filesToRemove) {
|
|
await deleteAssistantFile(assistantId, file.id)
|
|
}
|
|
|
|
const collectionsToAdd = selectedAssistantCollections.filter(
|
|
selectedCollection =>
|
|
!startingAssistantCollections.some(
|
|
startingCollection =>
|
|
startingCollection.id === selectedCollection.id
|
|
)
|
|
)
|
|
|
|
const collectionsToRemove = startingAssistantCollections.filter(
|
|
startingCollection =>
|
|
selectedAssistantCollections.some(
|
|
selectedCollection =>
|
|
selectedCollection.id === startingCollection.id
|
|
)
|
|
)
|
|
|
|
for (const collection of collectionsToAdd) {
|
|
await createAssistantCollection({
|
|
user_id: item.user_id,
|
|
assistant_id: assistantId,
|
|
collection_id: collection.id
|
|
})
|
|
}
|
|
|
|
for (const collection of collectionsToRemove) {
|
|
await deleteAssistantCollection(assistantId, collection.id)
|
|
}
|
|
|
|
const toolsToAdd = selectedAssistantTools.filter(
|
|
selectedTool =>
|
|
!startingAssistantTools.some(
|
|
startingTool => startingTool.id === selectedTool.id
|
|
)
|
|
)
|
|
|
|
const toolsToRemove = startingAssistantTools.filter(startingTool =>
|
|
selectedAssistantTools.some(
|
|
selectedTool => selectedTool.id === startingTool.id
|
|
)
|
|
)
|
|
|
|
for (const tool of toolsToAdd) {
|
|
await createAssistantTool({
|
|
user_id: item.user_id,
|
|
assistant_id: assistantId,
|
|
tool_id: tool.id
|
|
})
|
|
}
|
|
|
|
for (const tool of toolsToRemove) {
|
|
await deleteAssistantTool(assistantId, tool.id)
|
|
}
|
|
|
|
let updatedAssistant = await updateAssistant(assistantId, rest)
|
|
|
|
if (image) {
|
|
const filePath = await uploadAssistantImage(updatedAssistant, image)
|
|
|
|
updatedAssistant = await updateAssistant(assistantId, {
|
|
image_path: filePath
|
|
})
|
|
|
|
const url = (await getAssistantImageFromStorage(filePath)) || ""
|
|
|
|
if (url) {
|
|
const response = await fetch(url)
|
|
const blob = await response.blob()
|
|
const base64 = await convertBlobToBase64(blob)
|
|
|
|
setAssistantImages(prev => [
|
|
...prev,
|
|
{
|
|
assistantId: updatedAssistant.id,
|
|
path: filePath,
|
|
base64,
|
|
url
|
|
}
|
|
])
|
|
}
|
|
}
|
|
|
|
await handleWorkspaceUpdates(
|
|
startingWorkspaces,
|
|
selectedWorkspaces,
|
|
assistantId,
|
|
deleteAssistantWorkspace,
|
|
createAssistantWorkspaces as any,
|
|
"assistant_id"
|
|
)
|
|
|
|
return updatedAssistant
|
|
},
|
|
tools: async (toolId: string, updateState: TablesUpdate<"tools">) => {
|
|
const updatedTool = await updateTool(toolId, updateState)
|
|
|
|
await handleWorkspaceUpdates(
|
|
startingWorkspaces,
|
|
selectedWorkspaces,
|
|
toolId,
|
|
deleteToolWorkspace,
|
|
createToolWorkspaces as any,
|
|
"tool_id"
|
|
)
|
|
|
|
return updatedTool
|
|
},
|
|
models: async (modelId: string, updateState: TablesUpdate<"models">) => {
|
|
const updatedModel = await updateModel(modelId, updateState)
|
|
|
|
await handleWorkspaceUpdates(
|
|
startingWorkspaces,
|
|
selectedWorkspaces,
|
|
modelId,
|
|
deleteModelWorkspace,
|
|
createModelWorkspaces as any,
|
|
"model_id"
|
|
)
|
|
|
|
return updatedModel
|
|
}
|
|
}
|
|
|
|
const stateUpdateFunctions = {
|
|
chats: setChats,
|
|
presets: setPresets,
|
|
prompts: setPrompts,
|
|
files: setFiles,
|
|
collections: setCollections,
|
|
assistants: setAssistants,
|
|
tools: setTools,
|
|
models: setModels
|
|
}
|
|
|
|
const handleUpdate = async () => {
|
|
try {
|
|
const updateFunction = updateFunctions[contentType]
|
|
const setStateFunction = stateUpdateFunctions[contentType]
|
|
|
|
if (!updateFunction || !setStateFunction) return
|
|
if (isTyping) return // Prevent update while typing
|
|
|
|
const updatedItem = await updateFunction(item.id, updateState)
|
|
|
|
setStateFunction((prevItems: any) =>
|
|
prevItems.map((prevItem: any) =>
|
|
prevItem.id === item.id ? updatedItem : prevItem
|
|
)
|
|
)
|
|
|
|
setIsOpen(false)
|
|
|
|
toast.success(`${contentType.slice(0, -1)} updated successfully`)
|
|
} catch (error) {
|
|
toast.error(`Error updating ${contentType.slice(0, -1)}. ${error}`)
|
|
}
|
|
}
|
|
|
|
const handleSelectWorkspace = (workspace: Tables<"workspaces">) => {
|
|
setSelectedWorkspaces(prevState => {
|
|
const isWorkspaceAlreadySelected = prevState.find(
|
|
selectedWorkspace => selectedWorkspace.id === workspace.id
|
|
)
|
|
|
|
if (isWorkspaceAlreadySelected) {
|
|
return prevState.filter(
|
|
selectedWorkspace => selectedWorkspace.id !== workspace.id
|
|
)
|
|
} else {
|
|
return [...prevState, workspace]
|
|
}
|
|
})
|
|
}
|
|
|
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
|
if (!isTyping && e.key === "Enter" && !e.shiftKey) {
|
|
e.preventDefault()
|
|
buttonRef.current?.click()
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Sheet open={isOpen} onOpenChange={setIsOpen}>
|
|
<SheetTrigger asChild>{children}</SheetTrigger>
|
|
|
|
<SheetContent
|
|
className="flex min-w-[450px] flex-col justify-between"
|
|
side="left"
|
|
onKeyDown={handleKeyDown}
|
|
>
|
|
<div className="grow overflow-auto">
|
|
<SheetHeader>
|
|
<SheetTitle className="text-2xl font-bold">
|
|
Edit {contentType.slice(0, -1)}
|
|
</SheetTitle>
|
|
</SheetHeader>
|
|
|
|
<div className="mt-4 space-y-3">
|
|
{workspaces.length > 1 && (
|
|
<div className="space-y-1">
|
|
<Label>Assigned Workspaces</Label>
|
|
|
|
<AssignWorkspaces
|
|
selectedWorkspaces={selectedWorkspaces}
|
|
onSelectWorkspace={handleSelectWorkspace}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{renderInputs(renderState[contentType])}
|
|
</div>
|
|
</div>
|
|
|
|
<SheetFooter className="mt-2 flex justify-between">
|
|
<SidebarDeleteItem item={item} contentType={contentType} />
|
|
|
|
<div className="flex grow justify-end space-x-2">
|
|
<Button variant="outline" onClick={() => setIsOpen(false)}>
|
|
Cancel
|
|
</Button>
|
|
|
|
<Button ref={buttonRef} onClick={handleUpdate}>
|
|
Save
|
|
</Button>
|
|
</div>
|
|
</SheetFooter>
|
|
</SheetContent>
|
|
</Sheet>
|
|
)
|
|
}
|