ZARAI / frontend /src /components /tools /ToolsPanel.jsx
Srajal2579
Deploy JARVIS
3e646d9
Raw History Blame Contribute Delete
7.52 kB
/**
* JARVIS — Tools Panel
* Direct tool execution: calculator, web search, code runner
*/
import React, { useState, useEffect } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import {
Wrench, Search, Calculator, Code2,
Play, Loader2, ChevronDown, ChevronUp, CheckCircle, XCircle,
} from 'lucide-react'
import clsx from 'clsx'
import toast from 'react-hot-toast'
import { toolsAPI } from '../../services/api'
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
const TOOLS = [
{
id: 'web_search',
label: 'Web Search',
icon: Search,
placeholder: 'Enter search query…',
example: 'latest AI research 2025',
color: 'text-blue-400',
},
{
id: 'calculator',
label: 'Calculator',
icon: Calculator,
placeholder: 'Math expression…',
example: 'sqrt(144) + 2**10',
color: 'text-yellow-400',
},
{
id: 'code_executor',
label: 'Code Runner',
icon: Code2,
placeholder: 'Python code…',
example: 'print([x**2 for x in range(10)])',
color: 'text-green-400',
multiline: true,
},
]
export default function ToolsPanel() {
const [expanded, setExpanded] = useState(true)
const [activeTool, setActiveTool] = useState('calculator')
const [argument, setArgument] = useState('')
const [result, setResult] = useState(null)
const [loading, setLoading] = useState(false)
const tool = TOOLS.find((t) => t.id === activeTool)
const run = async () => {
if (!argument.trim()) return
setLoading(true)
setResult(null)
try {
const data = await toolsAPI.executeTool(activeTool, argument.trim())
setResult(data)
if (!data.success) toast.error('Tool returned an error')
} catch (err) {
toast.error(err.message)
} finally {
setLoading(false)
}
}
const loadExample = () => {
setArgument(tool.example)
}
return (
<div className="bg-jarvis-surface border-t border-jarvis-border">
{/* Header */}
<div
className="flex items-center justify-between px-4 py-2.5 cursor-pointer
hover:bg-jarvis-panel/30 transition-colors"
onClick={() => setExpanded((v) => !v)}
>
<div className="flex items-center gap-2">
<Wrench size={13} className="text-jarvis-accent" />
<span className="text-xs font-medium text-jarvis-text">Tool Executor</span>
<span className="text-[10px] font-mono text-jarvis-dim">Direct access</span>
</div>
{expanded ? <ChevronDown size={13} className="text-jarvis-dim" /> :
<ChevronUp size={13} className="text-jarvis-dim" />}
</div>
<AnimatePresence>
{expanded && (
<motion.div
initial={{ height: 0 }}
animate={{ height: 'auto' }}
exit={{ height: 0 }}
className="overflow-hidden"
>
<div className="px-4 pb-4 space-y-3">
{/* Tool selector */}
<div className="flex gap-2">
{TOOLS.map((t) => {
const Icon = t.icon
return (
<button
key={t.id}
onClick={() => { setActiveTool(t.id); setArgument(''); setResult(null) }}
className={clsx(
'flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs',
'border transition-all duration-150',
activeTool === t.id
? 'bg-jarvis-accent/10 border-jarvis-accent/30 text-jarvis-accent'
: 'border-jarvis-border text-jarvis-dim hover:border-jarvis-muted hover:text-jarvis-muted',
)}
>
<Icon size={11} />
{t.label}
</button>
)
})}
</div>
{/* Input */}
<div className="flex gap-2">
{tool.multiline ? (
<textarea
value={argument}
onChange={(e) => setArgument(e.target.value)}
placeholder={tool.placeholder}
rows={3}
className="flex-1 input-field text-xs font-mono resize-none py-2"
/>
) : (
<input
type="text"
value={argument}
onChange={(e) => setArgument(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && run()}
placeholder={tool.placeholder}
className="flex-1 input-field text-xs font-mono"
/>
)}
<div className="flex flex-col gap-1">
<button
onClick={run}
disabled={!argument.trim() || loading}
className="btn-accent text-xs py-2 px-3 flex items-center gap-1"
>
{loading ? <Loader2 size={12} className="animate-spin" /> : <Play size={12} />}
Run
</button>
<button
onClick={loadExample}
className="btn-ghost text-[10px] py-1 px-2"
>
Example
</button>
</div>
</div>
{/* Result */}
<AnimatePresence>
{result && (
<motion.div
initial={{ opacity: 0, y: 4 }}
animate={{ opacity: 1, y: 0 }}
className={clsx(
'panel p-3 rounded-lg',
result.success ? 'border-jarvis-success/20' : 'border-jarvis-error/20',
)}
>
<div className="flex items-center gap-1.5 mb-2">
{result.success
? <CheckCircle size={12} className="text-jarvis-success" />
: <XCircle size={12} className="text-jarvis-error" />}
<span className={clsx(
'text-[10px] font-mono uppercase tracking-widest',
result.success ? 'text-jarvis-success' : 'text-jarvis-error',
)}>
{result.success ? 'Success' : 'Error'}
</span>
</div>
{activeTool === 'code_executor' ? (
<SyntaxHighlighter
language="text"
style={oneDark}
customStyle={{ margin: 0, padding: '8px', background: '#070c18', fontSize: '11px', borderRadius: '6px' }}
>
{result.result}
</SyntaxHighlighter>
) : (
<p className="text-xs text-jarvis-text font-mono whitespace-pre-wrap">
{result.result}
</p>
)}
</motion.div>
)}
</AnimatePresence>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
)
}