Skip to content

Commit

Permalink
Merge pull request #78 from olasunkanmi-SE/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
olasunkanmi-SE authored May 21, 2024
2 parents 4382efd + 3917bd8 commit 2ae761a
Show file tree
Hide file tree
Showing 15 changed files with 217 additions and 134 deletions.
10 changes: 6 additions & 4 deletions api/controllers/embed.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,21 @@ export class EmbeddingController {
}
const file = req.file;
const { buffer } = file;
const embeddingHandler: CreateDocumentEmbeddingHandler = new CreateDocumentEmbeddingHandler(buffer);
try {
const { title, documentType, domain } = documentRequestSchema.parse(req.body);
const metaData = JSON.parse(req.body.other);
const embeddingHandler: CreateDocumentEmbeddingHandler = new CreateDocumentEmbeddingHandler(buffer);
const { title, documentTypeId, domainId } = documentRequestSchema.parse(metaData);
const result = await embeddingHandler.handle({
title,
documentType,
domain,
documentTypeId,
domainId,
});
if (!result?.isSuccess) {
return res.json(Result.fail("unable to create embeddings", 400));
}
return res.json(result);
} catch (error) {
next(error);
generateErrorResponse(error, res, next);
}
}
Expand Down
4 changes: 2 additions & 2 deletions api/handlers/create-document-embed.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export class CreateDocumentEmbeddingHandler implements IRequestHandler<ICreateEm
async handle(request: ICreateEmbeddingRequestDTO): Promise<Result<boolean>> {
const embeddingService: EmbeddingService = new EmbeddingService(this.apiKey, this.pdf);
try {
const { title, documentType, domain } = request;
const result = await embeddingService.createDocumentsEmbeddings(title, documentType, domain);
const { title, documentTypeId, domainId } = request;
const result = await embeddingService.createDocumentsEmbeddings(title, documentTypeId, domainId);
if (!result) {
throw new HttpException(HTTP_RESPONSE_CODE.BAD_REQUEST, "An error occured, could not create embeddings");
}
Expand Down
6 changes: 1 addition & 5 deletions api/interfaces/embedding-service.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,7 @@ export interface IEmbeddingService {
}>;
cosineSimilarity(vecA: number[], vecB: number[]): number;
euclideanDistance(vecA: number[], vecB: number[]): number;
createDocumentsEmbeddings(
title: string,
documentType: DocumentTypeEnum,
domain: DomainEnum
): Promise<Result<boolean>>;
createDocumentsEmbeddings(title: string, documentType: number, domain: number): Promise<Result<boolean>>;
getQueryMatches(
query: string,
matchCount: number,
Expand Down
6 changes: 3 additions & 3 deletions api/lib/validation-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { z } from "zod";
import { DocumentTypeEnum, DomainEnum } from "./constants";

const title = z.string();
const documentType = z.nativeEnum(DocumentTypeEnum);
const domain = z.nativeEnum(DomainEnum);
export const documentRequestSchema = z.object({ title, documentType, domain });
const documentTypeId = z.number();
const domainId = z.number();
export const documentRequestSchema = z.object({ title, documentTypeId, domainId });

const name = z.nativeEnum(DomainEnum);
export const domainRequestSchema = z.object({ name });
Expand Down
14 changes: 9 additions & 5 deletions api/repositories/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@ import { DefaultArgs } from "@prisma/client/runtime/library";

export class Database {
private static instance: Database;
protected prisma: PrismaClient<
Prisma.PrismaClientOptions,
{ log: "info" },
DefaultArgs
>;
protected prisma: PrismaClient<Prisma.PrismaClientOptions, { log: "info" }, DefaultArgs>;
constructor() {
this.prisma = new PrismaClient();
this.createIvfflatIndex();
Expand Down Expand Up @@ -45,4 +41,12 @@ export class Database {
}
return Database.instance;
}

static getPrisma(): PrismaClient<Prisma.PrismaClientOptions, { log: "info" }, DefaultArgs> {
try {
return this.getInstance().prisma;
} catch (error) {
throw Error(error);
}
}
}
1 change: 0 additions & 1 deletion api/repositories/document-type.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { DocumentTypeEnum, HTTP_RESPONSE_CODE } from "../lib/constants";
import { HttpException } from "../exceptions/exception";

export class DocumentTypeRepository extends Database {
instance: DocumentTypeRepository;
constructor() {
super();
}
Expand Down
4 changes: 2 additions & 2 deletions api/repositories/dtos/dtos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export interface ICreateEmbeddingDTO {

export interface ICreateEmbeddingRequestDTO {
title: string;
documentType: DocumentTypeEnum;
domain: DomainEnum;
documentTypeId: number;
domainId: number;
}

export interface ICreateDomainRequestDTO {
Expand Down
1 change: 0 additions & 1 deletion api/repositories/embedding.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ export class EmbeddingRepository extends Database {
matchThreshold: number,
documentId: number
): Promise<IQueryMatch[]> {
console.log({ documentId });
//change text to document_embedding
//check how to select textembedding from DB
const matches = await this.prisma.$queryRaw`
Expand Down
18 changes: 5 additions & 13 deletions api/services/embed.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { DocumentTypeService } from "./document-type.service";
import { DocumentService } from "./document.service";
import { DomainService } from "./domain.service";
import { oneLine } from "common-tags";
import { Database } from "../repositories/database";

/**The `role` parameter in the `ContentPart` object is used to specify the role of the text content in relation to the task being performed.
* the following roles are commonly used:
Expand Down Expand Up @@ -154,20 +155,11 @@ export class EmbeddingService extends GenerativeAIService implements IEmbeddingS
* @returns {Promise<boolean>} - A promise that resolves to true if the document and embeddings are created successfully, false otherwise.
* @throws {Error} - If the document type or domain doesn't exist, or if unable to create document embeddings.
*/
async createDocumentsEmbeddings(
title: string,
documentType: DocumentTypeEnum,
domain: DomainEnum
): Promise<Result<boolean>> {
//Check why there are errors in this trnascation
//put this in a prisma transaction
async createDocumentsEmbeddings(title: string, documentTypeId: number, domainId: number): Promise<Result<boolean>> {
try {
const documentRepository: DocumentRepository = new DocumentRepository();
const domainService: DomainService = new DomainService();
const documentTypeService: DocumentTypeService = new DocumentTypeService();
const docType: IDocumentTypeModel | undefined = await documentTypeService.getDocumentType(documentType);
const documentTypeId: number = docType.id;

const docDomain: IDomainModel | undefined = await domainService.getDomain(domain);
const domainId: number = docDomain.id;
const document: IDocumentModel = await documentRepository.create(title);
let documentId: number;

Expand All @@ -190,6 +182,7 @@ export class EmbeddingService extends GenerativeAIService implements IEmbeddingS
}
} catch (error) {
console.error(error);
return Result.fail("Failed to create document embeddings", 500);
}
}

Expand Down Expand Up @@ -232,7 +225,6 @@ export class EmbeddingService extends GenerativeAIService implements IEmbeddingS
const result: GenerateContentResult = await aiModel.generateContent(prompt);
const response: EnhancedGenerateContentResponse = result.response;
const text: string = response.text();
console.log(text);
return text;
}

Expand Down
9 changes: 3 additions & 6 deletions presentation/src/components/ChatForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import { Button, Card, Container, Form, ListGroup, Row, Stack } from "react-boot
import useAxiosPrivate from "../hooks/useAxiosPrivate";
import { IDataItem } from "../interfaces/document.interface";
import { FileUploader } from "./DragAndDrop";
import Books from "./DropDown";
import { DropDown } from "./DropDown";
import NavBar from "./NavBar";
import Example from "./Modal";

interface IHistory {
role: string;
Expand All @@ -24,6 +23,7 @@ export function Thread() {

const handleBookSelect = (bookData: IDataItem) => {
setSelectedBook(bookData);
console.log(selectedBook);
};

const formAction = async () => {
Expand Down Expand Up @@ -86,7 +86,7 @@ export function Thread() {
<div className="p-2"></div>
<div className="p-2 ms-auto">
<div>
<Books onDataItemSelect={handleBookSelect} model="document" />
<DropDown onDataItemSelect={handleBookSelect} model="document" />
</div>
</div>
<div className="p-2">
Expand Down Expand Up @@ -180,9 +180,6 @@ export function Thread() {
</div>
</div>
<div style={{ width: "18rem" }} className="col-lg-3 col-md-4 col-sm-6">
<div>
<Example />
</div>
<div style={{ marginTop: "10px" }}>
<Card style={{ backgroundColor: "#000", borderColor: "#fff", color: "#fff" }}>
<Card.Body>
Expand Down
70 changes: 31 additions & 39 deletions presentation/src/components/DragAndDrop.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,33 @@
import React from "react";
import { useDropzone, DropzoneOptions } from "react-dropzone";
import useAxiosPrivate from "../hooks/useAxiosPrivate";
import React, { useState } from "react";
import { DropzoneOptions, useDropzone } from "react-dropzone";
import { AppModal } from "./Modal";

export const FileUploader: React.FC = () => {
const axiosPrivate = useAxiosPrivate();

const handleFileUpload = async (file: File) => {
try {
const formData = createFormData(file);
const response = await uploadFileToServer(formData);
console.log("File uploaded successfully:", response.data.message);
} catch (error) {
console.error("Error uploading file:", error);
}
const [showModal, setShowModal] = useState<boolean>(false);

const [pdf, setPdf] = useState<File>();

const handleShowModal = () => {
setShowModal(true);
};

const onHideModal = () => {
setShowModal(false);
};

const onDrop = (acceptedFiles: File[]) => {
const onDrop = async (acceptedFiles: File[]) => {
if (acceptedFiles.length > 0) {
const file = acceptedFiles[0];
if (file.type !== "application/pdf") {
throw new Error("Only PDF files are allowed");
if (file) {
if (file.type !== "application/pdf") {
throw new Error("Only PDF files are allowed");
}
setPdf(file);
handleShowModal();
}
console.log("File uploaded:", file);
handleFileUpload(file);
}
};

const createFormData = (file: File) => {
const formData = new FormData();
formData.append("pdf", file);
return formData;
};

const uploadFileToServer = async (formData: FormData) => {
const url = "/embed/documents";
const headers = { "Content-Type": "multipart/form-data" };
const response = await axiosPrivate.post(url, formData, { headers });
return response;
};

const options: DropzoneOptions = {
onDrop,
maxSize: 5 * 1024 * 1024,
Expand All @@ -48,13 +37,16 @@ export const FileUploader: React.FC = () => {
const { getRootProps, getInputProps, isDragActive } = useDropzone(options);

return (
<div {...getRootProps({ className: `dropzone ${isDragActive ? "active" : ""}` })}>
<input {...getInputProps()} />
{isDragActive ? (
<p>Drop your PDF document here...</p>
) : (
<p>Drag and drop a PDF doc here, or click to select a PDF doc</p>
)}
</div>
<>
<AppModal show={showModal} onHide={onHideModal} pdfData={pdf} />
<div {...getRootProps({ className: `dropzone ${isDragActive ? "active" : ""}` })}>
<input {...getInputProps()} />
{isDragActive ? (
<p>Drop your PDF document here...</p>
) : (
<p>Drag and drop a PDF doc here, or click to select a PDF doc</p>
)}
</div>
</>
);
};
35 changes: 23 additions & 12 deletions presentation/src/components/DropDown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@ import { useEffect, useState } from "react";
import Dropdown from "react-bootstrap/Dropdown";
import useAxiosPrivate from "../hooks/useAxiosPrivate";
import { IDataItem } from "../interfaces/document.interface";
import { getLocalStorageData, setLocalStorageData } from "../utils";
import { capitalizeFirstLetter, getLocalStorageData, setLocalStorageData } from "../utils";
import { MODEL_URLS, MODELS } from "../constants";

interface IBookProps {
onDataItemSelect: (data: IDataItem) => void;
model: string;
}

function Books({ onDataItemSelect, model }: Readonly<IBookProps>) {
export const DropDown = ({ onDataItemSelect, model }: Readonly<IBookProps>) => {
const axiosPrivate = useAxiosPrivate();
const [selectedDataItems, setSelectedDataItems] = useState<string>("Select Book");
const prompt = model === MODELS.document ? "Select Book" : "Select";
const [selectedDataItems, setSelectedDataItems] = useState<string>(prompt);
const [dataItems, setDataItems] = useState([]);

useEffect(() => {
Expand All @@ -33,6 +34,7 @@ function Books({ onDataItemSelect, model }: Readonly<IBookProps>) {
fetchData();
}, []);

// Todo To truly decouple this drop down, pass in the modelUrls as props
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const getDataItems = async (model: string): Promise<any> => {
let url = "";
Expand All @@ -55,7 +57,12 @@ function Books({ onDataItemSelect, model }: Readonly<IBookProps>) {
const handleSelect = async (eventKey: string | null) => {
if (eventKey) {
setSelectedDataItems(eventKey);
const selectedItemData = dataItems.find((item: { title: string }) => item.title === eventKey);
let selectedItemData;
if (model === MODELS.document) {
selectedItemData = dataItems.find((item: { title: string }) => item.title === eventKey);
} else {
selectedItemData = dataItems.find((item: { name: string }) => item.name === eventKey);
}
if (selectedItemData) {
onDataItemSelect(selectedItemData);
}
Expand All @@ -68,17 +75,21 @@ function Books({ onDataItemSelect, model }: Readonly<IBookProps>) {
{selectedDataItems}
</Dropdown.Toggle>
<Dropdown.Menu>
{dataItems?.map((document: { title: string; id: number }) => (
<Dropdown.Item eventKey={document.title} key={document.id}>
{document.title}
</Dropdown.Item>
))}
{dataItems?.map((document: { title: string; name: string; id: number }) =>
model === MODELS.document ? (
<Dropdown.Item eventKey={document.title} key={document.id}>
{capitalizeFirstLetter(document.title.toLocaleUpperCase())}
</Dropdown.Item>
) : (
<Dropdown.Item eventKey={document.name} key={document.id}>
{capitalizeFirstLetter(document.name)}
</Dropdown.Item>
)
)}
</Dropdown.Menu>
</Dropdown>
);
} catch (error) {
console.error(error);
}
}

export default Books;
};
Loading

0 comments on commit 2ae761a

Please sign in to comment.