Skip to content

Commit

Permalink
Merge pull request #74 from kartAI/feat/72-cadaid-backend-integration
Browse files Browse the repository at this point in the history
Feat: CADAiD backend integration
  • Loading branch information
andreaslhjulstad authored Nov 6, 2024
2 parents 810430e + 8a2b5e6 commit 462bf9e
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 44 deletions.
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ services:
NEXT_PUBLIC_CLIENTVAR: "clientvar"
working_dir: /app
ports:
- "80:3000"
- "3000:3000"
image: t3-app
environment:
- DATABASE_URL=${DATABASE_URL}
Expand Down
1 change: 1 addition & 0 deletions webapp/prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "debian-openssl-3.0.x"]
}

datasource db {
Expand Down
148 changes: 123 additions & 25 deletions webapp/src/components/CADAiD.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
"use client";

import React, { useState } from 'react';
import FileList from './FileList';
import Results from './Results';
import FilePreview from './FilePreview';
import type { Detection } from '~/types/detection';
import React, { useState } from "react";
import FileList from "./FileList";
import Results from "./Results";
import FilePreview from "./FilePreview";
import type { Detection } from "~/types/detection";
import { api } from "~/trpc/react";
import { Input } from "./ui/input";

async function fetchDetection(formData: FormData): Promise<Detection[]> {
const response = await fetch('http://localhost:5001/detect/', {
method: 'POST',
const response = await fetch("http://localhost:5001/detect/", {
method: "POST",
body: formData,
})
});

if (!response.ok) {
throw new Error('Failed to upload files');
throw new Error("Failed to upload files");
}

return response.json() as Promise<Detection[]>;
Expand All @@ -24,23 +26,33 @@ const CadaidPage: React.FC = () => {
const [files, setFiles] = useState<File[]>([]);
const [results, setResults] = useState<Detection[]>([]);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [applicationId, setApplicationId] = useState<number | null>(null);
const createApplicationMutation =
api.application.createApplication.useMutation();
const createResponseMutation = api.response.createResponse.useMutation();
const createModelMutation = api.model.createModel.useMutation();
const utils = api.useUtils();

/**
* Handles file upload by sending selected files to the backend.
* @param event - The file input change event.
*/
const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
const handleFileUpload = async (
event: React.ChangeEvent<HTMLInputElement>,
) => {
if (event.target.files) {
const uploadedFiles = Array.from(event.target.files);
if (uploadedFiles.length === 0) {
setErrorMessage('No files selected');
setErrorMessage("No files selected");
return;
}

const ALLOWED_FILE_TYPES = ['application/pdf', 'image/jpeg', 'image/png'];
const invalidFiles = uploadedFiles.filter(file => !ALLOWED_FILE_TYPES.includes(file.type));
const ALLOWED_FILE_TYPES = ["application/pdf", "image/jpeg", "image/png"];
const invalidFiles = uploadedFiles.filter(
(file) => !ALLOWED_FILE_TYPES.includes(file.type),
);
if (invalidFiles.length > 0) {
setErrorMessage('Invalid file type.');
setErrorMessage("Invalid file type.");
return;
}

Expand All @@ -49,15 +61,64 @@ const CadaidPage: React.FC = () => {

const formData = new FormData();
uploadedFiles.forEach((file) => {
formData.append('uploaded_files', file);
formData.append("uploaded_files", file);
});

try {
const detections = await fetchDetection(formData);
setResults((prevResults) => [...prevResults, ...detections]);

// Add detections to the database
const modelData = await utils.model.getModelsByName.ensureData({
modelName: "CADAiD",
});

// Kind of awful, maybe change the getModelsByName to return a single model, since the names are unique, but have to change schema for that
let model = modelData.find((model) => model.modelName === "CADAiD");

if (!model) {
console.log("CREATING MODEL");
const newModel = await createModelMutation.mutateAsync({
modelName: "CADAiD",
});
model = newModel;
}

if (!modelData) {
// Model doesn't exist, create it
createModelMutation.mutate({ modelName: model.modelName });
}

// Use a local variable to hold the application ID from state
let applicationIdToUse = applicationId;

if (!applicationIdToUse) {
// User hasn't entered an application ID, create a new application
const application = await createApplicationMutation.mutateAsync({
status: "NEW",
address: "Adresseveien 123",
municipality: "Oslo",
submissionDate: new Date(),
});
setApplicationId(application.applicationID);
applicationIdToUse = application.applicationID;
}

detections.forEach((detection) => {
createResponseMutation
.mutateAsync({
response: JSON.stringify(detection),
modelID: model.modelID,
applicationID: applicationIdToUse,
})
.catch((error) => {
console.error(error);
setErrorMessage("En feil oppsto under lagring av responsen.");
});
});
} catch (error) {
console.error(error);
setErrorMessage('An error occurred while uploading files.');
setErrorMessage("En feil oppsto under opplasting av filer.");
} finally {
setIsLoading(false); // Ensure loading state is reset
}
Expand All @@ -69,25 +130,62 @@ const CadaidPage: React.FC = () => {
* @param fileName - The name of the file to remove.
*/
const handleFileRemove = (fileName: string) => {
setFiles((prevFiles) => prevFiles.filter(file => file.name !== fileName));
setResults((prevResults) => prevResults.filter(result => result.file_name !== fileName));
setFiles((prevFiles) => prevFiles.filter((file) => file.name !== fileName));
setResults((prevResults) =>
prevResults.filter((result) => result.file_name !== fileName),
);
};

const handleApplicationIdChanged = (
event: React.ChangeEvent<HTMLInputElement>,
) => {
try {
setApplicationId(parseInt(event.target.value));
} catch (error) {
setErrorMessage("Ugyldig byggesaks-id");
console.error(error);
setApplicationId(null);
}
};

return (
<div className="px-20 min-h-screen flex flex-col md:flex-row" data-cy="main-container">
<div
className="flex min-h-screen flex-col p-6 md:flex-row"
data-cy="main-container"
>
{/* Left Column */}
<div className="w-full md:w-1/3 md:pr-4" data-cy="left-column">
<FileList files={files} onRemove={handleFileRemove} onUpload={handleFileUpload}/>

<h1 className="mb-5 mt-10 text-left text-3xl font-bold">CADAiD</h1>
<span className="my-10 text-left text-xl">
Her kan du laste opp og verifisere plantegningene dine.
</span>
<div className="mt-4">
<label htmlFor=".application-id">
Skriv inn en byggesaks-id (la den være tom for å lage en ny
byggesak):
</label>
<Input
className="mt-2"
id="application-id"
onChange={handleApplicationIdChanged}
placeholder="Byggesaks-id"
/>
</div>
<FileList
files={files}
onRemove={handleFileRemove}
onUpload={handleFileUpload}
/>

{isLoading && (
<div className="mb-4 flex justify-center items-center">
<div className="animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-blue-500"></div>
<div className="mb-4 flex items-center justify-center">
<div className="h-10 w-10 animate-spin rounded-full border-b-2 border-t-2 border-blue-500"></div>
</div>
)}

{errorMessage && (
<div
className="mb-4 p-2 text-red-700 bg-red-100 rounded"
className="mb-4 rounded bg-red-100 p-2 text-red-700"
role="alert"
aria-live="assertive"
data-cy="submission-validation"
Expand All @@ -100,7 +198,7 @@ const CadaidPage: React.FC = () => {
</div>

{/* Right Column */}
<div className="w-full md:w-2/3 pt-10" data-cy="right-column">
<div className="w-full pt-10 md:w-2/3" data-cy="right-column">
<FilePreview files={files} />
</div>
</div>
Expand Down
31 changes: 15 additions & 16 deletions webapp/src/components/FileList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { FaTrash } from 'react-icons/fa';
import UploadButton from './UploadButton';
import React from "react";
import { FaTrash } from "react-icons/fa";
import UploadButton from "./UploadButton";

interface FileListProps {
files: File[];
Expand All @@ -10,22 +10,21 @@ interface FileListProps {

const FileList: React.FC<FileListProps> = ({ files, onRemove, onUpload }) => {
return (
<div className="mb-6 bg-white rounded-lg">
<h1 className="mt-10 text-left text-3xl font-bold mb-5">CADAiD</h1>
<span className="my-10 text-left text-xl">
Her kan du laste opp og verifisere plantegningene dine.
</span>
<div className="mb-6 rounded-lg bg-white">
<ul className="mt-10" data-cy="file-list">
{files.map((file) => (
<li key={file.name} className="flex items-center justify-between p-3 border rounded-lg">
<li
key={file.name}
className="flex items-center justify-between rounded-lg border p-3"
>
<span className="font-medium">{file.name}</span>
<button
className="cursor-pointer hover:text-red-700"
onClick={() => onRemove(file.name)}
aria-label={`Remove file ${file.name}`}
>
<FaTrash />
</button>
<button
className="cursor-pointer hover:text-red-700"
onClick={() => onRemove(file.name)}
aria-label={`Remove file ${file.name}`}
>
<FaTrash />
</button>
</li>
))}
</ul>
Expand Down
24 changes: 24 additions & 0 deletions webapp/src/components/ui/input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as React from "react";

import { cn } from "~/lib/utils";

export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;

const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
ref={ref}
{...props}
/>
);
},
);
Input.displayName = "Input";

export { Input };
5 changes: 3 additions & 2 deletions webapp/src/server/api/root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { applicationRouter } from "./routers/application";
import { documentRouter } from "./routers/document";
import { modelErrorRouter } from "./routers/model-error";
import { arkivGptRouter } from "./routers/arkivgpt";
import { responseRouter } from "./routers/response";

/**
* This is the primary router for your server.
Expand All @@ -14,8 +15,8 @@ export const appRouter = createTRPCRouter({
model: modelRouter,
application: applicationRouter,
document: documentRouter,
response: modelErrorRouter,
modelErrors: modelErrorRouter,
response: responseRouter,
modelError: modelErrorRouter,
arkivgpt: arkivGptRouter,
});

Expand Down

0 comments on commit 462bf9e

Please sign in to comment.