import os import requests import random import logging import json try: from PyPDF2 import PdfReader except ImportError: # Fallback for Docker environment from pypdf import PdfReader # Set up logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Check for environment variable or use a default value for testing def get_api_key(): api_key = os.environ.get('DEEPSEEK_API_KEY') if not api_key: logger.warning("No DEEPSEEK_API_KEY environment variable found. Using mock responses.") return api_key # Mock evaluation result for testing def get_mock_evaluation(story_excerpt): # Use a short excerpt of the story in the evaluation to make it look personalized story_start = story_excerpt[:100].replace("\n", " ").strip() if len(story_start) > 50: story_start = story_start[:50] + "..." # Generate random scores but keep them reasonable unity_score = random.randint(6, 9) characters_score = random.randint(6, 9) decisive_moment_score = random.randint(7, 10) language_score = random.randint(5, 9) time_place_score = random.randint(6, 9) plot_score = random.randint(7, 10) ending_score = random.randint(6, 10) message_score = random.randint(6, 9) total_score = unity_score + characters_score + decisive_moment_score + language_score + \ time_place_score + plot_score + ending_score + message_score # Mock evaluation text return { "evaluation": f"""📋 التقييم: وحدة الحدث: {unity_score}/10 القصة تدور حول حدث رئيسي بشكل جيد، وتحافظ على تماسك الأحداث. الشخصيات المحدودة والمعرّفة: {characters_score}/10 الشخصيات محددة بوضوح ومتميزة، مع تطوير مناسب للشخصيات الرئيسية. التركيز على لحظة حاسمة: {decisive_moment_score}/10 القصة تبني بمهارة نحو لحظة التحول الرئيسية التي تغير مسار الأحداث. الإيجاز واقتصاد اللغة: {language_score}/10 اللغة موجزة ومعبرة، تنقل المعاني بفعالية دون إطناب غير ضروري. وحدة الزمان والمكان: {time_place_score}/10 هناك استخدام متناسق للإطار الزماني والمكاني، مما يعزز تماسك القصة. حبكة جيدة البناء: {plot_score}/10 الحبكة متطورة بشكل منطقي ومتماسك، مع تسلسل واضح للأحداث. نهاية مؤثرة: {ending_score}/10 النهاية تترك انطباعًا قويًا وتوفر إغلاقًا مناسبًا للأحداث. رسالة أو موضوع واضح: {message_score}/10 تُظهر القصة رسالة واضحة تتطور بشكل طبيعي من خلال الأحداث. النتيجة النهائية: {total_score}/80 {"هذا عمل أدبي متميز يتسم بقوة البناء والتماسك. القصة تحقق توازنًا جيدًا بين تطوير الشخصيات وتقدم الحبكة." if total_score > 65 else "رغم وجود عناصر قوية في القصة، هناك مجال للتحسين في بعض الجوانب. ننصح بالتركيز على تعزيز تماسك الأحداث وتطوير الشخصيات بشكل أعمق."} ملاحظة: بداية القصة "{story_start}" تظهر إمكانات جيدة وتجذب اهتمام القارئ.""", "fixed_story": None } def review_story(pdf_path): """ Review a story from a PDF file """ try: # Load text from PDF text = "" try: with open(pdf_path, "rb") as f: reader = PdfReader(f) text = "\n".join([p.extract_text() for p in reader.pages if p.extract_text()]) text = " ".join(text.split()) except Exception as e: logger.error(f"PDF loading failed: {e}") return {"evaluation": f"Error loading PDF: {e}", "fixed_story": None} # Get evaluation api_key = get_api_key() if not api_key: # Return mock evaluation logger.info("Using mock evaluation for PDF") return get_mock_evaluation(text) # Use API for evaluation return call_api_for_evaluation(text) except Exception as e: logger.error(f"Error in review_story: {e}") return {"evaluation": f"Error processing story: {e}", "fixed_story": None} def review_story_text(story_text): """ Review a story provided as text directly """ try: # Get evaluation api_key = get_api_key() if not api_key: # Return mock evaluation logger.info("Using mock evaluation for text") return get_mock_evaluation(story_text) # Use API for evaluation return call_api_for_evaluation(story_text) except Exception as e: logger.error(f"Error in review_story_text: {e}") return {"evaluation": f"Error processing story text: {e}", "fixed_story": None} def call_api_for_evaluation(story): """ Call the DeepSeek API for story evaluation """ api_key = get_api_key() if not api_key: return get_mock_evaluation(story) evaluation_prompt = f""" You are a professional literary critic specializing in the art of the short story. Your task is to evaluate the following story according to 8 essential criteria used in literary criticism. You must write the full evaluation in Modern Standard Arabic, using a clear and organized style. 🔹 For each criterion: Give a score out of 10. Write a brief explanation (one or two lines) that justifies the score. 🔹 At the end of the evaluation: Add up the scores to get a final result out of 80. If the score is above 65: Praise the story as a successful and well-crafted literary work. If the score is 65 or lower: Provide constructive criticism that highlights the main weaknesses and suggests how to improve them. Start the evaluation with a title that includes an emoji, such as: 📋 التقييم: 🔹 The evaluation criteria are: 1. Unity of event: Does the story revolve around a single main incident or situation? 2. Limited and defined characters: Does the story include a small number of clear and distinctive characters? 3. Focus on a decisive moment: Does the story highlight a turning point or critical decision? 4. Conciseness and economy of language: Is the language focused and free of unnecessary details? 5. Unity of time and place: Does the story take place in a specific time and setting? 6. Well-structured plot: Is there a clear logical sequence (beginning, middle, end)? 7. Impactful ending: Does the ending leave an emotional or intellectual impact? 8. Clear message or theme: Does the story convey a specific idea or feeling clearly? Evaluate the following story based on these criteria: {story} """ url = "https://api.deepseek.com/v1/chat/completions" headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } payload_eval = { "model": "deepseek-chat", "messages": [ {"role": "system", "content": "You are a formal short story evaluator."}, {"role": "user", "content": evaluation_prompt.strip()} ], "temperature": random.uniform(0.9, 1.0), "max_tokens": 2500 } try: logger.info("Sending request to DeepSeek API") response_eval = requests.post(url, headers=headers, json=payload_eval) if response_eval.status_code != 200: logger.error(f"API Error: {response_eval.status_code} - {response_eval.text}") return {"evaluation": f"Error from API: {response_eval.text}", "fixed_story": None} evaluation_result = response_eval.json()["choices"][0]["message"]["content"] logger.info("Successfully received evaluation from API") return { "evaluation": evaluation_result.strip(), "fixed_story": None } except Exception as e: logger.error(f"API call failed: {e}") return {"evaluation": f"Error calling API: {e}", "fixed_story": None}