feat: add database support, conversation sidebar, and improve chat streaming

- Update .gitignore to exclude database files, extensions, and .cursor directory
- Enhance chat streaming API with safe enqueue and close functions to prevent errors
- Integrate ConversationSidebar component into ClientHome for better UI
- Add dependencies like better-sqlite3 and esbuild packages for database functionality

This commit introduces database persistence for conversations, improves error handling in streaming responses, and adds a sidebar for managing chats, enhancing overall app robustness and user experience.
This commit is contained in:
Evandro Fonseca Junior committed 2025-10-28 14:14:07 -03:00
1 parent a3f8226fda
commit 0cc7a81319
34 files changed
+2370 -198

No files matched your search

+100
View File
@@ -0,0 +1,100 @@
import Database from "better-sqlite3";
export interface Embedding {
id: string;
message_id: string;
vector: Buffer;
}
export interface SearchResult {
id: string;
content: string;
role: string;
distance: number;
conversation_id: string;
}
export class EmbeddingRepository {
constructor(private db: Database.Database) {}
async create(messageId: string, vector: Float32Array): Promise<void> {
const id = crypto.randomUUID();
const blob = Buffer.from(vector.buffer);
this.db
.prepare(
`
INSERT INTO embeddings (id, message_id, vector)
VALUES (?, ?, ?)
`
)
.run(id, messageId, blob);
}
async searchSimilar(
queryVector: Float32Array,
limit: number = 5
): Promise<SearchResult[]> {
const queryBlob = Buffer.from(queryVector.buffer);
try {
// Tentar busca vetorial se extensão estiver disponível
return this.db
.prepare(
`
SELECT
m.id, m.content, m.role, m.conversation_id,
vector_distance(e.vector, ?) as distance
FROM embeddings e
JOIN messages m ON m.id = e.message_id
ORDER BY distance ASC
LIMIT ?
`
)
.all(queryBlob, limit) as SearchResult[];
} catch (error) {
console.warn(
"Busca vetorial não disponível, usando busca simples:",
error
);
// Fallback: busca simples por conteúdo
return this.db
.prepare(
`
SELECT
m.id, m.content, m.role, m.conversation_id,
0.0 as distance
FROM messages m
WHERE m.content LIKE ?
ORDER BY m.timestamp DESC
LIMIT ?
`
)
.all(`%${queryVector.toString()}%`, limit) as SearchResult[];
}
}
findByMessage(messageId: string): Embedding | null {
return this.db
.prepare(
`
SELECT * FROM embeddings WHERE message_id = ?
`
)
.get(messageId) as Embedding | null;
}
deleteByMessage(messageId: string): void {
this.db
.prepare("DELETE FROM embeddings WHERE message_id = ?")
.run(messageId);
}
count(): number {
const result = this.db
.prepare("SELECT COUNT(*) as count FROM embeddings")
.get() as { count: number };
return result.count;
}
}