Skip to content

Commit

Permalink
Merge pull request #65 from kartAI/feat/14-instance-of-planprat
Browse files Browse the repository at this point in the history
Feat/14 instance of planprat
  • Loading branch information
andreaslhjulstad authored Nov 8, 2024
2 parents 462bf9e + db3325c commit 1233c1e
Show file tree
Hide file tree
Showing 13 changed files with 214 additions and 37 deletions.
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
# NTNU KPRO AI Assistant


## Prerequisites

Before you start, make sure the following tools are installed on your system:

- **Git:** Version control system to clone the project repository [Download Git](https://git-scm.com/downloads)
- **Docker:** To containerize the application and ensure it runs consistently across different environments [Download Docker](https://www.docker.com/products/docker-desktop)

## Setup

Start by going into the `/webapp` folder, making a copy of the `.env.example` file and renaming it to `.env`. This file contains the environment variables that the application needs to run. Open the `.env` file and update the environment variables according to your local or production setup.

## Usage

To run the project, you can use the following commands:

```bash
docker compose up --build -d
docker compose --env-file ./webapp/.env up --build -d
```
This command will build the Docker images (if necessary) and run the containers in the background. You can access the clientside code at [http://localhost:3000](http://localhost:3000) and the API at [http://localhost:8000](http://localhost:8000).

This command will build the Docker images (if necessary) and run the containers in the background. You can access the clientside code at [http://localhost:3000](http://localhost:3000) and the API at [http://localhost:8000](http://localhost:8000).
The Swagger documentation for the API is available at [http://localhost:8000/docs](http://localhost:8000/docs).

To stop the containers, you can use the following command:

```bash
docker compose down
```

## Documentation
* [Developer Setup](/docs/manuals/developer_setup.md)
* [T3 Start Guide](/docs/manuals/t3_guide.md)

- [Developer Setup](/docs/manuals/developer_setup.md)
- [T3 Start Guide](/docs/manuals/t3_guide.md)
23 changes: 21 additions & 2 deletions backend/src/main.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
import logging
from fastapi import FastAPI, HTTPException, Query, UploadFile
from fastapi import status
from pydantic import BaseModel
from fastapi.middleware.cors import CORSMiddleware


from src.types import SummaryResponse, PlanPratRequest, PlanPratResponse
from src.services.reader import Reader
from src.services.readers.factory import create_reader
from src.services.agent import invoke_agent, invoke_plan_agent

app = FastAPI()

app = FastAPI(
title="KPRO API AI system",
description="retrives tekst from user and returns an answer based on building regulations.",
version="1.0.0",
)

ORIGINS = [
"http://localhost:3000",
"http://localhost:80",
"http://localhost",
]
# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=ORIGINS,
allow_credentials=True,
allow_methods=["POST", "GET", "OPTIONS"],
allow_headers=["*"],
)


logging.basicConfig(filename="summary-assistant.log", level=logging.INFO)
Expand Down
4 changes: 2 additions & 2 deletions backend/src/services/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ def invoke_plan_agent(query: str) -> str:
"""
)
print(f"context: {_retrieve_law_context()}", flush=True)
logger.info(f"context: {_retrieve_law_context()}")
generate = prompt | llm
response = generate.invoke({"query": query, "context": _retrieve_law_context()})
print(f"Response: {response}", flush=True)
logger.info(f"Response: {response}")
return response.content


Expand Down
4 changes: 4 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ services:
- "3000:3000"
image: t3-app
environment:
- ARKIVGPT_URL=http://host.docker.internal:80/api
- PLANPRAT_URL=http://host.docker.internal:8000/plan-prat/
- DATABASE_URL=${DATABASE_URL}
- NEXTAUTH_URL=${NEXTAUTH_URL}
extra_hosts:
- "host.docker.internal:host-gateway"
api:
platform: "linux/amd64"
build:
Expand Down
3 changes: 3 additions & 0 deletions webapp/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ DATABASE_URL="mysql://root:password@localhost:3306/ntnu-kpro-ai-assistant"
# NEXTAUTH_SECRET=""
NEXTAUTH_URL="http://localhost:3000"

# Planprat API
PLANPRAT_URL="http://localhost:8000/plan-prat"

# Next Auth Discord Provider
DISCORD_CLIENT_ID=""
DISCORD_CLIENT_SECRET=""
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions webapp/src/app/for-soknad/planprat/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { PlanPrat } from "~/components/PlanPrat";

export default function PlanpratPage() {
return(
<div className="lg:w-2/4 mx-auto">
<PlanPrat/>
</div>
)
}
1 change: 1 addition & 0 deletions webapp/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default async function Home() {
className="rounded-md xl:ml-20"
/>
</figure>

</main>
);
}
9 changes: 9 additions & 0 deletions webapp/src/app/under-soknad/planprat/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { PlanPrat } from "~/components/PlanPrat";

export default function PlanpratPage() {
return(
<div className="lg:w-2/4 mx-auto">
<PlanPrat/>
</div>
)
}
109 changes: 109 additions & 0 deletions webapp/src/components/PlanPrat.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"use client";
import { type ChangeEvent, useState, type KeyboardEvent } from "react";
import Image from "next/image";
import { api } from "~/trpc/react";

export function PlanPrat() {
const [error, setError] = useState("");
const [text, setText] = useState<string>("");
const [chatItems, setChatItems] = useState<
{ text: string; isUser: boolean }[]
>([]);
const utils = api.useUtils();

async function queryPlanprat(queryInput: string) {
try {
const response = await utils.planprat.fetchResponse.fetch({
query: queryInput,
});

return response;
} catch (error) {
console.error(error);
setError("Error: Failed to retrieve response.");
}
}

const handleTextChange = (e: ChangeEvent<HTMLTextAreaElement>): void => {
// Update state with textarea input
setText(e.target.value);
};

const handleSubmit = async (): Promise<void> => {
if (text.trim()) {
setChatItems((prevChatItems) => [
{ text: text, isUser: true },
...prevChatItems,
]); //question
const sendText = text;
setText("");
const response = await queryPlanprat(sendText);
if (!response) return;
setChatItems((prevChatItems) => [
{ text: response.answer, isUser: false },
...prevChatItems,
]);
}
};

const handleKeyDown = async (
e: KeyboardEvent<HTMLTextAreaElement>,
): Promise<void> => {
if (e.key === "Enter") {
e.preventDefault();
await handleSubmit();
}
};
return (
<div className="bg-white p-10">
<section className="rounded-lg shadow-lg">
<h1 className="w-full rounded-lg bg-kartAI-blue pb-6 pt-1 text-center text-white">
PlanPrat
</h1>
<div id="planprat-input-output" className="relative w-full p-2">
<ul
id="planprat-output "
className="flex h-96 w-full flex-col-reverse overflow-y-auto rounded-lg p-2 shadow-inner"
>
{chatItems.map((chatItem, index) => (
<li
data-cy="chat-output"
className={
chatItem.isUser
? "m-2 ml-6 self-end rounded-lg border-2 p-2 text-black shadow-lg"
: "m-2 mr-6 self-start rounded-lg bg-kartAI-blue p-2 text-white shadow-lg"
}
key={index}
>
{chatItem.text}
</li>
))}
</ul>
<textarea
id="planprat-input"
className="mt-2 w-full rounded-lg p-2 pr-12 text-black shadow-inner"
placeholder="Still meg et spørsmål ..."
value={text}
onChange={handleTextChange}
onKeyDown={handleKeyDown}
></textarea>
<button
type="submit"
id="planprat-input-button"
className="absolute bottom-8 right-4 rounded"
onClick={handleSubmit}
>
<Image
src="/Ikoner/Dark/SVG/Comment.svg"
alt="send"
height="30"
width="30"
className="rounded bg-kartAI-blue p-1 text-white"
></Image>
</button>
</div>
<p className="py-4 text-center text-red-500">{error}</p>
</section>
</div>
);
}
2 changes: 2 additions & 0 deletions webapp/src/server/api/root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createCallerFactory, createTRPCRouter } from "~/server/api/trpc";
import { applicationRouter } from "./routers/application";
import { documentRouter } from "./routers/document";
import { modelErrorRouter } from "./routers/model-error";
import { planpratRouter } from "./routers/planprat";
import { arkivGptRouter } from "./routers/arkivgpt";
import { responseRouter } from "./routers/response";

Expand All @@ -15,6 +16,7 @@ export const appRouter = createTRPCRouter({
model: modelRouter,
application: applicationRouter,
document: documentRouter,
planprat: planpratRouter,
response: responseRouter,
modelError: modelErrorRouter,
arkivgpt: arkivGptRouter,
Expand Down
31 changes: 3 additions & 28 deletions webapp/src/server/api/routers/arkivgpt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const mapToArkivGPTSummaryResponse = (
};
};

const ARKIVGPT_URL = process.env.ARKIVGPT_URL;
const ARKIVGPT_URL = process.env.ARKIVGPT_URL ?? "";

export const arkivGptRouter = createTRPCRouter({
fetchResponse: publicProcedure
Expand All @@ -44,25 +44,7 @@ export const arkivGptRouter = createTRPCRouter({
.query(async ({ input }) => {
const { gnr, bnr, snr } = input;

let responses: ArkivGPTSummaryResponse[] = [];

try {
responses = await fetchResponse(gnr, bnr, snr);
} catch (error) {
if (axios.isAxiosError(error)) {
console.error(
"Error fetching response for document:",
error.response?.data,
);
console.error(
"Failed to fetch ArkivGPT response",
error.response?.data,
);
}
console.error(
"An unknown error occurred while fetching response for document",
);
}
const responses = await fetchResponse(gnr, bnr, snr);

return responses;
}),
Expand Down Expand Up @@ -116,14 +98,7 @@ const fetchResponse = async (gnr: number, bnr: number, snr: number) => {
return mappedResponses;
} catch (error) {
if (axios.isAxiosError(error)) {
console.error(
"Error fetching response for document:",
error.response?.data,
);
throw new Error("Failed to fetch ArkivGPT response");
console.error("Failed to fetch ArkivGPT response:", error.message);
}
throw new Error(
"An unknown error occurred while fetching response for document",
);
}
};
39 changes: 39 additions & 0 deletions webapp/src/server/api/routers/planprat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { z } from "zod";
import { createTRPCRouter, publicProcedure } from "../trpc";
import axios, { AxiosError, type AxiosResponse } from "axios";

interface PlanpratResponse {
answer: string;
}

const PLANPRAT_URL = process.env.PLANPRAT_URL ?? "";

export const planpratRouter = createTRPCRouter({
fetchResponse: publicProcedure
.input(
z.object({
query: z.string(),
}),
)
.query(async ({ input }) => {
const { query } = input;

try {
const response: AxiosResponse<PlanpratResponse> = await axios.post(
PLANPRAT_URL,
{
query: query,
},
);

return response.data as unknown as PlanpratResponse;
} catch (error) {
if (error instanceof AxiosError) {
throw new Error(`Failed to retrieve response: ${error.message}`);
} else {
console.error(error);
throw new Error(`Unkown error`);
}
}
}),
});

0 comments on commit 1233c1e

Please sign in to comment.