Chat: rendu Markdown, blocs de code copiables, metriques de tokens, apercu HTML

Frontend
- MessageContent: rendu Markdown complet (react-markdown + remark-gfm) avec
  blocs de code dans un cadre + bouton Copier, et styles thematises (titres,
  listes, gras, liens, tableaux, citations).
- ChatPanel: affiche sous chaque reponse de l'agent la vitesse (tokens/s) et le
  nombre de jetons generes / en entree.
- PreviewPanel: iframe en sandbox="allow-scripts" pour que le JS des pages
  generees s'execute (animations, canvas) tout en gardant une origine opaque.
- Icon: ajout CopyIcon / CheckIcon.

Backend
- agent: cumule les metriques Ollama (eval_count, eval_duration,
  prompt_eval_count) et calcule les tokens/seconde, emis dans l'evenement final.
- chat: persiste les stats dans meta et les renvoie au client.
- agent_config: l'invite systeme par defaut demande un formatage Markdown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 4.8 committed 2026-06-30 13:45:59 +02:00
1 parent aed5f8148c
commit e8e6a1446c
10 files changed
+1683 -13

No files matched your search

+18 -2
View File
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
import { useStore } from "../store/useStore";
import { ClipIcon, LokiMark, SendIcon } from "../components/Icon";
import { ToolCard } from "../components/ToolCard";
import { MessageContent } from "../components/MessageContent";
import type { Message, ToolCall } from "../api/client";
/** Panneau central : barre de contexte, fil de conversation, composer. */
@@ -218,8 +219,10 @@ function Bubble({
</div>
)}
{(msg.content || pending) && (
<div className="text-sm leading-[1.65] text-ink-2 whitespace-pre-wrap">
{msg.content || (
<div className="text-sm leading-[1.65] text-ink-2">
{msg.content ? (
<MessageContent text={msg.content} />
) : (
<span className="text-muted-2">{pendingStatus || "Génération…"}</span>
)}
{pending && (
@@ -227,6 +230,19 @@ function Bubble({
)}
</div>
)}
{!pending && msg.meta?.stats && (
<div className="mt-2 flex items-center gap-2.5 font-mono text-[10.5px] text-muted-4">
{msg.meta.stats.tokens_per_sec != null && (
<span className="text-muted-3">
{msg.meta.stats.tokens_per_sec} tok/s
</span>
)}
<span>{msg.meta.stats.eval_count} jetons</span>
{msg.meta.stats.prompt_eval_count > 0 && (
<span>· {msg.meta.stats.prompt_eval_count} en entrée</span>
)}
</div>
)}
</div>
</div>
);
+4 -1
View File
@@ -107,7 +107,10 @@ export function PreviewPanel() {
title="aperçu"
srcDoc={previewContent}
className="h-full w-full border-0 bg-white"
sandbox="allow-same-origin"
// allow-scripts : sans ça, le JS de la page générée ne s'exécute pas
// (animations, canvas…). On garde une origine opaque (pas
// d'allow-same-origin) pour que le script ne puisse pas atteindre Loki.
sandbox="allow-scripts"
/>
) : (
<pre className="m-0 whitespace-pre-wrap p-4 font-mono text-[11.5px] leading-relaxed text-[#2a2018]">