mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(logs): relax fileSchema so execution logs with files render again (#4495)
* fix(logs): relax fileSchema so execution logs with files render again * improvement(logs): align fileSchema with shared UserFile type - contracts/logs.ts: replace local fileSchema with mediaUserFileSchema (the established UserFile boundary schema with .passthrough()) - file-download.tsx: drop local FileData interface, use UserFile from @/executor/types * improvement(contracts): promote userFileSchema to primitives Move the canonical UserFile boundary schema out of tools/media/shared.ts (where it didn't belong — logs aren't media tools) into primitives.ts as userFileSchema. Update logs, stt, and video contracts to import from the shared primitive. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
98f8e854eb
commit
d081ab283e
+6
-17
@@ -5,31 +5,20 @@ import { createLogger } from '@sim/logger'
|
||||
import { ArrowDown } from 'lucide-react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Button, Loader } from '@/components/emcn'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { extractWorkspaceIdFromExecutionKey, getViewerUrl } from '@/lib/uploads/utils/file-utils'
|
||||
import type { UserFile } from '@/executor/types'
|
||||
|
||||
const logger = createLogger('FileCards')
|
||||
|
||||
interface FileData {
|
||||
id?: string
|
||||
name: string
|
||||
size: number
|
||||
type: string
|
||||
key: string
|
||||
url: string
|
||||
uploadedAt: string
|
||||
expiresAt: string
|
||||
storageProvider?: 's3' | 'blob' | 'local'
|
||||
bucketName?: string
|
||||
}
|
||||
|
||||
interface FileCardsProps {
|
||||
files: FileData[]
|
||||
files: UserFile[]
|
||||
isExecutionFile?: boolean
|
||||
workspaceId?: string
|
||||
}
|
||||
|
||||
interface FileCardProps {
|
||||
file: FileData
|
||||
file: UserFile
|
||||
isExecutionFile?: boolean
|
||||
workspaceId?: string
|
||||
}
|
||||
@@ -157,7 +146,7 @@ export function FileDownload({
|
||||
className,
|
||||
workspaceId,
|
||||
}: {
|
||||
file: FileData
|
||||
file: UserFile
|
||||
isExecutionFile?: boolean
|
||||
className?: string
|
||||
workspaceId?: string
|
||||
@@ -220,7 +209,7 @@ export function FileDownload({
|
||||
return (
|
||||
<Button
|
||||
variant='ghost'
|
||||
className={`h-7 px-2 text-xs ${className}`}
|
||||
className={cn('h-7 px-2 text-xs', className)}
|
||||
onClick={handleDownload}
|
||||
disabled={isDownloading}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { z } from 'zod'
|
||||
import { userFileSchema } from '@/lib/api/contracts/primitives'
|
||||
import { defineRouteContract } from '@/lib/api/contracts/types'
|
||||
|
||||
const comparisonOperatorSchema = z.enum(['=', '>', '<', '>=', '<=', '!='])
|
||||
@@ -66,19 +67,6 @@ const workflowSummarySchema = z
|
||||
})
|
||||
.partial()
|
||||
|
||||
const fileSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
size: z.number(),
|
||||
type: z.string(),
|
||||
url: z.string(),
|
||||
key: z.string(),
|
||||
uploadedAt: z.string(),
|
||||
expiresAt: z.string(),
|
||||
storageProvider: z.enum(['s3', 'blob', 'local']).optional(),
|
||||
bucketName: z.string().optional(),
|
||||
})
|
||||
|
||||
const tokenBreakdownSchema = z
|
||||
.object({
|
||||
total: z.number().optional(),
|
||||
@@ -237,7 +225,7 @@ export const workflowLogSummarySchema = z.object({
|
||||
|
||||
export const workflowLogDetailSchema = workflowLogSummarySchema.extend({
|
||||
executionData: executionDataDetailSchema,
|
||||
files: z.array(fileSchema).nullable(),
|
||||
files: z.array(userFileSchema).nullable(),
|
||||
})
|
||||
|
||||
export type WorkflowLogSummary = z.output<typeof workflowLogSummarySchema>
|
||||
|
||||
@@ -59,6 +59,26 @@ export const workflowIdSchema = z.string().min(1, 'Workflow ID is required')
|
||||
* Use `.optional()` / `.default(...)` at the call site, not here, so each
|
||||
* query field controls its own omission/default semantics.
|
||||
*/
|
||||
/**
|
||||
* Canonical boundary schema for `UserFile` (`apps/sim/executor/types.ts`) — the
|
||||
* shape produced by the executor and persisted in `workflowExecutionLogs.files`,
|
||||
* forwarded through tool inputs, and rendered in the logs UI. `.passthrough()`
|
||||
* tolerates legacy/extra fields on stored rows (e.g. `uploadedAt`, `expiresAt`,
|
||||
* `storageProvider`) without rejecting the whole payload.
|
||||
*/
|
||||
export const userFileSchema = z
|
||||
.object({
|
||||
id: z.string().optional().default(''),
|
||||
name: z.string().min(1),
|
||||
url: z.string().optional().default(''),
|
||||
size: z.coerce.number().nonnegative(),
|
||||
type: z.string().optional().default('application/octet-stream'),
|
||||
key: z.string().min(1),
|
||||
context: z.string().optional(),
|
||||
base64: z.string().optional(),
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
export const booleanQueryFlagSchema = z.preprocess(
|
||||
(value) => {
|
||||
if (typeof value === 'boolean') return value
|
||||
|
||||
@@ -3,19 +3,6 @@ import { z } from 'zod'
|
||||
export const AWS_REGION_PATTERN =
|
||||
/^(eu-isoe|us-isob|us-iso|us-gov|af|ap|ca|cn|eu|il|me|mx|sa|us)-(central|north|northeast|northwest|south|southeast|southwest|east|west)-\d{1,2}$/
|
||||
|
||||
export const mediaUserFileSchema = z
|
||||
.object({
|
||||
id: z.string().optional().default(''),
|
||||
name: z.string().min(1),
|
||||
url: z.string().optional().default(''),
|
||||
size: z.coerce.number().nonnegative(),
|
||||
type: z.string().optional().default('application/octet-stream'),
|
||||
key: z.string().min(1),
|
||||
context: z.string().optional(),
|
||||
base64: z.string().optional(),
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
export const toolJsonResponseSchema = z
|
||||
.object({
|
||||
success: z.boolean().optional(),
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { z } from 'zod'
|
||||
import { mediaUserFileSchema, toolJsonResponseSchema } from '@/lib/api/contracts/tools/media/shared'
|
||||
import { userFileSchema } from '@/lib/api/contracts/primitives'
|
||||
import { toolJsonResponseSchema } from '@/lib/api/contracts/tools/media/shared'
|
||||
import { defineRouteContract } from '@/lib/api/contracts/types'
|
||||
|
||||
export const sttProviders = ['whisper', 'deepgram', 'elevenlabs', 'assemblyai', 'gemini'] as const
|
||||
const MISSING_STT_FIELDS_ERROR = 'Missing required fields: provider and apiKey'
|
||||
|
||||
export const sttUserFileSchema = mediaUserFileSchema.extend({
|
||||
export const sttUserFileSchema = userFileSchema.extend({
|
||||
type: z.string().optional().default(''),
|
||||
})
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { z } from 'zod'
|
||||
import { mediaUserFileSchema, toolJsonResponseSchema } from '@/lib/api/contracts/tools/media/shared'
|
||||
import { userFileSchema } from '@/lib/api/contracts/primitives'
|
||||
import { toolJsonResponseSchema } from '@/lib/api/contracts/tools/media/shared'
|
||||
import { defineRouteContract } from '@/lib/api/contracts/types'
|
||||
|
||||
export const videoProviders = ['runway', 'veo', 'luma', 'minimax', 'falai'] as const
|
||||
@@ -19,7 +20,7 @@ export const videoToolBodySchema = z
|
||||
duration: z.coerce.number().optional(),
|
||||
aspectRatio: z.string().optional(),
|
||||
resolution: z.string().optional(),
|
||||
visualReference: mediaUserFileSchema.optional(),
|
||||
visualReference: userFileSchema.optional(),
|
||||
cameraControl: z.unknown().optional(),
|
||||
endpoint: z.string().optional(),
|
||||
promptOptimizer: z.boolean().optional(),
|
||||
|
||||
Reference in New Issue
Block a user