From 2749132e9ac32f5493df4cf72b97621b7d69b06e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=27=C3=A9lectron=20rare?= <108685187+electron-rare@users.noreply.github.com> Date: Tue, 19 May 2026 12:17:33 +0200 Subject: [PATCH] feat: restyle chat playground to design system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The playground was built with stock Tailwind utilities (slate/emerald/ amber boxes) and looked like a different app dropped into the site's editorial paper/ink design. styles.css already carried a purpose-built chat design (.chat-banner, .chat-body, .msg, .chat-input) that the components never used. Rewire all four components to those classes: - MessageBubble: editorial .msg grid — mono speaker label, serif user text, sans assistant text, ink code blocks. - ChatPlayground: ink banner, editorial AI-disclaimer footnote, the empty state and seed prompts restyled. - PromptInput: paper input dock; the send button becomes a stop button while streaming. min-width:0 on the textarea keeps mobile intact. - ParamsPanel: mono labels, rule borders, accent-blue controls. User-facing copy is now French, consistent with the rest of the site. --- .../ChatPlayground/ChatPlayground.tsx | 69 ++--- .../ChatPlayground/MessageBubble.tsx | 41 ++- .../components/ChatPlayground/ParamsPanel.tsx | 29 +- .../components/ChatPlayground/PromptInput.tsx | 106 ++++---- apps/cockpit-public/src/styles.css | 254 +++++++++++++++++- .../tests/components/ChatPlayground.test.tsx | 2 +- .../tests/components/PromptInput.test.tsx | 10 +- 7 files changed, 369 insertions(+), 142 deletions(-) diff --git a/apps/cockpit-public/src/components/ChatPlayground/ChatPlayground.tsx b/apps/cockpit-public/src/components/ChatPlayground/ChatPlayground.tsx index d480161..a1ec547 100644 --- a/apps/cockpit-public/src/components/ChatPlayground/ChatPlayground.tsx +++ b/apps/cockpit-public/src/components/ChatPlayground/ChatPlayground.tsx @@ -1,5 +1,4 @@ import { type ChatMessage, useChatStream } from '@/hooks/useChatStream'; -import { Square } from 'lucide-react'; import { useEffect, useState } from 'react'; import { MessageBubble } from './MessageBubble'; import { type ChatParams, ParamsPanel } from './ParamsPanel'; @@ -74,41 +73,35 @@ export function ChatPlayground({ modelId, modelDisplayName }: Props) { } }; + const isEmpty = messages.length === 0 && !isStreaming && !error; + return ( -
-
-

Chat — {modelDisplayName}

-

{modelId}

-

- ⚠️ - You are interacting with an AI. Replies may be inaccurate, biased, or fabricated and must - not be treated as professional advice. See the{' '} - - transparency page - {' '} - for model provenance and limitations. -

-
+ <> +
+ + + {modelDisplayName} + + SSE streaming +
+ +

+ + Réponses générées par IA — potentiellement inexactes, biaisées ou fabriquées, à ne pas + traiter comme un avis professionnel. Voir la démarche qualité. +

-
- {messages.length === 0 && !isStreaming && !error && ( -
-

- Posez votre première question à {modelDisplayName}. +

+ {isEmpty && ( +
+

+ Posez votre première question à {modelDisplayName}.

-
+
{EXAMPLE_PROMPTS.map((p) => ( - ))} @@ -124,24 +117,16 @@ export function ChatPlayground({ modelId, modelDisplayName }: Props) { /> ))} {isStreaming && } - {error &&

Error: {error}

} + {error &&

Erreur : {error}

}
- {isStreaming && ( - - )} -
+ ); } diff --git a/apps/cockpit-public/src/components/ChatPlayground/MessageBubble.tsx b/apps/cockpit-public/src/components/ChatPlayground/MessageBubble.tsx index 02632ed..822918c 100644 --- a/apps/cockpit-public/src/components/ChatPlayground/MessageBubble.tsx +++ b/apps/cockpit-public/src/components/ChatPlayground/MessageBubble.tsx @@ -7,30 +7,27 @@ interface Props { } export function MessageBubble({ speaker, content, streaming }: Props) { - const align = - speaker === 'user' - ? 'ml-auto bg-slate-100 text-slate-900' - : 'mr-auto bg-emerald-50 text-slate-900'; - // During the gap between request and first token (Cloudflare can buffer SSE - // for several seconds before any chunk reaches the client) show three - // pulsing dots so the user gets immediate feedback. + // Between the request and the first token, Cloudflare can buffer the SSE + // stream for several seconds — show pulsing dots so the user gets + // immediate feedback that something is happening. const isThinking = streaming && !content; return ( -
- {isThinking ? ( - - - - - - ) : ( - <> - {content} - {streaming && ( - - )} - - )} +
+
{speaker === 'user' ? 'vous' : 'ailiance'}
+
+ {isThinking ? ( + + + + + + ) : ( + <> + {content} + {streaming && } + + )} +
); } diff --git a/apps/cockpit-public/src/components/ChatPlayground/ParamsPanel.tsx b/apps/cockpit-public/src/components/ChatPlayground/ParamsPanel.tsx index 34640d0..122c9a6 100644 --- a/apps/cockpit-public/src/components/ChatPlayground/ParamsPanel.tsx +++ b/apps/cockpit-public/src/components/ChatPlayground/ParamsPanel.tsx @@ -1,4 +1,3 @@ -import { ChevronDown, ChevronRight } from 'lucide-react'; import { useState } from 'react'; export interface ChatParams { @@ -16,19 +15,22 @@ export function ParamsPanel({ value, onChange }: Props) { const [open, setOpen] = useState(false); return ( -
+
{open && ( -
-