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
16 changes: 13 additions & 3 deletions apps/sim/app/api/tools/onedrive/upload/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,30 @@ import { createLogger } from '@/lib/logs/console/logger'
import { processSingleFileToUserFile } from '@/lib/uploads/utils/file-utils'
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
import { generateRequestId } from '@/lib/utils'
import { normalizeExcelValues } from '@/tools/onedrive/utils'

export const dynamic = 'force-dynamic'

const logger = createLogger('OneDriveUploadAPI')

const MICROSOFT_GRAPH_BASE = 'https://graph.microsoft.com/v1.0'

const ExcelCellSchema = z.union([z.string(), z.number(), z.boolean(), z.null()])
const ExcelRowSchema = z.array(ExcelCellSchema)
const ExcelValuesSchema = z.union([
z.string(),
z.array(ExcelRowSchema),
z.array(z.record(ExcelCellSchema)),
])

const OneDriveUploadSchema = z.object({
accessToken: z.string().min(1, 'Access token is required'),
fileName: z.string().min(1, 'File name is required'),
file: z.any().optional(), // UserFile object (optional for blank Excel creation)
folderId: z.string().optional().nullable(),
mimeType: z.string().optional(),
// Optional Excel write-after-create inputs
values: z.array(z.array(z.union([z.string(), z.number(), z.boolean(), z.null()]))).optional(),
values: ExcelValuesSchema.optional(),
})

export async function POST(request: NextRequest) {
Expand All @@ -46,6 +55,7 @@ export async function POST(request: NextRequest) {

const body = await request.json()
const validatedData = OneDriveUploadSchema.parse(body)
const excelValues = normalizeExcelValues(validatedData.values)

let fileBuffer: Buffer
let mimeType: string
Expand Down Expand Up @@ -180,7 +190,7 @@ export async function POST(request: NextRequest) {
// If this is an Excel creation and values were provided, write them using the Excel API
let excelWriteResult: any | undefined
const shouldWriteExcelContent =
isExcelCreation && Array.isArray(validatedData.values) && validatedData.values.length > 0
isExcelCreation && Array.isArray(excelValues) && excelValues.length > 0

if (shouldWriteExcelContent) {
try {
Expand Down Expand Up @@ -232,7 +242,7 @@ export async function POST(request: NextRequest) {
logger.warn(`[${requestId}] Error listing worksheets, using default Sheet1`, listError)
}

let processedValues: any = validatedData.values || []
let processedValues: any = excelValues || []

if (
Array.isArray(processedValues) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,17 +221,26 @@ export function Code({
// Derived state
const effectiveLanguage = (languageValue as 'javascript' | 'python' | 'json') || language

const trimmedCode = code.trim()
const containsReferencePlaceholders =
trimmedCode.includes('{{') ||
trimmedCode.includes('}}') ||
trimmedCode.includes('<') ||
trimmedCode.includes('>')

const shouldValidateJson = effectiveLanguage === 'json' && !containsReferencePlaceholders

const isValidJson = useMemo(() => {
if (subBlockId !== 'responseFormat' || !code.trim()) {
if (!shouldValidateJson || !trimmedCode) {
return true
}
try {
JSON.parse(code)
JSON.parse(trimmedCode)
return true
} catch {
return false
}
}, [subBlockId, code])
}, [shouldValidateJson, trimmedCode])

const gutterWidthPx = useMemo(() => {
const lineCount = code.split('\n').length
Expand Down Expand Up @@ -309,14 +318,29 @@ export function Code({
: storeValue

// Effects: JSON validation
const lastValidationStatus = useRef<boolean>(true)

useEffect(() => {
if (onValidationChange && subBlockId === 'responseFormat') {
const timeoutId = setTimeout(() => {
onValidationChange(isValidJson)
}, 150)
return () => clearTimeout(timeoutId)
if (!onValidationChange) return

const nextStatus = shouldValidateJson ? isValidJson : true
if (lastValidationStatus.current === nextStatus) {
return
}
}, [isValidJson, onValidationChange, subBlockId])

lastValidationStatus.current = nextStatus

if (!shouldValidateJson) {
onValidationChange(nextStatus)
return
}

const timeoutId = setTimeout(() => {
onValidationChange(nextStatus)
}, 150)

return () => clearTimeout(timeoutId)
}, [isValidJson, onValidationChange, shouldValidateJson])

// Effects: AI stream handlers setup
useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ const renderLabel = (
<div className='flex items-center gap-[6px] whitespace-nowrap'>
{config.title}
{required && <span className='ml-0.5'>*</span>}
{config.id === 'responseFormat' && (
{config.type === 'code' && config.language === 'json' && (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<AlertTriangle
Expand Down
27 changes: 17 additions & 10 deletions apps/sim/blocks/blocks/onedrive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createLogger } from '@/lib/logs/console/logger'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { OneDriveResponse } from '@/tools/onedrive/types'
import { normalizeExcelValuesForToolParams } from '@/tools/onedrive/utils'

const logger = createLogger('OneDriveBlock')

Expand Down Expand Up @@ -78,9 +79,10 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
{
id: 'values',
title: 'Values',
type: 'long-input',
placeholder:
'Enter values as JSON array of arrays (e.g., [["A1","B1"],["A2","B2"]]) or an array of objects',
type: 'code',
language: 'json',
generationType: 'json-object',
placeholder: 'Enter a JSON array of rows (e.g., [["A1","B1"],["A2","B2"]])',
condition: {
field: 'operation',
value: 'create_file',
Expand All @@ -89,6 +91,13 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
value: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
},
},
wandConfig: {
enabled: true,
prompt:
'Generate a JSON array of arrays that can be written directly into an Excel worksheet.',
placeholder: 'Describe the table you want to generate...',
generationType: 'json-object',
},
required: false,
},
// File upload (basic mode)
Expand Down Expand Up @@ -351,17 +360,15 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
params: (params) => {
const { credential, folderId, fileId, mimeType, values, downloadFileName, ...rest } = params

let parsedValues
try {
parsedValues = values ? JSON.parse(values as string) : undefined
} catch (error) {
throw new Error('Invalid JSON format for values')
let normalizedValues: ReturnType<typeof normalizeExcelValuesForToolParams>
if (values !== undefined) {
normalizedValues = normalizeExcelValuesForToolParams(values)
}

return {
credential,
...rest,
values: parsedValues,
values: normalizedValues,
folderId: folderId || undefined,
fileId: fileId || undefined,
pageSize: rest.pageSize ? Number.parseInt(rest.pageSize as string, 10) : undefined,
Expand All @@ -380,7 +387,7 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
fileReference: { type: 'json', description: 'File reference from previous block' },
content: { type: 'string', description: 'Text content to upload' },
mimeType: { type: 'string', description: 'MIME type of file to create' },
values: { type: 'string', description: 'Cell values for new Excel as JSON' },
values: { type: 'json', description: 'Cell values for new Excel as JSON' },
fileId: { type: 'string', description: 'File ID to download' },
downloadFileName: { type: 'string', description: 'File name override for download' },
folderId: { type: 'string', description: 'Folder ID' },
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/blocks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export interface SubBlockConfig {
}
})
// Props specific to 'code' sub-block type
language?: 'javascript' | 'json'
language?: 'javascript' | 'json' | 'python'
generationType?: GenerationType
collapsible?: boolean // Whether the code block can be collapsed
defaultCollapsed?: boolean // Whether the code block is collapsed by default
Expand Down
4 changes: 3 additions & 1 deletion apps/sim/tools/onedrive/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ export interface OneDriveToolParams {
pageToken?: string
exportMimeType?: string
// Optional Excel write parameters (used when creating an .xlsx without file content)
values?: (string | number | boolean | null)[][]
values?:
| (string | number | boolean | null)[][]
| Array<Record<string, string | number | boolean | null>>
}

export type OneDriveResponse =
Expand Down
49 changes: 49 additions & 0 deletions apps/sim/tools/onedrive/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { OneDriveToolParams } from '@/tools/onedrive/types'

export type ExcelCell = string | number | boolean | null
export type ExcelArrayValues = ExcelCell[][]
export type ExcelObjectValues = Array<Record<string, ExcelCell>>
export type NormalizedExcelValues = ExcelArrayValues | ExcelObjectValues

/**
* Ensures Excel values are always represented as arrays before hitting downstream tooling.
* Accepts JSON strings, array-of-arrays, or array-of-objects and normalizes them.
*/
export function normalizeExcelValues(values: unknown): NormalizedExcelValues | undefined {
if (values === null || values === undefined) {
return undefined
}

if (typeof values === 'string') {
const trimmed = values.trim()
if (!trimmed) {
return undefined
}

try {
const parsed = JSON.parse(trimmed)
if (!Array.isArray(parsed)) {
throw new Error('Excel values must be an array of rows or array of objects')
}
return parsed as NormalizedExcelValues
} catch (_error) {
throw new Error('Invalid JSON format for values')
}
}

if (Array.isArray(values)) {
return values as NormalizedExcelValues
}

throw new Error('Excel values must be an array of rows or array of objects')
}

/**
* Convenience helper for contexts that expect the narrower ToolParams typing.
*/
export function normalizeExcelValuesForToolParams(
values: unknown
): OneDriveToolParams['values'] | undefined {
const normalized = normalizeExcelValues(values)
return normalized as OneDriveToolParams['values'] | undefined
Comment thread
icecrasher321 marked this conversation as resolved.
}