mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
fix(onedrive): parse array values correctly (#1981)
* fix(onedrive): parse array values correctly * fix onedrive * fix * fix onedrive input parsing by reusing code subblock * fix type
This commit is contained in:
committed by
GitHub
parent
dab70a8f1d
commit
785f847c48
@@ -6,6 +6,7 @@ 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'
|
||||
|
||||
@@ -13,6 +14,14 @@ 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'),
|
||||
@@ -20,7 +29,7 @@ const OneDriveUploadSchema = z.object({
|
||||
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) {
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
@@ -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) &&
|
||||
|
||||
@@ -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
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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',
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
@@ -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' },
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 =
|
||||
|
||||
49
apps/sim/tools/onedrive/utils.ts
Normal file
49
apps/sim/tools/onedrive/utils.ts
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user