feat: add emoji-regex and remove-accents dependencies; enhance chat and setup pages
- Added `emoji-regex` and `remove-accents` packages to improve text processing capabilities. - Implemented automatic Ollama startup in the chat page for better user experience. - Enhanced setup page to include GPU selection based on hardware specifications. - Updated chat input to support real-time validation and improved user feedback. - Refactored message handling in chat to incorporate new processing features.
This commit is contained in:
1 parent
4275295e4f
commit
84ea2dec19
51 files changed
+7857
-573
No files matched your search
@@ -1,71 +1,125 @@
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { useState } from 'react';
|
||||
import { useState, useMemo } from 'react';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Message } from "@/hooks/use-chat";
|
||||
import { Bot, User } from "lucide-react";
|
||||
import { markdownRenderers } from "./markdown-renderers";
|
||||
import { MessageActions } from "./message-actions";
|
||||
import { MessageBackground } from "./message-background";
|
||||
import { MessageStepThinking } from "./message-step-thinking";
|
||||
import type { ThinkingMessageMetadata } from "@/hooks/use-chat";
|
||||
|
||||
interface ChatMessageProps {
|
||||
message: Message;
|
||||
isStreaming?: boolean;
|
||||
}
|
||||
|
||||
export function ChatMessage({ message, isStreaming = false }: ChatMessageProps) {
|
||||
// Verificar se é mensagem de processo de pensamento
|
||||
const isThinkingMessage = message.metadata &&
|
||||
typeof message.metadata === 'object' &&
|
||||
'type' in message.metadata &&
|
||||
message.metadata.type === 'thinking';
|
||||
|
||||
if (isThinkingMessage) {
|
||||
return <MessageStepThinking metadata={message.metadata as ThinkingMessageMetadata} />;
|
||||
}
|
||||
|
||||
export function ChatMessage({ message }: { message: Message }) {
|
||||
const isUser = message.role === 'user';
|
||||
const [selected, setSelected] = useState(false);
|
||||
const isEmpty = !message.content || message.content.trim() === '';
|
||||
|
||||
// Memoizar processamento de conteúdo
|
||||
const processedContent = useMemo(() => {
|
||||
let content = message.content || '';
|
||||
// Remove metadata tags
|
||||
content = content.replace(/<metadata>[\s\S]*?<\/metadata>/gi, '').trim();
|
||||
|
||||
// Also remove common prefixes
|
||||
const prefixes = [
|
||||
'Bloco oculto de metadados:', 'Metadata:', 'Metadados:',
|
||||
'Hidden metadata block:', 'JSON metadata:', '---'
|
||||
];
|
||||
|
||||
for (const prefix of prefixes) {
|
||||
const regex = new RegExp(`${prefix}\\s*$`, 'i');
|
||||
if (regex.test(content)) {
|
||||
content = content.replace(regex, '').trim();
|
||||
}
|
||||
}
|
||||
|
||||
return content || message.content;
|
||||
}, [message.content]);
|
||||
|
||||
return (
|
||||
<MessageBackground
|
||||
role={isUser ? 'user' : 'assistant'}
|
||||
state={selected ? 'selected' : 'normal'}
|
||||
className={cn("cursor-pointer")}
|
||||
className={cn(
|
||||
"cursor-default transition-colors rounded-xl",
|
||||
selected && "bg-muted/40",
|
||||
isUser ? "mb-2" : "mb-8"
|
||||
)}
|
||||
onClick={() => setSelected((v) => !v)}
|
||||
tabIndex={0}
|
||||
>
|
||||
<div className="flex-shrink-0 mt-1">
|
||||
<div className="flex-shrink-0 mt-0.5">
|
||||
<div className={cn(
|
||||
"w-8 h-8 rounded-md flex items-center justify-center",
|
||||
isUser ? "bg-primary text-primary-foreground" : "bg-secondary text-secondary-foreground"
|
||||
"w-8 h-8 rounded-full flex items-center justify-center shadow-sm ring-1 ring-border/50 backdrop-blur-sm",
|
||||
isUser
|
||||
? "bg-background text-muted-foreground"
|
||||
: "bg-primary/10 text-primary ring-primary/20"
|
||||
)}>
|
||||
{isUser ? <User className="w-5 h-5" /> : <Bot className="w-5 h-5" />}
|
||||
{isUser ? <User className="w-4 h-4" /> : <Bot className="w-5 h-5" />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 overflow-hidden min-w-0">
|
||||
<div className="prose dark:prose-invert max-w-none break-words leading-relaxed">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={markdownRenderers}
|
||||
>
|
||||
{(() => {
|
||||
let content = message.content || '';
|
||||
// Remove metadata tags
|
||||
content = content.replace(/<metadata>[\s\S]*?<\/metadata>/gi, '').trim();
|
||||
|
||||
// Also remove common prefixes if they appear at the end
|
||||
const prefixes = [
|
||||
'Bloco oculto de metadados:',
|
||||
'Metadata:',
|
||||
'Metadados:',
|
||||
'Hidden metadata block:',
|
||||
'JSON metadata:',
|
||||
'---'
|
||||
];
|
||||
|
||||
for (const prefix of prefixes) {
|
||||
const regex = new RegExp(`${prefix}\\s*$`, 'i');
|
||||
if (regex.test(content)) {
|
||||
content = content.replace(regex, '').trim();
|
||||
}
|
||||
}
|
||||
|
||||
return content || message.content;
|
||||
})()}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-hidden min-w-0 pt-1">
|
||||
{isUser ? (
|
||||
// Estilo específico para mensagem do usuário (mais destaque, fonte maior)
|
||||
<div className="text-lg sm:text-xl font-medium tracking-tight text-foreground/90 leading-relaxed break-words whitespace-pre-wrap">
|
||||
{processedContent}
|
||||
</div>
|
||||
) : (
|
||||
// Estilo Assistant
|
||||
isEmpty && isStreaming ? (
|
||||
<div className="flex items-center gap-2 text-muted-foreground py-1">
|
||||
<div className="w-2 h-2 bg-primary rounded-full animate-pulse" />
|
||||
<span className="text-sm font-medium">Gerando resposta...</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className={cn(
|
||||
"prose prose-neutral dark:prose-invert max-w-none break-words leading-7 text-base text-foreground/90",
|
||||
"prose-headings:font-semibold prose-headings:tracking-tight prose-h1:text-2xl prose-h2:text-xl prose-h3:text-lg",
|
||||
"prose-p:leading-7 prose-li:leading-7",
|
||||
"prose-pre:bg-muted/50 prose-pre:border prose-pre:border-border/50 prose-pre:rounded-xl",
|
||||
isStreaming && "relative"
|
||||
)}>
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={markdownRenderers}
|
||||
>
|
||||
{processedContent}
|
||||
</ReactMarkdown>
|
||||
{isStreaming && !isEmpty && (
|
||||
<span className="inline-block w-2 h-4 bg-primary ml-1 animate-pulse rounded-full align-middle" />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
{!isUser && (
|
||||
<div className="absolute top-4 right-4">
|
||||
|
||||
{!isUser && !isStreaming && (
|
||||
<div className={cn(
|
||||
"absolute -bottom-8 left-12 flex items-center gap-2 opacity-0 transition-opacity duration-200",
|
||||
"group-hover:opacity-100",
|
||||
selected && "opacity-100"
|
||||
)}>
|
||||
<MessageActions content={message.content} role={message.role} />
|
||||
</div>
|
||||
)}
|
||||
</MessageBackground>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user