Pular para o conteúdo principal

Biblioteca de Documentos

A Biblioteca de Documentos permite gerir e consultar documentos que alimentam o conhecimento do assistente IA.

Visão Geral

A biblioteca funciona como a base de conhecimento do APAH Assistant, permitindo:

  • Upload de documentos (PDF, Word, etc.)
  • Processamento automático com OCR e extração de texto
  • Indexação vetorial para busca semântica
  • Gestão de documentos (ativar/desativar, editar metadados)

Funcionalidades

📤 Upload de Documentos

Suporte para múltiplos formatos:

FormatoExtensãoSuportado
PDF.pdf
Word.docx
Texto.txt
Markdown.md

🔍 Processamento de Documentos

Após o upload, os documentos passam por um pipeline de processamento:

Upload → Extração → Chunking → Embedding → Indexação
│ │ │ │ │
│ │ │ │ └─ Guardar na BD
│ │ │ └─ Gerar vetores
│ │ └─ Dividir em chunks
│ └─ Extrair texto (PDF/DOCX)
└─ Validar ficheiro

📊 Gestão de Documentos

Interface administrativa para:

  • Listar todos os documentos
  • Ver detalhes e metadados
  • Ativar/desativar documentos
  • Eliminar documentos
  • Pesquisar por título/descrição

Arquitetura

Modelo de Dados

// Documento principal
export const documents = pgTable("documents", {
id: text("id").primaryKey(),
title: text("title").notNull(),
description: text("description"),
fileName: text("file_name").notNull(),
fileType: text("file_type").notNull(),
fileSize: integer("file_size").notNull(),
filePath: text("file_path").notNull(),
uploadedBy: text("uploaded_by").references(() => users.id),
isActive: boolean("is_active").default(true),
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow(),
});

// Chunks com embeddings
export const documentChunks = pgTable("document_chunks", {
id: text("id").primaryKey(),
documentId: text("document_id").references(() => documents.id),
content: text("content").notNull(),
embedding: vector("embedding", { dimensions: 1024 }),
metadata: jsonb("metadata"),
chunkIndex: integer("chunk_index").notNull(),
});

Pipeline de Processamento

1. Extração de Texto

import { PDFLoader } from "@langchain/community/document_loaders/fs/pdf";
import mammoth from "mammoth";

async function extractText(file: File): Promise<string> {
const fileType = file.type;

if (fileType === "application/pdf") {
const loader = new PDFLoader(file);
const docs = await loader.load();
return docs.map((d) => d.pageContent).join("\n");
}

if (fileType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document") {
const result = await mammoth.extractRawText({ buffer: await file.arrayBuffer() });
return result.value;
}

// Texto simples
return await file.text();
}

2. Chunking

import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";

const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
chunkOverlap: 200,
separators: ["\n\n", "\n", ". ", " ", ""],
});

const chunks = await splitter.splitText(documentText);

3. Geração de Embeddings

import { CohereEmbeddings } from "@langchain/cohere";

const embeddings = new CohereEmbeddings({
model: "embed-multilingual-v3.0",
});

const vectors = await embeddings.embedDocuments(chunks);

4. Indexação

for (let i = 0; i < chunks.length; i++) {
await db.insert(documentChunks).values({
id: generateId(),
documentId: document.id,
content: chunks[i],
embedding: vectors[i],
chunkIndex: i,
metadata: { page: calculatePage(i) },
});
}

API de Biblioteca

Endpoints REST

// Upload de documento
POST /api/library/upload
Content-Type: multipart/form-data

// Listar documentos
GET /api/library/documents

// Detalhes do documento
GET /api/library/documents/:id

// Atualizar documento
PATCH /api/library/documents/:id

// Eliminar documento
DELETE /api/library/documents/:id

tRPC Routers

export const libraryRouter = createTRPCRouter({
// Listar documentos
list: protectedProcedure
.input(
z.object({
search: z.string().optional(),
page: z.number().default(1),
limit: z.number().default(10),
})
)
.query(async ({ ctx, input }) => {
return ctx.db.query.documents.findMany({
where: input.search ? ilike(documents.title, `%${input.search}%`) : undefined,
limit: input.limit,
offset: (input.page - 1) * input.limit,
orderBy: desc(documents.createdAt),
});
}),

// Atualizar metadados
update: protectedProcedure
.input(
z.object({
id: z.string(),
title: z.string().optional(),
description: z.string().optional(),
isActive: z.boolean().optional(),
})
)
.mutation(async ({ ctx, input }) => {
const { id, ...data } = input;
return ctx.db
.update(documents)
.set({ ...data, updatedAt: new Date() })
.where(eq(documents.id, id));
}),

// Eliminar documento
delete: protectedProcedure.input(z.object({ id: z.string() })).mutation(async ({ ctx, input }) => {
// Eliminar chunks primeiro (cascade)
await ctx.db.delete(documentChunks).where(eq(documentChunks.documentId, input.id));

// Eliminar documento
return ctx.db.delete(documents).where(eq(documents.id, input.id));
}),
});

Interface de Utilizador

Lista de Documentos

┌─────────────────────────────────────────────────────────────────┐
│ Biblioteca de Documentos [+ Upload] │
├─────────────────────────────────────────────────────────────────┤
│ 🔍 Pesquisar documentos... │
├─────────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 📄 Diretrizes HAP 2024.pdf │ │
│ │ Diretrizes europeias para tratamento de HAP │ │
│ │ 📅 12 Nov 2024 • 2.4 MB • ✅ Ativo │ │
│ │ [Editar] [Desativar] [Eliminar] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 📄 Estudo Clinico HAP.pdf │ │
│ │ Resultados do estudo de fase 3... │ │
│ │ 📅 05 Out 2024 • 1.8 MB • ✅ Ativo │ │
│ │ [Editar] [Desativar] [Eliminar] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ◀ 1 2 3 4 5 ▶ │
└─────────────────────────────────────────────────────────────────┘

Diálogo de Upload

┌─────────────────────────────────────────────────────────────────┐
│ Upload de Documento [✕] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 📤 Arraste um ficheiro ou clique para │ │
│ │ selecionar │ │
│ │ │ │
│ │ Formatos: PDF, DOCX, TXT, MD │ │
│ │ Tamanho máximo: 10 MB │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Título: [_________________________________] │
│ │
│ Descrição: │
│ [ ] │
│ [ ] │
│ │
│ [Cancelar] [📤 Fazer Upload] │
└─────────────────────────────────────────────────────────────────┘

Busca Semântica

A biblioteca suporta busca semântica para encontrar documentos relevantes:

async function searchDocuments(query: string, limit = 5) {
// Gerar embedding da query
const queryEmbedding = await embeddings.embedQuery(query);

// Buscar chunks similares
const results = await db
.select({
chunk: documentChunks,
document: documents,
similarity: cosineDistance(documentChunks.embedding, queryEmbedding),
})
.from(documentChunks)
.innerJoin(documents, eq(documentChunks.documentId, documents.id))
.where(eq(documents.isActive, true))
.orderBy(cosineDistance(documentChunks.embedding, queryEmbedding))
.limit(limit);

return results;
}

Permissões

AçãoAdminEditorViewer
Ver documentos
Upload
Editar
Ativar/Desativar
Eliminar

Boas Práticas

  1. Qualidade dos Documentos - Use documentos de fontes confiáveis
  2. Metadados Completos - Preencha título e descrição adequadamente
  3. Tamanho Razoável - Documentos muito grandes devem ser divididos
  4. Revisão Regular - Desative documentos desatualizados
  5. Formatos Preferidos - PDF com texto (não imagens) funciona melhor