File size: 9,216 Bytes
e335d27
 
313ca41
 
 
 
0e3dbd4
313ca41
0e3dbd4
 
 
 
 
 
 
 
 
 
 
 
313ca41
 
 
0e3dbd4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bc61ea0
0e3dbd4
 
 
 
 
 
 
313ca41
 
 
 
 
 
 
0e3dbd4
313ca41
 
0e3dbd4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313ca41
0e3dbd4
 
 
 
 
 
 
 
 
 
 
 
313ca41
0e3dbd4
 
 
 
 
313ca41
0e3dbd4
 
 
 
 
313ca41
0e3dbd4
 
 
 
94fce93
0e3dbd4
 
 
 
 
313ca41
0e3dbd4
 
 
 
313ca41
 
0e3dbd4
 
313ca41
 
 
 
 
0e3dbd4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313ca41
0e3dbd4
 
 
 
 
313ca41
 
0e3dbd4
313ca41
 
0e3dbd4
 
 
 
 
313ca41
 
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
from flask import Flask, render_template, request, jsonify
from werkzeug.utils import secure_filename
import os
import json
from textwrap import dedent
from crewai import Agent, Crew, Process, Task
from crewai_tools import SerperDevTool, PDFSearchTool
from crewai import LLM
from typing import List, Dict, Union, Optional
import tempfile
import logging
from datetime import datetime
import hashlib

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

app = Flask(__name__)

# Configuration
UPLOAD_FOLDER = tempfile.gettempdir()
ALLOWED_EXTENSIONS = {'pdf'}
MAX_FILE_SIZE = 16 * 1024 * 1024  # 16MB
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = MAX_FILE_SIZE

# Load environment variables
def get_env_variable(var_name: str) -> str:
    """Safely get environment variable with error handling."""
    value = os.environ.get(var_name)
    if value is None:
        raise ValueError(f"Environment variable {var_name} is not set")
    return value

try:
    # API Keys configuration
    os.environ["GOOGLE_API_KEY"] = get_env_variable("GEMINI_API_KEY")
    os.environ["GEMINI_API_KEY"] = get_env_variable("GEMINI_API_KEY")
    os.environ["SERPER_API_KEY"] = get_env_variable("SERPER_API_KEY")
except ValueError as e:
    logger.error(f"Configuration error: {e}")
    raise

# Initialize LLM
llm = LLM(
    model="gemini/gemini-1.5-flash",
    temperature=0.7,
    timeout=120,
    max_tokens=8000,
)

# Initialize tools
search_tool = SerperDevTool()

def create_pdf_tool(pdf_path: Optional[str] = None) -> PDFSearchTool:
    """Create a PDFSearchTool with optional PDF path."""
    config = {
        'llm': {
            'provider': 'google',
            'config': {
                'model': 'gemini-1.5-flash',
            },
        },
        'embedder': {
            'provider': 'google',
            'config': {
                'model': 'models/embedding-001',
                'task_type': 'retrieval_document',
            },
        },
    }
    
    if pdf_path:
        return PDFSearchTool(pdf=pdf_path, config=config)
    return PDFSearchTool(config=config)

def allowed_file(filename: str) -> bool:
    """Check if the file extension is allowed."""
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

def generate_safe_filename(filename: str) -> str:
    """Generate a safe filename with timestamp and hash."""
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    file_hash = hashlib.md5(filename.encode()).hexdigest()[:10]
    ext = filename.rsplit('.', 1)[1].lower()
    return f"upload_{timestamp}_{file_hash}.{ext}"

class FlashcardGenerator:
    def __init__(self, topic: str, pdf_path: Optional[str] = None):
        self.topic = topic
        self.pdf_path = pdf_path
        self.researcher = self._create_researcher()
        self.writer = self._create_writer()

    def _create_researcher(self) -> Agent:
        """Create the researcher agent with appropriate tools."""
        tools = [search_tool]
        if self.pdf_path:
            tools.append(create_pdf_tool(self.pdf_path))
        
        return Agent(
            role='Chercheur de Sujets',
            goal=dedent(f"""Trouver les informations les plus pertinentes et précises sur {self.topic}
            en utilisant l'API SerpApi et en analysant les PDFs fournis."""),
            backstory=dedent("""Un chercheur expert spécialisé dans la collecte d'informations sur divers sujets.
            Capable d'utiliser l'API SerpApi pour des recherches précises et d'analyser des documents PDF."""),
            tools=tools,
            llm=llm,
            verbose=True,
            allow_delegation=False
        )

    def _create_writer(self) -> Agent:
        """Create the writer agent."""
        return Agent(
            role='Rédacteur de Flashcards',
            goal=dedent("""Créer des flashcards claires et concises en format question-réponse
            basées sur les informations fournies par le Chercheur."""),
            backstory=dedent("""Un expert en pédagogie et en création de matériel d'apprentissage.
            Capable de transformer des informations complexes en flashcards simples et mémorisables."""),
            llm=llm,
            verbose=True,
            allow_delegation=False
        )

    def create_research_task(self) -> Task:
        """Create the research task."""
        description = f"""Effectuer une recherche approfondie sur le sujet '{self.topic}'."""
        if self.pdf_path:
            description += f" Analyser également le contenu du PDF fourni: {self.pdf_path}"
        
        return Task(
            description=dedent(description),
            expected_output="Une liste d'informations pertinentes sur le sujet.",
            agent=self.researcher
        )

    def create_flashcard_task(self, research_task: Task) -> Task:
        """Create the flashcard creation task."""
        return Task(
            description=dedent("""Transformer les informations fournies par le Chercheur
            en une série de flashcards au format JSON. je veux une vingtaine de flashcard très robuste et difficile . Chaque flashcard doit avoir une question
            d'un côté et une réponse concise de l'autre. Les réponses doivent être claires et informatives."""),
            expected_output="Une liste de flashcards au format JSON.",
            agent=self.writer,
            context=[research_task]
        )

    def generate(self) -> List[Dict[str, str]]:
        """Generate flashcards using the crew workflow."""
        research_task = self.create_research_task()
        flashcard_task = self.create_flashcard_task(research_task)

        crew = Crew(
            agents=[self.researcher, self.writer],
            tasks=[research_task, flashcard_task],
            process=Process.sequential,
            verbose=True
        )

        result = crew.kickoff()
        return self.extract_json_from_result(result.tasks_output[-1].raw)

    @staticmethod
    def extract_json_from_result(result_text: str) -> List[Dict[str, str]]:
        """Extract and validate JSON from the result text."""
        try:
            json_start = result_text.find('[')
            json_end = result_text.rfind(']') + 1
            if json_start == -1 or json_end == 0:
                raise ValueError("JSON non trouvé dans le résultat")
            
            json_str = result_text[json_start:json_end]
            flashcards = json.loads(json_str)
            
            # Validate flashcard format
            for card in flashcards:
                if not isinstance(card, dict) or 'question' not in card or 'answer' not in card:
                    raise ValueError("Format de flashcard invalide")
            
            return flashcards
        except (json.JSONDecodeError, ValueError) as e:
            logger.error(f"Error extracting JSON: {str(e)}")
            raise ValueError(f"Erreur lors de l'extraction du JSON : {str(e)}")

@app.route('/')
def index():
    """Render the main page."""
    return render_template('index.html')

@app.route('/generate', methods=['POST'])
def generate_flashcards():
    """Handle flashcard generation requests."""
    try:
        # Validate topic
        topic = request.form.get('topic')
        if not topic:
            return jsonify({'error': 'Veuillez entrer un sujet.'}), 400

        # Handle file upload
        pdf_path = None
        if 'file' in request.files:
            file = request.files['file']
            if file and file.filename:
                if not allowed_file(file.filename):
                    return jsonify({'error': 'Format de fichier non supporté. Veuillez utiliser un PDF.'}), 400
                
                if file.content_length and file.content_length > MAX_FILE_SIZE:
                    return jsonify({'error': 'Le fichier est trop volumineux. Maximum 16MB.'}), 400
                
                # Generate safe filename and save file
                safe_filename = generate_safe_filename(file.filename)
                pdf_path = os.path.join(app.config['UPLOAD_FOLDER'], safe_filename)
                file.save(pdf_path)
                logger.info(f"File saved: {pdf_path}")

        try:
            # Generate flashcards
            generator = FlashcardGenerator(topic, pdf_path)
            flashcards = generator.generate()
            
            return jsonify({
                'success': True,
                'flashcards': flashcards
            })
        
        finally:
            # Clean up PDF file
            if pdf_path and os.path.exists(pdf_path):
                os.remove(pdf_path)
                logger.info(f"File cleaned up: {pdf_path}")

    except Exception as e:
        logger.error(f"Error generating flashcards: {str(e)}")
        return jsonify({'error': str(e)}), 500

@app.errorhandler(413)
def request_entity_too_large(error):
    """Handle file size limit exceeded error."""
    return jsonify({'error': 'Le fichier est trop volumineux. Maximum 16MB.'}), 413

if __name__ == '__main__':
    app.run(debug=True)