From 5c34f82d91314c2c444ba62a99098c700c584fbe Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 6 Feb 2025 12:48:41 -0800 Subject: [PATCH] Added youtube search tool/block --- blocks/blocks/youtube.ts | 57 +++++++++++++++++++++++++ blocks/index.ts | 3 ++ components/icons.tsx | 17 +++++++- components/ui/command.tsx | 6 +++ tools/index.ts | 32 ++------------ tools/youtube/search.ts | 89 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 174 insertions(+), 30 deletions(-) create mode 100644 blocks/blocks/youtube.ts create mode 100644 tools/youtube/search.ts diff --git a/blocks/blocks/youtube.ts b/blocks/blocks/youtube.ts new file mode 100644 index 000000000..461b2e05d --- /dev/null +++ b/blocks/blocks/youtube.ts @@ -0,0 +1,57 @@ +import { YouTubeIcon } from '@/components/icons' +import { YouTubeSearchResponse } from '@/tools/youtube/search' +import { BlockConfig } from '../types' + +export const YouTubeSearchBlock: BlockConfig = { + type: 'youtube_search', + toolbar: { + title: 'YouTube Search', + description: 'Search for videos on YouTube', + bgColor: '#FF0000', + icon: YouTubeIcon, + category: 'tools', + }, + tools: { + access: ['youtube_search'], + }, + workflow: { + inputs: { + apiKey: { type: 'string', required: true }, + query: { type: 'string', required: true }, + maxResults: { type: 'number', required: false }, + }, + outputs: { + response: { + type: { + items: 'json', + totalResults: 'number', + }, + }, + }, + subBlocks: [ + { + id: 'query', + title: 'Search Query', + type: 'short-input', + layout: 'full', + placeholder: 'Enter search query', + }, + { + id: 'apiKey', + title: 'YouTube API Key', + type: 'short-input', + layout: 'full', + placeholder: 'Enter YouTube API Key', + password: true, + }, + { + id: 'maxResults', + title: 'Max Results', + type: 'slider', + layout: 'half', + min: 0, + max: 20, + }, + ], + }, +} diff --git a/blocks/index.ts b/blocks/index.ts index 693f5f81b..663ec5a2c 100644 --- a/blocks/index.ts +++ b/blocks/index.ts @@ -12,6 +12,7 @@ import { SerperBlock } from './blocks/serper' import { SlackMessageBlock } from './blocks/slack' import { TavilyExtractBlock, TavilySearchBlock } from './blocks/tavily' import { TranslateBlock } from './blocks/translate' +import { YouTubeSearchBlock } from './blocks/youtube' import { BlockConfig } from './types' // Export blocks for ease of use @@ -30,6 +31,7 @@ export { TavilySearchBlock, TavilyExtractBlock, RouterBlock, + YouTubeSearchBlock, } // Registry of all block configurations @@ -48,6 +50,7 @@ const blocks: Record = { serper_search: SerperBlock, tavily_search: TavilySearchBlock, tavily_extract: TavilyExtractBlock, + youtube_search: YouTubeSearchBlock, } // Build a reverse mapping of tools to block types diff --git a/components/icons.tsx b/components/icons.tsx index 0fc2ddd89..480784f33 100644 --- a/components/icons.tsx +++ b/components/icons.tsx @@ -1124,4 +1124,19 @@ export const ConnectIcon = (props: SVGProps) => ( fill="currentColor" /> -); \ No newline at end of file +) + +export function YouTubeIcon(props: React.SVGProps) { + return ( + + YouTube + + + ) +} diff --git a/components/ui/command.tsx b/components/ui/command.tsx index 90c42fa3e..d6b78224e 100644 --- a/components/ui/command.tsx +++ b/components/ui/command.tsx @@ -21,6 +21,12 @@ import { cn } from '@/lib/utils' // This file is not typed correctly from shadcn, so we're disabling the type checker // @ts-nocheck +// This file is not typed correctly from shadcn, so we're disabling the type checker +// @ts-nocheck + +// This file is not typed correctly from shadcn, so we're disabling the type checker +// @ts-nocheck + const Command = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { diff --git a/tools/index.ts b/tools/index.ts index 7a2672331..b637fb338 100644 --- a/tools/index.ts +++ b/tools/index.ts @@ -17,36 +17,9 @@ import { extractTool as tavilyExtract } from './tavily/extract' import { searchTool as tavilySearch } from './tavily/search' import { ToolConfig, ToolResponse } from './types' import { chatTool as xaiChat } from './xai/chat' +import { youtubeSearchTool } from './youtube/search' -/** - * Registry of all available tools. Each tool is designed for specific use cases: - * - * AI Model Tools: - * - openai_chat: Advanced language model for general conversation, reasoning, and task completion - * - anthropic_chat: Claude model specialized in detailed analysis and complex reasoning - * - google_chat: PaLM model for general conversation and task assistance - * - xai_chat: Specialized AI model for explainable AI interactions - * - deepseek_chat: Code-specialized language model for programming tasks - * - deepseek_reasoner: Advanced reasoning model for complex problem-solving - * - * Web & Data Tools: - * - http_request: Make HTTP requests to any API endpoint with custom headers and body - * - firecrawl_scrape: Extract structured data from web pages with advanced scraping - * - jina_readurl: Efficiently read and parse content from web URLs - * - serper_search: Perform web searches with high-quality, structured results - * - tavily_search: AI-powered web search with comprehensive results - * - tavily_extract: Extract specific information from web content - * - * Business Integration Tools: - * - hubspot_contacts: Manage and query HubSpot CRM contacts - * - salesforce_opportunities: Handle Salesforce sales opportunities - * - slack_message: Send messages to Slack channels or users - * - github_repoinfo: Fetch detailed information about GitHub repositories - * - * Utility Tools: - * - function_execute: Execute custom JavaScript functions with provided parameters - * - crewai_vision: Process and analyze images with AI capabilities - */ +// Registry of all available tools export const tools: Record = { // AI Models openai_chat: openAIChat, @@ -76,6 +49,7 @@ export const tools: Record = { serper_search: serperSearch, tavily_search: tavilySearch, tavily_extract: tavilyExtract, + youtube_search: youtubeSearchTool, } // Get a tool by its ID diff --git a/tools/youtube/search.ts b/tools/youtube/search.ts new file mode 100644 index 000000000..fa06a4fe4 --- /dev/null +++ b/tools/youtube/search.ts @@ -0,0 +1,89 @@ +import { ToolConfig, ToolResponse } from '../types' + +export interface YouTubeSearchParams { + apiKey: string + query: string + maxResults?: number + pageToken?: string +} + +export interface YouTubeSearchResponse extends ToolResponse { + output: { + items: Array<{ + videoId: string + title: string + description: string + thumbnail: string + }> + totalResults: number + nextPageToken?: string + } +} + +export const youtubeSearchTool: ToolConfig = { + id: 'youtube_search', + name: 'YouTube Search', + description: 'Search for videos on YouTube using the YouTube Data API.', + version: '1.0.0', + params: { + query: { + type: 'string', + required: true, + description: 'Search query for YouTube videos', + }, + apiKey: { + type: 'string', + required: true, + requiredForToolCall: true, + description: 'YouTube API Key', + }, + maxResults: { + type: 'number', + required: false, + default: 5, + description: 'Maximum number of videos to return', + }, + }, + request: { + url: (params: YouTubeSearchParams) => { + let url = `https://www.googleapis.com/youtube/v3/search?part=snippet&type=video&key=${params.apiKey}&q=${encodeURIComponent( + params.query + )}` + url += `&maxResults=${params.maxResults || 5}` + return url + }, + method: 'GET', + headers: () => ({ + 'Content-Type': 'application/json', + }), + }, + transformResponse: async (response: Response): Promise => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error?.message || 'YouTube API error') + } + const items = (data.items || []).map((item: any) => ({ + videoId: item.id?.videoId, + title: item.snippet?.title, + description: item.snippet?.description, + thumbnail: + item.snippet?.thumbnails?.default?.url || + item.snippet?.thumbnails?.medium?.url || + item.snippet?.thumbnails?.high?.url || + '', + })) + return { + success: true, + output: { + items, + totalResults: data.pageInfo?.totalResults || 0, + nextPageToken: data.nextPageToken, + }, + } + }, + transformError: (error: any): string => { + const message = error.error?.message || error.message || 'YouTube search failed' + const code = error.error?.code || error.code + return `${message} (${code})` + }, +}