diff --git a/.env b/.env index 900cd02..979e608 100644 --- a/.env +++ b/.env @@ -2,4 +2,5 @@ OPENAI_API_KEY=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 JWT_SECRET_KEY=6e9c124ba92e8814719dcb0f21200c8aa4d0f119a994ac5e06eb90a366c83ab2 JWT_TEST_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0In0.Emrs2D3BmMP4b3zMjw0fJTPeyMwWEBDbxx2vvaWguO0 GOOGLE_APPLICATION_CREDENTIALS=firebase-configs/storied-phalanx-349916.json -HEY_GEN_TOKEN=MjY4MDE0MjdjZmNhNDFmYTlhZGRkNmI3MGFlMzYwZDItMTY5NTExNzY3MA== \ No newline at end of file +HEY_GEN_TOKEN=MjY4MDE0MjdjZmNhNDFmYTlhZGRkNmI3MGFlMzYwZDItMTY5NTExNzY3MA== +GPT_ZERO_API_KEY=0195b9bb24c5439899f71230809c74af diff --git a/.gitignore b/.gitignore index b8f579b..e7f296a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ __pycache__ .idea .env -.DS_Store \ No newline at end of file +.DS_Store +/firebase-configs/test_firebase.json diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/ielts-be.iml b/.idea/ielts-be.iml index 7af039d..2b859b5 100644 --- a/.idea/ielts-be.iml +++ b/.idea/ielts-be.iml @@ -1,24 +1,14 @@ - - - + - + - - - - + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index d56657a..6601cfb 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,10 @@ - + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..35eb1dd 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app.py b/app.py index 88af278..07d324a 100644 --- a/app.py +++ b/app.py @@ -15,6 +15,7 @@ from helper.heygen_api import create_video, create_videos_and_save_to_db from helper.openai_interface import * from helper.question_templates import * from helper.speech_to_text_helper import * +from helper.gpt_zero import GPTZero from heygen.AvatarEnum import AvatarEnum load_dotenv() @@ -30,6 +31,8 @@ FIREBASE_BUCKET = os.getenv('FIREBASE_BUCKET') firebase_admin.initialize_app(cred) +gpt_zero = GPTZero(os.getenv('GPT_ZERO_API_KEY')) + thread_event = threading.Event() # Configure logging @@ -309,6 +312,9 @@ def grade_writing_task_1(): response["perfect_answer"] = get_perfect_answer(question, 150)["perfect_answer"] response["overall"] = fix_writing_overall(response["overall"], response["task_response"]) response['fixed_text'] = get_fixed_text(answer) + ai_detection = gpt_zero.run_detection(answer) + if ai_detection is not None: + response['ai_detection'] = ai_detection return response except Exception as e: return str(e) @@ -449,6 +455,9 @@ def grade_writing_task_2(): response["perfect_answer"] = get_perfect_answer(question, 250)["perfect_answer"] response["overall"] = fix_writing_overall(response["overall"], response["task_response"]) response['fixed_text'] = get_fixed_text(answer) + ai_detection = gpt_zero.run_detection(answer) + if ai_detection is not None: + response['ai_detection'] = ai_detection return response except Exception as e: return str(e) diff --git a/helper/gpt_zero.py b/helper/gpt_zero.py new file mode 100644 index 0000000..08c4f1a --- /dev/null +++ b/helper/gpt_zero.py @@ -0,0 +1,50 @@ +from logging import getLogger +from typing import Dict, Optional +import requests + + +class GPTZero: + _GPT_ZERO_ENDPOINT = 'https://api.gptzero.me/v2/predict/text' + + def __init__(self, gpt_zero_key: str): + self._logger = getLogger(__name__) + if gpt_zero_key is None: + self._logger.warning('GPT Zero key was not included! Skipping ai detection when grading.') + self._gpt_zero_key = gpt_zero_key + self._header = { + 'x-api-key': gpt_zero_key + } + + def run_detection(self, text: str): + if self._gpt_zero_key is None: + return None + data = { + 'document': text, + 'version': '', + 'multilingual': False + } + response = requests.post(self._GPT_ZERO_ENDPOINT, headers=self._header, json=data) + if response.status_code != 200: + self._logger.error(f'GPT\'s Zero Endpoint returned with {response.status_code}: {response.json()}') + return None + return self._parse_detection(response.json()) + + def _parse_detection(self, response: Dict) -> Optional[Dict]: + try: + text_scan = response["documents"][0] + filtered_sentences = [ + { + "sentence": item["sentence"], + "highlight_sentence_for_ai": item["highlight_sentence_for_ai"] + } + for item in text_scan["sentences"] + ] + return { + "class_probabilities": text_scan["class_probabilities"], + "confidence_category": text_scan["confidence_category"], + "predicted_class": text_scan["predicted_class"], + "sentences": filtered_sentences + } + except Exception as e: + self._logger.error(f'Failed to parse GPT\'s Zero response: {str(e)}') + return None diff --git a/helper/heygen_api.py b/helper/heygen_api.py index d0e2d8c..864794b 100644 --- a/helper/heygen_api.py +++ b/helper/heygen_api.py @@ -1,17 +1,19 @@ import os import random import time +from logging import getLogger import requests from dotenv import load_dotenv -import app from helper.constants import * from helper.firebase_helper import upload_file_firebase_get_url, save_to_db_with_id from heygen.AvatarEnum import AvatarEnum load_dotenv() +logger = getLogger(__name__) + # Get HeyGen token TOKEN = os.getenv("HEY_GEN_TOKEN") FIREBASE_BUCKET = os.getenv('FIREBASE_BUCKET') @@ -37,7 +39,7 @@ def create_videos_and_save_to_db(exercises, template, id): if found_exercises_1: exercise_1 = found_exercises_1[0] sp1_questions = [] - app.app.logger.info('Creating video for speaking part 1') + logger.info('Creating video for speaking part 1') for question in exercise_1["questions"]: sp1_result = create_video(question, avatar) if sp1_result is not None: @@ -51,7 +53,7 @@ def create_videos_and_save_to_db(exercises, template, id): } sp1_questions.append(video) else: - app.app.logger.error("Failed to create video for part 1 question: " + exercise_1["question"]) + logger.error("Failed to create video for part 1 question: " + exercise_1["question"]) template["exercises"][0]["prompts"] = sp1_questions template["exercises"][0]["first_title"] = exercise_1["first_topic"] template["exercises"][0]["second_title"] = exercise_1["second_topic"] @@ -62,7 +64,7 @@ def create_videos_and_save_to_db(exercises, template, id): # Check if any elements were found if found_exercises_2: exercise_2 = found_exercises_2[0] - app.app.logger.info('Creating video for speaking part 2') + logger.info('Creating video for speaking part 2') sp2_result = create_video(exercise_2["question"], avatar) if sp2_result is not None: sound_file_path = VIDEO_FILES_PATH + sp2_result @@ -76,7 +78,7 @@ def create_videos_and_save_to_db(exercises, template, id): template["exercises"][1]["video_url"] = sp2_video_url template["exercises"][1]["video_path"] = sp2_video_path else: - app.app.logger.error("Failed to create video for part 2 question: " + exercise_2["question"]) + logger.error("Failed to create video for part 2 question: " + exercise_2["question"]) # Speaking 3 # Using list comprehension to find the element with the desired value in the 'type' field @@ -85,7 +87,7 @@ def create_videos_and_save_to_db(exercises, template, id): if found_exercises_3: exercise_3 = found_exercises_3[0] sp3_questions = [] - app.app.logger.info('Creating videos for speaking part 3') + logger.info('Creating videos for speaking part 3') for question in exercise_3["questions"]: result = create_video(question, avatar) if result is not None: @@ -99,7 +101,7 @@ def create_videos_and_save_to_db(exercises, template, id): } sp3_questions.append(video) else: - app.app.logger.error("Failed to create video for part 3 question: " + question) + logger.error("Failed to create video for part 3 question: " + question) template["exercises"][2]["prompts"] = sp3_questions template["exercises"][2]["title"] = exercise_3["topic"] @@ -111,7 +113,7 @@ def create_videos_and_save_to_db(exercises, template, id): template["exercises"].pop(0) save_to_db_with_id("speaking", template, id) - app.app.logger.info('Saved speaking to DB with id ' + id + " : " + str(template)) + logger.info('Saved speaking to DB with id ' + id + " : " + str(template)) def create_video(text, avatar): @@ -132,8 +134,8 @@ def create_video(text, avatar): } } response = requests.post(create_video_url, headers=POST_HEADER, json=data) - app.app.logger.info(response.status_code) - app.app.logger.info(response.json()) + logger.info(response.status_code) + logger.info(response.json()) # GET TO CHECK STATUS AND GET VIDEO WHEN READY video_id = response.json()["data"]["video_id"] @@ -152,11 +154,11 @@ def create_video(text, avatar): error = response_data["data"]["error"] if status != "completed" and error is None: - app.app.logger.info(f"Status: {status}") + logger.info(f"Status: {status}") time.sleep(10) # Wait for 10 second before the next request - app.app.logger.info(response.status_code) - app.app.logger.info(response.json()) + logger.info(response.status_code) + logger.info(response.json()) # DOWNLOAD VIDEO download_url = response.json()['data']['video_url'] @@ -170,8 +172,8 @@ def create_video(text, avatar): output_path = os.path.join(output_directory, output_filename) with open(output_path, 'wb') as f: f.write(response.content) - app.app.logger.info(f"File '{output_filename}' downloaded successfully.") + logger.info(f"File '{output_filename}' downloaded successfully.") return output_filename else: - app.app.logger.error(f"Failed to download file. Status code: {response.status_code}") + logger.error(f"Failed to download file. Status code: {response.status_code}") return None