File size: 14,480 Bytes
ce7ee25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fc01712
 
ce7ee25
fc01712
 
 
 
 
 
 
 
 
 
 
 
ce7ee25
 
 
34771bf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ce7ee25
 
 
 
 
 
fc01712
ce7ee25
 
fc01712
 
 
 
ce7ee25
 
 
 
fc01712
 
ce7ee25
fc01712
 
ce7ee25
 
 
 
 
 
 
 
 
fc01712
ce7ee25
 
 
 
 
 
 
 
 
 
 
 
 
34771bf
 
 
 
 
 
 
 
ce7ee25
 
 
 
 
 
34771bf
 
 
 
 
 
 
 
ce7ee25
34771bf
ce7ee25
 
 
 
34771bf
 
 
 
 
 
 
 
ce7ee25
 
 
 
 
 
 
 
fc01712
ce7ee25
fc01712
 
 
 
 
ce7ee25
 
 
 
 
 
 
fc01712
 
 
 
ce7ee25
 
 
 
 
 
 
 
 
fc01712
ce7ee25
 
 
 
fc01712
ce7ee25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fc01712
ce7ee25
fc01712
 
 
 
ce7ee25
 
 
 
 
 
 
 
 
fc01712
 
 
ce7ee25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fc01712
ce7ee25
fc01712
 
 
 
ce7ee25
fc01712
ce7ee25
 
fc01712
 
ce7ee25
 
fc01712
 
ce7ee25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fc01712
 
 
ce7ee25
 
 
 
 
 
fc01712
ce7ee25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fc01712
ce7ee25
fc01712
 
 
 
 
 
ce7ee25
 
 
 
 
fc01712
 
ce7ee25
 
 
 
 
 
 
fc01712
ce7ee25
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# from flask import Flask, Blueprint, jsonify, send_file, abort, make_response, request
from flask import Flask, Blueprint, jsonify, send_file, abort, make_response, request, current_app

from flask_cors import CORS
import os
print(f"GOOGLE_APPLICATION_CREDENTIALS: {os.getenv('GOOGLE_APPLICATION_CREDENTIALS')}")
import io
import uuid
import requests
import re 

questions_bp = Blueprint("questions", __name__)

app = Flask(__name__)
CORS(app)

@app.route('/')
def home():
    return "Welcome to the Flask app! The server is running."

# read from env as a fallback (works for local .env and HF Secrets)
COHERE_API_KEY = os.getenv("COHERE_API_KEY", "")

def _cohere_headers():
    """
    Prefer a key set on the Flask app (e.g., in verification.py: app.config["COHERE_API_KEY"]),
    otherwise fall back to the environment variable COHERE_API_KEY.
    """
    api_key = current_app.config.get("COHERE_API_KEY") or COHERE_API_KEY
    if not api_key:
        return None
    return {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    }

# (1) UPDATED: Cohere v2 Chat endpoint
COHERE_API_URL = 'https://api.cohere.com/v2/chat'

def _extract_text_v2(resp_json: dict) -> str:
    """
    v2 /chat returns:
    { "message": { "content": [ { "type": "text", "text": "..." } ] } }
    """
    msg = resp_json.get("message", {})
    content = msg.get("content", [])
    if isinstance(content, list) and content:
        block = content[0]
        if isinstance(block, dict):
            return (block.get("text") or "").strip()
    return ""

def validate_topic(topic):
    validation_prompt = f"""
   ou are a highly knowledgeable AI grammar expert. Your task is to evaluate whether the given topic relates to **English grammar** or not.

    **Input Topic:** "{topic}"

    ### **Instructions:**
    - If the input **exactly refers to** grammar concepts (such as **parts of speech**, **verb tenses**, **sentence structure**, **grammar rules**, etc.), respond with `"Grammar"`.
    - If the input **seems to be a general question or concept** that is **not directly related to grammar**, such as general knowledge, science, history, or unrelated fields, respond with `"Not Grammar"`.
    - If the input is in the form of a **question** (e.g., "What is subject-verb agreement?"), respond with `"ask grammar topics"`.
    - If the topic refers to a **specific grammar concept** (e.g., **noun**, **verb**, **preposition**, **past tense**, etc.), always classify it as `"Grammar"`.
    - **Do not include any explanations or examples**. Your answer must only be `"Grammar"`, `"Not Grammar"`, or `"ask grammar topics"`, depending on whether the topic is relevant to grammar.
    - If the input is **unclear**, err on the side of classifying it as `"Not Grammar"` rather than `"Grammar"`.

    Your response must only be one of these three options:
    - `"Grammar"`
    - `"Not Grammar"`
    - `"ask grammar topics"`
    No extra text or explanation.
    """

    headers = _cohere_headers()
    if not headers:
        return "Error: COHERE_API_KEY not set"

    # (2) UPDATED: messages payload
    payload = {
        'model': 'command-r-08-2024',
        'messages': [
            {'role': 'user', 'content': validation_prompt}
        ],
        'max_tokens': 5
    }

    try:
        response = requests.post(COHERE_API_URL, json=payload, headers=headers)
        # (3) UPDATED: v2 parsing
        validation_result = _extract_text_v2(response.json())

        if validation_result not in ["Grammar", "Not Grammar", "ask grammar topics"]:
            return "Not Grammar"
        return validation_result

    except Exception as e:
        return f"Error: {str(e)}"

@questions_bp.post('/generate-questions')
def generate_questions_test():
    try:
        data = request.get_json()
        topic = data.get('topic', '').strip()
        validation_result = validate_topic(topic)

        if validation_result != "Grammar":
            return jsonify({"message": "Please enter a valid **grammar topic**, not a general word or unrelated question."}), 400

        difficulty = data.get('difficulty', 'basic')
        print(f"Generating {difficulty} questions for topic: {topic}")

        if difficulty == 'basic':
            prompt = f"""
            Generate five **completely new and unique** very basic-level fill-in-the-blank grammar questions **every time** on the topic '{topic}'. 

            ### Rules:
            - Generate five unique fill-in-the-blank grammar questions based on the topic '{topic}'.
            - Each question must have exactly one blank represented by '_______' (not two blanks or underscores inside the sentence).
            - Each question must have a different theme for variety.
            - Use different sentence structures; avoid predictable patterns.
            - Avoid long words or abstract concepts.
            - Focus on the topic '{topic}', and ensure the blank is the key part of speech.
            - Each question must include the correct answer in parentheses at the end.
            - Do not include any explanations or instructions—only the five questions.
            """
        elif difficulty == 'intermediate':
            prompt = f"""
            Generate five **completely new and unique** intermediate-level fill-in-the-blank grammar questions **every time** on the topic '{topic}'.

            ### Rules:
            - Generate five unique fill-in-the-blank grammar questions based on the topic '{topic}'.
            - Each question must have exactly one blank represented by '_______'.
            - Slightly more challenging than basic-level; use a wider range of sentence structures and vocabulary.
            - Each question must have a different theme.
            - Sentences should be longer and include more detail.
            - Focus on the topic '{topic}', and ensure the blank is the key part of speech.
            - Each question must include the correct answer in parentheses at the end.
            - Do not include any explanations or instructions—only the five questions.
            """
        elif difficulty == 'expert':
            prompt = f"""
            Generate five **completely new and unique** advanced-level (C1) fill-in-the-blank grammar questions **every time** on the topic '{topic}'.

            ### Rules:
            - Generate five unique fill-in-the-blank grammar questions based on the topic '{topic}'.
            - Each question must have exactly one blank represented by '_______'.
            - More challenging than intermediate (C1); require expert-level mastery of grammar and context.
            - Ensure varied and sophisticated vocabulary; avoid basic words.
            - Each question should require nuanced comprehension; test advanced grammar patterns.
            - The blank must be the key part of the sentence (not an obvious answer).
            - Each question must include the correct answer in parentheses at the end.
            - Do not include any explanations or instructions—only the five questions.
            """
        else:
            return jsonify({"error": "Invalid difficulty level"}), 400

        headers = _cohere_headers()
        if not headers:
            return jsonify({"error": "COHERE_API_KEY not set on the server"}), 500

        # (2) UPDATED: messages payload
        payload = {
            'model': 'command-r-08-2024',
            'messages': [
                {'role': 'user', 'content': prompt}
            ],
            'max_tokens': 1000
        }

        response = requests.post(COHERE_API_URL, json=payload, headers=headers)
        print("Response status code:", response.status_code)
        print("Response content:", response.text)

        if response.status_code == 200:
            # (3) UPDATED: v2 parsing
            text = _extract_text_v2(response.json())
            # Return same style as before but adapted (text content)
            return jsonify({"text": text})
        else:
            return jsonify({"error": "Failed to fetch questions", "details": response.text}), 500
    except Exception as e:
        return jsonify({"error": str(e)}), 500

@questions_bp.post('/validate-answer')
def validate_answer():
    try:
        data = request.get_json()
        topic = data.get('topic', '')
        question = data.get('question', '')
        user_answer = data.get('user_answer', '')

        if not question or not user_answer or not topic:
            return jsonify({'error': 'Topic, question, and user answer are required'}), 400

        prompt = f"""
        You are a highly knowledgeable grammar assistant. Validate whether the user's answer to the following question is correct or not based on {topic}. If the answer is incorrect, provide a helpful hint.
        
        Topic: {topic}
        Question: "{question}"
        User's Answer: "{user_answer}"

        Is the answer correct? If not, please explain why and give a hint.
        """

        headers = _cohere_headers()
        if not headers:
            return jsonify({"error": "COHERE_API_KEY not set on the server"}), 500

        # (2) UPDATED: messages payload
        payload = {
            'model': 'command-r-08-2024',
            'messages': [
                {'role': 'user', 'content': prompt}
            ],
            'max_tokens': 100,
            'temperature': 0.7
        }

        response = requests.post(COHERE_API_URL, headers=headers, json=payload)
        print(f"Status Code: {response.status_code}")
        print(f"Response Body: {response.text}")

        if response.status_code == 200:
            # (3) UPDATED: v2 parsing
            text = _extract_text_v2(response.json())
            return jsonify({"text": text})
        else:
            return jsonify({'error': 'Failed to fetch data from Cohere API'}), 500

    except Exception as e:
        return jsonify({'error': str(e)}), 500

# Function to validate an individual answer using Cohere API
def validate_answer_with_ai(topic, question, user_answer):
    try:
        prompt = f"""
        You are a highly knowledgeable grammar assistant. Validate whether the user's answer to the following question is correct or not based on {topic}. If the answer is incorrect, provide a helpful hint.

        Topic: {topic}
        Question: "{question}"
        User's Answer: "{user_answer}"

        Is the answer correct? If not, please explain why and give a hint.
        """

        headers = _cohere_headers()
        if not headers:
            return "Error: COHERE_API_KEY not set"

        # (2) UPDATED: messages payload
        payload = {
            'model': 'command-r-08-2024',
            'messages': [
                {'role': 'user', 'content': prompt}
            ],
            'max_tokens': 200,
            'temperature': 0.7
        }

        # (1) UPDATED URL used here too
        response = requests.post(COHERE_API_URL, headers=headers, json=payload)

        if response.status_code == 200:
            # (3) UPDATED: v2 parsing
            return _extract_text_v2(response.json())
        else:
            return f"Error: {response.status_code} - {response.text}"

    except Exception as e:
        return f"An error occurred: {str(e)}"

@questions_bp.post('/validate-all-answers')
def validate_all_answers():
    try:
        data = request.get_json()
        questions = data.get('questions', [])

        if not questions:
            return jsonify({'error': 'No questions provided'}), 400

        validation_results = []

        for item in questions:
            topic = item.get('topic', '')
            question = item.get('question', '')
            user_answer = item.get('user_answer', '')

            if not topic or not question or not user_answer:
                validation_results.append({
                    'question': question,
                    'error': 'Missing required fields (topic, question, or answer).'
                })
                continue

            validation_response = validate_answer_with_ai(topic, question, user_answer)

            hint = None
            if isinstance(validation_response, str) and (
                "incorrect" in validation_response.lower() or "not correct" in validation_response.lower()
            ):
                hint = generate_hint(topic, question, user_answer)

            validation_results.append({
                'question': question,
                'user_answer': user_answer,
                'validation_response': validation_response,
                'hint': hint
            })

        return jsonify({'results': validation_results})

    except Exception as e:
        return jsonify({'error': str(e)}), 500

def generate_hint(topic, question, user_answer):
    try:
        prompt = f"""
        You are a highly skilled grammar assistant. Your task is to generate a helpful hint for the user to improve their answer based on the following question.

        Topic: {topic}
        Question: "{question}"
        User's Answer: "{user_answer}"

        If the user's answer is incorrect, provide a specific, actionable hint to help the user correct their answer.
        The hint should include:
        - Explanation of the error made by the user.
        - A hint on the correct grammatical structure or word form.
        - A hint on how to structure the sentence correctly **without revealing the exact answer**.

        Please make sure the hint is **clear** and **helpful** for the user, **without revealing the correct answer**.
        """

        headers = _cohere_headers()
        if not headers:
            return "Error: COHERE_API_KEY not set"

        # (2) UPDATED: messages payload
        payload = {
            'model': 'command-r-08-2024',
            'messages': [
                {'role': 'user', 'content': prompt}
            ],
            'max_tokens': 250,
            'temperature': 0.7
        }

        response = requests.post(COHERE_API_URL, headers=headers, json=payload)

        if response.status_code == 200:
            # (3) UPDATED: v2 parsing
            return _extract_text_v2(response.json())
        else:
            return f"Error: {response.status_code} - {response.text}"

    except Exception as e:
        return f"An error occurred: {str(e)}"

if __name__ == '__main__':
    app.register_blueprint(questions_bp, url_prefix='')  # local exposure
    app.run(host='0.0.0.0', port=5012, debug=True)