chatbot-ui/db/files.ts

317 lines
6.9 KiB
TypeScript

import { supabase } from "@/lib/supabase/browser-client"
import { TablesInsert, TablesUpdate } from "@/supabase/types"
import mammoth from "mammoth"
import { toast } from "sonner"
import { uploadFile } from "./storage/files"
export const getFileById = async (fileId: string) => {
const { data: file, error } = await supabase
.from("files")
.select("*")
.eq("id", fileId)
.single()
if (!file) {
throw new Error(error.message)
}
return file
}
export const getFileWorkspacesByWorkspaceId = async (workspaceId: string) => {
const { data: workspace, error } = await supabase
.from("workspaces")
.select(
`
id,
name,
files (*)
`
)
.eq("id", workspaceId)
.single()
if (!workspace) {
throw new Error(error.message)
}
return workspace
}
export const getFileWorkspacesByFileId = async (fileId: string) => {
const { data: file, error } = await supabase
.from("files")
.select(
`
id,
name,
workspaces (*)
`
)
.eq("id", fileId)
.single()
if (!file) {
throw new Error(error.message)
}
return file
}
export const createFileBasedOnExtension = async (
file: File,
fileRecord: TablesInsert<"files">,
workspace_id: string,
embeddingsProvider: "openai" | "local"
) => {
const fileExtension = file.name.split(".").pop()
if (fileExtension === "docx") {
const arrayBuffer = await file.arrayBuffer()
const result = await mammoth.extractRawText({
arrayBuffer
})
return createDocXFile(
result.value,
file,
fileRecord,
workspace_id,
embeddingsProvider
)
} else {
return createFile(file, fileRecord, workspace_id, embeddingsProvider)
}
}
// For non-docx files
export const createFile = async (
file: File,
fileRecord: TablesInsert<"files">,
workspace_id: string,
embeddingsProvider: "openai" | "local"
) => {
let validFilename = fileRecord.name.replace(/[^a-z0-9.]/gi, "_").toLowerCase()
const extension = file.name.split(".").pop()
const extensionIndex = validFilename.lastIndexOf(".")
const baseName = validFilename.substring(0, (extensionIndex < 0) ? undefined : extensionIndex)
const maxBaseNameLength = 100 - (extension?.length || 0) - 1
if (baseName.length > maxBaseNameLength) {
fileRecord.name = baseName.substring(0, maxBaseNameLength) + "." + extension
} else {
fileRecord.name = baseName + "." + extension
}
const { data: createdFile, error } = await supabase
.from("files")
.insert([fileRecord])
.select("*")
.single()
if (error) {
throw new Error(error.message)
}
await createFileWorkspace({
user_id: createdFile.user_id,
file_id: createdFile.id,
workspace_id
})
const filePath = await uploadFile(file, {
name: createdFile.name,
user_id: createdFile.user_id,
file_id: createdFile.name
})
await updateFile(createdFile.id, {
file_path: filePath
})
const formData = new FormData()
formData.append("file_id", createdFile.id)
formData.append("embeddingsProvider", embeddingsProvider)
const response = await fetch("/api/retrieval/process", {
method: "POST",
body: formData
})
if (!response.ok) {
const jsonText = await response.text()
const json = JSON.parse(jsonText)
console.error(
`Error processing file:${createdFile.id}, status:${response.status}, response:${json.message}`
)
toast.error("Failed to process file. Reason:" + json.message, {
duration: 10000
})
await deleteFile(createdFile.id)
}
const fetchedFile = await getFileById(createdFile.id)
return fetchedFile
}
// // Handle docx files
export const createDocXFile = async (
text: string,
file: File,
fileRecord: TablesInsert<"files">,
workspace_id: string,
embeddingsProvider: "openai" | "local"
) => {
const { data: createdFile, error } = await supabase
.from("files")
.insert([fileRecord])
.select("*")
.single()
if (error) {
throw new Error(error.message)
}
await createFileWorkspace({
user_id: createdFile.user_id,
file_id: createdFile.id,
workspace_id
})
const filePath = await uploadFile(file, {
name: createdFile.name,
user_id: createdFile.user_id,
file_id: createdFile.name
})
await updateFile(createdFile.id, {
file_path: filePath
})
const response = await fetch("/api/retrieval/process/docx", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
text: text,
fileId: createdFile.id,
embeddingsProvider,
fileExtension: "docx"
})
})
if (!response.ok) {
const jsonText = await response.text()
const json = JSON.parse(jsonText)
console.error(
`Error processing file:${createdFile.id}, status:${response.status}, response:${json.message}`
)
toast.error("Failed to process file. Reason:" + json.message, {
duration: 10000
})
await deleteFile(createdFile.id)
}
const fetchedFile = await getFileById(createdFile.id)
return fetchedFile
}
export const createFiles = async (
files: TablesInsert<"files">[],
workspace_id: string
) => {
const { data: createdFiles, error } = await supabase
.from("files")
.insert(files)
.select("*")
if (error) {
throw new Error(error.message)
}
await createFileWorkspaces(
createdFiles.map(file => ({
user_id: file.user_id,
file_id: file.id,
workspace_id
}))
)
return createdFiles
}
export const createFileWorkspace = async (item: {
user_id: string
file_id: string
workspace_id: string
}) => {
const { data: createdFileWorkspace, error } = await supabase
.from("file_workspaces")
.insert([item])
.select("*")
.single()
if (error) {
throw new Error(error.message)
}
return createdFileWorkspace
}
export const createFileWorkspaces = async (
items: { user_id: string; file_id: string; workspace_id: string }[]
) => {
const { data: createdFileWorkspaces, error } = await supabase
.from("file_workspaces")
.insert(items)
.select("*")
if (error) throw new Error(error.message)
return createdFileWorkspaces
}
export const updateFile = async (
fileId: string,
file: TablesUpdate<"files">
) => {
const { data: updatedFile, error } = await supabase
.from("files")
.update(file)
.eq("id", fileId)
.select("*")
.single()
if (error) {
throw new Error(error.message)
}
return updatedFile
}
export const deleteFile = async (fileId: string) => {
const { error } = await supabase.from("files").delete().eq("id", fileId)
if (error) {
throw new Error(error.message)
}
return true
}
export const deleteFileWorkspace = async (
fileId: string,
workspaceId: string
) => {
const { error } = await supabase
.from("file_workspaces")
.delete()
.eq("file_id", fileId)
.eq("workspace_id", workspaceId)
if (error) throw new Error(error.message)
return true
}