feat(chat): add conversation selection and enhance message rendering
- Add currentConversationId prop to ChatContainer for loading selected conversations - Implement logic to load messages when conversation is selected and clear when deselected - Improve user message rendering with proper whitespace handling - Enhance MarkdownRenderer with content normalization and adjusted spacing for better readability - Add paragraph styling to markdown output for improved layout This enables users to switch between conversations seamlessly and provides cleaner message display.
This commit is contained in:
1 parent
0cc7a81319
commit
d5dd3a9f09
11 files changed
+54
-13
No files matched your search
@@ -62,6 +62,7 @@ export function ClientHome({ offline, models }: ClientHomeProps) {
|
||||
models={models}
|
||||
offline={offline}
|
||||
onConversationCreated={handleConversationCreated}
|
||||
currentConversationId={currentConversationId}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -15,12 +15,14 @@ interface ChatContainerProps {
|
||||
models: ModelInfo[];
|
||||
offline: boolean;
|
||||
onConversationCreated?: () => void;
|
||||
currentConversationId?: string | null;
|
||||
}
|
||||
|
||||
export function ChatContainer({
|
||||
models,
|
||||
offline,
|
||||
onConversationCreated,
|
||||
currentConversationId: propCurrentConversationId,
|
||||
}: ChatContainerProps) {
|
||||
const { prefs, ready, update } = useUserPrefs();
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
@@ -42,6 +44,17 @@ export function ChatContainer({
|
||||
}
|
||||
}, [ready, prefs.selectedModel]);
|
||||
|
||||
// Carregar mensagens quando uma conversa é selecionada
|
||||
useEffect(() => {
|
||||
if (propCurrentConversationId) {
|
||||
loadConversation(propCurrentConversationId);
|
||||
} else {
|
||||
// Limpar mensagens quando nenhuma conversa está selecionada
|
||||
setMessages([]);
|
||||
setConversationId(null);
|
||||
}
|
||||
}, [propCurrentConversationId]);
|
||||
|
||||
// Salvar modelo quando selecionado
|
||||
const handleModelChange = (modelName: string) => {
|
||||
setSelectedModel(modelName);
|
||||
|
||||
@@ -34,9 +34,9 @@ export function ChatMessage({
|
||||
: "bg-[var(--surface)] text-[var(--foreground)]"
|
||||
}`}
|
||||
>
|
||||
<div className="whitespace-pre-wrap break-words">
|
||||
<div className="break-words">
|
||||
{isUser ? (
|
||||
message.content
|
||||
<div className="whitespace-pre-wrap">{message.content}</div>
|
||||
) : (
|
||||
<MarkdownRenderer content={message.content} />
|
||||
)}
|
||||
|
||||
@@ -12,6 +12,12 @@ type MarkdownRendererProps = {
|
||||
};
|
||||
|
||||
export function MarkdownRenderer({ content }: MarkdownRendererProps) {
|
||||
// Normalizar quebras de linha e espaços
|
||||
const normalizedContent = content
|
||||
.replace(/\n{3,}/g, "\n\n") // Máximo 2 quebras consecutivas
|
||||
.replace(/[ \t]+$/gm, "") // Remover espaços no final das linhas
|
||||
.trim();
|
||||
|
||||
return (
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
@@ -29,7 +35,7 @@ export function MarkdownRenderer({ content }: MarkdownRendererProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative my-3 rounded-lg border border-[var(--border)] bg-[var(--background)]">
|
||||
<div className="relative my-4 rounded-lg border border-[var(--border)] bg-[var(--background)]">
|
||||
<button
|
||||
type="button"
|
||||
className="absolute right-2 top-2 rounded px-2 py-1 text-xs bg-[var(--surface)]/70 hover:bg-[var(--surface)] transition-colors"
|
||||
@@ -47,27 +53,45 @@ export function MarkdownRenderer({ content }: MarkdownRendererProps) {
|
||||
);
|
||||
},
|
||||
h1: ({ children }: any) => (
|
||||
<h1 className="text-xl font-bold mt-3 mb-1">{children}</h1>
|
||||
<h1 className="text-xl font-bold mt-4 mb-2 first:mt-0">{children}</h1>
|
||||
),
|
||||
h2: ({ children }: any) => (
|
||||
<h2 className="text-lg font-semibold mt-2 mb-1">{children}</h2>
|
||||
<h2 className="text-lg font-semibold mt-3 mb-2">{children}</h2>
|
||||
),
|
||||
h3: ({ children }: any) => (
|
||||
<h3 className="text-base font-semibold mt-2 mb-1">{children}</h3>
|
||||
<h3 className="text-base font-semibold mt-3 mb-1">{children}</h3>
|
||||
),
|
||||
h4: ({ children }: any) => (
|
||||
<h4 className="text-sm font-semibold mt-1 mb-1">{children}</h4>
|
||||
<h4 className="text-sm font-semibold mt-2 mb-1">{children}</h4>
|
||||
),
|
||||
h5: ({ children }: any) => (
|
||||
<h5 className="text-sm font-medium mt-1 mb-1">{children}</h5>
|
||||
<h5 className="text-sm font-medium mt-2 mb-1">{children}</h5>
|
||||
),
|
||||
h6: ({ children }: any) => (
|
||||
<h6 className="text-xs font-medium mt-1 mb-1">{children}</h6>
|
||||
<h6 className="text-xs font-medium mt-2 mb-1">{children}</h6>
|
||||
),
|
||||
p: ({ children }: any) => <p className="mb-1">{children}</p>,
|
||||
p: ({ children }: any) => (
|
||||
<p className="mb-3 leading-relaxed">{children}</p>
|
||||
),
|
||||
ul: ({ children }: any) => (
|
||||
<ul className="mb-3 ml-4 list-disc space-y-1">{children}</ul>
|
||||
),
|
||||
ol: ({ children }: any) => (
|
||||
<ol className="mb-3 ml-4 list-decimal space-y-1">{children}</ol>
|
||||
),
|
||||
li: ({ children }: any) => (
|
||||
<li className="leading-relaxed">{children}</li>
|
||||
),
|
||||
blockquote: ({ children }: any) => (
|
||||
<blockquote className="border-l-4 border-[var(--border)] pl-4 my-3 italic text-[var(--foreground)]/80">
|
||||
{children}
|
||||
</blockquote>
|
||||
),
|
||||
hr: () => <hr className="my-4 border-[var(--border)]" />,
|
||||
br: () => <br className="mb-1" />,
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
{normalizedContent}
|
||||
</ReactMarkdown>
|
||||
);
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3
-1
@@ -91,7 +91,9 @@ export async function ensureModelExists(
|
||||
} else if (chunk.status === "success") {
|
||||
console.log(`\n✅ Modelo ${modelName} baixado com sucesso!`);
|
||||
if (description) {
|
||||
console.log(`🎯 ${description.replace("será usado", "agora funciona")}`);
|
||||
console.log(
|
||||
`🎯 ${description.replace("será usado", "agora funciona")}`
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
+1
-1
@@ -131,7 +131,7 @@ export async function initializeEmbeddingModel(): Promise<void> {
|
||||
try {
|
||||
await ensureModelExists(
|
||||
"nomic-embed-text",
|
||||
"Este modelo será usado para busca vetorial das mensagens"
|
||||
"Este modelo será usado para busca vetorial das mensagens (274MB - 768 dimensões)"
|
||||
);
|
||||
console.log("✅ Modelo de embedding pronto!");
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
Reference in new issue
Block a user