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}
|
models={models}
|
||||||
offline={offline}
|
offline={offline}
|
||||||
onConversationCreated={handleConversationCreated}
|
onConversationCreated={handleConversationCreated}
|
||||||
|
currentConversationId={currentConversationId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -15,12 +15,14 @@ interface ChatContainerProps {
|
|||||||
models: ModelInfo[];
|
models: ModelInfo[];
|
||||||
offline: boolean;
|
offline: boolean;
|
||||||
onConversationCreated?: () => void;
|
onConversationCreated?: () => void;
|
||||||
|
currentConversationId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ChatContainer({
|
export function ChatContainer({
|
||||||
models,
|
models,
|
||||||
offline,
|
offline,
|
||||||
onConversationCreated,
|
onConversationCreated,
|
||||||
|
currentConversationId: propCurrentConversationId,
|
||||||
}: ChatContainerProps) {
|
}: ChatContainerProps) {
|
||||||
const { prefs, ready, update } = useUserPrefs();
|
const { prefs, ready, update } = useUserPrefs();
|
||||||
const [messages, setMessages] = useState<Message[]>([]);
|
const [messages, setMessages] = useState<Message[]>([]);
|
||||||
@@ -42,6 +44,17 @@ export function ChatContainer({
|
|||||||
}
|
}
|
||||||
}, [ready, prefs.selectedModel]);
|
}, [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
|
// Salvar modelo quando selecionado
|
||||||
const handleModelChange = (modelName: string) => {
|
const handleModelChange = (modelName: string) => {
|
||||||
setSelectedModel(modelName);
|
setSelectedModel(modelName);
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ export function ChatMessage({
|
|||||||
: "bg-[var(--surface)] text-[var(--foreground)]"
|
: "bg-[var(--surface)] text-[var(--foreground)]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="whitespace-pre-wrap break-words">
|
<div className="break-words">
|
||||||
{isUser ? (
|
{isUser ? (
|
||||||
message.content
|
<div className="whitespace-pre-wrap">{message.content}</div>
|
||||||
) : (
|
) : (
|
||||||
<MarkdownRenderer content={message.content} />
|
<MarkdownRenderer content={message.content} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ type MarkdownRendererProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function MarkdownRenderer({ content }: 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 (
|
return (
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={[remarkGfm]}
|
remarkPlugins={[remarkGfm]}
|
||||||
@@ -29,7 +35,7 @@ export function MarkdownRenderer({ content }: MarkdownRendererProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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
|
<button
|
||||||
type="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"
|
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: ({ 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: ({ 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: ({ 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: ({ 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: ({ 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: ({ 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>
|
</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") {
|
} else if (chunk.status === "success") {
|
||||||
console.log(`\n✅ Modelo ${modelName} baixado com sucesso!`);
|
console.log(`\n✅ Modelo ${modelName} baixado com sucesso!`);
|
||||||
if (description) {
|
if (description) {
|
||||||
console.log(`🎯 ${description.replace("será usado", "agora funciona")}`);
|
console.log(
|
||||||
|
`🎯 ${description.replace("será usado", "agora funciona")}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -131,7 +131,7 @@ export async function initializeEmbeddingModel(): Promise<void> {
|
|||||||
try {
|
try {
|
||||||
await ensureModelExists(
|
await ensureModelExists(
|
||||||
"nomic-embed-text",
|
"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!");
|
console.log("✅ Modelo de embedding pronto!");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
Reference in new issue
Block a user