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
28 changes: 22 additions & 6 deletions apps/sim/tools/google_drive/upload.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { GoogleDriveToolParams, GoogleDriveUploadResponse } from '@/tools/google_drive/types'
import { GOOGLE_WORKSPACE_MIME_TYPES, SOURCE_MIME_TYPES } from '@/tools/google_drive/utils'
import {
GOOGLE_WORKSPACE_MIME_TYPES,
handleSheetsFormat,
SOURCE_MIME_TYPES,
} from '@/tools/google_drive/utils'
import type { ToolConfig } from '@/tools/types'

const logger = createLogger('GoogleDriveUploadTool')
Expand Down Expand Up @@ -96,13 +100,27 @@ export const uploadTool: ToolConfig<GoogleDriveToolParams, GoogleDriveUploadResp
throw new Error(data.error?.message || 'Failed to create file in Google Drive')
}

// Now upload content to the created file
const fileId = data.id
const requestedMimeType = params?.mimeType || 'text/plain'
const authHeader =
response.headers.get('Authorization') || `Bearer ${params?.accessToken || ''}`

// For Google Workspace formats, use the appropriate source MIME type for content upload
let preparedContent: string | undefined =
typeof params?.content === 'string' ? (params?.content as string) : undefined

if (requestedMimeType === 'application/vnd.google-apps.spreadsheet' && params?.content) {
const { csv, rowCount, columnCount } = handleSheetsFormat(params.content as unknown)
if (csv !== undefined) {
preparedContent = csv
logger.info('Prepared CSV content for Google Sheets upload', {
fileId,
fileName: params?.fileName,
rowCount,
columnCount,
})
}
}

const uploadMimeType = GOOGLE_WORKSPACE_MIME_TYPES.includes(requestedMimeType)
? SOURCE_MIME_TYPES[requestedMimeType] || 'text/plain'
: requestedMimeType
Expand All @@ -122,7 +140,7 @@ export const uploadTool: ToolConfig<GoogleDriveToolParams, GoogleDriveUploadResp
Authorization: authHeader,
'Content-Type': uploadMimeType,
},
body: params?.content || '',
body: preparedContent !== undefined ? preparedContent : params?.content || '',
}
)

Expand All @@ -136,7 +154,6 @@ export const uploadTool: ToolConfig<GoogleDriveToolParams, GoogleDriveUploadResp
throw new Error(uploadError.error?.message || 'Failed to upload content to file')
}

// For Google Workspace documents, update the name again to ensure it sticks after conversion
if (GOOGLE_WORKSPACE_MIME_TYPES.includes(requestedMimeType)) {
logger.info('Updating file name to ensure it persists after conversion', {
fileId,
Expand Down Expand Up @@ -165,7 +182,6 @@ export const uploadTool: ToolConfig<GoogleDriveToolParams, GoogleDriveUploadResp
}
}

// Get the final file data
const finalFileResponse = await fetch(
`https://www.googleapis.com/drive/v3/files/${fileId}?supportsAllDrives=true&fields=id,name,mimeType,webViewLink,webContentLink,size,createdTime,modifiedTime,parents`,
{
Expand Down
73 changes: 73 additions & 0 deletions apps/sim/tools/google_drive/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,76 @@ export const SOURCE_MIME_TYPES: Record<string, string> = {
'application/vnd.google-apps.spreadsheet': 'text/csv',
'application/vnd.google-apps.presentation': 'application/vnd.ms-powerpoint',
}

export function handleSheetsFormat(input: unknown): {
csv?: string
rowCount: number
columnCount: number
} {
let workingValue: unknown = input

if (typeof workingValue === 'string') {
try {
workingValue = JSON.parse(workingValue)
} catch (_error) {
const csvString = workingValue as string
return { csv: csvString, rowCount: 0, columnCount: 0 }
}
}

if (!Array.isArray(workingValue)) {
return { rowCount: 0, columnCount: 0 }
}

let table: unknown[] = workingValue

if (
table.length > 0 &&
typeof (table as any)[0] === 'object' &&
(table as any)[0] !== null &&
!Array.isArray((table as any)[0])
) {
const allKeys = new Set<string>()
;(table as any[]).forEach((obj) => {
if (obj && typeof obj === 'object') {
Object.keys(obj).forEach((key) => allKeys.add(key))
}
})
const headers = Array.from(allKeys)
const rows = (table as any[]).map((obj) => {
if (!obj || typeof obj !== 'object') {
return Array(headers.length).fill('')
}
return headers.map((key) => {
const value = (obj as Record<string, unknown>)[key]
if (value !== null && typeof value === 'object') {
return JSON.stringify(value)
}
return value === undefined ? '' : (value as any)
})
})
table = [headers, ...rows]
}

const escapeCell = (cell: unknown): string => {
if (cell === null || cell === undefined) return ''
const stringValue = String(cell)
const mustQuote = /[",\n\r]/.test(stringValue)
const doubledQuotes = stringValue.replace(/"/g, '""')
return mustQuote ? `"${doubledQuotes}"` : doubledQuotes
}

const rowsAsStrings = (table as unknown[]).map((row) => {
if (!Array.isArray(row)) {
return escapeCell(row)
}
return row.map((cell) => escapeCell(cell)).join(',')
})

const csv = rowsAsStrings.join('\r\n')
const rowCount = Array.isArray(table) ? (table as any[]).length : 0
const columnCount =
Array.isArray(table) && Array.isArray((table as any[])[0]) ? (table as any[])[0].length : 0

return { csv, rowCount, columnCount }
}