import React, { useState, useEffect } from 'react' import { Button } from '@/components/ui/button' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Progress } from '@/components/ui/progress' import { Badge } from '@/components/ui/badge' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { EnhancementLogs } from './EnhancementLogs' import { Bot, Zap, CheckCircle, AlertCircle, Clock, Server, Cpu, Memory, Settings, Play, Pause, RotateCcw, HardDrive } from 'lucide-react' interface OllamaModel { name: string description: string size?: string modified_at?: string } interface OllamaStatus { available: boolean server_url: string models: OllamaModel[] } interface ProcessingStats { total: number processed: number successful: number failed: number currentMemory?: string estimatedTimeRemaining?: string } interface OllamaEnhancementProps { currentProject?: string onEnhancementComplete?: () => void websocket?: WebSocket mode?: 'memories' | 'tasks' } const MODEL_RECOMMENDATIONS = { lightweight: [ { name: 'llama3.1:8b', description: 'Fast, good quality (4GB RAM)', category: 'balanced' }, { name: 'llama3.2:3b', description: 'Very fast, decent quality (2GB RAM)', category: 'lightweight' }, { name: 'phi3:mini', description: 'Ultra-fast, basic quality (1GB RAM)', category: 'lightweight' } ], balanced: [ { name: 'llama3.1:8b', description: 'Best all-around choice (4GB RAM)', category: 'balanced' }, { name: 'mistral:7b', description: 'Good alternative (4GB RAM)', category: 'balanced' }, { name: 'codellama:7b', description: 'Better for code content (4GB RAM)', category: 'balanced' } ], quality: [ { name: 'llama3.1:70b', description: 'Highest quality (40GB+ RAM)', category: 'quality' }, { name: 'mixtral:8x7b', description: 'Excellent quality (26GB RAM)', category: 'quality' }, { name: 'llama3.1:13b', description: 'High quality (8GB RAM)', category: 'quality' } ] } export function OllamaEnhancement({ currentProject, onEnhancementComplete, websocket, mode = 'memories' }: OllamaEnhancementProps) { const [ollamaStatus, setOllamaStatus] = useState(null) const [isCheckingStatus, setIsCheckingStatus] = useState(false) const [selectedModel, setSelectedModel] = useState('llama3.1:8b') const [batchSize, setBatchSize] = useState(5) const [limit, setLimit] = useState(50) const [category, setCategory] = useState('all') const [status, setStatus] = useState('all') const [skipExisting, setSkipExisting] = useState(true) const [isProcessing, setIsProcessing] = useState(false) const [processingStats, setProcessingStats] = useState(null) const [processingLog, setProcessingLog] = useState([]) const [lastError, setLastError] = useState(null) // Check Ollama status on component mount and periodically useEffect(() => { checkOllamaStatus() // Check status every 10 seconds const interval = setInterval(checkOllamaStatus, 10000) return () => clearInterval(interval) }, []) const checkOllamaStatus = async () => { setIsCheckingStatus(true) setLastError(null) try { const response = await fetch('/api/mcp-tools/check_ollama_status', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ show_models: true }) }) const result = await response.json() if (result.content) { // Parse the response text to extract status const content = result.content const isAvailable = content.includes('✅ Ollama server is running') if (isAvailable) { // Extract models from response const models: OllamaModel[] = [] const modelLines = content.split('\n').filter((line: string) => line.includes('→')) for (const line of modelLines) { const match = line.match(/→\s*(.+?)\s*\((.+?)\)/) if (match) { models.push({ name: match[1].trim(), description: match[2].trim() }) } } setOllamaStatus({ available: true, server_url: 'http://localhost:11434', models }) } else { setOllamaStatus({ available: false, server_url: 'http://localhost:11434', models: [] }) } } } catch (error) { // Only log if it's not a network error (Ollama not running is expected) if (!(error instanceof TypeError && error.message.includes('NetworkError'))) { console.warn('Ollama not available:', error) } setLastError('Ollama not available') setOllamaStatus({ available: false, server_url: 'http://localhost:11434', models: [] }) } finally { setIsCheckingStatus(false) } } const startBatchProcessing = async () => { setIsProcessing(true) setProcessingStats(null) setProcessingLog([]) setLastError(null) try { const params = { limit, model: selectedModel, batch_size: batchSize, skip_existing: skipExisting, ...(currentProject && { project: currentProject }), ...(category !== 'all' && { category }), ...(mode === 'tasks' && status !== 'all' && { status }) } setProcessingLog(prev => [...prev, `🚀 Starting batch processing with ${selectedModel}...`]) // Add proper timeout and better error handling const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), 120000) // 2 minute timeout const endpoint = mode === 'tasks' ? '/api/mcp-tools/batch_enhance_tasks_ollama' : '/api/mcp-tools/batch_enhance_memories_ollama' const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params), signal: controller.signal }) clearTimeout(timeoutId) if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`) } const result = await response.json() if (result.content) { const content = result.content setProcessingLog(prev => [...prev, content]) // Parse results for stats const successMatch = content.match(/✅ Successfully enhanced: (\d+)/) const failedMatch = content.match(/❌ Failed to enhance: (\d+)/) const totalMatch = content.match(/📊 Total processed: (\d+)/) if (successMatch && failedMatch && totalMatch) { const total = parseInt(totalMatch[1]) const successful = parseInt(successMatch[1]) const failed = parseInt(failedMatch[1]) setProcessingStats({ total, processed: total, successful, failed }) } onEnhancementComplete?.() } else if (result.error) { throw new Error(result.error) } } catch (error) { let errorMessage = `Ollama ${mode} batch processing failed` if (error.name === 'AbortError') { errorMessage = 'Request timed out - batch processing took too long' } else if (error instanceof TypeError && error.message.includes('fetch')) { errorMessage = 'Network error - server may be unavailable' } else { errorMessage = error.message || errorMessage } console.error('Batch processing error:', error) setLastError(errorMessage) setProcessingLog(prev => [...prev, `❌ Error: ${errorMessage}`]) } finally { setIsProcessing(false) } } const getModelCategory = (modelName: string) => { const allModels = [ ...MODEL_RECOMMENDATIONS.lightweight, ...MODEL_RECOMMENDATIONS.balanced, ...MODEL_RECOMMENDATIONS.quality ] const model = allModels.find(m => m.name === modelName) return model?.category || 'balanced' } const getModelBadgeColor = (category: string) => { switch (category) { case 'lightweight': return 'bg-green-500/20 text-green-300 border-green-500/30' case 'balanced': return 'bg-blue-500/20 text-blue-300 border-blue-500/30' case 'quality': return 'bg-purple-500/20 text-purple-300 border-purple-500/30' default: return 'bg-gray-500/20 text-gray-300 border-gray-500/30' } } const estimateProcessingTime = (itemCount: number, model: string) => { const timePerItem: Record = { 'phi3:mini': 2, 'llama3.2:3b': 3, 'llama3.1:8b': 5, 'mistral:7b': 6, 'llama3.1:13b': 8, 'mixtral:8x7b': 15, 'llama3.1:70b': 30 } const baseTime = timePerItem[model] || 5 const totalSeconds = itemCount * baseTime const minutes = Math.ceil(totalSeconds / 60) return minutes < 2 ? `~${totalSeconds} seconds` : `~${minutes} minutes` } return (
{/* Status Card */} Ollama Local AI Status {/* Status Indicator */}
{ollamaStatus?.available ? ( <>
Connected
{ollamaStatus.server_url} ) : ( <>
Not Available
Check Setup )}
{/* Models Info */} {ollamaStatus?.available && ollamaStatus.models.length > 0 && (

Available Models ({ollamaStatus.models.length})

{ollamaStatus.models.slice(0, 3).map((model) => (
{model.name} {getModelCategory(model.name)}
{model.description && ( {model.description} )}
))} {ollamaStatus.models.length > 3 && (
+{ollamaStatus.models.length - 3} more models
)}
)} {/* Setup Instructions */} {!ollamaStatus?.available && (

Setup Required

1. Install Ollama: curl -fsSL https://ollama.ai/install.sh | sh
2. Start server: ollama serve
3. Pull model: ollama pull llama3.1:8b
)} {lastError && (
{lastError}
)}
{/* Enhancement Controls */} {ollamaStatus?.available && ( {mode === 'tasks' ? 'Task Enhancement Controls' : 'Memory Enhancement Controls'} {/* Model Selection */}
Estimated time for {limit} {mode}: {estimateProcessingTime(limit, selectedModel)}
{/* Processing Options */}
setLimit(parseInt(e.target.value) || 50)} className="bg-gray-700 border-gray-600 text-white" min="1" max="500" />
setBatchSize(parseInt(e.target.value) || 5)} className="bg-gray-700 border-gray-600 text-white" min="1" max="10" />
{mode === 'tasks' ? (
) : (
setSkipExisting(e.target.checked)} className="w-4 h-4 text-violet-600 bg-gray-700 border-gray-600 rounded focus:ring-violet-500" /> Skip memories with existing titles
)}
{mode === 'tasks' && (
setSkipExisting(e.target.checked)} className="w-4 h-4 text-violet-600 bg-gray-700 border-gray-600 rounded focus:ring-violet-500" /> Skip tasks with existing titles/descriptions
)} {/* Processing Button */}
)} {/* Processing Progress */} {(isProcessing || processingStats) && ( Processing Progress {processingStats && ( <>
Progress {processingStats.processed}/{processingStats.total}
{processingStats.successful}
Successful
{processingStats.failed}
Failed
{processingStats.total}
Total
)} {processingLog.length > 0 && (

Processing Log

{processingLog.slice(-5).map((log, i) => (
{log}
))}
)}
)} {/* Enhancement Logs */}
) }