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
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 58s
This commit is contained in:
@@ -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 . .
|
||||
|
||||
|
||||
@@ -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">"{result.chatResponse}"</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">
|
||||
"{result.chatResponse}"
|
||||
</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'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>
|
||||
|
||||
@@ -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'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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
178
memento-note/fix-admin-settings.js
Normal file
178
memento-note/fix-admin-settings.js
Normal 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");
|
||||
11
memento-note/fix-card-footer.js
Normal file
11
memento-note/fix-card-footer.js
Normal 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");
|
||||
192
memento-note/fix-mcp-settings.js
Normal file
192
memento-note/fix-mcp-settings.js
Normal 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");
|
||||
@@ -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 :",
|
||||
|
||||
@@ -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;
|
||||
|
||||
71
memento-note/rewrite-ai-tabs.js
Normal file
71
memento-note/rewrite-ai-tabs.js
Normal 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");
|
||||
Reference in New Issue
Block a user