Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
52d2d41
create feedback endpoint
ryanrrogers Oct 2, 2023
a440de6
add error code handling
ryanrrogers Oct 2, 2023
fb488d6
cleanup URL patterns
ryanrrogers Oct 2, 2023
b2c82a1
add mock status codes to response
ryanrrogers Oct 2, 2023
4eb5f76
create upload attachment endpoint
ryanrrogers Oct 2, 2023
02b657c
add file attachment endpoint
ryanrrogers Oct 2, 2023
a3a83c1
add doc strings to jira feedback methods
ryanrrogers Oct 3, 2023
af4b9fb
Merge pull request #37 from CodeForPhilly/jira-feedback
taichan03 Oct 3, 2023
79a0865
added upload
kristo-baricevic Oct 4, 2023
09256a7
added icon and border styling
kristo-baricevic Oct 4, 2023
bb98aa0
Merge pull request #1 from kristo-baricevic/feedback-form2
kristo-baricevic Oct 4, 2023
7d56c33
start imports from dev server to monorepo. Decomissioning dev server.
ryanrrogers Oct 5, 2023
5af4e3f
update url schemas
ryanrrogers Oct 5, 2023
5f348cc
one more url schema change
ryanrrogers Oct 5, 2023
15f469a
fix some urls
ryanrrogers Oct 5, 2023
a435aa8
minor fixes to methods
ryanrrogers Oct 5, 2023
4c1d4d9
Merge pull request #40 from CodeForPhilly/dev-server-imports
taichan03 Oct 6, 2023
7127a70
basic css changes
kristo-baricevic Oct 8, 2023
8e0c437
Merge branch 'feedback-form'
kristo-baricevic Oct 8, 2023
6a2638d
added onSubmit logic
kristo-baricevic Oct 8, 2023
4108b16
small edits to form
kristo-baricevic Oct 9, 2023
b17f546
Merge remote-tracking branch 'upstream/main'
kristo-baricevic Oct 9, 2023
eaef9f0
edited Submit
kristo-baricevic Oct 10, 2023
93954ae
Merge branch 'feedback-form2'
kristo-baricevic Oct 10, 2023
a6b655d
added CORS into backend
kristo-baricevic Oct 10, 2023
bc535c7
formatting
kristo-baricevic Oct 10, 2023
78b53cc
getting 201
kristo-baricevic Oct 10, 2023
3f494d8
201 shown
kristo-baricevic Oct 10, 2023
dfb5bad
added comments
kristo-baricevic Oct 10, 2023
fc74d5d
removed debug log
kristo-baricevic Oct 10, 2023
c099978
feedback image returns 200
kristo-baricevic Oct 10, 2023
39c998b
added image preview
kristo-baricevic Oct 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ in order to launch a development container with interactive shell.
### Teardown project
1. Run
```make teardown-project```
in order to tear down any exisiting development containers.
in order to tear down any existing development containers.
20 changes: 20 additions & 0 deletions backend/balancer/controllers/chatgpt.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,26 @@
# XXX: remove csrf_exempt usage before production
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def chatgpt(request: str) -> JsonResponse:
"""
Takes a diagnosis and returns a table of the most commonly prescribed medications for that diagnosis.
"""
openai.api_key = os.environ.get("OPENAI_API_KEY")
data: dict[str, str] = json.loads(request.body)

if data is not None:
diagnosis: str = data["prompt"]
ai_response = openai.ChatCompletion.create(
model="gpt-4",
messages= [
{"role": "system", "content": f"Balancer is a powerful tool for selecting bipolar medication for patients. We are open-source and available for free use. Converstation: {diagnosis}."}
]
)
return JsonResponse({"message": ai_response})

return JsonResponse({"error": "Failed to retrieve results from ChatGPT."})


@csrf_exempt
def extract_text(request: str) -> JsonResponse:
Expand Down
194 changes: 194 additions & 0 deletions backend/balancer/controllers/jira.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
from django.http import JsonResponse
from django import forms
import requests
import json
import os

# XXX: remove csrf_exempt usage before production
from django.views.decorators.csrf import csrf_exempt


@csrf_exempt
def create_new_feedback(request: str) -> JsonResponse:
"""
Create a new feedback request in Jira Service Desk.
"""
token: str = os.environ.get("JIRA_API_KEY")

data: dict[str, str] = json.loads(request.body)
name: str = data["name"]
email: str = data["email"]
message: str = data["message"]

url: str = "https://balancer.atlassian.net/rest/servicedeskapi/request"

headers: dict[str, str] = {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": f"Basic {token}",
}

payload: str = json.dumps(
{
"requestFieldValues": {
"summary": f"{name} - Feedback",
"customfield_10061": email,
"description": message,
},
"requestTypeId": "33",
"serviceDeskId": "2",
}
)

response: requests.Response = requests.request(
"POST", url, data=payload, headers=headers
)
match response.status_code:
case 201:
response_body: dict[str, str] = json.loads(response.text)
issue_key: str = response_body["issueKey"]
return JsonResponse(
{"status": 201, "message": "Feedback submitted", "issueKey": issue_key}
)
case 400:
return JsonResponse({"status": 400, "message": "Invalid request"})
case 401 | 403:
return JsonResponse({"status": 401, "message": "Unauthorized request"})
case _:
return JsonResponse({"status": 500, "message": "Internal server error"})


class UploadAttachmentForm(forms.Form):
issueKey: forms.CharField = forms.CharField(max_length=50)
attachment = forms.FileField()


@csrf_exempt
def upload_servicedesk_attachment(request: str) -> JsonResponse:
"""
Upload file to temporary files in Jira Service Desk.
"""
token: str = os.environ.get("JIRA_API_KEY")
form: UploadAttachmentForm = UploadAttachmentForm(request.POST, request.FILES)
if form.is_valid():
url: str = f"https://balancer.atlassian.net/rest/servicedeskapi/servicedesk/2/attachTemporaryFile"

headers: dict[str, str] = {
"Accept": "application/json",
"X-Atlassian-Token": "no-check",
"Authorization": f"Basic {token}",
}

response: requests.Response = requests.request(
"POST", url, files={"file": request.FILES["attachment"]}, headers=headers
)
match response.status_code:
case 201:
response_body: dict[str, str] = json.loads(response.text)
temp_attachment_id: str = response_body["temporaryAttachments"][0][
"temporaryAttachmentId"
]
issue_key: str = request.POST.get("issueKey")
return JsonResponse(
{
"status": 200,
"message": "Attachment uploaded to temporary files",
"tempAttachmentId": temp_attachment_id,
"issueKey": issue_key,
}
)
case 400:
return JsonResponse({"status": 400, "message": "Invalid request"})
case 401 | 403:
return JsonResponse({"status": 401, "message": "Unauthorized request"})
case _:
return JsonResponse({"status": 500, "message": "Internal server error"})
return JsonResponse({"status": 400, "message": "Invalid form object"})


@csrf_exempt
def attach_feedback_attachment(request: str) -> JsonResponse:
"""
Attach a temporary file to a Jira Service Desk issue.
"""
token: str = os.environ.get("JIRA_API_KEY")
data: dict[str, str] = json.loads(request.body)
issue_key: str = data["issueKey"]
temp_attachment_id: str = data["tempAttachmentId"]
print(issue_key)

url: str = f"https://balancer.atlassian.net/rest/servicedeskapi/request/{issue_key}/attachment"

headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": f"Basic {token}",
}

payload: str = json.dumps(
{"public": True, "temporaryAttachmentIds": [temp_attachment_id]}
)

response: requests.Response = requests.request(
"POST", url, data=payload, headers=headers
)
match response.status_code:
case 201:
return JsonResponse({"status": 201, "message": f"File attached to issue {issue_key}"})
case 400:
return JsonResponse({"status": 400, "message": "Invalid request"})
case 401 | 403:
return JsonResponse({"status": 401, "message": "Unauthorized request"})
case _:
return JsonResponse({"status": 500, "message": "Internal server error"})


# These functions are used to get Jira data, but shouldn't be enabled in production.
# Keep these commented out unless in use.

# @csrf_exempt
# def get_jira_servicedesk_list(request: str) -> JsonResponse:
# """Get jira service desk list."""
# url = "https://balancer.atlassian.net/rest/servicedeskapi/servicedesk"
# headers = {
# "Accept": "application/json",
# "Authorization": "Basic ",
# }

# response = requests.request("GET", url, headers=headers)
# print(
# json.dumps(
# json.loads(response.text), sort_keys=True, indent=4, separators=(",", ": ")
# )
# )
# return JsonResponse({"message": "complete"})


# @csrf_exempt
# def get_jira_request_types(request: str) -> JsonResponse:
# url = "https://balancer.atlassian.net/rest/servicedeskapi/servicedesk/2/requesttype"

# headers = {"Accept": "application/json", "Authorization": "Basic "}

# response = requests.request("GET", url, headers=headers)

# print(
# json.dumps(
# json.loads(response.text), sort_keys=True, indent=4, separators=(",", ": ")
# )
# )
# return JsonResponse({"message": "complete"})

# @csrf_exempt
# def get_required_request_type_fields(request: str) -> JsonResponse:
# url = "https://balancer.atlassian.net/rest/servicedeskapi/servicedesk/2/requesttype/33/field"

# headers = {
# "Accept": "application/json",
# "Content-Type": "application/json",
# "Authorization": "Basic "
# }

# response = requests.request("GET", url, headers=headers)
# print(json.dumps(json.loads(response.text), sort_keys=True, indent=4, separators=(",", ": ")))
# return JsonResponse({"message": "complete"})
34 changes: 34 additions & 0 deletions backend/balancer/controllers/listDrugs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from django.http import JsonResponse
import os
import openai
import json

# XXX: remove csrf_exempt usage before production
from django.views.decorators.csrf import csrf_exempt


@csrf_exempt
def medication(request):
openai.api_key = os.environ.get("OPENAI_API_KEY")
data = json.loads(request.body)

if data is not None:
diagnosis = data["diagnosis"]
else:
return JsonResponse(
{"error": "Diagnosis not found. Request must include diagnosis."}
)

ai_response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{
"role": "system",
"content": f"Please provide the most commonly prescribed medications for {diagnosis}. I want only the drug names seperated by a comma and noting else"
}
],
)

drug_list = ai_response["choices"][0]["message"]["content"].split(",")
# Return the JSON response with drugs list
return JsonResponse({"drugs": drug_list})
47 changes: 47 additions & 0 deletions backend/balancer/controllers/risk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from django.http import JsonResponse
import os
import openai
import json

# XXX: remove csrf_exempt usage before production
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def medication(request):
openai.api_key = os.environ.get("OPENAI_API_KEY")
data = json.loads(request.body)

if data is not None:
diagnosis = data["diagnosis"]
else:
return JsonResponse({"error": "Diagnosis not found. Request must include diagnosis."})

ai_response = openai.ChatCompletion.create(
model = "gpt-3.5-turbo",
messages = [
{
"role": "system",
"content": f"You are to provide a concise list of 5 key benefits and 5 key risks for the medication suggested when taking it for Bipolar. Each point should be short, clear and be kept under 10 words. Begin the benefits section with !!!benefits!!! and the risks section with !!!risk!!!. Please provide this information for the medication: {diagnosis}."
}
]
)

content = ai_response['choices'][0]['message']['content']

if '!!!benefits!!!' not in content or '!!!risks!!!' not in content:
return JsonResponse({"error": "Unexpected format in the response content."})

# Split the content into benefits and risks sections
benefits_selection = content.split('!!!risks!!!')[0].replace('!!!benefits!!!', '').strip()
risks_selection = content.split('!!!risks!!!')[1].strip()

# Split the sections into individiual points
# Taking every second item as the benefits and risks are on alternate lines
benefits = benefits_selection.split('\n')
risks = risks_selection.split('\n')
content = content

return JsonResponse({
'benefits': benefits,
'risks': risks
})
17 changes: 17 additions & 0 deletions backend/balancer/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"corsheaders",
]

MIDDLEWARE = [
Expand All @@ -51,6 +52,7 @@
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"corsheaders.middleware.CorsMiddleware",
]

ROOT_URLCONF = "balancer.urls"
Expand Down Expand Up @@ -125,3 +127,18 @@
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

CORS_ALLOW_ALL_ORIGINS = False

CORS_ALLOWED_ORIGINS = [
"http://localhost:3000",
]

CORS_ALLOW_METHODS = [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE",
"OPTIONS",
]
12 changes: 9 additions & 3 deletions backend/balancer/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@
"""
from django.contrib import admin
from django.urls import path
from balancer.controllers import chatgpt
from balancer.controllers import chatgpt, jira, listDrugs, risk

urlpatterns = [
path("admin/", admin.site.urls),
path("extract_text/", chatgpt.extract_text, name="post_web_text"),
path("diagnosis/", chatgpt.diagnosis, name="post_diagnosis"),
path("api/chatgpt/extract_text/", chatgpt.extract_text, name="post_web_text"),
path("api/chatgpt/diagnosis/", chatgpt.diagnosis, name="post_diagnosis"),
path("api/chatgpt/chat", chatgpt.chatgpt, name="chatgpt"),
path("api/chatgpt/list_drugs", listDrugs.medication, name="listDrugs"),
path("api/chatgpt/risk", risk.medication, name="risk"),
path("api/jira/create_new_feedback/", jira.create_new_feedback, name="create_new_feedback"),
path("api/jira/upload_servicedesk_attachment/", jira.upload_servicedesk_attachment, name="upload_servicedesk_attachment"),
path("api/jira/attach_feedback_attachment/", jira.attach_feedback_attachment, name="attach_feedback_attachment"),
]
3 changes: 2 additions & 1 deletion backend/config/docker/examples/sample.env
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
OPENAI_API_KEY=
OPENAI_API_KEY=
JIRA_API_TOKEN=
Binary file added frontend/src/assets/upload-image-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 9 additions & 6 deletions frontend/src/pages/Feedback/Feedback.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import FeedbackForm from "./FeedbackForm.tsx";
import Welcome from "../../components/Welcome/Welcome.tsx";
import Layout from "../Layout/Layout";

function Feedback() {
return (
<Layout>
<div className="mt-20 flex w-full max-w-6xl flex-col items-center justify-center md:mt-28">
<Welcome
subHeader="Feedback"
descriptionText="Leave feedback for the Balancer Team."
/>
<FeedbackForm />
<div className="mt-10">
<h1 className="head_text"></h1>
<h2 className="desc">Feedback</h2>
<p className="mx-auto mt-5 hidden
max-w-[100%] text-center font-satoshi text-log text-gray-400 sm:text-x; md:block">
Leave Feedback for the Balancer Team.
</p>
<FeedbackForm />
</div>
</div>
</Layout>
);
Expand Down
Loading