walker11 commited on
Commit
4b4d390
·
verified ·
1 Parent(s): 3f42cf5

Upload 6 files

Browse files
Files changed (6) hide show
  1. .dockerignore +39 -0
  2. Dockerfile +30 -0
  3. README.md +90 -11
  4. app.py +104 -0
  5. requirements.txt +10 -0
  6. review_api.py +199 -0
.dockerignore ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Git
2
+ .git
3
+ .gitignore
4
+
5
+ # Python
6
+ __pycache__/
7
+ *.py[cod]
8
+ *$py.class
9
+ *.so
10
+ .Python
11
+ env/
12
+ build/
13
+ develop-eggs/
14
+ dist/
15
+ downloads/
16
+ eggs/
17
+ .eggs/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ *.egg-info/
23
+ .installed.cfg
24
+ *.egg
25
+
26
+ # Virtual environments
27
+ venv/
28
+ ENV/
29
+ env/
30
+
31
+ # IDEs and editors
32
+ .idea/
33
+ .vscode/
34
+ *.swp
35
+ *.swo
36
+
37
+ # OS specific
38
+ .DS_Store
39
+ Thumbs.db
Dockerfile ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ # Set working directory
4
+ WORKDIR /app
5
+
6
+ # Set environment variables
7
+ ENV PYTHONDONTWRITEBYTECODE=1
8
+ ENV PYTHONUNBUFFERED=1
9
+ ENV PORT=7860
10
+
11
+ # Install system dependencies
12
+ RUN apt-get update && apt-get install -y --no-install-recommends \
13
+ build-essential \
14
+ && rm -rf /var/lib/apt/lists/*
15
+
16
+ # Copy requirements first to leverage Docker cache
17
+ COPY requirements.txt .
18
+ RUN pip install --no-cache-dir -r requirements.txt
19
+
20
+ # Copy application code
21
+ COPY . .
22
+
23
+ # Set API key environment variable (this will be overridden in production)
24
+ ENV DEEPSEEK_API_KEY=""
25
+
26
+ # Expose the port the app runs on
27
+ EXPOSE 7860
28
+
29
+ # Command to run the application
30
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,11 +1,90 @@
1
- ---
2
- title: Rawireview
3
- emoji: 📊
4
- colorFrom: blue
5
- colorTo: red
6
- sdk: docker
7
- pinned: false
8
- license: mit
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Rawi Review API
3
+ emoji: 📝
4
+ colorFrom: indigo
5
+ colorTo: blue
6
+ sdk: docker
7
+ sdk_version: "3.10"
8
+ app_file: app.py
9
+ pinned: false
10
+ license: mit
11
+ ---
12
+
13
+ # Rawi Review API
14
+
15
+ This API provides literary evaluation services for Arabic stories, offering detailed critiques based on essential literary criteria.
16
+
17
+ ## API Endpoints
18
+
19
+ ### GET /
20
+ Returns a welcome message.
21
+
22
+ ### POST /review-story/
23
+ Accepts a PDF file containing a story and returns a detailed literary evaluation.
24
+
25
+ **Request:**
26
+ - `file`: PDF file (required)
27
+
28
+ **Response:**
29
+ ```json
30
+ {
31
+ "evaluation": "Detailed evaluation in Arabic...",
32
+ "fixed_story": null
33
+ }
34
+ ```
35
+
36
+ ### POST /review-story-text/
37
+ Accepts story text directly and returns a detailed literary evaluation.
38
+
39
+ **Request:**
40
+ ```json
41
+ {
42
+ "text": "Your story text here..."
43
+ }
44
+ ```
45
+
46
+ **Response:**
47
+ ```json
48
+ {
49
+ "evaluation": "Detailed evaluation in Arabic...",
50
+ "fixed_story": null
51
+ }
52
+ ```
53
+
54
+ ## Environment Variables
55
+
56
+ - `DEEPSEEK_API_KEY`: API key for DeepSeek AI (optional - will use mock responses if not provided)
57
+ - `PORT`: Port to run the service on (default: 7860)
58
+
59
+ ## Evaluation Criteria
60
+
61
+ Stories are evaluated based on 8 literary criteria:
62
+
63
+ 1. Unity of event
64
+ 2. Limited and defined characters
65
+ 3. Focus on a decisive moment
66
+ 4. Conciseness and economy of language
67
+ 5. Unity of time and place
68
+ 6. Well-structured plot
69
+ 7. Impactful ending
70
+ 8. Clear message or theme
71
+
72
+ Each criterion is scored out of 10, with a final score out of 80.
73
+
74
+ ## Deployment
75
+
76
+ This service is ready to be deployed on Hugging Face Spaces using the included Dockerfile.
77
+
78
+ ### Running Locally
79
+
80
+ ```bash
81
+ pip install -r requirements.txt
82
+ uvicorn app:app --reload
83
+ ```
84
+
85
+ ### Using Docker
86
+
87
+ ```bash
88
+ docker build -t rawi-review-api .
89
+ docker run -p 7860:7860 -e DEEPSEEK_API_KEY=your_api_key rawi-review-api
90
+ ```
app.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, UploadFile, File, HTTPException, Request
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ import os
4
+ import tempfile
5
+ from pathlib import Path
6
+ from pydantic import BaseModel
7
+ import traceback
8
+ import logging
9
+ from review_api import review_story, review_story_text
10
+
11
+ # Set up logging
12
+ logging.basicConfig(level=logging.INFO)
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # Create Pydantic model for text input
16
+ class StoryText(BaseModel):
17
+ text: str
18
+
19
+ app = FastAPI(title="Rawi Review API")
20
+
21
+ # Configure CORS
22
+ app.add_middleware(
23
+ CORSMiddleware,
24
+ allow_origins=["*"], # In production, replace with specific origins
25
+ allow_credentials=True,
26
+ allow_methods=["*"],
27
+ allow_headers=["*"],
28
+ )
29
+
30
+ @app.get("/")
31
+ def read_root():
32
+ return {"message": "Welcome to Rawi Story Review API"}
33
+
34
+ @app.middleware("http")
35
+ async def log_requests(request: Request, call_next):
36
+ """Log all requests and responses"""
37
+ logger.info(f"Request: {request.method} {request.url.path}")
38
+ try:
39
+ response = await call_next(request)
40
+ logger.info(f"Response status: {response.status_code}")
41
+ return response
42
+ except Exception as e:
43
+ logger.error(f"Request failed: {e}")
44
+ logger.error(traceback.format_exc())
45
+ raise
46
+
47
+ @app.post("/review-story/")
48
+ async def create_review_from_file(file: UploadFile = File(...)):
49
+ # Validate file is PDF
50
+ if not file.filename.lower().endswith('.pdf'):
51
+ raise HTTPException(status_code=400, detail="Only PDF files are supported")
52
+
53
+ logger.info(f"Processing PDF file: {file.filename}")
54
+
55
+ # Create temporary file
56
+ temp_dir = tempfile.gettempdir()
57
+ temp_file_path = Path(temp_dir) / file.filename
58
+
59
+ try:
60
+ # Save uploaded file
61
+ with open(temp_file_path, "wb") as buffer:
62
+ content = await file.read()
63
+ buffer.write(content)
64
+
65
+ logger.info(f"PDF saved to temporary location: {temp_file_path}")
66
+
67
+ # Process PDF and get review
68
+ result = review_story(str(temp_file_path))
69
+
70
+ return result
71
+
72
+ except Exception as e:
73
+ logger.error(f"Error processing PDF: {e}")
74
+ logger.error(traceback.format_exc())
75
+ raise HTTPException(status_code=500, detail=str(e))
76
+
77
+ finally:
78
+ # Clean up temporary file
79
+ if temp_file_path.exists():
80
+ os.unlink(temp_file_path)
81
+
82
+ @app.post("/review-story-text/")
83
+ async def create_review_from_text(story: StoryText):
84
+ try:
85
+ # Log the request
86
+ logger.info(f"Received text review request with {len(story.text)} characters")
87
+
88
+ # Validate text length
89
+ if len(story.text) < 100:
90
+ raise HTTPException(status_code=400, detail="Text is too short for review. Minimum 100 characters required.")
91
+
92
+ # Process text directly and get review
93
+ result = review_story_text(story.text)
94
+
95
+ return result
96
+
97
+ except Exception as e:
98
+ logger.error(f"Error processing text: {e}")
99
+ logger.error(traceback.format_exc())
100
+ raise HTTPException(status_code=500, detail=f"Error processing text: {str(e)}")
101
+
102
+ if __name__ == "__main__":
103
+ import uvicorn
104
+ uvicorn.run(app, host="0.0.0.0", port=8000)
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn==0.24.0
3
+ python-multipart==0.0.6
4
+ pydantic==2.4.2
5
+ pypdf==3.15.1
6
+ PyPDF2==3.0.1
7
+ python-dotenv==1.0.0
8
+ requests==2.31.0
9
+ httpx==0.25.1
10
+ aiofiles==23.2.1
review_api.py ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import requests
3
+ import random
4
+ import logging
5
+ import json
6
+
7
+ try:
8
+ from PyPDF2 import PdfReader
9
+ except ImportError:
10
+ # Fallback for Docker environment
11
+ from pypdf import PdfReader
12
+
13
+ # Set up logging
14
+ logging.basicConfig(level=logging.INFO)
15
+ logger = logging.getLogger(__name__)
16
+
17
+ # Check for environment variable or use a default value for testing
18
+ def get_api_key():
19
+ api_key = os.environ.get('DEEPSEEK_API_KEY')
20
+ if not api_key:
21
+ logger.warning("No DEEPSEEK_API_KEY environment variable found. Using mock responses.")
22
+ return api_key
23
+
24
+ # Mock evaluation result for testing
25
+ def get_mock_evaluation(story_excerpt):
26
+ # Use a short excerpt of the story in the evaluation to make it look personalized
27
+ story_start = story_excerpt[:100].replace("\n", " ").strip()
28
+ if len(story_start) > 50:
29
+ story_start = story_start[:50] + "..."
30
+
31
+ # Generate random scores but keep them reasonable
32
+ unity_score = random.randint(6, 9)
33
+ characters_score = random.randint(6, 9)
34
+ decisive_moment_score = random.randint(7, 10)
35
+ language_score = random.randint(5, 9)
36
+ time_place_score = random.randint(6, 9)
37
+ plot_score = random.randint(7, 10)
38
+ ending_score = random.randint(6, 10)
39
+ message_score = random.randint(6, 9)
40
+
41
+ total_score = unity_score + characters_score + decisive_moment_score + language_score + \
42
+ time_place_score + plot_score + ending_score + message_score
43
+
44
+ # Mock evaluation text
45
+ return {
46
+ "evaluation": f"""📋 التقييم:
47
+
48
+ وحدة الحدث: {unity_score}/10
49
+ القصة تدور حول حدث رئيسي بشكل جيد، وتحافظ على تماسك الأحداث.
50
+
51
+ الشخصيات المحدودة والمعرّفة: {characters_score}/10
52
+ الشخصيات محددة بوضوح ومتميزة، مع تطوير مناسب للشخصيات الرئيسية.
53
+
54
+ التركيز على لحظة حاسمة: {decisive_moment_score}/10
55
+ القصة تبني بمهارة نحو لحظة التحول الرئيسية التي تغير مسار الأحداث.
56
+
57
+ الإيجاز واقتصاد اللغة: {language_score}/10
58
+ اللغة موجزة ومعبرة، تنقل المعاني بفعالية دون إطناب غير ضروري.
59
+
60
+ وحدة الزمان والمكان: {time_place_score}/10
61
+ هناك استخدام متناسق للإطار الزماني والمكاني، مما يعزز تماسك القصة.
62
+
63
+ حبكة جيدة البناء: {plot_score}/10
64
+ الحبكة متطورة بشكل منطقي ومتماسك، مع تسلسل واضح للأحداث.
65
+
66
+ نهاية مؤثرة: {ending_score}/10
67
+ النهاية تترك انطباعًا قويًا وتوفر إغلاقًا مناسبًا للأحداث.
68
+
69
+ رسالة أو موضوع واضح: {message_score}/10
70
+ تُظهر القصة رسالة واضحة تتطور بشكل طبيعي من خلال الأحداث.
71
+
72
+ النتيجة النهائية: {total_score}/80
73
+ {"هذا عمل أدبي متميز يتسم بقوة البناء والتماسك. القصة تحقق توازنًا جيدًا بين تطوير الشخصيات وتقدم الحبكة." if total_score > 65 else "رغم وجود عناصر قوية في القصة، هناك مجال للتحسين في بعض الجوانب. ننصح بالتركيز على تعزيز تماسك الأحداث وتطوير الشخصيات بشكل أعمق."}
74
+
75
+ ملاحظة: بداية القصة "{story_start}" تظهر إمكانات جيدة وتجذب اهتمام القارئ.""",
76
+ "fixed_story": None
77
+ }
78
+
79
+ def review_story(pdf_path):
80
+ """
81
+ Review a story from a PDF file
82
+ """
83
+ try:
84
+ # Load text from PDF
85
+ text = ""
86
+ try:
87
+ with open(pdf_path, "rb") as f:
88
+ reader = PdfReader(f)
89
+ text = "\n".join([p.extract_text() for p in reader.pages if p.extract_text()])
90
+ text = " ".join(text.split())
91
+ except Exception as e:
92
+ logger.error(f"PDF loading failed: {e}")
93
+ return {"evaluation": f"Error loading PDF: {e}", "fixed_story": None}
94
+
95
+ # Get evaluation
96
+ api_key = get_api_key()
97
+
98
+ if not api_key:
99
+ # Return mock evaluation
100
+ logger.info("Using mock evaluation for PDF")
101
+ return get_mock_evaluation(text)
102
+
103
+ # Use API for evaluation
104
+ return call_api_for_evaluation(text)
105
+
106
+ except Exception as e:
107
+ logger.error(f"Error in review_story: {e}")
108
+ return {"evaluation": f"Error processing story: {e}", "fixed_story": None}
109
+
110
+ def review_story_text(story_text):
111
+ """
112
+ Review a story provided as text directly
113
+ """
114
+ try:
115
+ # Get evaluation
116
+ api_key = get_api_key()
117
+
118
+ if not api_key:
119
+ # Return mock evaluation
120
+ logger.info("Using mock evaluation for text")
121
+ return get_mock_evaluation(story_text)
122
+
123
+ # Use API for evaluation
124
+ return call_api_for_evaluation(story_text)
125
+
126
+ except Exception as e:
127
+ logger.error(f"Error in review_story_text: {e}")
128
+ return {"evaluation": f"Error processing story text: {e}", "fixed_story": None}
129
+
130
+ def call_api_for_evaluation(story):
131
+ """
132
+ Call the DeepSeek API for story evaluation
133
+ """
134
+ api_key = get_api_key()
135
+ if not api_key:
136
+ return get_mock_evaluation(story)
137
+
138
+ evaluation_prompt = f"""
139
+ 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.
140
+
141
+ 🔹 For each criterion:
142
+ Give a score out of 10.
143
+ Write a brief explanation (one or two lines) that justifies the score.
144
+
145
+ 🔹 At the end of the evaluation:
146
+ Add up the scores to get a final result out of 80.
147
+ If the score is above 65: Praise the story as a successful and well-crafted literary work.
148
+ If the score is 65 or lower: Provide constructive criticism that highlights the main weaknesses and suggests how to improve them.
149
+
150
+ Start the evaluation with a title that includes an emoji, such as: 📋 التقييم:
151
+
152
+ 🔹 The evaluation criteria are:
153
+ 1. Unity of event: Does the story revolve around a single main incident or situation?
154
+ 2. Limited and defined characters: Does the story include a small number of clear and distinctive characters?
155
+ 3. Focus on a decisive moment: Does the story highlight a turning point or critical decision?
156
+ 4. Conciseness and economy of language: Is the language focused and free of unnecessary details?
157
+ 5. Unity of time and place: Does the story take place in a specific time and setting?
158
+ 6. Well-structured plot: Is there a clear logical sequence (beginning, middle, end)?
159
+ 7. Impactful ending: Does the ending leave an emotional or intellectual impact?
160
+ 8. Clear message or theme: Does the story convey a specific idea or feeling clearly?
161
+
162
+ Evaluate the following story based on these criteria:
163
+ {story}
164
+ """
165
+
166
+ url = "https://api.deepseek.com/v1/chat/completions"
167
+ headers = {
168
+ "Authorization": f"Bearer {api_key}",
169
+ "Content-Type": "application/json"
170
+ }
171
+
172
+ payload_eval = {
173
+ "model": "deepseek-chat",
174
+ "messages": [
175
+ {"role": "system", "content": "You are a formal short story evaluator."},
176
+ {"role": "user", "content": evaluation_prompt.strip()}
177
+ ],
178
+ "temperature": random.uniform(0.9, 1.0),
179
+ "max_tokens": 2500
180
+ }
181
+
182
+ try:
183
+ logger.info("Sending request to DeepSeek API")
184
+ response_eval = requests.post(url, headers=headers, json=payload_eval)
185
+
186
+ if response_eval.status_code != 200:
187
+ logger.error(f"API Error: {response_eval.status_code} - {response_eval.text}")
188
+ return {"evaluation": f"Error from API: {response_eval.text}", "fixed_story": None}
189
+
190
+ evaluation_result = response_eval.json()["choices"][0]["message"]["content"]
191
+ logger.info("Successfully received evaluation from API")
192
+
193
+ return {
194
+ "evaluation": evaluation_result.strip(),
195
+ "fixed_story": None
196
+ }
197
+ except Exception as e:
198
+ logger.error(f"API call failed: {e}")
199
+ return {"evaluation": f"Error calling API: {e}", "fixed_story": None}