style: refine chat UI consistency and enhance dark mode (#393)
This commit is contained in:
committed by
GitHub
Unverified
parent
99681777a0
commit
9502d9b1c5
@@ -353,7 +353,7 @@ export function ChatInput({ onSend, onStop, disabled = false, sending = false, i
|
||||
)}
|
||||
|
||||
{/* Input Row */}
|
||||
<div className={`flex items-end gap-1.5 bg-white dark:bg-accent/50 rounded-[28px] shadow-sm border border-black/5 dark:border-white/10 p-1.5 transition-shadow ${dragOver ? 'ring-2 ring-primary' : 'focus-within:ring-1 focus-within:ring-black/5 dark:focus-within:ring-white/10'}`}>
|
||||
<div className={`flex items-end gap-1.5 bg-white dark:bg-[#1a1a19] rounded-[28px] shadow-sm border p-1.5 transition-all ${dragOver ? 'border-primary ring-1 ring-primary' : 'border-black/10 dark:border-white/10'}`}>
|
||||
|
||||
{/* Attach Button */}
|
||||
<Button
|
||||
@@ -383,7 +383,7 @@ export function ChatInput({ onSend, onStop, disabled = false, sending = false, i
|
||||
onPaste={handlePaste}
|
||||
placeholder={disabled ? 'Gateway not connected...' : ''}
|
||||
disabled={disabled}
|
||||
className="min-h-[40px] max-h-[200px] resize-none border-0 focus-visible:ring-0 shadow-none bg-transparent py-2.5 px-2 text-[15px] placeholder:text-muted-foreground/60 leading-relaxed"
|
||||
className="min-h-[40px] max-h-[200px] resize-none border-0 focus-visible:ring-0 focus-visible:ring-offset-0 shadow-none bg-transparent py-2.5 px-2 text-[15px] placeholder:text-muted-foreground/60 leading-relaxed"
|
||||
rows={1}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* with markdown, thinking sections, images, and tool cards.
|
||||
*/
|
||||
import { useState, useCallback, useEffect, memo } from 'react';
|
||||
import { User, Sparkles, Copy, Check, ChevronDown, ChevronRight, Wrench, FileText, Film, Music, FileArchive, File, X, FolderOpen, ZoomIn, Loader2, CheckCircle2, AlertCircle } from 'lucide-react';
|
||||
import { Sparkles, Copy, Check, ChevronDown, ChevronRight, Wrench, FileText, Film, Music, FileArchive, File, X, FolderOpen, ZoomIn, Loader2, CheckCircle2, AlertCircle } from 'lucide-react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { createPortal } from 'react-dom';
|
||||
@@ -71,16 +71,11 @@ export const ChatMessage = memo(function ChatMessage({
|
||||
)}
|
||||
>
|
||||
{/* Avatar */}
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-8 w-8 shrink-0 items-center justify-center rounded-full mt-1',
|
||||
isUser
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'bg-gradient-to-br from-indigo-500 to-purple-600 text-white',
|
||||
)}
|
||||
>
|
||||
{isUser ? <User className="h-4 w-4" /> : <Sparkles className="h-4 w-4" />}
|
||||
</div>
|
||||
{!isUser && (
|
||||
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full mt-1 bg-black/5 dark:bg-white/5 text-foreground">
|
||||
<Sparkles className="h-4 w-4" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Content */}
|
||||
<div
|
||||
@@ -148,7 +143,7 @@ export const ChatMessage = memo(function ChatMessage({
|
||||
) : (
|
||||
<div
|
||||
key={`local-${i}`}
|
||||
className="w-36 h-36 rounded-xl border overflow-hidden bg-muted flex items-center justify-center text-muted-foreground"
|
||||
className="w-36 h-36 rounded-xl border border-black/10 dark:border-white/10 bg-black/5 dark:bg-white/5 flex items-center justify-center text-muted-foreground"
|
||||
>
|
||||
<File className="h-8 w-8" />
|
||||
</div>
|
||||
@@ -209,7 +204,7 @@ export const ChatMessage = memo(function ChatMessage({
|
||||
}
|
||||
if (isImage && !file.preview) {
|
||||
return (
|
||||
<div key={`local-${i}`} className="w-36 h-36 rounded-xl border overflow-hidden bg-muted flex items-center justify-center text-muted-foreground">
|
||||
<div key={`local-${i}`} className="w-36 h-36 rounded-xl border border-black/10 dark:border-white/10 bg-black/5 dark:bg-white/5 flex items-center justify-center text-muted-foreground">
|
||||
<File className="h-8 w-8" />
|
||||
</div>
|
||||
);
|
||||
@@ -286,7 +281,7 @@ function ToolStatusBar({
|
||||
{isError && <AlertCircle className="h-3.5 w-3.5 text-destructive shrink-0" />}
|
||||
<Wrench className="h-3 w-3 shrink-0 opacity-60" />
|
||||
<span className="font-mono text-[12px] font-medium">{tool.name}</span>
|
||||
{duration && <span className="text-[11px] opacity-60">{duration}</span>}
|
||||
{duration && <span className="text-[11px] opacity-60">{tool.summary ? `(${duration})` : duration}</span>}
|
||||
{tool.summary && (
|
||||
<span className="truncate text-[11px] opacity-70">{tool.summary}</span>
|
||||
)}
|
||||
@@ -342,8 +337,8 @@ function MessageBubble({
|
||||
'relative rounded-2xl px-4 py-3',
|
||||
!isUser && 'w-full',
|
||||
isUser
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'bg-muted',
|
||||
? 'bg-[#0a84ff] text-white shadow-sm'
|
||||
: 'bg-black/5 dark:bg-white/5 text-foreground',
|
||||
)}
|
||||
>
|
||||
{isUser ? (
|
||||
@@ -398,7 +393,7 @@ function ThinkingBlock({ content }: { content: string }) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="w-full rounded-lg border border-border/50 bg-muted/30 text-sm">
|
||||
<div className="w-full rounded-xl border border-black/10 dark:border-white/10 bg-black/5 dark:bg-white/5 text-[14px]">
|
||||
<button
|
||||
className="flex items-center gap-2 w-full px-3 py-2 text-muted-foreground hover:text-foreground transition-colors"
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
@@ -437,7 +432,7 @@ function FileIcon({ mimeType, className }: { mimeType: string; className?: strin
|
||||
|
||||
function FileCard({ file }: { file: AttachedFileMeta }) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 rounded-lg border border-border px-3 py-2 bg-muted/30 max-w-[220px]">
|
||||
<div className="flex items-center gap-3 rounded-xl border border-black/10 dark:border-white/10 px-3 py-2.5 bg-black/5 dark:bg-white/5 max-w-[220px]">
|
||||
<FileIcon mimeType={file.mimeType} className="h-5 w-5 shrink-0 text-muted-foreground" />
|
||||
<div className="min-w-0 overflow-hidden">
|
||||
<p className="text-xs font-medium truncate">{file.fileName}</p>
|
||||
@@ -469,7 +464,7 @@ function ImageThumbnail({
|
||||
void filePath; void base64; void mimeType;
|
||||
return (
|
||||
<div
|
||||
className="relative w-36 h-36 rounded-xl border overflow-hidden bg-muted group/img cursor-zoom-in"
|
||||
className="relative w-36 h-36 rounded-xl border overflow-hidden border-black/10 dark:border-white/10 bg-black/5 dark:bg-white/5 group/img cursor-zoom-in"
|
||||
onClick={onPreview}
|
||||
>
|
||||
<img src={src} alt={fileName} className="w-full h-full object-cover" />
|
||||
@@ -500,7 +495,7 @@ function ImagePreviewCard({
|
||||
void filePath; void base64; void mimeType;
|
||||
return (
|
||||
<div
|
||||
className="relative max-w-xs rounded-lg border overflow-hidden group/img cursor-zoom-in"
|
||||
className="relative max-w-xs rounded-xl border overflow-hidden border-black/10 dark:border-white/10 bg-black/5 dark:bg-white/5 group/img cursor-zoom-in"
|
||||
onClick={onPreview}
|
||||
>
|
||||
<img src={src} alt={fileName} className="block w-full" />
|
||||
@@ -595,7 +590,7 @@ function ToolCard({ name, input }: { name: string; input: unknown }) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-border/50 bg-muted/20 text-sm">
|
||||
<div className="rounded-xl border border-black/10 dark:border-white/10 bg-black/5 dark:bg-white/5 text-[14px]">
|
||||
<button
|
||||
className="flex items-center gap-2 w-full px-3 py-1.5 text-muted-foreground hover:text-foreground transition-colors"
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
|
||||
@@ -77,18 +77,7 @@ export function Chat() {
|
||||
}
|
||||
}, [sending, streamingTimestamp]);
|
||||
|
||||
// Gateway not running
|
||||
if (!isGatewayRunning) {
|
||||
return (
|
||||
<div className="flex h-[calc(100vh-8rem)] flex-col items-center justify-center text-center p-8">
|
||||
<AlertCircle className="h-12 w-12 text-yellow-500 mb-4" />
|
||||
<h2 className="text-xl font-semibold mb-2">{t('gatewayNotRunning')}</h2>
|
||||
<p className="text-muted-foreground max-w-md">
|
||||
{t('gatewayRequired')}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// Gateway not running block has been completely removed so the UI always renders.
|
||||
|
||||
const streamMsg = streamingMessage && typeof streamingMessage === 'object'
|
||||
? streamingMessage as unknown as { role?: string; content?: unknown; timestamp?: number }
|
||||
|
||||
Reference in New Issue
Block a user