diff --git a/docker-compose.yml b/docker-compose.yml index 25c4c0e..8d11a9f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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} diff --git a/webapp/prisma/schema.prisma b/webapp/prisma/schema.prisma index 859b51e..b0e4b94 100644 --- a/webapp/prisma/schema.prisma +++ b/webapp/prisma/schema.prisma @@ -1,5 +1,6 @@ generator client { provider = "prisma-client-js" + binaryTargets = ["native", "debian-openssl-3.0.x"] } datasource db { diff --git a/webapp/src/components/CADAiD.tsx b/webapp/src/components/CADAiD.tsx index a88a72f..565f022 100644 --- a/webapp/src/components/CADAiD.tsx +++ b/webapp/src/components/CADAiD.tsx @@ -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 { - 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; @@ -24,23 +26,33 @@ const CadaidPage: React.FC = () => { const [files, setFiles] = useState([]); const [results, setResults] = useState([]); const [errorMessage, setErrorMessage] = useState(null); + const [applicationId, setApplicationId] = useState(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) => { + const handleFileUpload = async ( + event: React.ChangeEvent, + ) => { 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; } @@ -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 } @@ -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, + ) => { + try { + setApplicationId(parseInt(event.target.value)); + } catch (error) { + setErrorMessage("Ugyldig byggesaks-id"); + console.error(error); + setApplicationId(null); + } }; return ( -
+
{/* Left Column */}
- - +

CADAiD

+ + Her kan du laste opp og verifisere plantegningene dine. + +
+ + +
+ + {isLoading && ( -
-
+
+
)} {errorMessage && (
{
{/* Right Column */} -
+
diff --git a/webapp/src/components/FileList.tsx b/webapp/src/components/FileList.tsx index adce75c..43ccf87 100644 --- a/webapp/src/components/FileList.tsx +++ b/webapp/src/components/FileList.tsx @@ -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[]; @@ -10,22 +10,21 @@ interface FileListProps { const FileList: React.FC = ({ files, onRemove, onUpload }) => { return ( -
-

CADAiD

- - Her kan du laste opp og verifisere plantegningene dine. - +
    {files.map((file) => ( -
  • +
  • {file.name} - +
  • ))}
diff --git a/webapp/src/components/ui/input.tsx b/webapp/src/components/ui/input.tsx new file mode 100644 index 0000000..5a1e6b6 --- /dev/null +++ b/webapp/src/components/ui/input.tsx @@ -0,0 +1,24 @@ +import * as React from "react"; + +import { cn } from "~/lib/utils"; + +export type InputProps = React.InputHTMLAttributes; + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ); + }, +); +Input.displayName = "Input"; + +export { Input }; diff --git a/webapp/src/server/api/root.ts b/webapp/src/server/api/root.ts index e594164..7dd7069 100644 --- a/webapp/src/server/api/root.ts +++ b/webapp/src/server/api/root.ts @@ -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. @@ -14,8 +15,8 @@ export const appRouter = createTRPCRouter({ model: modelRouter, application: applicationRouter, document: documentRouter, - response: modelErrorRouter, - modelErrors: modelErrorRouter, + response: responseRouter, + modelError: modelErrorRouter, arkivgpt: arkivGptRouter, });