feat: redesign AI test page with Ethereal Precision v2 (horizontal layout, ultra-wide) and fix Dockerfile OpenSSL issue
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 58s

This commit is contained in:
Antigravity
2026-05-03 13:09:04 +00:00
parent b611ec874d
commit aee4b17306
10 changed files with 751 additions and 226 deletions

View File

@@ -20,6 +20,10 @@ RUN npx prisma generate
FROM node:22-bookworm-slim AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
openssl \
&& rm -rf /var/lib/apt/lists/*
COPY --from=deps /app/node_modules ./node_modules
COPY . .

View File

@@ -1,11 +1,10 @@
'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 { Loader2, CheckCircle2, XCircle, Clock, Zap, Info, Shield, Brain, MessageSquare, Search } from 'lucide-react'
import { useLanguage } from '@/lib/i18n'
interface TestResult {
@@ -65,14 +64,14 @@ export function AI_TESTER({ type }: { type: 'tags' | 'embeddings' | 'chat' }) {
if (data.success) {
toast.success(
`${t('admin.aiTest.testSuccessToast', { type: type === 'tags' ? 'Tags' : 'Embeddings' })}`,
`${t('admin.aiTest.testSuccessToast', { type: type === 'tags' ? 'Tags' : type === 'chat' ? 'Chat' : 'Embeddings' })}`,
{
description: `Provider: ${data.provider} | Time: ${endTime - startTime}ms`
}
)
} else {
toast.error(
`${t('admin.aiTest.testFailedToast', { type: type === 'tags' ? 'Tags' : 'Embeddings' })}`,
`${t('admin.aiTest.testFailedToast', { type: type === 'tags' ? 'Tags' : type === 'chat' ? 'Chat' : 'Embeddings' })}`,
{
description: data.error || 'Unknown error'
}
@@ -93,7 +92,7 @@ export function AI_TESTER({ type }: { type: 'tags' | 'embeddings' | 'chat' }) {
}
const getProviderInfo = () => {
if (!config) return { provider: t('admin.aiTest.testing'), model: t('admin.aiTest.testing') }
if (!config) return { provider: '...', model: '...' }
if (type === 'tags') {
return {
@@ -116,127 +115,164 @@ export function AI_TESTER({ type }: { type: 'tags' | 'embeddings' | 'chat' }) {
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 className="space-y-8">
{/* Current Config Summary */}
<div className="rounded-2xl border border-border bg-muted/20 overflow-hidden shadow-inner">
<div className="px-4 py-2.5 border-b border-border/50 bg-muted/40 flex items-center gap-2">
{type === 'tags' ? <Brain className="h-4 w-4 text-primary" /> :
type === 'embeddings' ? <Search className="h-4 w-4 text-green-600" /> :
<MessageSquare className="h-4 w-4 text-violet-600" />}
<span className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground">{t('admin.aiTest.provider')}</span>
</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 className="p-6 space-y-5">
<div className="flex items-center justify-between gap-6">
<span className="text-sm font-bold text-muted-foreground truncate">{t('admin.aiTest.provider')}</span>
<Badge variant="secondary" className="font-mono text-xs py-1 h-7 px-4 bg-background border-border rounded-xl shadow-sm">
{providerInfo.provider}
</Badge>
</div>
<div className="flex items-center justify-between gap-6">
<span className="text-sm font-bold text-muted-foreground truncate">{t('admin.aiTest.model')}</span>
<span className="text-xs font-mono font-bold text-foreground truncate bg-background/80 px-3 py-1 rounded-lg border border-border/50" title={providerInfo.model}>
{providerInfo.model}
</span>
</div>
</div>
</div>
{/* Test Button */}
{/* Run Test Action */}
<Button
onClick={runTest}
disabled={isLoading}
className="w-full"
variant={result?.success ? 'default' : result?.success === false ? 'destructive' : 'default'}
className={`w-full shadow-lg py-7 h-auto rounded-2xl transition-all duration-300 relative overflow-hidden group/btn ${
isLoading ? 'opacity-90' : 'hover:scale-[1.02] active:scale-[0.98] hover:shadow-xl'
} ${
result?.success ? 'bg-primary' : result?.success === false ? 'bg-destructive hover:bg-destructive/90' : 'bg-primary hover:bg-primary/90'
}`}
>
<div className="absolute inset-0 bg-gradient-to-r from-white/0 via-white/10 to-white/0 -translate-x-full group-hover/btn:animate-[shimmer_2s_infinite] pointer-events-none" />
{isLoading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
{t('admin.aiTest.testing')}
</>
<div className="flex flex-col items-center gap-2">
<Loader2 className="h-6 w-6 animate-spin" />
<span className="text-xs font-bold tracking-widest uppercase">{t('admin.aiTest.testing')}...</span>
</div>
) : (
<>
<Zap className="mr-2 h-4 w-4" />
{t('admin.aiTest.runTest')}
</>
<div className="flex items-center justify-center gap-4">
<div className={`w-10 h-10 rounded-xl flex items-center justify-center transition-all ${
result?.success ? 'bg-white/20' : 'bg-white/20 group-hover/btn:scale-110'
}`}>
<Zap className="h-5 w-5" />
</div>
<span className="font-black tracking-tight text-lg py-1">
{t('admin.aiTest.runTest')}
</span>
</div>
)}
</Button>
{/* Results */}
{/* Result Display */}
{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 className={`rounded-xl border transition-all duration-300 animate-in fade-in slide-in-from-top-2 ${
result.success
? 'bg-green-500/[0.03] border-green-500/20 shadow-green-500/[0.02] shadow-lg'
: 'bg-destructive/[0.03] border-destructive/20 shadow-destructive/[0.02] shadow-lg'
}`}>
<div className="p-5 space-y-5">
{/* Status Header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
{result.success ? (
<div className="w-8 h-8 rounded-full bg-green-500/20 flex items-center justify-center text-green-600">
<CheckCircle2 className="h-5 w-5" />
</div>
) : (
<div className="w-8 h-8 rounded-full bg-destructive/20 flex items-center justify-center text-destructive">
<XCircle className="h-5 w-5" />
</div>
)}
<span className={`font-bold tracking-tight ${result.success ? 'text-green-700 dark:text-green-400' : 'text-destructive'}`}>
{result.success ? t('admin.aiTest.testPassed') : t('admin.aiTest.testFailed')}
</span>
</div>
{result.responseTime && (
<div className="flex items-center gap-1.5 px-2 py-1 bg-background/50 rounded-md border border-border/50 text-[10px] font-bold text-muted-foreground">
<Clock className="h-3 w-3" />
{result.responseTime}ms
</div>
)}
</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 */}
{/* Response Content - Tags */}
{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 className="space-y-3 pt-3 border-t border-border/10">
<div className="flex items-center gap-2 text-xs font-semibold text-muted-foreground uppercase tracking-widest">
<Info className="h-3.5 w-3.5" />
{t('admin.aiTest.generatedTags')}
</div>
<div className="flex flex-wrap gap-2">
{result.tags.map((tag, idx) => (
<Badge
<div
key={idx}
variant="secondary"
className="text-sm"
className="group relative flex items-center gap-2 px-3 py-1.5 bg-background border border-border rounded-lg text-sm font-medium hover:border-primary/30 hover:bg-primary/[0.02] transition-colors"
>
{tag.tag}
<span className="ml-1 text-xs opacity-70">
({Math.round(tag.confidence * 100)}%)
<span className="text-foreground">{tag.tag}</span>
<span className="text-[10px] text-muted-foreground bg-muted px-1 rounded font-mono">
{Math.round(tag.confidence * 100)}%
</span>
</Badge>
</div>
))}
</div>
</div>
)}
{/* Chat Response */}
{/* Response Content - Chat */}
{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 className="space-y-3 pt-3 border-t border-border/10">
<div className="flex items-center gap-2 text-xs font-semibold text-muted-foreground uppercase tracking-widest">
<MessageSquare className="h-3.5 w-3.5" />
Modèle Réponse
</div>
<div className="p-3 bg-muted rounded-lg">
<p className="text-sm italic">&quot;{result.chatResponse}&quot;</p>
<div className="p-4 bg-background/50 border border-border/50 rounded-xl relative">
<div className="absolute -left-1.5 top-4 w-3 h-3 bg-background border-l border-t border-border/50 rotate-[-45deg] rounded-sm" />
<p className="text-sm leading-relaxed text-foreground italic">
&quot;{result.chatResponse}&quot;
</p>
</div>
</div>
)}
{/* Embeddings Results */}
{/* Response Content - Embeddings */}
{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 className="space-y-4 pt-3 border-t border-border/10">
<div className="grid grid-cols-2 gap-4">
<div className="bg-background/50 border border-border/50 rounded-xl p-4 text-center">
<div className="text-2xl font-black tracking-tighter text-foreground">
{result.embeddingLength}
</div>
<div className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mt-1">
{t('admin.aiTest.vectorDimensions')}
</div>
</div>
<div className="text-xs text-center text-muted-foreground mt-1">
{t('admin.aiTest.vectorDimensions')}
<div className="bg-background/50 border border-border/50 rounded-xl p-4 text-center flex flex-col justify-center">
<div className="flex items-center justify-center gap-1">
<div className="w-1.5 h-1.5 rounded-full bg-green-500 animate-pulse" />
<span className="text-xs font-bold text-foreground">Active Vector</span>
</div>
</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 className="space-y-2">
<div className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">
{t('admin.aiTest.first5Values')}
</div>
<div className="p-3 bg-muted/50 rounded-lg font-mono text-[11px] leading-none flex justify-between overflow-x-auto whitespace-nowrap gap-2 scrollbar-hide border border-border/50">
{result.firstValues.slice(0, 5).map((v, i) => (
<span key={i} className="text-foreground tabular-nums">
{v.toFixed(4)}
</span>
))}
</div>
</div>
)}
@@ -245,32 +281,30 @@ export function AI_TESTER({ type }: { type: 'tags' | 'embeddings' | 'chat' }) {
{/* 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 className="space-y-3 pt-3 border-t border-border/10">
<div className="p-4 bg-destructive/[0.05] border border-destructive/20 rounded-xl">
<div className="flex items-center gap-2 mb-2">
<Info className="h-4 w-4 text-destructive" />
<span className="text-xs font-bold text-destructive uppercase tracking-widest">Détails de l&apos;erreur</span>
</div>
<p className="text-sm text-destructive font-medium leading-relaxed">{result.error}</p>
{result.details && (
<details className="mt-4 group">
<summary className="text-[10px] font-bold uppercase tracking-widest cursor-pointer text-muted-foreground hover:text-destructive transition-colors">
{t('admin.aiTest.technicalDetails')}
</summary>
<div className="mt-2 p-3 bg-black/5 dark:bg-black/20 rounded-lg overflow-x-auto border border-border/50">
<pre className="text-[10px] font-mono text-muted-foreground leading-tight">
{JSON.stringify(result.details, null, 2)}
</pre>
</div>
</details>
)}
</div>
</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>
)}
</div>

View File

@@ -1,8 +1,7 @@
'use client'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { ArrowLeft, TestTube } from 'lucide-react'
import { ArrowLeft, TestTube, Info, Sparkles, Zap, Brain, MessageSquare, Search } from 'lucide-react'
import { AI_TESTER } from './ai-tester'
import { useLanguage } from '@/lib/i18n'
@@ -10,109 +9,140 @@ export default function AITestPage() {
const { t } = useLanguage()
return (
<div className="container mx-auto py-10 px-4 max-w-6xl">
<div className="flex justify-between items-center mb-8">
<div className="flex items-center gap-3">
<a href="/admin/settings">
<Button variant="outline" size="icon">
<ArrowLeft className="h-4 w-4" />
</Button>
</a>
<div>
<h1 className="text-3xl font-bold flex items-center gap-2">
<TestTube className="h-8 w-8" />
{t('admin.aiTest.title')}
</h1>
<p className="text-muted-foreground mt-1">
{t('admin.aiTest.description')}
</p>
<div className="bg-background flex flex-col min-h-screen">
<div className="flex-1 overflow-y-auto">
<div className="max-w-[1800px] mx-auto px-6 md:px-16 py-12 space-y-16">
{/* Header - Even larger and more spaced */}
<div className="flex flex-col md:flex-row md:items-end justify-between gap-10 border-b border-border/40 pb-12">
<div className="flex items-start gap-8">
<a href="/admin/settings" className="mt-2">
<Button variant="outline" size="icon" className="rounded-3xl h-16 w-16 border-border/60 bg-card hover:bg-muted transition-all shadow-md group">
<ArrowLeft className="h-7 w-7 group-hover:-translate-x-1 transition-transform" />
</Button>
</a>
<div>
<div className="flex items-center gap-4 mb-3">
<div className="w-12 h-12 rounded-2xl bg-primary/10 flex items-center justify-center text-primary shadow-inner">
<TestTube className="h-7 w-7" />
</div>
<span className="text-sm font-black uppercase tracking-[0.3em] text-primary/70">Diagnostics Haute Précision</span>
</div>
<h1 className="text-5xl md:text-7xl font-black tracking-tight text-foreground leading-[1.1]">
{t('admin.aiTest.title')}
</h1>
<p className="text-muted-foreground mt-4 text-xl font-medium max-w-3xl leading-relaxed">
{t('admin.aiTest.description')}
</p>
</div>
</div>
<div className="flex flex-col items-end gap-3 px-8 py-5 bg-card border border-border/50 rounded-[2.5rem] shadow-sm">
<div className="flex items-center gap-4">
<div className="w-3 h-3 rounded-full bg-green-500 animate-pulse" />
<span className="text-base font-black uppercase tracking-widest text-foreground">Système Actif</span>
</div>
<p className="text-xs text-muted-foreground font-bold uppercase tracking-widest">Latence: Optimisée</p>
</div>
</div>
{/* New Horizontal Section for each Test - Maximum Width Utilization */}
<div className="space-y-12">
{/* 1. Tags Test - Horizontal Layout */}
<div className="bg-card rounded-[4rem] border border-border/60 shadow-xl overflow-hidden hover:shadow-2xl transition-all duration-700 group flex flex-col xl:flex-row">
<div className="xl:w-1/3 p-12 md:p-16 border-b xl:border-b-0 xl:border-r border-border/40 bg-gradient-to-br from-primary/[0.05] to-transparent relative overflow-hidden">
<div className="absolute -right-10 -bottom-10 opacity-[0.03] group-hover:opacity-[0.08] transition-all duration-700 group-hover:scale-125 group-hover:-rotate-12">
<Brain className="h-80 w-80 text-primary" />
</div>
<div className="relative space-y-8">
<div className="w-20 h-20 rounded-[1.5rem] bg-background flex items-center justify-center text-4xl shadow-2xl border border-border/50 group-hover:scale-110 transition-transform duration-500">
🏷
</div>
<div>
<h3 className="text-3xl md:text-4xl font-black text-foreground tracking-tight mb-4">{t('admin.aiTest.tagsTestTitle')}</h3>
<p className="text-lg text-muted-foreground font-bold opacity-80 leading-relaxed">{t('admin.aiTest.tagsTestDescription')}</p>
</div>
<div className="flex flex-wrap gap-3">
<span className="px-4 py-2 bg-primary/10 rounded-xl text-primary text-[10px] font-black uppercase tracking-widest">Auto-Labeling</span>
<span className="px-4 py-2 bg-primary/10 rounded-xl text-primary text-[10px] font-black uppercase tracking-widest">Suggestions</span>
</div>
</div>
</div>
<div className="xl:w-2/3 p-12 md:p-16 bg-gradient-to-l from-transparent to-primary/[0.01]">
<div className="max-w-4xl">
<AI_TESTER type="tags" />
</div>
</div>
</div>
{/* 2. Embeddings Test - Horizontal Layout */}
<div className="bg-card rounded-[4rem] border border-border/60 shadow-xl overflow-hidden hover:shadow-2xl transition-all duration-700 group flex flex-col xl:flex-row">
<div className="xl:w-1/3 p-12 md:p-16 border-b xl:border-b-0 xl:border-r border-border/40 bg-gradient-to-br from-green-500/[0.05] to-transparent relative overflow-hidden">
<div className="absolute -right-10 -bottom-10 opacity-[0.03] group-hover:opacity-[0.08] transition-all duration-700 group-hover:scale-125 group-hover:rotate-12">
<Search className="h-80 w-80 text-green-500" />
</div>
<div className="relative space-y-8">
<div className="w-20 h-20 rounded-[1.5rem] bg-background flex items-center justify-center text-4xl shadow-2xl border border-border/50 group-hover:scale-110 transition-transform duration-500">
🔍
</div>
<div>
<h3 className="text-3xl md:text-4xl font-black text-foreground tracking-tight mb-4">{t('admin.aiTest.embeddingsTestTitle')}</h3>
<p className="text-lg text-muted-foreground font-bold opacity-80 leading-relaxed">{t('admin.aiTest.embeddingsTestDescription')}</p>
</div>
<div className="flex flex-wrap gap-3">
<span className="px-4 py-2 bg-green-500/10 rounded-xl text-green-600 text-[10px] font-black uppercase tracking-widest">Vector Store</span>
<span className="px-4 py-2 bg-green-500/10 rounded-xl text-green-600 text-[10px] font-black uppercase tracking-widest">Similarity</span>
</div>
</div>
</div>
<div className="xl:w-2/3 p-12 md:p-16 bg-gradient-to-l from-transparent to-green-500/[0.01]">
<div className="max-w-4xl">
<AI_TESTER type="embeddings" />
</div>
</div>
</div>
{/* 3. Chat Test - Horizontal Layout */}
<div className="bg-card rounded-[4rem] border border-border/60 shadow-xl overflow-hidden hover:shadow-2xl transition-all duration-700 group flex flex-col xl:flex-row">
<div className="xl:w-1/3 p-12 md:p-16 border-b xl:border-b-0 xl:border-r border-border/40 bg-gradient-to-br from-violet-500/[0.05] to-transparent relative overflow-hidden">
<div className="absolute -right-10 -bottom-10 opacity-[0.03] group-hover:opacity-[0.08] transition-all duration-700 group-hover:scale-125 group-hover:-rotate-6">
<MessageSquare className="h-80 w-80 text-violet-500" />
</div>
<div className="relative space-y-8">
<div className="w-20 h-20 rounded-[1.5rem] bg-background flex items-center justify-center text-4xl shadow-2xl border border-border/50 group-hover:scale-110 transition-transform duration-500">
💬
</div>
<div>
<h3 className="text-3xl md:text-4xl font-black text-foreground tracking-tight mb-4">{t('admin.aiTest.chatTestTitle')}</h3>
<p className="text-lg text-muted-foreground font-bold opacity-80 leading-relaxed">{t('admin.aiTest.chatTestDescription')}</p>
</div>
<div className="flex flex-wrap gap-3">
<span className="px-4 py-2 bg-violet-500/10 rounded-xl text-violet-600 text-[10px] font-black uppercase tracking-widest">Conversational</span>
<span className="px-4 py-2 bg-violet-500/10 rounded-xl text-violet-600 text-[10px] font-black uppercase tracking-widest">Streaming</span>
</div>
</div>
</div>
<div className="xl:w-2/3 p-12 md:p-16 bg-gradient-to-l from-transparent to-violet-500/[0.01]">
<div className="max-w-4xl">
<AI_TESTER type="chat" />
</div>
</div>
</div>
</div>
{/* Tips Section - Broad and visible */}
<div className="bg-amber-500/5 border border-amber-500/20 rounded-[3rem] p-12 flex flex-col md:flex-row items-center gap-10">
<div className="w-24 h-24 rounded-3xl bg-amber-500/10 flex items-center justify-center text-5xl shrink-0 shadow-inner">
💡
</div>
<div className="space-y-4">
<h4 className="text-2xl font-black text-amber-700 dark:text-amber-400 uppercase tracking-tight">{t('admin.aiTest.tipTitle')}</h4>
<p className="text-xl text-amber-600/90 dark:text-amber-300/80 leading-relaxed font-bold">
{t('admin.aiTest.tipContent')}
</p>
</div>
</div>
</div>
</div>
<div className="grid gap-6 md:grid-cols-3">
{/* Tags Provider Test */}
<Card className="border-primary/20 dark:border-primary/30">
<CardHeader className="bg-primary/5 dark:bg-primary/10">
<CardTitle className="flex items-center gap-2">
<span className="text-2xl">🏷</span>
{t('admin.aiTest.tagsTestTitle')}
</CardTitle>
<CardDescription>
{t('admin.aiTest.tagsTestDescription')}
</CardDescription>
</CardHeader>
<CardContent className="pt-6">
<AI_TESTER type="tags" />
</CardContent>
</Card>
{/* Embeddings Provider Test */}
<Card className="border-green-200 dark:border-green-900">
<CardHeader className="bg-green-50/50 dark:bg-green-950/20">
<CardTitle className="flex items-center gap-2">
<span className="text-2xl">🔍</span>
{t('admin.aiTest.embeddingsTestTitle')}
</CardTitle>
<CardDescription>
{t('admin.aiTest.embeddingsTestDescription')}
</CardDescription>
</CardHeader>
<CardContent className="pt-6">
<AI_TESTER type="embeddings" />
</CardContent>
</Card>
{/* Chat Provider Test */}
<Card className="border-violet-200 dark:border-violet-900">
<CardHeader className="bg-violet-50/50 dark:bg-violet-950/20">
<CardTitle className="flex items-center gap-2">
<span className="text-2xl">💬</span>
Fournisseur de chat
</CardTitle>
<CardDescription>
Testez le fournisseur IA responsable de l&apos;assistant conversationnel
</CardDescription>
</CardHeader>
<CardContent className="pt-6">
<AI_TESTER type="chat" />
</CardContent>
</Card>
</div>
{/* Info Section */}
<Card className="mt-6">
<CardHeader>
<CardTitle> {t('admin.aiTest.howItWorksTitle')}</CardTitle>
</CardHeader>
<CardContent className="space-y-4 text-sm">
<div>
<h4 className="font-semibold mb-2">{t('admin.aiTest.tagsGenerationTest')}</h4>
<ul className="list-disc list-inside space-y-1 text-muted-foreground">
<li>{t('admin.aiTest.tagsStep1')}</li>
<li>{t('admin.aiTest.tagsStep2')}</li>
<li>{t('admin.aiTest.tagsStep3')}</li>
<li>{t('admin.aiTest.tagsStep4')}</li>
</ul>
</div>
<div>
<h4 className="font-semibold mb-2">{t('admin.aiTest.embeddingsTestLabel')}</h4>
<ul className="list-disc list-inside space-y-1 text-muted-foreground">
<li>{t('admin.aiTest.embeddingsStep1')}</li>
<li>{t('admin.aiTest.embeddingsStep2')}</li>
<li>{t('admin.aiTest.embeddingsStep3')}</li>
<li>{t('admin.aiTest.embeddingsStep4')}</li>
</ul>
</div>
<div className="bg-amber-50 dark:bg-amber-950/20 p-4 rounded-lg border border-amber-200 dark:border-amber-900">
<p className="font-semibold text-amber-900 dark:text-amber-100">💡 {t('admin.aiTest.tipTitle')}</p>
<p className="text-amber-800 dark:text-amber-200 mt-1">
{t('admin.aiTest.tipContent')}
</p>
</div>
</CardContent>
</Card>
</div>
)
}

View File

@@ -5,7 +5,7 @@ import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-normal transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{
variants: {
variant: {
@@ -21,14 +21,14 @@ const buttonVariants = cva(
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
"icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
"icon-sm": "size-8",
"icon-lg": "size-10",
default: "min-h-9 h-auto px-4 py-2 has-[>svg]:px-3",
xs: "min-h-6 h-auto gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: "min-h-8 h-auto gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
lg: "min-h-10 h-auto rounded-md px-6 has-[>svg]:px-4",
icon: "size-9 shrink-0",
"icon-xs": "size-6 rounded-md shrink-0 [&_svg:not([class*='size-'])]:size-3",
"icon-sm": "size-8 shrink-0",
"icon-lg": "size-10 shrink-0",
},
},
defaultVariants: {

View File

@@ -0,0 +1,178 @@
const fs = require('fs');
const file = 'app/(admin)/admin/settings/admin-settings-form.tsx';
let content = fs.readFileSync(file, 'utf-8');
// 1. Add icons
content = content.replace(
"import { TestTube, ExternalLink, RefreshCw } from 'lucide-react'",
"import { TestTube, ExternalLink, RefreshCw, Shield, Brain, Mail, Wrench } from 'lucide-react'"
);
// 2. Change root wrapper to 2 columns
content = content.replace(
'<div className="space-y-6">',
`<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
{/* Left Column */}
<div className="flex flex-col gap-6">`
);
// 3. Close left column and open right column right before AI Providers
content = content.replace(
' <Card>\n <CardHeader>\n <CardTitle>{t(\'admin.ai.title\')}</CardTitle>',
` </div>
{/* Right Column */}
<div className="flex flex-col gap-6">
<Card>
<CardHeader>
<CardTitle>{t('admin.ai.title')}</CardTitle>`
);
// 4. Move Email from after AI to Left Column? No, let's keep the order and just split them.
// Wait, in my previous attempt I put Security + AI on left, and Email + Tools on right?
// Let's just do that to be safe.
// Let's put Security + AI in Left Column, and Email + Tools in Right Column.
// So:
// Left Column ends after AI Providers.
// Right Column starts before Email.
content = content.replace(
' <Card>\n <CardHeader>\n <CardTitle>{t(\'admin.email.title\')}</CardTitle>',
` </div>
{/* Right Column */}
<div className="flex flex-col gap-6">
<Card>
<CardHeader>
<CardTitle>{t('admin.email.title')}</CardTitle>`
);
// Add the closing div for Right Column at the very end
content = content.replace(
' </div>\n )\n}\n',
' </div>\n </div>\n )\n}\n'
);
// Now let's replace all Card components with the custom design system
// Security Card
content = content.replace(
'<Card>\n <CardHeader>\n <CardTitle>{t(\'admin.security.title\')}</CardTitle>\n <CardDescription>{t(\'admin.security.description\')}</CardDescription>\n </CardHeader>',
`<div className="bg-card rounded-lg border border-border shadow-sm overflow-hidden">
<div className="flex items-center gap-3 p-6 border-b border-border">
<div className="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center text-primary shrink-0">
<Shield className="h-5 w-5" />
</div>
<div>
<h2 className="font-semibold text-foreground">{t('admin.security.title')}</h2>
<p className="text-sm text-muted-foreground">{t('admin.security.description')}</p>
</div>
</div>`
);
content = content.replace(/<CardContent/g, '<div');
content = content.replace(/<\/CardContent>/g, '</div>');
content = content.replace(/<CardFooter>/g, '<div className="px-6 pb-6">');
content = content.replace(/<\/CardFooter>/g, '</div>');
content = content.replace(
'</form>\n </Card>',
'</form>\n </div>'
); // do it 4 times
content = content.replace('</form>\n </Card>', '</form>\n </div>');
content = content.replace('</form>\n </Card>', '</form>\n </div>');
content = content.replace('</form>\n </Card>', '</form>\n </div>');
// AI Card
content = content.replace(
'<Card>\n <CardHeader>\n <CardTitle>{t(\'admin.ai.title\')}</CardTitle>\n <CardDescription>{t(\'admin.ai.description\')}</CardDescription>\n </CardHeader>',
`<div className="bg-card rounded-lg border border-border shadow-sm overflow-hidden">
<div className="flex items-center gap-3 p-6 border-b border-border">
<div className="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center text-primary shrink-0">
<Brain className="h-5 w-5" />
</div>
<div>
<h2 className="font-semibold text-foreground">{t('admin.ai.title')}</h2>
<p className="text-sm text-muted-foreground">{t('admin.ai.description')}</p>
</div>
</div>`
);
// Email Card
content = content.replace(
'<Card>\n <CardHeader>\n <CardTitle>{t(\'admin.email.title\')}</CardTitle>\n <CardDescription>{t(\'admin.email.description\')}</CardDescription>\n </CardHeader>',
`<div className="bg-card rounded-lg border border-border shadow-sm overflow-hidden">
<div className="flex items-center gap-3 p-6 border-b border-border">
<div className="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center text-primary shrink-0">
<Mail className="h-5 w-5" />
</div>
<div>
<h2 className="font-semibold text-foreground">{t('admin.email.title')}</h2>
<p className="text-sm text-muted-foreground">{t('admin.email.description')}</p>
</div>
</div>`
);
// Tools Card
content = content.replace(
'<Card>\n <CardHeader>\n <CardTitle>{t(\'admin.tools.title\')}</CardTitle>\n <CardDescription>{t(\'admin.tools.description\')}</CardDescription>\n </CardHeader>',
`<div className="bg-card rounded-lg border border-border shadow-sm overflow-hidden">
<div className="flex items-center gap-3 p-6 border-b border-border">
<div className="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center text-primary shrink-0">
<Wrench className="h-5 w-5" />
</div>
<div>
<h2 className="font-semibold text-foreground">{t('admin.tools.title')}</h2>
<p className="text-sm text-muted-foreground">{t('admin.tools.description')}</p>
</div>
</div>`
);
// Replace AI block styling
content = content.replace(
'p-4 border rounded-lg bg-primary/5 dark:bg-primary/10',
'p-4 border border-border/50 rounded-lg bg-muted/50'
);
content = content.replace(
'p-4 border rounded-lg bg-green-50/50 dark:bg-green-950/20',
'p-4 border border-border/50 rounded-lg bg-muted/50 mt-4'
);
content = content.replace(
'p-4 border rounded-lg bg-blue-50/50 dark:bg-blue-950/20',
'p-4 border border-border/50 rounded-lg bg-muted/50 mt-4'
);
// Also replace dark text colors inside headers
content = content.replace(
'<h3 className="text-base font-semibold flex items-center gap-2">\\n <span className="text-primary">🏷️</span>',
'<h3 className="text-base font-semibold flex items-center gap-2 text-foreground">\\n <span className="text-primary">🏷️</span>'
);
content = content.replace(
'<h3 className="text-base font-semibold flex items-center gap-2">\\n <span className="text-green-600">🔍</span>',
'<h3 className="text-base font-semibold flex items-center gap-2 text-foreground">\\n <span className="text-green-600">🔍</span>'
);
content = content.replace(
'<h3 className="text-base font-semibold flex items-center gap-2">\\n <span className="text-blue-600">💬</span>',
'<h3 className="text-base font-semibold flex items-center gap-2 text-foreground">\\n <span className="text-blue-600">💬</span>'
);
// Email / Search test result classes
content = content.replace(
/border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950\/30 dark:text-green-300/g,
'border-green-500/20 bg-green-500/10 text-green-600'
);
content = content.replace(
/border-red-200 bg-red-50 text-red-800 dark:border-red-800 dark:bg-red-950\/30 dark:text-red-300/g,
'border-red-500/20 bg-red-500/10 text-red-600'
);
// CardFooter specific layout fixes since it became a div
content = content.replace(
'<div className="flex justify-between pb-6">',
'<div className="px-6 pb-6 flex justify-between">'
); // Might not match exact, let's fix manually if needed.
// Wait, the CardFooter replacement was: content.replace(/<CardFooter>/g, '<div className="px-6 pb-6">');
// Then there was <CardFooter className="flex justify-between"> which became <div className="flex justify-between">... Need to make sure it has padding.
content = content.replace(
'<div className="flex justify-between">',
'<div className="px-6 pb-6 flex justify-between">'
);
fs.writeFileSync(file, content);
console.log("Success");

View File

@@ -0,0 +1,11 @@
const fs = require('fs');
const file = 'app/(admin)/admin/settings/admin-settings-form.tsx';
let content = fs.readFileSync(file, 'utf-8');
// Replace any leftover <CardFooter> tags
content = content.replace(/<CardFooter>/g, '<div className="px-6 pb-6">');
content = content.replace(/<CardFooter className="([^"]+)">/g, '<div className="px-6 pb-6 $1">');
content = content.replace(/<\/CardFooter>/g, '</div>');
fs.writeFileSync(file, content);
console.log("Fixed CardFooters");

View File

@@ -0,0 +1,192 @@
const fs = require('fs');
const file = 'components/mcp/mcp-settings-panel.tsx';
let content = fs.readFileSync(file, 'utf-8');
// Replace standard <Card className="p-6"> with our styled headers
// Section 1
content = content.replace(
`<Card className="p-6">
<div className="flex items-start gap-3">
<Info className="h-5 w-5 text-blue-500 mt-0.5 shrink-0" />
<div>
<h2 className="text-lg font-semibold">{t('mcpSettings.whatIsMcp.title')}</h2>`,
`<div className="bg-card rounded-lg border border-border shadow-sm overflow-hidden">
<div className="flex items-center gap-3 p-6 border-b border-border">
<div className="w-10 h-10 rounded-full bg-blue-500/10 flex items-center justify-center text-blue-500 shrink-0">
<Info className="h-5 w-5" />
</div>
<div>
<h2 className="font-semibold text-foreground">{t('mcpSettings.whatIsMcp.title')}</h2>
</div>
</div>
<div className="p-6 pt-4">`
);
// We need to close the p-6 div, wait, the <Card> ends with </Card>. So we need to replace </Card> carefully.
// Let's do it section by section or just globally replace </Card> with </div></div> since we added a new wrapping div.
// For Section 1, the end is </Card>
content = content.replace(
` <ExternalLink className="h-3 w-3" />
</a>
</div>
</div>
</Card>`,
` <ExternalLink className="h-3 w-3" />
</a>
</div>
</div>`
); // The original has </div></div></Card>
// Section 2
content = content.replace(
`<Card className="p-6">
<div className="flex items-center gap-3 mb-4">
<Server className="h-5 w-5 shrink-0" />
<h2 className="text-lg font-semibold">{t('mcpSettings.serverStatus.title')}</h2>
</div>`,
`<div className="bg-card rounded-lg border border-border shadow-sm overflow-hidden">
<div className="flex items-center gap-3 p-6 border-b border-border">
<div className="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center text-primary shrink-0">
<Server className="h-5 w-5" />
</div>
<div>
<h2 className="font-semibold text-foreground">{t('mcpSettings.serverStatus.title')}</h2>
</div>
</div>
<div className="p-6">`
);
content = content.replace(
` </div>
)}
</div>
</Card>`,
` </div>
)}
</div>
</div>
</div>`
);
// Section 3
content = content.replace(
`<Card className="p-6">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<Key className="h-5 w-5 shrink-0" />
<div>
<h2 className="text-lg font-semibold">{t('mcpSettings.apiKeys.title')}</h2>
<p className="text-sm text-gray-500">
{t('mcpSettings.apiKeys.description')}
</p>
</div>
</div>`,
`<div className="bg-card rounded-lg border border-border shadow-sm overflow-hidden">
<div className="flex items-center justify-between p-6 border-b border-border">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center text-primary shrink-0">
<Key className="h-5 w-5" />
</div>
<div>
<h2 className="font-semibold text-foreground">{t('mcpSettings.apiKeys.title')}</h2>
<p className="text-sm text-muted-foreground">
{t('mcpSettings.apiKeys.description')}
</p>
</div>
</div>`
);
content = content.replace(
` ))}
</div>
)}
</Card>`,
` ))}
</div>
)}
</div>`
);
// Wait! I forgot to add `<div className="p-6">` after the header for Section 3!
// The header ended at <Dialog open...
content = content.replace(
` <CreateKeyDialog
onGenerate={handleGenerate}
isPending={isPending}
/>
</Dialog>
</div>`,
` <CreateKeyDialog
onGenerate={handleGenerate}
isPending={isPending}
/>
</Dialog>
</div>
<div className="p-6">`
);
// Section 4
// Wait, Section 4 is a subcomponent! <ConfigInstructions>
content = content.replace(
`<Card className="p-6">
<div className="flex items-center gap-3 mb-4">
<ExternalLink className="h-5 w-5 shrink-0" />
<div>
<h2 className="text-lg font-semibold">
{t('mcpSettings.configInstructions.title')}
</h2>
<p className="text-sm text-gray-500">
{t('mcpSettings.configInstructions.description')}
</p>
</div>
</div>`,
`<div className="bg-card rounded-lg border border-border shadow-sm overflow-hidden">
<div className="flex items-center gap-3 p-6 border-b border-border">
<div className="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center text-primary shrink-0">
<ExternalLink className="h-5 w-5" />
</div>
<div>
<h2 className="font-semibold text-foreground">
{t('mcpSettings.configInstructions.title')}
</h2>
<p className="text-sm text-muted-foreground">
{t('mcpSettings.configInstructions.description')}
</p>
</div>
</div>
<div className="p-6 space-y-2">`
);
content = content.replace(
` ))}
</div>
</Card>`,
` ))}
</div>
</div>`
);
// In Section 4, remove `<div className="space-y-2">` because I merged it into `<div className="p-6 space-y-2">`
content = content.replace(
`<div className="p-6 space-y-2">\n <div className="space-y-2">`,
`<div className="p-6 space-y-2">`
);
// Fix colors
content = content.replace(/text-gray-500/g, 'text-muted-foreground');
content = content.replace(/text-gray-600/g, 'text-muted-foreground');
content = content.replace(/text-gray-400/g, 'text-muted-foreground');
content = content.replace(/bg-gray-100 dark:bg-gray-800/g, 'bg-muted');
content = content.replace(/bg-gray-50 dark:bg-gray-900/g, 'bg-muted/50');
content = content.replace(/bg-gray-50/g, 'bg-muted/50');
content = content.replace(/dark:hover:bg-gray-900/g, 'hover:bg-muted');
// Remove Card import
content = content.replace("import { Card } from '@/components/ui/card'", "");
// Grid layout for mcp-settings-panel root
content = content.replace(
'<div className="space-y-6">',
'<div className="columns-1 lg:columns-2 gap-6 space-y-6">'
);
fs.writeFileSync(file, content);
console.log("Done");

View File

@@ -1092,6 +1092,8 @@
"tagsTestDescription": "Testez le fournisseur IA responsable des suggestions d\u0027étiquettes automatiques",
"embeddingsTestTitle": "Test d\u0027embeddings",
"embeddingsTestDescription": "Testez le fournisseur IA responsable des embeddings de recherche sémantique",
"chatTestTitle": "Test de chat assistant",
"chatTestDescription": "Testez le fournisseur IA responsable de l\u0027assistant de discussion",
"howItWorksTitle": "Fonctionnement des tests",
"tagsGenerationTest": "🏷️ Test de génération d\u0027étiquettes :",
"tagsStep1": "Envoie une note exemple au fournisseur IA",
@@ -1103,6 +1105,11 @@
"embeddingsStep2": "Génère une représentation vectorielle (liste de nombres)",
"embeddingsStep3": "Affiche les dimensions de l\u0027embedding et des exemples de valeurs",
"embeddingsStep4": "Vérifie que le vecteur est valide et correctement formaté",
"chatGenerationTest": "💬 Test de chat assistant :",
"chatStep1": "Envoie un message de test à l\u0027assistant",
"chatStep2": "Demande une réponse concise sur le rôle de l\u0027IA",
"chatStep3": "Affiche la réponse générée par le modèle",
"chatStep4": "Vérifie la fluidité et le temps de réponse",
"tipContent": "Vous pouvez utiliser différents fournisseurs pour les étiquettes et les embeddings ! Par exemple, utilisez Ollama (gratuit) pour les étiquettes et OpenAI (meilleure qualité) pour les embeddings afin d\u0027optimiser les coûts et les performances.",
"provider": "Fournisseur :",
"model": "Modèle :",

View File

@@ -26,11 +26,9 @@ const nextConfig: NextConfig = {
// functions during concurrent transitions, amplifying timing issues.
reactStrictMode: false,
// TEMP: disable Turbopack due React #310 loop on /admin routes in production builds.
// We keep webpack pipeline until upstream fix is confirmed.
};
module.exports = {
// Allow development origins for HMR and Dev Server access
// @ts-ignore - Some NextConfig versions might require this in experimental
allowedDevOrigins: ['192.168.1.83'],
}
};
export default nextConfig;

View File

@@ -0,0 +1,71 @@
const fs = require('fs');
const file = 'app/(admin)/admin/settings/admin-settings-form.tsx';
let content = fs.readFileSync(file, 'utf-8');
// 1. Add state variable
content = content.replace(
"const { t } = useLanguage()",
"const { t } = useLanguage()\n const [activeAiTab, setActiveAiTab] = useState<'tags' | 'embeddings' | 'chat'>('tags')"
);
// 2. Add Tab Switcher UI and wrap each block
// Find the start of AI form
const aiFormStart = `<div className="p-6 space-y-6">
{/* Tags Generation Provider */}
<div className="space-y-4 p-4 border border-border/50 rounded-lg bg-muted/50">`;
const newAiFormStart = `<div className="px-6 pt-6">
<div className="flex border-b border-border/50 overflow-x-auto">
<button type="button" onClick={() => setActiveAiTab('tags')} className={\`px-4 py-2.5 text-sm font-medium border-b-2 whitespace-nowrap \${activeAiTab === 'tags' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground hover:border-border'}\`}>🏷️ Tags</button>
<button type="button" onClick={() => setActiveAiTab('embeddings')} className={\`px-4 py-2.5 text-sm font-medium border-b-2 whitespace-nowrap \${activeAiTab === 'embeddings' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground hover:border-border'}\`}>🔍 Embeddings</button>
<button type="button" onClick={() => setActiveAiTab('chat')} className={\`px-4 py-2.5 text-sm font-medium border-b-2 whitespace-nowrap \${activeAiTab === 'chat' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground hover:border-border'}\`}>💬 Chat</button>
</div>
</div>
<div className="p-6 space-y-6">
{/* Tags Generation Provider */}
<div className={\`space-y-4 p-4 border border-border/50 rounded-lg bg-muted/50 \${activeAiTab === 'tags' ? 'block' : 'hidden'}\`}>`;
content = content.replace(aiFormStart, newAiFormStart);
// Now find Embeddings and Chat and wrap them in hidden condition
const embeddingsStart = `{/* Embeddings Provider */}
<div className="space-y-4 p-4 border border-border/50 rounded-lg bg-muted/50 mt-4">`;
const newEmbeddingsStart = `{/* Embeddings Provider */}
<div className={\`space-y-4 p-4 border border-border/50 rounded-lg bg-muted/50 \${activeAiTab === 'embeddings' ? 'block' : 'hidden'}\`}>`;
content = content.replace(embeddingsStart, newEmbeddingsStart);
const chatStart = `{/* Chat Provider */}
<div className="space-y-4 p-4 border border-border/50 rounded-lg bg-muted/50 mt-4">`;
const newChatStart = `{/* Chat Provider */}
<div className={\`space-y-4 p-4 border border-border/50 rounded-lg bg-muted/50 \${activeAiTab === 'chat' ? 'block' : 'hidden'}\`}>`;
content = content.replace(chatStart, newChatStart);
// 3. Fix button overflows everywhere.
// Find: <div className="px-6 pb-6 flex justify-between pt-6"> (AI)
content = content.replace(
'<div className="px-6 pb-6 flex justify-between pt-6">',
'<div className="px-6 pb-6 flex flex-col sm:flex-row gap-3 sm:justify-between pt-6">'
);
// Find: <div className="px-6 pb-6 flex justify-between"> (Email, Tools, Security?)
// Tools:
content = content.replace(
'<div className="px-6 pb-6 flex justify-between">',
'<div className="px-6 pb-6 flex flex-col sm:flex-row gap-3 sm:justify-between">'
);
content = content.replace(
'<div className="px-6 pb-6 flex justify-between">',
'<div className="px-6 pb-6 flex flex-col sm:flex-row gap-3 sm:justify-between">'
);
// Email:
content = content.replace(
'<div className="px-6 pb-6 flex justify-between pt-6">',
'<div className="px-6 pb-6 flex flex-col sm:flex-row gap-3 sm:justify-between pt-6">'
);
// Security doesn't have justify-between, it just has <div className="px-6 pb-6">
// Write back
fs.writeFileSync(file, content);
console.log("Done");