Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/src/components/AttachmentPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { getFileType } from "@/utils/fileUtils"
import { getFileType } from "shared/fileUtils"
import { getFileIcon } from "@/components/ChatBox"

interface AttachmentPreviewProps {
Expand Down
30 changes: 26 additions & 4 deletions frontend/src/components/ChatBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ import {
validateAndDeduplicateFiles,
createImagePreview,
cleanupPreviewUrls,
getFileType,
} from "@/utils/fileUtils"
import { getFileType } from "shared/fileUtils"
import { authFetch } from "@/utils/authFetch"

interface SelectedFile {
Expand Down Expand Up @@ -977,15 +977,37 @@ export const ChatBox = React.forwardRef<ChatBoxRef, ChatBoxProps>(
return ext || "file"
}

const removeFile = useCallback((id: string) => {
const removeFile = useCallback(async (id: string) => {
const fileToRemove = selectedFiles.find((f) => f.id === id)

// If the file has metadata with fileId (meaning it's already uploaded), delete it from the server
if (fileToRemove?.metadata?.fileId) {
try {
const response = await api.files.delete.$post({
json: {
attachment: fileToRemove.metadata
}
})

if (!response.ok) {
const errorText = await response.text()
console.error(`Failed to delete attachment: ${errorText}`)
// Still remove from UI even if server deletion fails
}
} catch (error) {
console.error('Error deleting attachment:', error)
// Still remove from UI even if server deletion fails
}
}

// Remove from UI
setSelectedFiles((prev) => {
const fileToRemove = prev.find((f) => f.id === id)
if (fileToRemove?.preview) {
URL.revokeObjectURL(fileToRemove.preview)
}
return prev.filter((f) => f.id !== id)
})
}, [])
}, [selectedFiles])

const { handleFileSelect, handleFileChange } = createFileSelectionHandlers(
fileInputRef,
Expand Down
26 changes: 2 additions & 24 deletions frontend/src/lib/common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,9 @@ import {
DataSourceEntity,
WebSearchEntity,
FileType,
MIME_TYPE_MAPPINGS,
EXTENSION_MAPPINGS,
} from "shared/types"
import { LoadingSpinner } from "@/routes/_authenticated/admin/integrations/google"
import { getFileType } from "shared/fileUtils"

// Define placeholder entities if they don't exist in shared/types
const PdfEntity = { Default: "pdf_default" } as const
Expand Down Expand Up @@ -165,30 +164,9 @@ export const getIcon = (
}
}

// Helper function to determine FileType from a file
export const getFileType = (file: File): FileType => {
// First check by MIME type
for (const [fileType, mimeTypes] of Object.entries(MIME_TYPE_MAPPINGS)) {
if ((mimeTypes as readonly string[]).includes(file.type)) {
return fileType as FileType
}
}

// Fallback to extension checking
const fileName = file.name.toLowerCase()
for (const [fileType, extensions] of Object.entries(EXTENSION_MAPPINGS)) {
if ((extensions as readonly string[]).some(ext => fileName.endsWith(ext))) {
return fileType as FileType
}
}

// Default fallback
return FileType.FILE
}

// Icon mapping from FileType to SVG component
export const getFileIcon = (file: File) => {
const fileType = getFileType(file)
const fileType = getFileType({type: file.type, name: file.name})

switch (fileType) {
case FileType.TEXT:
Expand Down
26 changes: 1 addition & 25 deletions frontend/src/utils/fileUtils.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,11 @@
import { isValidFile, isImageFile } from "shared/fileUtils"
import { SelectedFile } from "@/components/ClFileUpload"
import { authFetch } from "./authFetch"
import { FileType, MIME_TYPE_MAPPINGS, EXTENSION_MAPPINGS, UploadStatus } from "shared/types"
import { UploadStatus } from "shared/types"

// Generate unique ID for files
export const generateFileId = () => Math.random().toString(36).substring(2, 9)

export const getFileType = ({ type, name }: { type: string, name: string }): FileType => {
const fileName = name.toLowerCase()
const mimeType = type.toLowerCase()
const baseMime = mimeType.split(";")[0]

// Check each file type category using the mappings
for (const [fileType, mimeTypes] of Object.entries(MIME_TYPE_MAPPINGS)) {
// Check MIME type first (more reliable)
if (mimeTypes.some(mime => baseMime === mime)) {
return fileType as FileType
}
}

// Fallback to extension-based detection
for (const [fileType, extensions] of Object.entries(EXTENSION_MAPPINGS)) {
if (extensions.some(ext => fileName.endsWith(ext))) {
return fileType as FileType
}
}

// Default fallback
return FileType.FILE
}

// Create preview URL for image files
export const createImagePreview = (file: File): string | undefined => {
if (isImageFile(file.type)) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"dependencies": {
"zustand": "^5.0.8"
}
}
}
5 changes: 3 additions & 2 deletions server/api/chat/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import {
VespaChatUserSchema,
type VespaSearchResult,
type VespaSearchResults,
AttachmentEntity,
} from "@xyne/vespa-ts/types"
import { APIError } from "openai"
import { insertChatTrace } from "@/db/chatTrace"
Expand Down Expand Up @@ -121,7 +122,6 @@ import { getModelValueFromLabel } from "@/ai/modelConfig"
import {
buildContext,
buildUserQuery,
expandSheetIds,
getThreadContext,
isContextSelected,
UnderstandMessageAndAnswer,
Expand Down Expand Up @@ -156,6 +156,7 @@ import { getDateForAI } from "@/utils/index"
import { validateVespaIdInAgentIntegrations } from "@/search/utils"
import { getAuth, safeGet } from "../agent"
import { applyFollowUpContext } from "@/utils/parseAttachment"
import { expandSheetIds } from "@/search/utils"
const {
JwtPayloadKey,
defaultBestModel,
Expand Down Expand Up @@ -392,7 +393,7 @@ const checkAndYieldCitationsForAgent = async function* (
}

// we dont want citations for attachments in the chat
if (item.source.entity === KnowledgeBaseEntity.Attachment) {
if (Object.values(AttachmentEntity).includes(item.source.entity as AttachmentEntity)) {
continue
}

Expand Down
Loading
Loading