File size: 20,200 Bytes
5347681
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
import streamlit as st
import networkx as nx
import plotly.graph_objects as go
from dotenv import load_dotenv
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
from typing import List, Dict, Optional
import json
import pandas as pd
import time
from datetime import datetime
import random
import re
from PIL import Image
import logging

# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Load environment variables
load_dotenv()

AI71_BASE_URL = "https://api.ai71.ai/v1/"
AI71_API_KEY = "api71-api-92fc2ef9-9f3c-47e5-a019-18e257b04af2"

# Initialize the Falcon model
chat = ChatOpenAI(
    model="tiiuae/falcon-180B-chat",
    api_key=AI71_API_KEY,
    base_url=AI71_BASE_URL,
    temperature=0.7,
)

class RoadmapStep(BaseModel):
    title: str
    description: str
    resources: List[Dict[str, str]] = Field(default_factory=list)
    estimated_time: str
    how_to_use: Optional[str] = None

class Roadmap(BaseModel):
    steps: Dict[str, RoadmapStep] = Field(default_factory=dict)

def clean_json(content):
    # Remove any leading or trailing whitespace
    content = content.strip()
    
    # Ensure the content starts and ends with curly braces
    if not content.startswith('{'):
        content = '{' + content
    if not content.endswith('}'):
        content = content + '}'
    
    # Remove any newline characters and extra spaces
    content = ' '.join(content.split())
    
    # Escape any unescaped double quotes within string values
    content = re.sub(r'(?<!\\)"(?=(?:(?:[^"]*"){2})*[^"]*$)', r'\"', content)
    
    return content

def ensure_valid_json(content):
    # First, apply our existing cleaning function
    content = clean_json(content)
    
    # Use regex to find and fix unquoted property names
    pattern = r'(\{|\,)\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*:'
    content = re.sub(pattern, r'\1 "\2":', content)
    
    # Replace single quotes with double quotes
    content = content.replace("'", '"')
    
    # Attempt to parse the JSON to catch any remaining issues
    try:
        json_obj = json.loads(content)
        return json.dumps(json_obj)  # Return a properly formatted JSON string
    except json.JSONDecodeError as e:
        # If we still can't parse it, log the error and return None
        logger.error(f"Failed to parse JSON after cleaning: {str(e)}")
        logger.debug(f"Problematic JSON: {content}")
        return None

def generate_roadmap(topic):
    levels = [
        "knowledge",
        "comprehension",
        "application",
        "analysis",
        "synthesis",
        "evaluation"
    ]

    roadmap = Roadmap()

    for level in levels:
        try:
            logger.info(f"Generating roadmap step for topic: {topic} at {level} level")
            step = generate_simplified_step(topic, level, chat)
            roadmap.steps[level] = step
            logger.info(f"Added step for {level} level")
            
        except Exception as e:
            logger.error(f"Error in generate_roadmap for {level}: {str(e)}")
            step = create_fallback_step(topic, level, chat)
            roadmap.steps[level] = step

    logger.info("Roadmap generation complete")
    return roadmap

def generate_diverse_resources(topic, level):
    encoded_topic = topic.replace(' ', '+')
    encoded_level = level.replace(' ', '+')
    
    resource_templates = [
        {"title": "Wikipedia", "url": f"https://en.wikipedia.org/wiki/{topic.replace(' ', '_')}"},
        {"title": "YouTube Overview", "url": f"https://www.youtube.com/results?search_query={encoded_topic}+{encoded_level}"},
        {"title": "Coursera Courses", "url": f"https://www.coursera.org/search?query={encoded_topic}"},
        {"title": "edX Courses", "url": f"https://www.edx.org/search?q={encoded_topic}"},
        {"title": "Brilliant", "url": f"https://brilliant.org/search/?q={encoded_topic}"},
        {"title": "Google Scholar", "url": f"https://scholar.google.com/scholar?q={encoded_topic}"},
        {"title": "MIT OpenCourseWare", "url": f"https://ocw.mit.edu/search/?q={encoded_topic}"},
        {"title": "Khan Academy", "url": f"https://www.khanacademy.org/search?query={encoded_topic}"},
        {"title": "TED Talks", "url": f"https://www.ted.com/search?q={encoded_topic}"},
        {"title": "arXiv Papers", "url": f"https://arxiv.org/search/?query={encoded_topic}&searchtype=all"},
        {"title": "ResearchGate", "url": f"https://www.researchgate.net/search/publication?q={encoded_topic}"},
        {"title": "Academic Earth", "url": f"https://academicearth.org/search/?q={encoded_topic}"},
    ]
    
    # Randomly select 5-7 resources
    num_resources = random.randint(5, 7)
    selected_resources = random.sample(resource_templates, num_resources)
    
    return selected_resources

def create_fallback_step(topic, level, chat):
    def generate_component(prompt, default_value):
        try:
            response = chat.invoke([{"role": "system", "content": prompt}])
            return response.content.strip() or default_value
        except Exception as e:
            logger.error(f"Error generating component: {str(e)}")
            return default_value

    # Generate title
    title_prompt = f"Create a concise title (max 10 words) for a study step about {topic} at the {level} level of Bloom's Taxonomy."
    default_title = f"{level.capitalize()} Step for {topic}"
    title = generate_component(title_prompt, default_title)

    # Generate description
    description_prompt = f"""Write a detailed description (500-700 words) for a study step about {topic} at the {level} level of Bloom's Taxonomy. 

    Explain what this step entails, how the user should approach it, and why it's important for mastering the topic at this level. 

    The description should be specific to {topic} and not a generic explanation of the Bloom's Taxonomy level."""
    default_description = f"In this step, you will focus on {topic} at the {level} level. This involves understanding key concepts and theories related to {topic}. Engage with the provided resources to build a strong foundation."
    description = generate_component(description_prompt, default_description)

    # Generate estimated time
    time_prompt = f"Estimate the time needed to complete a study step about {topic} at the {level} level of Bloom's Taxonomy. Provide the answer in a format like '3-4 days' or '1-2 weeks'."
    default_time = "3-4 days"
    estimated_time = generate_component(time_prompt, default_time)

    # Generate how to use
    how_to_use_prompt = f"""Write a paragraph (100-150 words) on how to effectively use the {level} level of Bloom's Taxonomy when studying {topic}. 

    Include tips and strategies specific to {topic} at this {level} level."""
    default_how_to_use = f"Explore the provided resources and take notes on key concepts related to {topic}. Practice explaining these concepts in your own words to reinforce your understanding at the {level} level."
    how_to_use = generate_component(how_to_use_prompt, default_how_to_use)

    return RoadmapStep(
        title=title,
        description=description,
        resources=generate_diverse_resources(topic, level),
        estimated_time=estimated_time,
        how_to_use=how_to_use
    )

def create_interactive_graph(roadmap):
    G = nx.DiGraph()
    color_map = {
        'Knowledge': '#FF6B6B',
        'Comprehension': '#4ECDC4',
        'Application': '#45B7D1',
        'Analysis': '#FFA07A',
        'Synthesis': '#98D8C8',
        'Evaluation': '#F9D56E'
    }
    
    for i, (level, step) in enumerate(roadmap.steps.items()):
        G.add_node(step.title, level=level.capitalize(), pos=(i, -i))
    
    pos = nx.get_node_attributes(G, 'pos')
    
    edge_trace = go.Scatter(
        x=[], y=[],
        line=dict(width=2, color='#888'),
        hoverinfo='none',
        mode='lines')

    node_trace = go.Scatter(
        x=[], y=[],
        mode='markers+text',
        hoverinfo='text',
        marker=dict(
            showscale=False,
            color=[],
            size=30,
            line_width=2
        ),
        text=[],
        textposition="top center"
    )

    for node in G.nodes():
        x, y = pos[node]
        node_trace['x'] += (x,)
        node_trace['y'] += (y,)
        node_info = f"{node}<br>Level: {G.nodes[node]['level']}"
        node_trace['text'] += (node_info,)
        node_trace['marker']['color'] += (color_map.get(G.nodes[node]['level'], '#CCCCCC'),)

    fig = go.Figure(data=[edge_trace, node_trace],
                    layout=go.Layout(
                        title='Interactive Study Roadmap',
                        titlefont_size=16,
                        showlegend=False,
                        hovermode='closest',
                        margin=dict(b=20,l=5,r=5,t=40),
                        annotations=[dict(
                            text="",
                            showarrow=False,
                            xref="paper", yref="paper",
                            x=0.005, y=-0.002
                        )],
                        xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
                        yaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
                        plot_bgcolor='rgba(0,0,0,0)',
                        paper_bgcolor='rgba(0,0,0,0)'
                    ))
    
    # Add a color legend
    for level, color in color_map.items():
        fig.add_trace(go.Scatter(
            x=[None], y=[None],
            mode='markers',
            marker=dict(size=10, color=color),
            showlegend=True,
            name=level
        ))
    
    fig.update_layout(legend=dict(
        orientation="h",
        yanchor="bottom",
        y=1.02,
        xanchor="right",
        x=1
    ))
    
    return fig

def get_user_progress(roadmap):
    if 'user_progress' not in st.session_state:
        st.session_state.user_progress = {}
    
    for level, step in roadmap.steps.items():
        if step.title not in st.session_state.user_progress:
            st.session_state.user_progress[step.title] = 0
    
    return st.session_state.user_progress

def update_user_progress(step_title, progress):
    st.session_state.user_progress[step_title] = progress

def calculate_overall_progress(progress_dict):
    if not progress_dict:
        return 0
    total_steps = len(progress_dict)
    completed_steps = sum(1 for progress in progress_dict.values() if progress == 100)
    return (completed_steps / total_steps) * 100

def generate_simplified_step(topic, level, chat):
    prompt = f"""Create a detailed study step for the topic: {topic} at the {level} level of Bloom's Taxonomy.

    

    Provide:

    1. A descriptive title (max 10 words)

    2. A detailed description (500-700 words) explaining what this step entails, how the user should approach it, and why it's important for mastering the topic at this level. The description should be specific to {topic} and not a generic explanation of the Bloom's Taxonomy level.

    3. Estimated time for completion (e.g., 3-4 days, 1-2 weeks, etc.)

    4. A paragraph (100-150 words) on how to use this level effectively, including tips and strategies specific to {topic} at this {level} level



    Format your response as a valid JSON object with the following structure:

    {{

        "title": "Step title",

        "description": "Step description",

        "estimated_time": "Estimated time",

        "how_to_use": "Paragraph on how to use this level effectively"

    }}

    """
    
    try:
        response = chat.invoke([{"role": "system", "content": prompt}])
        valid_json = ensure_valid_json(response.content)
        if valid_json is None:
            raise ValueError("Failed to create valid JSON")
        
        step_dict = json.loads(valid_json)
        
        # Generate diverse resources
        resources = generate_diverse_resources(topic, level)
        
        return RoadmapStep(
            title=step_dict["title"],
            description=step_dict["description"],
            resources=resources,
            estimated_time=step_dict["estimated_time"],
            how_to_use=step_dict["how_to_use"]
        )
    except Exception as e:
        logger.error(f"Error in generate_simplified_step for {level}: {str(e)}")
        return create_fallback_step(topic, level, chat)



def display_step(step, level, user_progress):
    with st.expander(f"{level.capitalize()}: {step.title}"):
        st.write(f"**Description:** {step.description}")
        st.write(f"**Estimated Time:** {step.estimated_time}")
        st.write("**Resources:**")
        for resource in step.resources:
            st.markdown(f"- [{resource['title']}]({resource['url']})")
            if 'contribution' in resource:
                st.write(f"  *{resource['contribution']}*")
        
        # Check if how_to_use exists before displaying it
        if step.how_to_use:
            st.write("**How to use this level effectively:**")
            st.write(step.how_to_use)
        
        progress = st.slider(f"Progress for {step.title}", 0, 100, user_progress.get(step.title, 0), key=f"progress_{level}")
        update_user_progress(step.title, progress)

def main():
    st.set_page_config(page_title="S.H.E.R.L.O.C.K. Study Roadmap Generator", layout="wide")

    # Custom CSS for dark theme
    st.markdown("""

        <style>

        .stApp {

            background-color: #1E1E1E;

            color: #FFFFFF;

        }

        .stButton>button {

            background-color: #4CAF50;

            color: white;

            border-radius: 5px;

        }

        .stProgress > div > div > div > div {

            background-color: #4CAF50;

        }

        .streamlit-expanderHeader {

            background-color: #2E2E2E;

            color: #FFFFFF;

        }

        .streamlit-expanderContent {

            background-color: #2E2E2E;

            color: #FFFFFF;

        }

        </style>

    """, unsafe_allow_html=True)

    st.title("🧠 S.H.E.R.L.O.C.K. Study Roadmap Generator")
    st.write("Generate a comprehensive study roadmap based on first principles for any topic.")

    # Sidebar
    with st.sidebar:
        st.image("https://placekitten.com/300/200", caption="S.H.E.R.L.O.C.K.", use_column_width=True)
        st.markdown("""

        ## About S.H.E.R.L.O.C.K.

        **S**tudy **H**elper for **E**fficient **R**oadmaps and **L**earning **O**ptimization using **C**omprehensive **K**nowledge



        S.H.E.R.L.O.C.K. is your AI-powered study companion, designed to create personalized learning roadmaps for any topic. It breaks down complex subjects into manageable steps, ensuring a comprehensive understanding from fundamentals to advanced concepts.

        """)

        st.subheader("πŸ“‹ Todo List")
        if 'todos' not in st.session_state:
            st.session_state.todos = []
        
        new_todo = st.text_input("Add a new todo:")
        if st.button("Add Todo", key="add_todo"):
            if new_todo:
                st.session_state.todos.append({"task": new_todo, "completed": False})
                st.success("Todo added successfully!")
            else:
                st.warning("Please enter a todo item.")
        
        for i, todo in enumerate(st.session_state.todos):
            col1, col2, col3 = st.columns([0.05, 0.8, 0.15])
            with col1:
                todo['completed'] = st.checkbox("", todo['completed'], key=f"todo_{i}")
            with col2:
                st.write(todo['task'], key=f"todo_text_{i}")
            with col3:
                if st.button("πŸ—‘οΈ", key=f"delete_{i}", help="Delete todo"):
                    st.session_state.todos.pop(i)
                    st.experimental_rerun()

        st.subheader("⏱️ Pomodoro Timer")
        pomodoro_duration = st.slider("Pomodoro Duration (minutes)", 1, 60, 25)
        if st.button("Start Pomodoro"):
            progress_bar = st.progress(0)
            for i in range(pomodoro_duration * 60):
                time.sleep(1)
                progress_bar.progress((i + 1) / (pomodoro_duration * 60))
            st.success("Pomodoro completed!")
            if 'achievements' not in st.session_state:
                st.session_state.achievements = set()
            st.session_state.achievements.add("Consistent Learner")

    topic = st.text_input("πŸ“š Enter the topic you want to master:")
    
    if st.button("πŸš€ Generate Roadmap"):
        if topic:
            with st.spinner("🧠 Generating your personalized study roadmap..."):
                try:
                    logger.info(f"Starting roadmap generation for topic: {topic}")
                    roadmap = generate_roadmap(topic)
                    if roadmap and roadmap.steps:
                        logger.info("Roadmap generated successfully")
                        st.session_state.current_roadmap = roadmap
                        st.session_state.current_topic = topic
                        st.success("Roadmap generated successfully!")
                    else:
                        logger.warning("Generated roadmap is empty or invalid")
                        st.error("Failed to generate a valid roadmap. Please try again with a different topic.")
                except Exception as e:
                    logger.error(f"Error during roadmap generation: {str(e)}", exc_info=True)
                    st.error(f"An error occurred while generating the roadmap: {str(e)}")
    
    if 'current_roadmap' in st.session_state:
        st.subheader(f"πŸ“Š Study Roadmap for: {st.session_state.current_topic}")
        
        roadmap = st.session_state.current_roadmap
        fig = create_interactive_graph(roadmap)
        fig.update_layout(
            plot_bgcolor='rgba(0,0,0,0)',
            paper_bgcolor='rgba(0,0,0,0)',
            font_color='#FFFFFF'
        )
        st.plotly_chart(fig, use_container_width=True)
        
        user_progress = get_user_progress(roadmap)
        
        levels_description = {
            "knowledge": "Understanding and remembering basic facts and concepts",
            "comprehension": "Grasping the meaning and interpreting information",
            "application": "Using knowledge in new situations",
            "analysis": "Breaking information into parts and examining relationships",
            "synthesis": "Combining elements to form a new whole",
            "evaluation": "Making judgments based on criteria and standards"
        }
        
        for level, step in roadmap.steps.items():
            st.header(f"{level.capitalize()} Level")
            st.write(f"**Description:** {levels_description[level]}")
            st.write("**How to master this level:**")
            st.write(f"To master the {level} level, focus on {levels_description[level].lower()}. Engage with the resources provided, practice applying the concepts, and gradually build your understanding. Remember that mastery at this level is crucial before moving to the next.")
            display_step(step, level, user_progress)
        
        overall_progress = calculate_overall_progress(user_progress)
        st.progress(overall_progress / 100)
        st.write(f"Overall progress: {overall_progress:.2f}%")
        
        roadmap_json = json.dumps(roadmap.dict(), indent=2)
        st.download_button(
            label="πŸ“₯ Download Roadmap as JSON",
            data=roadmap_json,
            file_name="study_roadmap.json",
            mime="application/json"
        )

if __name__ == "__main__":
    main()