diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index e9fbd90b32..da739b9273 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -4151,7 +4151,7 @@ export function DuckDuckGoIcon(props: SVGProps) { return ( - + >(new Set()) - const { isUploading, uploadProgress, uploadFiles, clearError } = useKnowledgeUpload({ + const { isUploading, uploadProgress, uploadFiles, uploadError, clearError } = useKnowledgeUpload({ workspaceId, onUploadComplete: () => { logger.info(`Successfully uploaded ${files.length} files`) @@ -234,11 +233,7 @@ export function AddDocumentsModal({
{fileError && ( - - - Error - {fileError} - +

{fileError}

)}
@@ -341,24 +336,31 @@ export function AddDocumentsModal({
- - - + + {uploadError && ( +

+ {uploadError.message} +

+ )} +
+ + +
diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/components/create-base-modal/create-base-modal.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/components/create-base-modal/create-base-modal.tsx index 0973eece03..2362593b0d 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/components/create-base-modal/create-base-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/components/create-base-modal/create-base-modal.tsx @@ -17,7 +17,6 @@ import { ModalHeader, Textarea, } from '@/components/emcn' -import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' import { cn } from '@/lib/core/utils/cn' import { createLogger } from '@/lib/logs/console/logger' import { formatFileSize, validateKnowledgeBaseFile } from '@/lib/uploads/utils/file-utils' @@ -89,7 +88,7 @@ export function CreateBaseModal({ const scrollContainerRef = useRef(null) - const { uploadFiles, isUploading, uploadProgress, clearError } = useKnowledgeUpload({ + const { uploadFiles, isUploading, uploadProgress, uploadError, clearError } = useKnowledgeUpload({ workspaceId, onUploadComplete: (uploadedFiles) => { logger.info(`Successfully uploaded ${uploadedFiles.length} files`) @@ -280,21 +279,35 @@ export function CreateBaseModal({ const newKnowledgeBase = result.data if (files.length > 0) { - newKnowledgeBase.docCount = files.length - - if (onKnowledgeBaseCreated) { - onKnowledgeBaseCreated(newKnowledgeBase) + try { + const uploadedFiles = await uploadFiles(files, newKnowledgeBase.id, { + chunkSize: data.maxChunkSize, + minCharactersPerChunk: data.minChunkSize, + chunkOverlap: data.overlapSize, + recipe: 'default', + }) + + logger.info(`Successfully uploaded ${uploadedFiles.length} files`) + logger.info(`Started processing ${uploadedFiles.length} documents in the background`) + + newKnowledgeBase.docCount = uploadedFiles.length + + if (onKnowledgeBaseCreated) { + onKnowledgeBaseCreated(newKnowledgeBase) + } + } catch (uploadError) { + // If file upload fails completely, delete the knowledge base to avoid orphaned empty KB + logger.error('File upload failed, deleting knowledge base:', uploadError) + try { + await fetch(`/api/knowledge/${newKnowledgeBase.id}`, { + method: 'DELETE', + }) + logger.info(`Deleted orphaned knowledge base: ${newKnowledgeBase.id}`) + } catch (deleteError) { + logger.error('Failed to delete orphaned knowledge base:', deleteError) + } + throw uploadError } - - const uploadedFiles = await uploadFiles(files, newKnowledgeBase.id, { - chunkSize: data.maxChunkSize, - minCharactersPerChunk: data.minChunkSize, - chunkOverlap: data.overlapSize, - recipe: 'default', - }) - - logger.info(`Successfully uploaded ${uploadedFiles.length} files`) - logger.info(`Started processing ${uploadedFiles.length} documents in the background`) } else { if (onKnowledgeBaseCreated) { onKnowledgeBaseCreated(newKnowledgeBase) @@ -325,14 +338,6 @@ export function CreateBaseModal({
- {submitStatus && submitStatus.type === 'error' && ( - - - Error - {submitStatus.message} - - )} -
- - Error - {fileError} - +

{fileError}

)}
- - - + + {(submitStatus?.type === 'error' || uploadError) && ( +

+ {uploadError?.message || submitStatus?.message} +

+ )} +
+ + +
diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/hooks/use-knowledge-upload.ts b/apps/sim/app/workspace/[workspaceId]/knowledge/hooks/use-knowledge-upload.ts index fb7085e8eb..96c85cfae2 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/hooks/use-knowledge-upload.ts +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/hooks/use-knowledge-upload.ts @@ -1,5 +1,6 @@ import { useCallback, useState } from 'react' import { createLogger } from '@/lib/logs/console/logger' +import { getFileExtension, getMimeTypeFromExtension } from '@/lib/uploads/utils/file-utils' const logger = createLogger('KnowledgeUpload') @@ -143,6 +144,17 @@ const calculateThroughputMbps = (bytes: number, durationMs: number) => { */ const formatDurationSeconds = (durationMs: number) => Number((durationMs / 1000).toFixed(2)) +/** + * Gets the content type for a file, falling back to extension-based lookup if browser doesn't provide one + */ +const getFileContentType = (file: File): string => { + if (file.type?.trim()) { + return file.type + } + const extension = getFileExtension(file.name) + return getMimeTypeFromExtension(extension) +} + /** * Runs async operations with concurrency limit */ @@ -280,7 +292,7 @@ const getPresignedData = async ( }, body: JSON.stringify({ fileName: file.name, - contentType: file.type, + contentType: getFileContentType(file), fileSize: file.size, }), signal: localController.signal, @@ -529,7 +541,9 @@ export function useKnowledgeUpload(options: UseKnowledgeUploadOptions = {}) { throughputMbps: calculateThroughputMbps(file.size, durationMs), status: xhr.status, }) - resolve(createUploadedFile(file.name, fullFileUrl, file.size, file.type, file)) + resolve( + createUploadedFile(file.name, fullFileUrl, file.size, getFileContentType(file), file) + ) } else { logger.error('S3 PUT request failed', { status: xhr.status, @@ -597,7 +611,7 @@ export function useKnowledgeUpload(options: UseKnowledgeUploadOptions = {}) { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileName: file.name, - contentType: file.type, + contentType: getFileContentType(file), fileSize: file.size, }), }) @@ -736,7 +750,7 @@ export function useKnowledgeUpload(options: UseKnowledgeUploadOptions = {}) { const fullFileUrl = path.startsWith('http') ? path : `${window.location.origin}${path}` - return createUploadedFile(file.name, fullFileUrl, file.size, file.type, file) + return createUploadedFile(file.name, fullFileUrl, file.size, getFileContentType(file), file) } catch (error) { logger.error(`Multipart upload failed for ${file.name}:`, error) const durationMs = getHighResTime() - startTime @@ -800,7 +814,7 @@ export function useKnowledgeUpload(options: UseKnowledgeUploadOptions = {}) { file.name, filePath.startsWith('http') ? filePath : `${window.location.origin}${filePath}`, file.size, - file.type, + getFileContentType(file), file ) } finally { @@ -855,7 +869,7 @@ export function useKnowledgeUpload(options: UseKnowledgeUploadOptions = {}) { const batchRequest = { files: batchFiles.map((file) => ({ fileName: file.name, - contentType: file.type, + contentType: getFileContentType(file), fileSize: file.size, })), } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown.tsx index b72c66d1f6..ed1c16d2a8 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown.tsx @@ -1,4 +1,6 @@ +import type React from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { RepeatIcon, SplitIcon } from 'lucide-react' import { useShallow } from 'zustand/react/shallow' import { Popover, @@ -349,14 +351,24 @@ const getCaretViewportPosition = ( } /** - * Renders a tag icon with background color + * Renders a tag icon with background color - can use either a React icon component or a letter */ -const TagIcon: React.FC<{ icon: string; color: string }> = ({ icon, color }) => ( +const TagIcon: React.FC<{ + icon: string | React.ComponentType<{ className?: string }> + color: string +}> = ({ icon, color }) => (
- {icon} + {typeof icon === 'string' ? ( + {icon} + ) : ( + (() => { + const IconComponent = icon + return + })() + )}
) @@ -1385,7 +1397,17 @@ export const TagDropdown: React.FC = ({ blockColor = BLOCK_COLORS.PARALLEL } - const tagIcon = group.blockName.charAt(0).toUpperCase() + // Use actual block icon if available, otherwise fall back to special icons for loop/parallel or first letter + let tagIcon: string | React.ComponentType<{ className?: string }> = group.blockName + .charAt(0) + .toUpperCase() + if (blockConfig?.icon) { + tagIcon = blockConfig.icon + } else if (group.blockType === 'loop') { + tagIcon = RepeatIcon + } else if (group.blockType === 'parallel') { + tagIcon = SplitIcon + } return (
diff --git a/apps/sim/blocks/blocks/translate.ts b/apps/sim/blocks/blocks/translate.ts index 7b81ec6a66..5f7c954a1e 100644 --- a/apps/sim/blocks/blocks/translate.ts +++ b/apps/sim/blocks/blocks/translate.ts @@ -1,28 +1,15 @@ import { TranslateIcon } from '@/components/icons' import { isHosted } from '@/lib/core/config/environment' import { AuthMode, type BlockConfig } from '@/blocks/types' -import { - getAllModelProviders, - getHostedModels, - getProviderIcon, - providers, -} from '@/providers/utils' +import { getHostedModels, getProviderIcon, providers } from '@/providers/utils' import { useProvidersStore } from '@/stores/providers/store' const getCurrentOllamaModels = () => { return useProvidersStore.getState().providers.ollama.models } -const getTranslationPrompt = ( - targetLanguage: string -) => `You are a highly skilled translator. Your task is to translate the given text into ${targetLanguage || 'English'} while: -1. Preserving the original meaning and nuance -2. Maintaining appropriate formality levels -3. Adapting idioms and cultural references appropriately -4. Preserving formatting and special characters -5. Handling technical terms accurately - -Only return the translated text without any explanations or notes. The translation should be natural and fluent in ${targetLanguage || 'English'}.` +const getTranslationPrompt = (targetLanguage: string) => + `Translate the following text into ${targetLanguage || 'English'}. Output ONLY the translated text with no additional commentary, explanations, or notes.` export const TranslateBlock: BlockConfig = { type: 'translate', @@ -123,19 +110,17 @@ export const TranslateBlock: BlockConfig = { }, ], tools: { - access: ['openai_chat', 'anthropic_chat', 'google_chat'], + access: ['llm_chat'], config: { - tool: (params: Record) => { - const model = params.model || 'gpt-4o' - if (!model) { - throw new Error('No model selected') - } - const tool = getAllModelProviders()[model] - if (!tool) { - throw new Error(`Invalid model selected: ${model}`) - } - return tool - }, + tool: () => 'llm_chat', + params: (params: Record) => ({ + model: params.model, + systemPrompt: getTranslationPrompt(params.targetLanguage || 'English'), + context: params.context, + apiKey: params.apiKey, + azureEndpoint: params.azureEndpoint, + azureApiVersion: params.azureApiVersion, + }), }, }, inputs: { diff --git a/apps/sim/blocks/blocks/wordpress.ts b/apps/sim/blocks/blocks/wordpress.ts index bc1471f693..eb19e776a0 100644 --- a/apps/sim/blocks/blocks/wordpress.ts +++ b/apps/sim/blocks/blocks/wordpress.ts @@ -272,22 +272,35 @@ export const WordPressBlock: BlockConfig = { }, }, - // Media Operations + // Media Operations - File upload (basic mode) + { + id: 'fileUpload', + title: 'Upload File', + type: 'file-upload', + canonicalParamId: 'file', + placeholder: 'Upload a media file to WordPress', + condition: { field: 'operation', value: 'wordpress_upload_media' }, + mode: 'basic', + multiple: false, + required: false, + }, + // Variable reference (advanced mode) - for referencing files from previous blocks { id: 'file', - title: 'File', + title: 'File Reference', type: 'short-input', - placeholder: 'Base64 encoded file data or file URL', + canonicalParamId: 'file', + placeholder: 'Reference file from previous block (e.g., {{block_name.file}})', condition: { field: 'operation', value: 'wordpress_upload_media' }, - required: { field: 'operation', value: 'wordpress_upload_media' }, + mode: 'advanced', + required: false, }, { id: 'filename', - title: 'Filename', + title: 'Filename Override', type: 'short-input', - placeholder: 'image.jpg', + placeholder: 'Optional: Override filename (e.g., image.jpg)', condition: { field: 'operation', value: 'wordpress_upload_media' }, - required: { field: 'operation', value: 'wordpress_upload_media' }, }, { id: 'mediaTitle', @@ -756,7 +769,7 @@ export const WordPressBlock: BlockConfig = { case 'wordpress_upload_media': return { ...baseParams, - file: params.file, + file: params.fileUpload || params.file, filename: params.filename, title: params.mediaTitle, caption: params.caption, @@ -891,8 +904,9 @@ export const WordPressBlock: BlockConfig = { parent: { type: 'number', description: 'Parent page ID' }, menuOrder: { type: 'number', description: 'Menu order' }, // Media inputs - file: { type: 'string', description: 'File data (base64) or URL' }, - filename: { type: 'string', description: 'Filename with extension' }, + fileUpload: { type: 'json', description: 'File to upload (UserFile object)' }, + file: { type: 'json', description: 'File reference from previous block' }, + filename: { type: 'string', description: 'Optional filename override' }, mediaTitle: { type: 'string', description: 'Media title' }, caption: { type: 'string', description: 'Media caption' }, altText: { type: 'string', description: 'Alt text' }, diff --git a/apps/sim/blocks/blocks/youtube.ts b/apps/sim/blocks/blocks/youtube.ts index 83e5d5a1a0..855ffdb29f 100644 --- a/apps/sim/blocks/blocks/youtube.ts +++ b/apps/sim/blocks/blocks/youtube.ts @@ -26,7 +26,6 @@ export const YouTubeBlock: BlockConfig = { { label: 'Get Channel Videos', id: 'youtube_channel_videos' }, { label: 'Get Channel Playlists', id: 'youtube_channel_playlists' }, { label: 'Get Playlist Items', id: 'youtube_playlist_items' }, - { label: 'Get Related Videos', id: 'youtube_related_videos' }, { label: 'Get Video Comments', id: 'youtube_comments' }, ], value: () => 'youtube_search', @@ -250,25 +249,6 @@ export const YouTubeBlock: BlockConfig = { integer: true, condition: { field: 'operation', value: 'youtube_playlist_items' }, }, - // Get Related Videos operation inputs - { - id: 'videoId', - title: 'Video ID', - type: 'short-input', - placeholder: 'Enter YouTube video ID to find related videos', - required: true, - condition: { field: 'operation', value: 'youtube_related_videos' }, - }, - { - id: 'maxResults', - title: 'Max Results', - type: 'slider', - min: 1, - max: 50, - step: 1, - integer: true, - condition: { field: 'operation', value: 'youtube_related_videos' }, - }, // Get Video Comments operation inputs { id: 'videoId', @@ -317,7 +297,6 @@ export const YouTubeBlock: BlockConfig = { 'youtube_channel_videos', 'youtube_channel_playlists', 'youtube_playlist_items', - 'youtube_related_videos', 'youtube_comments', ], config: { @@ -340,8 +319,6 @@ export const YouTubeBlock: BlockConfig = { return 'youtube_channel_playlists' case 'youtube_playlist_items': return 'youtube_playlist_items' - case 'youtube_related_videos': - return 'youtube_related_videos' case 'youtube_comments': return 'youtube_comments' default: diff --git a/apps/sim/blocks/blocks/zendesk.ts b/apps/sim/blocks/blocks/zendesk.ts index d0f956cc75..1e31161c09 100644 --- a/apps/sim/blocks/blocks/zendesk.ts +++ b/apps/sim/blocks/blocks/zendesk.ts @@ -504,7 +504,48 @@ export const ZendeskBlock: BlockConfig = { subdomain: { type: 'string', description: 'Zendesk subdomain' }, }, outputs: { - success: { type: 'boolean', description: 'Operation success status' }, - output: { type: 'json', description: 'Operation result data' }, + // Ticket operations - list + tickets: { type: 'json', description: 'Array of ticket objects (get_tickets)' }, + // Ticket operations - single + ticket: { + type: 'json', + description: 'Single ticket object (get_ticket, create_ticket, update_ticket)', + }, + // User operations - list + users: { type: 'json', description: 'Array of user objects (get_users, search_users)' }, + // User operations - single + user: { + type: 'json', + description: 'Single user object (get_user, get_current_user, create_user, update_user)', + }, + // Organization operations - list + organizations: { + type: 'json', + description: 'Array of organization objects (get_organizations, autocomplete_organizations)', + }, + // Organization operations - single + organization: { + type: 'json', + description: + 'Single organization object (get_organization, create_organization, update_organization)', + }, + // Search operations + results: { type: 'json', description: 'Array of search result objects (search)' }, + count: { type: 'number', description: 'Number of matching results (search_count)' }, + // Bulk/async operations + jobStatus: { + type: 'json', + description: + 'Job status for async operations (create_tickets_bulk, update_tickets_bulk, merge_tickets, create_users_bulk, update_users_bulk, create_organizations_bulk)', + }, + // Delete operations + deleted: { + type: 'boolean', + description: 'Deletion confirmation (delete_ticket, delete_user, delete_organization)', + }, + // Pagination (shared across list operations) + paging: { type: 'json', description: 'Pagination information for list operations' }, + // Metadata (shared across all operations) + metadata: { type: 'json', description: 'Operation metadata including operation type' }, }, } diff --git a/apps/sim/lib/uploads/utils/validation.ts b/apps/sim/lib/uploads/utils/validation.ts index 46125f9b40..6ccfd5f330 100644 --- a/apps/sim/lib/uploads/utils/validation.ts +++ b/apps/sim/lib/uploads/utils/validation.ts @@ -133,6 +133,11 @@ export function validateFileType(fileName: string, mimeType: string): FileValida const baseMimeType = mimeType.split(';')[0].trim() + // Allow empty MIME types if the extension is supported (browsers often don't recognize certain file types) + if (!baseMimeType) { + return null + } + const allowedMimeTypes = SUPPORTED_MIME_TYPES[extension] if (!allowedMimeTypes.includes(baseMimeType)) { return { diff --git a/apps/sim/tools/llm/chat.ts b/apps/sim/tools/llm/chat.ts new file mode 100644 index 0000000000..536400734f --- /dev/null +++ b/apps/sim/tools/llm/chat.ts @@ -0,0 +1,130 @@ +import { createLogger } from '@/lib/logs/console/logger' +import { getProviderFromModel } from '@/providers/utils' +import type { ToolConfig, ToolResponse } from '@/tools/types' + +const logger = createLogger('LLMChatTool') + +interface LLMChatParams { + model: string + systemPrompt?: string + context: string + apiKey?: string + temperature?: number + maxTokens?: number + azureEndpoint?: string + azureApiVersion?: string +} + +interface LLMChatResponse extends ToolResponse { + output: { + content: string + model: string + tokens?: { + prompt?: number + completion?: number + total?: number + } + } +} + +export const llmChatTool: ToolConfig = { + id: 'llm_chat', + name: 'LLM Chat', + description: 'Send a chat completion request to any supported LLM provider', + version: '1.0.0', + + params: { + model: { + type: 'string', + required: true, + description: 'The model to use (e.g., gpt-4o, claude-sonnet-4-5, gemini-2.0-flash)', + }, + systemPrompt: { + type: 'string', + required: false, + description: 'System prompt to set the behavior of the assistant', + }, + context: { + type: 'string', + required: true, + description: 'The user message or context to send to the model', + }, + apiKey: { + type: 'string', + required: false, + visibility: 'hidden', + description: 'API key for the provider (uses platform key if not provided for hosted models)', + }, + temperature: { + type: 'number', + required: false, + description: 'Temperature for response generation (0-2)', + }, + maxTokens: { + type: 'number', + required: false, + description: 'Maximum tokens in the response', + }, + azureEndpoint: { + type: 'string', + required: false, + visibility: 'hidden', + description: 'Azure OpenAI endpoint URL', + }, + azureApiVersion: { + type: 'string', + required: false, + visibility: 'hidden', + description: 'Azure OpenAI API version', + }, + }, + + request: { + url: () => '/api/providers', + method: 'POST', + headers: () => ({ + 'Content-Type': 'application/json', + }), + body: (params) => { + const provider = getProviderFromModel(params.model) + + return { + provider, + model: params.model, + systemPrompt: params.systemPrompt, + context: JSON.stringify([{ role: 'user', content: params.context }]), + apiKey: params.apiKey, + temperature: params.temperature, + maxTokens: params.maxTokens, + azureEndpoint: params.azureEndpoint, + azureApiVersion: params.azureApiVersion, + } + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + const errorMessage = errorData.error || `LLM API error: ${response.status}` + logger.error('LLM chat request failed', { error: errorMessage }) + throw new Error(errorMessage) + } + + const data = await response.json() + + return { + success: true, + output: { + content: data.content, + model: data.model, + tokens: data.tokens, + }, + } + }, + + outputs: { + content: { type: 'string', description: 'The generated response content' }, + model: { type: 'string', description: 'The model used for generation' }, + tokens: { type: 'object', description: 'Token usage information' }, + }, +} diff --git a/apps/sim/tools/llm/index.ts b/apps/sim/tools/llm/index.ts new file mode 100644 index 0000000000..18d5f3f0cd --- /dev/null +++ b/apps/sim/tools/llm/index.ts @@ -0,0 +1 @@ +export { llmChatTool } from './chat' diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 522662679d..153765bce0 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -579,6 +579,7 @@ import { } from '@/tools/linear' import { linkedInGetProfileTool, linkedInSharePostTool } from '@/tools/linkedin' import { linkupSearchTool } from '@/tools/linkup' +import { llmChatTool } from '@/tools/llm' import { mailchimpAddMemberTagsTool, mailchimpAddMemberTool, @@ -1256,7 +1257,6 @@ import { youtubeChannelVideosTool, youtubeCommentsTool, youtubePlaylistItemsTool, - youtubeRelatedVideosTool, youtubeSearchTool, youtubeVideoDetailsTool, } from '@/tools/youtube' @@ -1327,6 +1327,7 @@ export const tools: Record = { openai_embeddings: openAIEmbeddingsTool, http_request: httpRequestTool, huggingface_chat: huggingfaceChatTool, + llm_chat: llmChatTool, function_execute: functionExecuteTool, vision_tool: visionTool, file_parser: fileParseTool, @@ -1526,7 +1527,6 @@ export const tools: Record = { youtube_comments: youtubeCommentsTool, youtube_channel_videos: youtubeChannelVideosTool, youtube_channel_playlists: youtubeChannelPlaylistsTool, - youtube_related_videos: youtubeRelatedVideosTool, notion_read: notionReadTool, notion_read_database: notionReadDatabaseTool, notion_write: notionWriteTool, diff --git a/apps/sim/tools/thinking/tool.ts b/apps/sim/tools/thinking/tool.ts index ef27111932..5e319d6144 100644 --- a/apps/sim/tools/thinking/tool.ts +++ b/apps/sim/tools/thinking/tool.ts @@ -12,9 +12,9 @@ export const thinkingTool: ToolConfig thought: { type: 'string', required: true, - visibility: 'hidden', + visibility: 'llm-only', description: - 'The thought process or instruction provided by the user in the Thinking Step block.', + 'Your internal reasoning, analysis, or thought process. Use this to think through the problem step by step before responding.', }, }, diff --git a/apps/sim/tools/wordpress/types.ts b/apps/sim/tools/wordpress/types.ts index faa2bd3396..fbb9483356 100644 --- a/apps/sim/tools/wordpress/types.ts +++ b/apps/sim/tools/wordpress/types.ts @@ -254,8 +254,8 @@ export interface WordPressListPagesResponse extends ToolResponse { // Upload Media export interface WordPressUploadMediaParams extends WordPressBaseParams { - file: string // Base64 encoded file data or URL - filename: string + file: any // UserFile object from file upload + filename?: string // Optional filename override title?: string caption?: string altText?: string diff --git a/apps/sim/tools/wordpress/upload_media.ts b/apps/sim/tools/wordpress/upload_media.ts index 2d91e05348..d02162cc0a 100644 --- a/apps/sim/tools/wordpress/upload_media.ts +++ b/apps/sim/tools/wordpress/upload_media.ts @@ -1,9 +1,8 @@ +import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' -import { - WORDPRESS_COM_API_BASE, - type WordPressUploadMediaParams, - type WordPressUploadMediaResponse, -} from './types' +import type { WordPressUploadMediaParams, WordPressUploadMediaResponse } from './types' + +const logger = createLogger('WordPressUploadMediaTool') export const uploadMediaTool: ToolConfig = { @@ -26,16 +25,16 @@ export const uploadMediaTool: ToolConfig `${WORDPRESS_COM_API_BASE}/${params.siteId}/media`, + url: () => '/api/tools/wordpress/upload', method: 'POST', - headers: (params) => { - // Determine content type from filename - const ext = params.filename.split('.').pop()?.toLowerCase() || '' - const mimeTypes: Record = { - jpg: 'image/jpeg', - jpeg: 'image/jpeg', - png: 'image/png', - gif: 'image/gif', - webp: 'image/webp', - svg: 'image/svg+xml', - pdf: 'application/pdf', - mp4: 'video/mp4', - mp3: 'audio/mpeg', - wav: 'audio/wav', - doc: 'application/msword', - docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - } - const contentType = mimeTypes[ext] || 'application/octet-stream' - - return { - 'Content-Type': contentType, - 'Content-Disposition': `attachment; filename="${params.filename}"`, - Authorization: `Bearer ${params.accessToken}`, - } - }, - body: (params) => { - // If the file is a base64 string, we need to decode it - // The body function returns the data directly for binary uploads - // In this case, we return the file data as-is and let the executor handle it - return params.file as any - }, + headers: () => ({ + 'Content-Type': 'application/json', + }), + body: (params) => ({ + accessToken: params.accessToken, + siteId: params.siteId, + file: params.file, + filename: params.filename, + title: params.title, + caption: params.caption, + altText: params.altText, + description: params.description, + }), }, transformResponse: async (response: Response) => { - if (!response.ok) { - const error = await response.json().catch(() => ({})) - throw new Error(error.message || `WordPress API error: ${response.status}`) - } - const data = await response.json() + if (!data.success) { + logger.error('Failed to upload media via custom API route', { + error: data.error, + }) + throw new Error(data.error || 'Failed to upload media to WordPress') + } + return { success: true, output: { - media: { - id: data.id, - date: data.date, - slug: data.slug, - type: data.type, - link: data.link, - title: data.title, - caption: data.caption, - alt_text: data.alt_text, - media_type: data.media_type, - mime_type: data.mime_type, - source_url: data.source_url, - media_details: data.media_details, - }, + media: data.output.media, }, } }, diff --git a/apps/sim/tools/youtube/index.ts b/apps/sim/tools/youtube/index.ts index e231fa4268..054ea30b00 100644 --- a/apps/sim/tools/youtube/index.ts +++ b/apps/sim/tools/youtube/index.ts @@ -3,7 +3,6 @@ import { youtubeChannelPlaylistsTool } from '@/tools/youtube/channel_playlists' import { youtubeChannelVideosTool } from '@/tools/youtube/channel_videos' import { youtubeCommentsTool } from '@/tools/youtube/comments' import { youtubePlaylistItemsTool } from '@/tools/youtube/playlist_items' -import { youtubeRelatedVideosTool } from '@/tools/youtube/related_videos' import { youtubeSearchTool } from '@/tools/youtube/search' import { youtubeVideoDetailsTool } from '@/tools/youtube/video_details' @@ -14,4 +13,3 @@ export { youtubePlaylistItemsTool } export { youtubeCommentsTool } export { youtubeChannelVideosTool } export { youtubeChannelPlaylistsTool } -export { youtubeRelatedVideosTool } diff --git a/apps/sim/tools/youtube/related_videos.ts b/apps/sim/tools/youtube/related_videos.ts deleted file mode 100644 index e2d1603961..0000000000 --- a/apps/sim/tools/youtube/related_videos.ts +++ /dev/null @@ -1,108 +0,0 @@ -import type { ToolConfig } from '@/tools/types' -import type { - YouTubeRelatedVideosParams, - YouTubeRelatedVideosResponse, -} from '@/tools/youtube/types' - -export const youtubeRelatedVideosTool: ToolConfig< - YouTubeRelatedVideosParams, - YouTubeRelatedVideosResponse -> = { - id: 'youtube_related_videos', - name: 'YouTube Related Videos', - description: 'Find videos related to a specific YouTube video.', - version: '1.0.0', - params: { - videoId: { - type: 'string', - required: true, - visibility: 'user-or-llm', - description: 'YouTube video ID to find related videos for', - }, - maxResults: { - type: 'number', - required: false, - visibility: 'user-only', - default: 10, - description: 'Maximum number of related videos to return (1-50)', - }, - pageToken: { - type: 'string', - required: false, - visibility: 'user-only', - description: 'Page token for pagination', - }, - apiKey: { - type: 'string', - required: true, - visibility: 'user-only', - description: 'YouTube API Key', - }, - }, - - request: { - url: (params: YouTubeRelatedVideosParams) => { - let url = `https://www.googleapis.com/youtube/v3/search?part=snippet&type=video&relatedToVideoId=${encodeURIComponent( - params.videoId - )}&key=${params.apiKey}` - url += `&maxResults=${Number(params.maxResults || 10)}` - if (params.pageToken) { - url += `&pageToken=${params.pageToken}` - } - return url - }, - method: 'GET', - headers: () => ({ - 'Content-Type': 'application/json', - }), - }, - - transformResponse: async (response: Response): Promise => { - const data = await response.json() - const items = (data.items || []).map((item: any) => ({ - videoId: item.id?.videoId, - title: item.snippet?.title, - description: item.snippet?.description, - thumbnail: - item.snippet?.thumbnails?.medium?.url || - item.snippet?.thumbnails?.default?.url || - item.snippet?.thumbnails?.high?.url || - '', - channelTitle: item.snippet?.channelTitle || '', - })) - return { - success: true, - output: { - items, - totalResults: data.pageInfo?.totalResults || 0, - nextPageToken: data.nextPageToken, - }, - } - }, - - outputs: { - items: { - type: 'array', - description: 'Array of related videos', - items: { - type: 'object', - properties: { - videoId: { type: 'string', description: 'YouTube video ID' }, - title: { type: 'string', description: 'Video title' }, - description: { type: 'string', description: 'Video description' }, - thumbnail: { type: 'string', description: 'Video thumbnail URL' }, - channelTitle: { type: 'string', description: 'Channel name' }, - }, - }, - }, - totalResults: { - type: 'number', - description: 'Total number of related videos available', - }, - nextPageToken: { - type: 'string', - description: 'Token for accessing the next page of results', - optional: true, - }, - }, -} diff --git a/apps/sim/tools/youtube/types.ts b/apps/sim/tools/youtube/types.ts index 6523db4713..a93129929f 100644 --- a/apps/sim/tools/youtube/types.ts +++ b/apps/sim/tools/youtube/types.ts @@ -166,27 +166,6 @@ export interface YouTubeChannelPlaylistsResponse extends ToolResponse { } } -export interface YouTubeRelatedVideosParams { - apiKey: string - videoId: string - maxResults?: number - pageToken?: string -} - -export interface YouTubeRelatedVideosResponse extends ToolResponse { - output: { - items: Array<{ - videoId: string - title: string - description: string - thumbnail: string - channelTitle: string - }> - totalResults: number - nextPageToken?: string - } -} - export type YouTubeResponse = | YouTubeSearchResponse | YouTubeVideoDetailsResponse @@ -195,4 +174,3 @@ export type YouTubeResponse = | YouTubeCommentsResponse | YouTubeChannelVideosResponse | YouTubeChannelPlaylistsResponse - | YouTubeRelatedVideosResponse diff --git a/apps/sim/tools/zendesk/autocomplete_organizations.ts b/apps/sim/tools/zendesk/autocomplete_organizations.ts index 7b0ef8ccbb..8eb681bfa4 100644 --- a/apps/sim/tools/zendesk/autocomplete_organizations.ts +++ b/apps/sim/tools/zendesk/autocomplete_organizations.ts @@ -129,16 +129,8 @@ export const zendeskAutocompleteOrganizationsTool: ToolConfig< }, outputs: { - success: { type: 'boolean', description: 'Operation success status' }, - output: { - type: 'object', - description: 'Organizations search results', - properties: { - organizations: { type: 'array', description: 'Array of organization objects' }, - paging: { type: 'object', description: 'Pagination information' }, - metadata: { type: 'object', description: 'Operation metadata' }, - success: { type: 'boolean', description: 'Operation success' }, - }, - }, + organizations: { type: 'array', description: 'Array of organization objects' }, + paging: { type: 'object', description: 'Pagination information' }, + metadata: { type: 'object', description: 'Operation metadata' }, }, } diff --git a/apps/sim/tools/zendesk/create_organization.ts b/apps/sim/tools/zendesk/create_organization.ts index c77162ba85..21c667b146 100644 --- a/apps/sim/tools/zendesk/create_organization.ts +++ b/apps/sim/tools/zendesk/create_organization.ts @@ -151,15 +151,7 @@ export const zendeskCreateOrganizationTool: ToolConfig< }, outputs: { - success: { type: 'boolean', description: 'Operation success status' }, - output: { - type: 'object', - description: 'Created organization data', - properties: { - organization: { type: 'object', description: 'Created organization object' }, - metadata: { type: 'object', description: 'Operation metadata' }, - success: { type: 'boolean', description: 'Operation success' }, - }, - }, + organization: { type: 'object', description: 'Created organization object' }, + metadata: { type: 'object', description: 'Operation metadata' }, }, } diff --git a/apps/sim/tools/zendesk/create_organizations_bulk.ts b/apps/sim/tools/zendesk/create_organizations_bulk.ts index e46f9d9a8f..52720d4024 100644 --- a/apps/sim/tools/zendesk/create_organizations_bulk.ts +++ b/apps/sim/tools/zendesk/create_organizations_bulk.ts @@ -103,15 +103,7 @@ export const zendeskCreateOrganizationsBulkTool: ToolConfig< }, outputs: { - success: { type: 'boolean', description: 'Operation success status' }, - output: { - type: 'object', - description: 'Bulk creation job status', - properties: { - jobStatus: { type: 'object', description: 'Job status object' }, - metadata: { type: 'object', description: 'Operation metadata' }, - success: { type: 'boolean', description: 'Operation success' }, - }, - }, + jobStatus: { type: 'object', description: 'Job status object' }, + metadata: { type: 'object', description: 'Operation metadata' }, }, } diff --git a/apps/sim/tools/zendesk/create_ticket.ts b/apps/sim/tools/zendesk/create_ticket.ts index d0fd81a994..9ad8984311 100644 --- a/apps/sim/tools/zendesk/create_ticket.ts +++ b/apps/sim/tools/zendesk/create_ticket.ts @@ -183,15 +183,7 @@ export const zendeskCreateTicketTool: ToolConfig< }, outputs: { - success: { type: 'boolean', description: 'Operation success status' }, - output: { - type: 'object', - description: 'Created ticket data', - properties: { - ticket: { type: 'object', description: 'Created ticket object' }, - metadata: { type: 'object', description: 'Operation metadata' }, - success: { type: 'boolean', description: 'Operation success' }, - }, - }, + ticket: { type: 'object', description: 'Created ticket object' }, + metadata: { type: 'object', description: 'Operation metadata' }, }, } diff --git a/apps/sim/tools/zendesk/create_tickets_bulk.ts b/apps/sim/tools/zendesk/create_tickets_bulk.ts index bf7084f7db..1d701411dd 100644 --- a/apps/sim/tools/zendesk/create_tickets_bulk.ts +++ b/apps/sim/tools/zendesk/create_tickets_bulk.ts @@ -104,15 +104,7 @@ export const zendeskCreateTicketsBulkTool: ToolConfig< }, outputs: { - success: { type: 'boolean', description: 'Operation success status' }, - output: { - type: 'object', - description: 'Bulk create job status', - properties: { - jobStatus: { type: 'object', description: 'Job status object' }, - metadata: { type: 'object', description: 'Operation metadata' }, - success: { type: 'boolean', description: 'Operation success' }, - }, - }, + jobStatus: { type: 'object', description: 'Job status object' }, + metadata: { type: 'object', description: 'Operation metadata' }, }, } diff --git a/apps/sim/tools/zendesk/create_user.ts b/apps/sim/tools/zendesk/create_user.ts index c8f138e377..b2a0d76fa2 100644 --- a/apps/sim/tools/zendesk/create_user.ts +++ b/apps/sim/tools/zendesk/create_user.ts @@ -163,15 +163,7 @@ export const zendeskCreateUserTool: ToolConfig