Some checks failed
Deploy to Production / Build and Deploy (push) Failing after 1m7s
Replaced ~100+ hardcoded French and English text strings across 30+ components with proper i18n t() calls. Added 57 new translation keys to all 15 locale files (ar, de, en, es, fa, fr, hi, it, ja, ko, nl, pl, pt, ru, zh). Key changes: - contextual-ai-chat.tsx: 30 French strings → t() (actions, toasts, labels, placeholders) - ai-chat.tsx: 15 French/English strings → t() (header, tabs, welcome, insights, history) - note-inline-editor.tsx: 20 French fallbacks removed (toolbar, save status, checklist) - lab-skeleton.tsx: French loading text → t() - admin-header.tsx, header.tsx, editor-connections-section.tsx: French fallbacks removed - New AI chat component, agent cards, sidebar, settings panel i18n cleanup Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
279 lines
9.7 KiB
TypeScript
279 lines
9.7 KiB
TypeScript
'use client'
|
|
|
|
import { Button } from '@/components/ui/button'
|
|
import { Card, CardContent } from '@/components/ui/card'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { useState, useEffect } from 'react'
|
|
import { toast } from 'sonner'
|
|
import { Loader2, CheckCircle2, XCircle, Clock, Zap, Info } from 'lucide-react'
|
|
import { useLanguage } from '@/lib/i18n'
|
|
|
|
interface TestResult {
|
|
success: boolean
|
|
provider?: string
|
|
model?: string
|
|
responseTime?: number
|
|
tags?: Array<{ tag: string; confidence: number }>
|
|
embeddingLength?: number
|
|
firstValues?: number[]
|
|
chatResponse?: string
|
|
error?: string
|
|
details?: any
|
|
}
|
|
|
|
export function AI_TESTER({ type }: { type: 'tags' | 'embeddings' | 'chat' }) {
|
|
const { t } = useLanguage()
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
const [result, setResult] = useState<TestResult | null>(null)
|
|
const [config, setConfig] = useState<any>(null)
|
|
|
|
useEffect(() => {
|
|
fetchConfig()
|
|
}, [])
|
|
|
|
const fetchConfig = async () => {
|
|
try {
|
|
const response = await fetch('/api/ai/config')
|
|
const data = await response.json()
|
|
setConfig(data)
|
|
|
|
if (data.previousTest) {
|
|
setResult(data.previousTest[type] || null)
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to fetch config:', error)
|
|
}
|
|
}
|
|
|
|
const runTest = async () => {
|
|
setIsLoading(true)
|
|
const startTime = Date.now()
|
|
|
|
try {
|
|
const response = await fetch(`/api/ai/test-${type}`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' }
|
|
})
|
|
|
|
const endTime = Date.now()
|
|
const data = await response.json()
|
|
|
|
setResult({
|
|
...data,
|
|
responseTime: endTime - startTime
|
|
})
|
|
|
|
if (data.success) {
|
|
toast.success(
|
|
`✅ ${t('admin.aiTest.testSuccessToast', { type: type === 'tags' ? 'Tags' : 'Embeddings' })}`,
|
|
{
|
|
description: `Provider: ${data.provider} | Time: ${endTime - startTime}ms`
|
|
}
|
|
)
|
|
} else {
|
|
toast.error(
|
|
`❌ ${t('admin.aiTest.testFailedToast', { type: type === 'tags' ? 'Tags' : 'Embeddings' })}`,
|
|
{
|
|
description: data.error || 'Unknown error'
|
|
}
|
|
)
|
|
}
|
|
} catch (error: any) {
|
|
const endTime = Date.now()
|
|
const errorResult = {
|
|
success: false,
|
|
error: error.message || 'Network error',
|
|
responseTime: endTime - startTime
|
|
}
|
|
setResult(errorResult)
|
|
toast.error(t('admin.aiTest.testError', { error: error.message }))
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}
|
|
|
|
const getProviderInfo = () => {
|
|
if (!config) return { provider: t('admin.aiTest.testing'), model: t('admin.aiTest.testing') }
|
|
|
|
if (type === 'tags') {
|
|
return {
|
|
provider: config.AI_PROVIDER_TAGS || 'ollama',
|
|
model: config.AI_MODEL_TAGS || 'granite4:latest'
|
|
}
|
|
} else if (type === 'chat') {
|
|
return {
|
|
provider: config.AI_PROVIDER_CHAT || config.AI_PROVIDER_TAGS || 'ollama',
|
|
model: config.AI_MODEL_CHAT || 'granite4:latest'
|
|
}
|
|
} else {
|
|
return {
|
|
provider: config.AI_PROVIDER_EMBEDDING || 'ollama',
|
|
model: config.AI_MODEL_EMBEDDING || 'embeddinggemma:latest'
|
|
}
|
|
}
|
|
}
|
|
|
|
const providerInfo = getProviderInfo()
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
{/* Provider Info */}
|
|
<div className="space-y-3 p-4 bg-muted/50 rounded-lg">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-sm font-medium">{t('admin.aiTest.provider')}</span>
|
|
<Badge variant="outline" className="text-xs">
|
|
{providerInfo.provider.toUpperCase()}
|
|
</Badge>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-sm font-medium">{t('admin.aiTest.model')}</span>
|
|
<span className="text-sm text-muted-foreground font-mono">
|
|
{providerInfo.model}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Test Button */}
|
|
<Button
|
|
onClick={runTest}
|
|
disabled={isLoading}
|
|
className="w-full"
|
|
variant={result?.success ? 'default' : result?.success === false ? 'destructive' : 'default'}
|
|
>
|
|
{isLoading ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
{t('admin.aiTest.testing')}
|
|
</>
|
|
) : (
|
|
<>
|
|
<Zap className="mr-2 h-4 w-4" />
|
|
{t('admin.aiTest.runTest')}
|
|
</>
|
|
)}
|
|
</Button>
|
|
|
|
{/* Results */}
|
|
{result && (
|
|
<Card className={result.success ? 'border-green-200 dark:border-green-900' : 'border-red-200 dark:border-red-900'}>
|
|
<CardContent className="pt-6">
|
|
{/* Status */}
|
|
<div className="flex items-center gap-2 mb-4">
|
|
{result.success ? (
|
|
<>
|
|
<CheckCircle2 className="h-5 w-5 text-green-600" />
|
|
<span className="font-semibold text-green-600 dark:text-green-400">{t('admin.aiTest.testPassed')}</span>
|
|
</>
|
|
) : (
|
|
<>
|
|
<XCircle className="h-5 w-5 text-red-600" />
|
|
<span className="font-semibold text-red-600 dark:text-red-400">{t('admin.aiTest.testFailed')}</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
{/* Response Time */}
|
|
{result.responseTime && (
|
|
<div className="flex items-center gap-2 text-sm text-muted-foreground mb-4">
|
|
<Clock className="h-4 w-4" />
|
|
<span>{t('admin.aiTest.responseTime', { time: result.responseTime })}</span>
|
|
</div>
|
|
)}
|
|
|
|
{/* Tags Results */}
|
|
{type === 'tags' && result.success && result.tags && (
|
|
<div className="space-y-3">
|
|
<div className="flex items-center gap-2">
|
|
<Info className="h-4 w-4 text-primary" />
|
|
<span className="text-sm font-medium">{t('admin.aiTest.generatedTags')}</span>
|
|
</div>
|
|
<div className="flex flex-wrap gap-2">
|
|
{result.tags.map((tag, idx) => (
|
|
<Badge
|
|
key={idx}
|
|
variant="secondary"
|
|
className="text-sm"
|
|
>
|
|
{tag.tag}
|
|
<span className="ml-1 text-xs opacity-70">
|
|
({Math.round(tag.confidence * 100)}%)
|
|
</span>
|
|
</Badge>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Chat Response */}
|
|
{type === 'chat' && result.success && result.chatResponse && (
|
|
<div className="space-y-3">
|
|
<div className="flex items-center gap-2">
|
|
<Info className="h-4 w-4 text-violet-600" />
|
|
<span className="text-sm font-medium">Réponse du modèle</span>
|
|
</div>
|
|
<div className="p-3 bg-muted rounded-lg">
|
|
<p className="text-sm italic">"{result.chatResponse}"</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Embeddings Results */}
|
|
{type === 'embeddings' && result.success && result.embeddingLength && (
|
|
<div className="space-y-3">
|
|
<div className="flex items-center gap-2">
|
|
<Info className="h-4 w-4 text-green-600" />
|
|
<span className="text-sm font-medium">{t('admin.aiTest.embeddingDimensions')}</span>
|
|
</div>
|
|
<div className="p-3 bg-muted rounded-lg">
|
|
<div className="text-2xl font-bold text-center">
|
|
{result.embeddingLength}
|
|
</div>
|
|
<div className="text-xs text-center text-muted-foreground mt-1">
|
|
{t('admin.aiTest.vectorDimensions')}
|
|
</div>
|
|
</div>
|
|
{result.firstValues && result.firstValues.length > 0 && (
|
|
<div className="space-y-1">
|
|
<span className="text-xs font-medium">{t('admin.aiTest.first5Values')}</span>
|
|
<div className="p-2 bg-muted rounded font-mono text-xs">
|
|
[{result.firstValues.slice(0, 5).map((v, i) => v.toFixed(4)).join(', ')}]
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Error Details */}
|
|
{!result.success && result.error && (
|
|
<div className="mt-4 p-3 bg-red-50 dark:bg-red-950/20 rounded-lg border border-red-200 dark:border-red-900">
|
|
<p className="text-sm font-medium text-red-900 dark:text-red-100">{t('admin.aiTest.error')}</p>
|
|
<p className="text-sm text-red-700 dark:text-red-300 mt-1">{result.error}</p>
|
|
{result.details && (
|
|
<details className="mt-2">
|
|
<summary className="text-xs cursor-pointer text-red-600 dark:text-red-400">
|
|
{t('admin.aiTest.technicalDetails')}
|
|
</summary>
|
|
<pre className="mt-2 text-xs overflow-auto p-2 bg-red-100 dark:bg-red-900/30 rounded">
|
|
{JSON.stringify(result.details, null, 2)}
|
|
</pre>
|
|
</details>
|
|
)}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{/* Loading State */}
|
|
{isLoading && (
|
|
<div className="text-center py-4">
|
|
<Loader2 className="h-8 w-8 animate-spin mx-auto text-primary" />
|
|
<p className="text-sm text-muted-foreground mt-2">
|
|
{t('admin.aiTest.testingType', { type: type === 'tags' ? 'tags generation' : 'embeddings' })}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|