mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-03 11:14:58 -05:00
consolidate more code
This commit is contained in:
@@ -1,33 +1,15 @@
|
||||
import { FirefliesIcon } from '@/components/icons'
|
||||
import { resolveHttpsUrlFromFileInput } from '@/lib/uploads/utils/file-utils'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import { AuthMode } from '@/blocks/types'
|
||||
import type { FirefliesResponse } from '@/tools/fireflies/types'
|
||||
import { getTrigger } from '@/triggers'
|
||||
|
||||
const resolveHttpsUrlFromFileInput = (fileInput: unknown): string | null => {
|
||||
if (!fileInput || typeof fileInput !== 'object') {
|
||||
return null
|
||||
}
|
||||
|
||||
const record = fileInput as Record<string, unknown>
|
||||
const url =
|
||||
typeof record.url === 'string'
|
||||
? record.url.trim()
|
||||
: typeof record.path === 'string'
|
||||
? record.path.trim()
|
||||
: ''
|
||||
|
||||
if (!url || !url.startsWith('https://')) {
|
||||
return null
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
export const FirefliesBlock: BlockConfig<FirefliesResponse> = {
|
||||
type: 'fireflies',
|
||||
name: 'Fireflies',
|
||||
name: 'Fireflies (Legacy)',
|
||||
description: 'Interact with Fireflies.ai meeting transcripts and recordings',
|
||||
hideFromToolbar: true,
|
||||
authMode: AuthMode.ApiKey,
|
||||
triggerAllowed: true,
|
||||
longDescription:
|
||||
@@ -618,9 +600,9 @@ const firefliesV2Inputs = FirefliesBlock.inputs
|
||||
export const FirefliesV2Block: BlockConfig<FirefliesResponse> = {
|
||||
...FirefliesBlock,
|
||||
type: 'fireflies_v2',
|
||||
name: 'Fireflies (File Only)',
|
||||
name: 'Fireflies',
|
||||
description: 'Interact with Fireflies.ai meeting transcripts and recordings',
|
||||
hideFromToolbar: true,
|
||||
hideFromToolbar: false,
|
||||
subBlocks: firefliesV2SubBlocks,
|
||||
tools: {
|
||||
...FirefliesBlock.tools,
|
||||
|
||||
@@ -1,32 +1,14 @@
|
||||
import { GoogleSlidesIcon } from '@/components/icons'
|
||||
import { resolveHttpsUrlFromFileInput } from '@/lib/uploads/utils/file-utils'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import { AuthMode } from '@/blocks/types'
|
||||
import type { GoogleSlidesResponse } from '@/tools/google_slides/types'
|
||||
|
||||
const resolveHttpsUrlFromFileInput = (fileInput: unknown): string | null => {
|
||||
if (!fileInput || typeof fileInput !== 'object') {
|
||||
return null
|
||||
}
|
||||
|
||||
const record = fileInput as Record<string, unknown>
|
||||
const url =
|
||||
typeof record.url === 'string'
|
||||
? record.url.trim()
|
||||
: typeof record.path === 'string'
|
||||
? record.path.trim()
|
||||
: ''
|
||||
|
||||
if (!url || !url.startsWith('https://')) {
|
||||
return null
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
export const GoogleSlidesBlock: BlockConfig<GoogleSlidesResponse> = {
|
||||
type: 'google_slides',
|
||||
name: 'Google Slides',
|
||||
name: 'Google Slides (Legacy)',
|
||||
description: 'Read, write, and create presentations',
|
||||
hideFromToolbar: true,
|
||||
authMode: AuthMode.OAuth,
|
||||
longDescription:
|
||||
'Integrate Google Slides into the workflow. Can read, write, create presentations, replace text, add slides, add images, get thumbnails, get page details, delete objects, duplicate objects, reorder slides, create tables, create shapes, and insert text.',
|
||||
@@ -963,9 +945,9 @@ const googleSlidesV2Inputs = GoogleSlidesBlock.inputs
|
||||
export const GoogleSlidesV2Block: BlockConfig<GoogleSlidesResponse> = {
|
||||
...GoogleSlidesBlock,
|
||||
type: 'google_slides_v2',
|
||||
name: 'Google Slides (File Only)',
|
||||
name: 'Google Slides',
|
||||
description: 'Read, write, and create presentations',
|
||||
hideFromToolbar: true,
|
||||
hideFromToolbar: false,
|
||||
subBlocks: googleSlidesV2SubBlocks,
|
||||
tools: {
|
||||
...GoogleSlidesBlock.tools,
|
||||
|
||||
@@ -388,21 +388,36 @@ export const SttV2Block: BlockConfig<SttBlockResponse> = {
|
||||
suffix: '_v2',
|
||||
fallbackToolId: 'stt_whisper_v2',
|
||||
}),
|
||||
params: (params) => ({
|
||||
provider: params.provider,
|
||||
apiKey: params.apiKey,
|
||||
model: params.model,
|
||||
audioFile: params.audioFile,
|
||||
audioFileReference: params.audioFileReference,
|
||||
language: params.language,
|
||||
timestamps: params.timestamps,
|
||||
diarization: params.diarization,
|
||||
translateToEnglish: params.translateToEnglish,
|
||||
sentiment: params.sentiment,
|
||||
entityDetection: params.entityDetection,
|
||||
piiRedaction: params.piiRedaction,
|
||||
summarization: params.summarization,
|
||||
}),
|
||||
params: (params) => {
|
||||
let audioInput = params.audioFile || params.audioFileReference
|
||||
if (audioInput && typeof audioInput === 'string') {
|
||||
try {
|
||||
audioInput = JSON.parse(audioInput)
|
||||
} catch {
|
||||
throw new Error('Audio file must be a valid file reference')
|
||||
}
|
||||
}
|
||||
if (audioInput && Array.isArray(audioInput)) {
|
||||
throw new Error(
|
||||
'File reference must be a single file, not an array. Use <block.files[0]> to select one file.'
|
||||
)
|
||||
}
|
||||
return {
|
||||
provider: params.provider,
|
||||
apiKey: params.apiKey,
|
||||
model: params.model,
|
||||
audioFile: audioInput,
|
||||
audioFileReference: undefined,
|
||||
language: params.language,
|
||||
timestamps: params.timestamps,
|
||||
diarization: params.diarization,
|
||||
translateToEnglish: params.translateToEnglish,
|
||||
sentiment: params.sentiment,
|
||||
entityDetection: params.entityDetection,
|
||||
piiRedaction: params.piiRedaction,
|
||||
summarization: params.summarization,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: sttV2Inputs,
|
||||
|
||||
@@ -116,6 +116,26 @@ export const VisionV2Block: BlockConfig<VisionResponse> = {
|
||||
suffix: '_v2',
|
||||
fallbackToolId: 'vision_tool_v2',
|
||||
}),
|
||||
params: (params) => {
|
||||
let imageInput = params.imageFile || params.imageFileReference
|
||||
if (imageInput && typeof imageInput === 'string') {
|
||||
try {
|
||||
imageInput = JSON.parse(imageInput)
|
||||
} catch {
|
||||
throw new Error('Image file must be a valid file reference')
|
||||
}
|
||||
}
|
||||
if (imageInput && Array.isArray(imageInput)) {
|
||||
throw new Error(
|
||||
'File reference must be a single file, not an array. Use <block.files[0]> to select one file.'
|
||||
)
|
||||
}
|
||||
return {
|
||||
...params,
|
||||
imageFile: imageInput,
|
||||
imageFileReference: undefined,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
subBlocks: [
|
||||
|
||||
@@ -451,9 +451,7 @@ function isCompleteUserFile(file: RawFileInput): file is UserFile {
|
||||
typeof file.url === 'string' &&
|
||||
typeof file.size === 'number' &&
|
||||
typeof file.type === 'string' &&
|
||||
typeof file.key === 'string' &&
|
||||
typeof file.uploadedAt === 'string' &&
|
||||
typeof file.expiresAt === 'string'
|
||||
typeof file.key === 'string'
|
||||
)
|
||||
}
|
||||
|
||||
@@ -461,6 +459,30 @@ function isUrlLike(value: string): boolean {
|
||||
return value.startsWith('http://') || value.startsWith('https://') || value.startsWith('/')
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts HTTPS URL from a file input object (UserFile or RawFileInput)
|
||||
* Returns null if no valid HTTPS URL is found
|
||||
*/
|
||||
export function resolveHttpsUrlFromFileInput(fileInput: unknown): string | null {
|
||||
if (!fileInput || typeof fileInput !== 'object') {
|
||||
return null
|
||||
}
|
||||
|
||||
const record = fileInput as Record<string, unknown>
|
||||
const url =
|
||||
typeof record.url === 'string'
|
||||
? record.url.trim()
|
||||
: typeof record.path === 'string'
|
||||
? record.path.trim()
|
||||
: ''
|
||||
|
||||
if (!url || !url.startsWith('https://')) {
|
||||
return null
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
function resolveStorageKeyFromRawFile(file: RawFileInput): string | null {
|
||||
if (file.key) {
|
||||
return file.key
|
||||
@@ -484,45 +506,26 @@ function resolveInternalFileUrl(file: RawFileInput): string {
|
||||
if (file.url && isInternalFileUrl(file.url)) {
|
||||
return file.url
|
||||
}
|
||||
|
||||
if (file.path && isInternalFileUrl(file.path)) {
|
||||
return file.path
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a single raw file object to UserFile format
|
||||
* @param file - Raw file object (must be a single file, not an array)
|
||||
* @param requestId - Request ID for logging
|
||||
* @param logger - Logger instance
|
||||
* @returns UserFile object
|
||||
* @throws Error if file is an array or has no storage key
|
||||
* Core conversion logic from RawFileInput to UserFile
|
||||
*/
|
||||
export function processSingleFileToUserFile(
|
||||
file: RawFileInput,
|
||||
requestId: string,
|
||||
logger: Logger
|
||||
): UserFile {
|
||||
if (Array.isArray(file)) {
|
||||
const errorMsg = `Expected a single file but received an array with ${file.length} file(s). Use a file input that accepts multiple files, or select a specific file from the array (e.g., {{block.files[0]}}).`
|
||||
logger.error(`[${requestId}] ${errorMsg}`)
|
||||
throw new Error(errorMsg)
|
||||
}
|
||||
|
||||
function convertToUserFile(file: RawFileInput, requestId: string, logger: Logger): UserFile | null {
|
||||
if (isCompleteUserFile(file)) {
|
||||
return {
|
||||
...file,
|
||||
url: resolveInternalFileUrl(file),
|
||||
url: resolveInternalFileUrl(file) || file.url,
|
||||
}
|
||||
}
|
||||
|
||||
const storageKey = resolveStorageKeyFromRawFile(file)
|
||||
|
||||
if (!storageKey) {
|
||||
logger.warn(`[${requestId}] File has no storage key: ${file.name || 'unknown'}`)
|
||||
throw new Error(`File has no storage key: ${file.name || 'unknown'}`)
|
||||
return null
|
||||
}
|
||||
|
||||
const userFile: UserFile = {
|
||||
@@ -541,12 +544,32 @@ export function processSingleFileToUserFile(
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts raw file objects (from file-upload or variable references) to UserFile format
|
||||
* Accepts either a single file or an array of files and normalizes to array output
|
||||
* @param files - Single file or array of raw file objects
|
||||
* @param requestId - Request ID for logging
|
||||
* @param logger - Logger instance
|
||||
* @returns Array of UserFile objects
|
||||
* Converts a single raw file object to UserFile format
|
||||
* @throws Error if file is an array or has no storage key
|
||||
*/
|
||||
export function processSingleFileToUserFile(
|
||||
file: RawFileInput,
|
||||
requestId: string,
|
||||
logger: Logger
|
||||
): UserFile {
|
||||
if (Array.isArray(file)) {
|
||||
const errorMsg = `Expected a single file but received an array with ${file.length} file(s). Use a file input that accepts multiple files, or select a specific file from the array (e.g., {{block.files[0]}}).`
|
||||
logger.error(`[${requestId}] ${errorMsg}`)
|
||||
throw new Error(errorMsg)
|
||||
}
|
||||
|
||||
const userFile = convertToUserFile(file, requestId, logger)
|
||||
if (!userFile) {
|
||||
const errorMsg = `File has no storage key: ${file.name || 'unknown'}`
|
||||
logger.warn(`[${requestId}] ${errorMsg}`)
|
||||
throw new Error(errorMsg)
|
||||
}
|
||||
|
||||
return userFile
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts raw file objects to UserFile format, accepting single or array input
|
||||
*/
|
||||
export function processFilesToUserFiles(
|
||||
files: RawFileInput | RawFileInput[],
|
||||
@@ -557,46 +580,16 @@ export function processFilesToUserFiles(
|
||||
const userFiles: UserFile[] = []
|
||||
|
||||
for (const file of filesArray) {
|
||||
try {
|
||||
if (Array.isArray(file)) {
|
||||
logger.warn(`[${requestId}] Skipping nested array in file input`)
|
||||
continue
|
||||
}
|
||||
if (Array.isArray(file)) {
|
||||
logger.warn(`[${requestId}] Skipping nested array in file input`)
|
||||
continue
|
||||
}
|
||||
|
||||
if (isCompleteUserFile(file)) {
|
||||
userFiles.push({
|
||||
...file,
|
||||
url: resolveInternalFileUrl(file),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
const storageKey = resolveStorageKeyFromRawFile(file)
|
||||
|
||||
if (!storageKey) {
|
||||
logger.warn(`[${requestId}] Skipping file without storage key: ${file.name || 'unknown'}`)
|
||||
continue
|
||||
}
|
||||
|
||||
const userFile: UserFile = {
|
||||
id: file.id || `file-${Date.now()}`,
|
||||
name: file.name,
|
||||
url: resolveInternalFileUrl(file),
|
||||
size: file.size,
|
||||
type: file.type || 'application/octet-stream',
|
||||
key: storageKey,
|
||||
context: file.context,
|
||||
base64: file.base64,
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Converted file to UserFile: ${userFile.name} (key: ${userFile.key})`
|
||||
)
|
||||
const userFile = convertToUserFile(file, requestId, logger)
|
||||
if (userFile) {
|
||||
userFiles.push(userFile)
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`[${requestId}] Skipping file: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
)
|
||||
} else {
|
||||
logger.warn(`[${requestId}] Skipping file without storage key: ${file.name || 'unknown'}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user