Switched from basic/advanced to blocks/tools and added toolbar tabs dynamic width calculation

This commit is contained in:
Emir Karabeg
2025-02-03 19:02:29 -08:00
parent a47c50c61e
commit 838fee32bc
16 changed files with 54 additions and 27 deletions

3
.gitignore vendored
View File

@@ -39,3 +39,6 @@ yarn-error.log*
# typescript # typescript
*.tsbuildinfo *.tsbuildinfo
next-env.d.ts next-env.d.ts
# cursorrules
.cursorrules

View File

@@ -1,29 +1,55 @@
'use client' 'use client'
import { useRef, useEffect, useState } from 'react'
interface ToolbarTabsProps { interface ToolbarTabsProps {
activeTab: 'basic' | 'advanced' activeTab: 'blocks' | 'tools'
onTabChange: (tab: 'basic' | 'advanced') => void onTabChange: (tab: 'blocks' | 'tools') => void
} }
export function ToolbarTabs({ activeTab, onTabChange }: ToolbarTabsProps) { export function ToolbarTabs({ activeTab, onTabChange }: ToolbarTabsProps) {
const blocksRef = useRef<HTMLButtonElement>(null)
const toolsRef = useRef<HTMLButtonElement>(null)
const [underlineStyle, setUnderlineStyle] = useState({
width: 0,
transform: '',
})
useEffect(() => {
const activeRef = activeTab === 'blocks' ? blocksRef : toolsRef
if (activeRef.current) {
const rect = activeRef.current.getBoundingClientRect()
const parentRect =
activeRef.current.parentElement?.getBoundingClientRect()
const offsetLeft = parentRect ? rect.left - parentRect.left : 0
setUnderlineStyle({
width: rect.width,
transform: `translateX(${offsetLeft}px)`,
})
}
}, [activeTab])
return ( return (
<div className="relative pt-5"> <div className="relative pt-5">
<div className="flex gap-8 px-6"> <div className="flex gap-8 px-6">
<button <button
onClick={() => onTabChange('basic')} ref={blocksRef}
onClick={() => onTabChange('blocks')}
className={`text-sm font-medium transition-colors hover:text-black ${ className={`text-sm font-medium transition-colors hover:text-black ${
activeTab === 'basic' ? 'text-black' : 'text-muted-foreground' activeTab === 'blocks' ? 'text-black' : 'text-muted-foreground'
}`} }`}
> >
Basic Blocks
</button> </button>
<button <button
onClick={() => onTabChange('advanced')} ref={toolsRef}
onClick={() => onTabChange('tools')}
className={`text-sm font-medium transition-colors hover:text-black ${ className={`text-sm font-medium transition-colors hover:text-black ${
activeTab === 'advanced' ? 'text-black' : 'text-muted-foreground' activeTab === 'tools' ? 'text-black' : 'text-muted-foreground'
}`} }`}
> >
Advanced Tools
</button> </button>
</div> </div>
@@ -32,10 +58,8 @@ export function ToolbarTabs({ activeTab, onTabChange }: ToolbarTabsProps) {
<div <div
className="absolute bottom-0 h-[1.5px] bg-black transition-transform duration-200" className="absolute bottom-0 h-[1.5px] bg-black transition-transform duration-200"
style={{ style={{
width: activeTab === 'advanced' ? '68px' : '38px', width: `${underlineStyle.width}px`,
transform: `translateX(${ transform: underlineStyle.transform,
activeTab === 'advanced' ? '91px' : '23.75px'
})`,
}} }}
/> />
</div> </div>

View File

@@ -15,7 +15,7 @@ import {
import { ScrollArea } from '@/components/ui/scroll-area' import { ScrollArea } from '@/components/ui/scroll-area'
export function Toolbar() { export function Toolbar() {
const [activeTab, setActiveTab] = useState<BlockCategory>('basic') const [activeTab, setActiveTab] = useState<BlockCategory>('blocks')
const [searchQuery, setSearchQuery] = useState('') const [searchQuery, setSearchQuery] = useState('')
const [isCollapsed, setIsCollapsed] = useState(false) const [isCollapsed, setIsCollapsed] = useState(false)

View File

@@ -10,7 +10,7 @@ export const AgentBlock: BlockConfig<ChatResponse> = {
description: 'Use any LLM', description: 'Use any LLM',
bgColor: '#7F2FFF', bgColor: '#7F2FFF',
icon: AgentIcon, icon: AgentIcon,
category: 'basic', category: 'blocks',
}, },
tools: { tools: {
access: ['openai.chat', 'anthropic.chat', 'google.chat', 'xai.chat', 'deepseek.chat', 'deepseek.reasoner'], access: ['openai.chat', 'anthropic.chat', 'google.chat', 'xai.chat', 'deepseek.chat', 'deepseek.reasoner'],

View File

@@ -9,7 +9,7 @@ export const ApiBlock: BlockConfig<RequestResponse> = {
description: 'Use any API', description: 'Use any API',
bgColor: '#2F55FF', bgColor: '#2F55FF',
icon: ApiIcon, icon: ApiIcon,
category: 'basic', category: 'blocks',
}, },
tools: { tools: {
access: ['http.request'] access: ['http.request']

View File

@@ -9,7 +9,7 @@ export const CrewAIVisionBlock: BlockConfig<VisionResponse> = {
description: 'Analyze images with vision models', description: 'Analyze images with vision models',
bgColor: '#FF5A50', bgColor: '#FF5A50',
icon: CrewAIIcon, icon: CrewAIIcon,
category: 'advanced' category: 'tools'
}, },
tools: { tools: {
access: ['crewai.vision'] access: ['crewai.vision']

View File

@@ -9,7 +9,7 @@ export const FirecrawlScrapeBlock: BlockConfig<ScrapeResponse> = {
description: 'Scrape website content', description: 'Scrape website content',
bgColor: '#FF613A', bgColor: '#FF613A',
icon: FirecrawlIcon, icon: FirecrawlIcon,
category: 'advanced' category: 'tools'
}, },
tools: { tools: {
access: ['firecrawl.scrape'] access: ['firecrawl.scrape']

View File

@@ -9,7 +9,7 @@ export const FunctionBlock: BlockConfig<CodeExecutionOutput> = {
description: 'Add custom logic', description: 'Add custom logic',
bgColor: '#FF8D2F', bgColor: '#FF8D2F',
icon: CodeIcon, icon: CodeIcon,
category: 'advanced', category: 'blocks',
}, },
tools: { tools: {
access: ['function.execute'] access: ['function.execute']

View File

@@ -9,7 +9,7 @@ export const GitHubBlock: BlockConfig<RepoInfoResponse> = {
description: 'Fetch GitHub repository information and metadata', description: 'Fetch GitHub repository information and metadata',
bgColor: '#ffffff', bgColor: '#ffffff',
icon: GithubIcon, icon: GithubIcon,
category: 'advanced', category: 'tools',
}, },
tools: { tools: {
access: ['github.repoinfo'] access: ['github.repoinfo']

View File

@@ -9,7 +9,7 @@ export const JinaBlock: BlockConfig<ReadUrlResponse> = {
description: 'Convert website content into text', description: 'Convert website content into text',
bgColor: '#333333', bgColor: '#333333',
icon: JinaAIIcon, icon: JinaAIIcon,
category: 'advanced', category: 'tools',
}, },
tools: { tools: {
access: ['jina.readurl'] access: ['jina.readurl']

View File

@@ -9,7 +9,7 @@ export const SlackMessageBlock: BlockConfig<SlackMessageResponse> = {
description: 'Send a message to Slack', description: 'Send a message to Slack',
bgColor: '#611f69', bgColor: '#611f69',
icon: SlackIcon, icon: SlackIcon,
category: 'advanced' category: 'tools'
}, },
tools: { tools: {
access: ['slack.message'] access: ['slack.message']

View File

@@ -19,7 +19,7 @@ export const TranslateBlock: BlockConfig<ChatResponse> = {
description: 'Translate text to any language', description: 'Translate text to any language',
bgColor: '#FF4B4B', bgColor: '#FF4B4B',
icon: TranslateIcon, icon: TranslateIcon,
category: 'advanced', category: 'tools',
}, },
tools: { tools: {
access: ['openai.chat', 'anthropic.chat', 'google.chat'], access: ['openai.chat', 'anthropic.chat', 'google.chat'],

View File

@@ -50,7 +50,7 @@ export const getBlock = (type: string): BlockConfig | undefined =>
blocks[type] blocks[type]
export const getBlocksByCategory = (category: 'basic' | 'advanced'): BlockConfig[] => export const getBlocksByCategory = (category: 'blocks' | 'tools'): BlockConfig[] =>
Object.values(blocks).filter(block => block.toolbar.category === category) Object.values(blocks).filter(block => block.toolbar.category === category)
export const getAllBlockTypes = (): string[] => export const getAllBlockTypes = (): string[] =>

View File

@@ -4,7 +4,7 @@ import { ToolResponse } from '@/tools/types'
import { ExtractToolOutput, ToolOutputToValueType } from './utils' import { ExtractToolOutput, ToolOutputToValueType } from './utils'
export type BlockIcon = (props: SVGProps<SVGSVGElement>) => JSX.Element export type BlockIcon = (props: SVGProps<SVGSVGElement>) => JSX.Element
export type BlockCategory = 'basic' | 'advanced' export type BlockCategory = 'blocks' | 'tools'
export type PrimitiveValueType = 'string' | 'number' | 'json' | 'boolean' | 'any' export type PrimitiveValueType = 'string' | 'number' | 'json' | 'boolean' | 'any'
export type ValueType = PrimitiveValueType | Record<string, PrimitiveValueType> export type ValueType = PrimitiveValueType | Record<string, PrimitiveValueType>

View File

@@ -55,7 +55,7 @@ describe('Serializer', () => {
toolbar: { toolbar: {
title: 'Agent Block', title: 'Agent Block',
description: 'Use any LLM', description: 'Use any LLM',
category: 'basic', category: 'blocks',
bgColor: '#7F2FFF' bgColor: '#7F2FFF'
} }
} }
@@ -87,7 +87,7 @@ describe('Serializer', () => {
toolbar: { toolbar: {
title: 'API Block', title: 'API Block',
description: 'Make HTTP requests', description: 'Make HTTP requests',
category: 'basic', category: '',
bgColor: '#00FF00' bgColor: '#00FF00'
} }
} }
@@ -359,7 +359,7 @@ describe('Serializer', () => {
metadata: { metadata: {
title: 'Agent Block', title: 'Agent Block',
description: 'Use any LLM', description: 'Use any LLM',
category: 'basic', category: 'blocks',
color: '#7F2FFF', color: '#7F2FFF',
type: 'agent' type: 'agent'
}, },

View File