File size: 11,283 Bytes
81e20b3
0638ab8
 
 
3bfff15
 
96f9398
 
 
194f0cb
d2083e1
0638ab8
 
 
5d57748
194f0cb
 
 
 
 
96f9398
0638ab8
 
81e20b3
 
96f9398
3bfff15
96f9398
db6c3a6
3bfff15
5d57748
194f0cb
 
 
 
 
 
3bfff15
194f0cb
db6c3a6
3bfff15
96f9398
 
202e2b1
 
5d57748
 
0638ab8
 
 
 
5d57748
01d6342
6b6af6b
 
 
202e2b1
5d57748
0638ab8
96f9398
 
 
0638ab8
4aaf4df
202e2b1
81e20b3
202e2b1
 
 
0638ab8
378b3e3
d2083e1
378b3e3
202e2b1
96f9398
d2083e1
d2b8207
202e2b1
d2b8207
378b3e3
d2b8207
0638ab8
d2b8207
202e2b1
d2b8207
 
202e2b1
378b3e3
 
 
202e2b1
96f9398
0638ab8
 
202e2b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3bfff15
 
378b3e3
01d6342
 
4aaf4df
202e2b1
4aaf4df
 
 
 
378b3e3
3bfff15
378b3e3
4aaf4df
378b3e3
202e2b1
378b3e3
 
3bfff15
 
 
 
 
378b3e3
3bfff15
378b3e3
 
 
 
 
 
 
 
 
 
 
 
202e2b1
378b3e3
01d6342
202e2b1
01d6342
 
 
 
 
 
 
 
3bfff15
01d6342
 
202e2b1
01d6342
 
0fa52bc
3bfff15
4aaf4df
01d6342
 
 
3bfff15
01d6342
3bfff15
378b3e3
3bfff15
 
9f9f17f
490f79a
0fa52bc
d2b8207
490f79a
d2b8207
0fa52bc
3bfff15
 
378b3e3
 
d2b8207
378b3e3
3bfff15
0638ab8
81e20b3
 
 
5d57748
01d6342
 
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
#app.py
import os
import io
import json
import base64
from PIL import Image

from flask import Flask, request, jsonify, send_file
from gtts import gTTS
from groq import Groq
import google.generativeai as genai

# --- CONFIGURAÇÃO INICIAL ---
app = Flask(__name__)

# --- CONFIGURAÇÃO DAS APIS LLM ---
genai_client = None
groq_client = None

# 1. Configuração Gemini
try:
    GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
    if GEMINI_API_KEY:
        genai.configure(api_key=GEMINI_API_KEY)
        genai_client = genai
    else:
        print("AVISO: GEMINI_API_KEY não configurada.")
except Exception as e:
    genai_client = None
    print(f"ERRO ao inicializar o cliente Gemini: {e}.")

# 2. Configuração Groq
try:
    GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
    if GROQ_API_KEY:
        groq_client = Groq(api_key=GROQ_API_KEY)
    else:
        print("AVISO: GROQ_API_KEY não configurada.")
except Exception as e:
    groq_client = None
    print(f"ERRO ao inicializar o cliente Groq: {e}.")


# --- ROTAS PRINCIPAIS ---

@app.route('/tts-proxy', methods=['POST'])
def tts_proxy():
    data = request.get_json()
    text = data.get('text', '')
    if not text:
        return jsonify({"error": "No text provided"}), 400
    try:
        tts = gTTS(text=text, lang='en', tld='co.uk')
        mp3_fp = io.BytesIO()
        tts.write_to_fp(mp3_fp)
        mp3_fp.seek(0)
        return send_file(mp3_fp, mimetype='audio/mpeg')
    except Exception as e:
        return jsonify({"error": f"Failed to generate audio via gTTS: {e}"}), 500

@app.route('/explain-proxy', methods=['POST'])
def explain_proxy():
    data = request.get_json()
    model_provider, model_name = data.get('model', 'gemini:gemini-2.5-flash-latest').split(':', 1)
    context_focus = data.get('context_focus', 'General/Social')
    custom_prompt = data.get('custom_prompt', None)
    word = data.get('word', '').strip()
    context = data.get('context', '')
    for_flashcard = data.get('for_flashcard', False)

    if (model_provider == 'gemini' and not genai_client) or (model_provider == 'groq' and not groq_client):
        return jsonify({"error": f"{model_provider.upper()}_API_KEY not configured."}), 503
    
    system_instruction_base = f"You are a professional English tutor. The user's study focus is '{context_focus}'. All your responses must be in ENGLISH."
    try:
        if custom_prompt:
            activity_text = get_ai_text_response(model_provider, model_name, system_instruction_base, custom_prompt)
            return jsonify({"explanation": activity_text})
        
        if not word: return jsonify({"error": "No word selected."}), 400
        
        if for_flashcard:
            schema = {"type": "object", "properties": {"term": {"type": "string"}, "translation": {"type": "string"}, "context_sentence": {"type": "string"}, "gapped_sentence": {"type": "string"}, "definition": {"type": "string"}}, "required": ["term", "translation", "context_sentence", "gapped_sentence", "definition"]}
            prompt = f"Analyze '{word}' in context: '{context}'. Generate a JSON for a flashcard. The 'gapped_sentence' must replace '{word}' with '______________'. You must strictly follow the JSON schema and provide valid, non-empty values for all fields."
            return jsonify(get_ai_text_response(model_provider, model_name, system_instruction_base, prompt, json_schema=schema))
        
        else: # Quick translation logic (not currently used in UI, but kept for potential future use)
            prompt = f"Analyze '{word}' in context: '{context}'. Provide a one-sentence English explanation, then '---', then the Portuguese translation."
            parts = get_ai_text_response(model_provider, model_name, system_instruction_base, prompt).split('---', 1)
            return jsonify({"explanation": parts[0].strip(), "translation": parts[1].strip() if len(parts) > 1 else 'N/A'})

    except Exception as e:
        return jsonify({"error": f"AI analysis failed: {e}"}), 500

# --- NOVA ROTA PARA FEEDBACK DE ATIVIDADES ---
@app.route('/activity-feedback', methods=['POST'])
def activity_feedback():
    data = request.get_json()
    model_provider, model_name = data.get('model', 'gemini:gemini-2.5-flash-latest').split(':', 1)
    context_focus = data.get('context_focus', 'General/Social')
    original_prompt = data.get('original_prompt', '')
    user_response = data.get('user_response', '')

    if not original_prompt or not user_response:
        return jsonify({"error": "Original prompt and user response are required."}), 400

    if (model_provider == 'gemini' and not genai_client) or (model_provider == 'groq' and not groq_client):
        return jsonify({"error": f"{model_provider.upper()}_API_KEY not configured."}), 503

    system_instruction = (
        "You are an expert English teacher providing feedback. "
        f"The user's study focus is '{context_focus}'. "
        "Your entire response MUST be in English. "
        "Provide clear, constructive feedback on the user's writing. "
        "Point out grammar, spelling, or style errors. "
        "Offer a corrected or improved version of their text. "
        "Structure your feedback with markdown for clarity (e.g., using ### Corrected Version)."
    )
    
    user_prompt = f"The original task was: \"{original_prompt}\"\n\nHere is the user's response:\n---\n{user_response}\n---\nPlease provide your feedback."

    try:
        feedback_text = get_ai_text_response(model_provider, model_name, system_instruction, user_prompt)
        return jsonify({"feedback": feedback_text})
    except Exception as e:
        return jsonify({"error": f"AI feedback failed: {e}"}), 500


@app.route('/analyze-image', methods=['POST'])
def analyze_image():
    if not genai_client: return jsonify({"error": "GEMINI_API_KEY not configured."}), 503
    data = request.get_json()
    base64_image = data.get('image')
    model_value = data.get('model', 'gemini:gemini-2.5-flash-latest')
    
    model_name = 'gemini-2.5-flash-latest'
    if model_value.startswith('gemini:'):
        model_name = model_value.split(':', 1)[1]

    if not base64_image: return jsonify({"error": "No image data."}), 400
    try:
        image = Image.open(io.BytesIO(base64.b64decode(base64_image.split(',')[1])))
        model = genai_client.GenerativeModel(model_name)
        schema = { "type": "object", "properties": { "vocabulary": { "type": "array", "items": { "type": "object", "properties": { "term": {"type": "string"}, "definition": {"type": "string"} }, "required": ["term", "definition"] } } }, "required": ["vocabulary"] }
        prompt = [ "Act as an English teacher. Identify 5-7 key objects/concepts in this image. For each, provide its English name and a simple definition. Return a single JSON object conforming to the schema.", image ]
        response = model.generate_content(prompt, generation_config={"response_mime_type": "application/json", "response_schema": schema})
        return jsonify(json.loads(response.text)['vocabulary'])
    except Exception as e:
        return jsonify({"error": f"Image analysis failed: {e}"}), 500

@app.route('/chat-with-ai', methods=['POST'])
def chat_with_ai():
    if not groq_client: return jsonify({"error": "GROQ_API_KEY not configured."}), 503
    data = request.get_json()
    history, user_message = data.get('history', []), data.get('message', '')
    if not user_message: return jsonify({"error": "No message."}), 400
    try:
        system = "You are 'Groq Chat', a friendly English tutor. Keep responses concise (1-2 sentences). If the user makes a grammar mistake, gently correct it. Ask questions to keep the conversation flowing. Always respond in English."
        messages = [{"role": "system", "content": system}] + history + [{"role": "user", "content": user_message}]
        response = groq_client.chat.completions.create(model="llama-3.1-8b-instant", messages=messages, temperature=0.7)
        return jsonify({"response": response.choices[0].message.content.strip()})
    except Exception as e:
        return jsonify({"error": f"AI chat failed: {e}"}), 500

@app.route('/pronunciation-feedback', methods=['POST'])
def pronunciation_feedback():
    if not groq_client: return jsonify({"error": "GROQ_API_KEY not configured."}), 503
    data = request.get_json()
    target_text, user_text = data.get('target_text'), data.get('user_text')
    if not target_text or not user_text: return jsonify({"error": "Required data missing."}), 400
    try:
        system_instruction = "You are an expert American English pronunciation coach. The user tried to say a target sentence, and their speech was transcribed. Based on the likely pronunciation differences, provide brief, friendly, and actionable feedback in Portuguese. Focus on 1-2 key points. If it's very close, praise the user."
        user_prompt = f"Target: \"{target_text}\"\nTranscription: \"{user_text}\"\n\nProvide pronunciation feedback."
        messages = [{"role": "system", "content": system_instruction}, {"role": "user", "content": user_prompt}]
        response = groq_client.chat.completions.create(model="llama-3.1-8b-instant", messages=messages, temperature=0.5)
        return jsonify({"feedback": response.choices[0].message.content.strip()})
    except Exception as e:
        return jsonify({"error": f"Pronunciation analysis failed: {e}"}), 500

@app.route('/generate-image', methods=['POST'])
def generate_image():
    if not genai_client: return jsonify({"error": "GEMINI_API_KEY not configured."}), 503
    data = request.get_json()
    prompt = data.get('prompt')
    if not prompt: return jsonify({"error": "Image prompt is required."}), 400
    try:
        model = genai_client.GenerativeModel(model_name='gemini-2.5-flash-image-preview')
        response = model.generate_content(prompt)
        base64_image_data = response.parts[0].inline_data.data
        return jsonify({"image_base64": base64_image_data})
    except Exception as e:
        return jsonify({"error": f"Image generation failed: {e}"}), 500

# --- FUNÇÃO AUXILIAR E ROTA RAIZ ---
def get_ai_text_response(provider, model_name, system_instruction, user_prompt, json_schema=None):
    if provider == 'gemini':
        model = genai_client.GenerativeModel(model_name, system_instruction=system_instruction)
        config = {}
        if json_schema:
            config = {"response_mime_type": "application/json", "response_schema": json_schema}
        response = model.generate_content(user_prompt, generation_config=config)
        return json.loads(response.text) if json_schema else response.text.strip()
        
    elif provider == 'groq':
        messages = [{"role": "system", "content": system_instruction}, {"role": "user", "content": user_prompt}]
        config = {'response_format': {"type": "json_object"}} if json_schema else {}
        response = groq_client.chat.completions.create(model=model_name, messages=messages, **config)
        return json.loads(response.choices[0].message.content) if json_schema else response.choices[0].message.content.strip()
    raise Exception(f"Unsupported provider: {provider}")

@app.route('/')
def root():
    return send_file('index.html')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=7860)