xpeditis2.0/apps/frontend/app/[locale]/dashboard/wiki/page.tsx
David 402d5bcbbe feat(ia): borne l'assistant au LCL et au transport international, et lui fait entretenir le wiki
L'assistant pouvait conclure « prenez plutot un conteneur complet » avec
l'autorite de la marque, et repondre sur du transport interieur. Il ne pouvait
pas non plus combler un trou de documentation qu'il venait de rencontrer.

Perimetre (openai-trade.adapter)
- SCOPE_RULES : LCL uniquement. Le FCL reste explicable — c'est du vocabulaire
  metier — mais jamais recommande, chiffre, ni presente comme la meilleure
  option. Un cas hors LCL part vers support@xpeditis.com.
- SCOPE_RULES : transport international uniquement. Le transport interieur, le
  demenagement et le transport de personnes sont refuses, pas traites « un peu ».
- La page wiki « LCL vs FCL » ne conseille plus le FCL : elle le decrit, et
  liste les cas hors perimetre a signaler au support. Corpus reconstruit.
- L'amorce « LCL ou FCL ? » devient une question sur le calcul du fret LCL.

Documentation interne d'abord (KNOWLEDGE_RULES)
- Les extraits du wiki sont dits source de reference, avant les connaissances
  generales du modele.

Entretien du wiki global
- Nouvelle capacite `contribute_wiki_page` (scope write) : quand le wiki ne
  couvre pas un sujet d'information generale sur le transport international,
  l'assistant ecrit la page. Un titre deja pris est mis a jour, pas duplique.
- `wiki-contribution-policy` (domaine) decide ce qui entre : refus du contenu
  qui conseille le FCL, du cas client (dossier, tarif, coordonnees) et du hors
  perimetre. Le prompt oriente, cette fonction empeche.
- Un sujet deja couvert par le wiki publie (score >= 0,62) est refuse.
- `WikiRetriever` indexe les contributions a cote du corpus fige, avec un index
  en memoire invalide par la revision du jeu ; une panne de ce cote ne coute pas
  la reponse.
- Page `/dashboard/wiki/complements`, etiquetee comme ecrite par l'assistant,
  et carte sur l'index du wiki.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 21:14:41 +02:00

179 lines
5.3 KiB
TypeScript

import { getTranslations } from 'next-intl/server';
import { Link } from '@/i18n/navigation';
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card';
import {
ScrollText,
ClipboardList,
Package,
Scale,
ShieldCheck,
Shield,
Calculator,
Globe,
Anchor,
AlertTriangle,
CreditCard,
Timer,
Sparkles,
type LucideIcon,
} from 'lucide-react';
type TopicKey =
| 'incoterms'
| 'documents'
| 'containers'
| 'lclFcl'
| 'customs'
| 'insurance'
| 'freight'
| 'ports'
| 'vgm'
| 'imdg'
| 'letterOfCredit'
| 'transitTime';
interface WikiTopic {
key: TopicKey;
icon: LucideIcon;
href: string;
tags: string[];
}
const wikiTopics: WikiTopic[] = [
{
key: 'incoterms',
icon: ScrollText,
href: '/dashboard/wiki/incoterms',
tags: ['FOB', 'CIF', 'EXW', 'DDP'],
},
{
key: 'documents',
icon: ClipboardList,
href: '/dashboard/wiki/documents-transport',
tags: ['B/L', 'Sea Waybill', 'Manifest'],
},
{
key: 'containers',
icon: Package,
href: '/dashboard/wiki/conteneurs',
tags: ["20'", "40'", 'Reefer', 'Open Top'],
},
{ key: 'lclFcl', icon: Scale, href: '/dashboard/wiki/lcl-vs-fcl', tags: ['LCL', 'FCL'] },
{
key: 'customs',
icon: ShieldCheck,
href: '/dashboard/wiki/douanes',
tags: ['HS Code', 'EUR.1', 'AEO'],
},
{
key: 'insurance',
icon: Shield,
href: '/dashboard/wiki/assurance',
tags: ['ICC A', 'ICC B', 'ICC C'],
},
{
key: 'freight',
icon: Calculator,
href: '/dashboard/wiki/calcul-fret',
tags: ['CBM', 'THC', 'BAF', 'CAF'],
},
{
key: 'ports',
icon: Globe,
href: '/dashboard/wiki/ports-routes',
tags: ['Hub', 'Suez', 'Panama'],
},
{ key: 'vgm', icon: Anchor, href: '/dashboard/wiki/vgm', tags: ['SOLAS', 'VGM'] },
{ key: 'imdg', icon: AlertTriangle, href: '/dashboard/wiki/imdg', tags: ['IMDG', 'MSDS', 'DG'] },
{
key: 'letterOfCredit',
icon: CreditCard,
href: '/dashboard/wiki/lettre-credit',
tags: ['L/C', 'SWIFT', 'UCP 600'],
},
{
key: 'transitTime',
icon: Timer,
href: '/dashboard/wiki/transit-time',
tags: ['Cut-off', 'Free time', 'Demurrage'],
},
];
export default async function WikiPage() {
const t = await getTranslations('dashboard.wiki');
const tComplements = await getTranslations('dashboard.wikiComplements');
return (
<div className="space-y-6">
{/* Header */}
<div className="mb-8">
<h1 className="text-3xl font-bold text-brand-navy">{t('title')}</h1>
<p className="mt-2 text-neutral-600">{t('description')}</p>
</div>
{/* Cards Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{wikiTopics.map(topic => {
const IconComponent = topic.icon;
return (
<Link key={topic.href} href={topic.href} className="block group">
<Card className="h-full transition-all duration-200 hover:shadow-lg hover:border-brand-blue bg-white">
<CardHeader>
<div className="flex items-start justify-between">
<div className="h-10 w-10 rounded-lg bg-brand-blue/5 flex items-center justify-center">
<IconComponent className="h-5 w-5 text-brand-navy" />
</div>
</div>
<CardTitle className="mt-3 transition-colors group-hover:text-brand-blue">
{t(`topics.${topic.key}.title` as any)}
</CardTitle>
<CardDescription className="text-neutral-600">
{t(`topics.${topic.key}.description` as any)}
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-2">
{topic.tags.map(tag => (
<span
key={tag}
className="px-2 py-1 text-xs font-medium bg-brand-blue/5 text-brand-navy rounded-full"
>
{tag}
</span>
))}
</div>
</CardContent>
</Card>
</Link>
);
})}
</div>
{/* Compléments écrits par l'assistant, quand une question a trouvé un trou. */}
<Link href="/dashboard/wiki/complements" className="block group">
<Card className="transition-all duration-200 hover:shadow-lg hover:border-brand-blue bg-white">
<CardHeader>
<div className="h-10 w-10 rounded-lg bg-brand-blue/5 flex items-center justify-center">
<Sparkles className="h-5 w-5 text-brand-navy" aria-hidden="true" />
</div>
<CardTitle className="mt-3 transition-colors group-hover:text-brand-blue">
{tComplements('cardTitle')}
</CardTitle>
<CardDescription className="text-neutral-600">
{tComplements('cardDescription')}
</CardDescription>
</CardHeader>
</Card>
</Link>
{/* Footer info */}
<div className="mt-8 p-4 bg-brand-blue/5 rounded-lg">
<p className="text-sm text-brand-navy">
<span className="font-semibold">{t('needHelp')} </span>
{t('helpText')}
</p>
</div>
</div>
);
}