CoolFace
Apppublic

SolusOps/Study-with-ChampAI

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
app.py243 linesDownload Raw Back to root
1from __future__ import annotations2import gradio as gr3from config.settings import SETTINGS4from services.model_router import ModelRouter5from quiz.models import QuizSession6from quiz.engine import QuizEngine7from storage.local_db import DB8from storage.mastery import MasteryStore9from agents.orchestrator import LearningOrchestrator10from ui.theme import build_champ_theme, CHAMP_CSS11 12print("[StudyWithChampAI] Initializing...")13_db = DB(SETTINGS.db_path)14_mastery_store = MasteryStore(_db)15_router = ModelRouter(16    ocr_model=SETTINGS.ocr_model_id,17    reasoning_model=SETTINGS.reasoning_model_id,18    multilingual_model=SETTINGS.multilingual_model_id,19    speech_model=SETTINGS.speech_model_id,20    hf_api_key=SETTINGS.hf_api_key,21    featherless_api_key=SETTINGS.featherless_api_key,22    max_tokens=SETTINGS.max_new_tokens,23    temperature=SETTINGS.temperature,24)25_orchestrator = LearningOrchestrator(26    _router, _db, _mastery_store, questions_per_quest=SETTINGS.questions_per_quest)27_engine = QuizEngine()28_QUEST_CACHE: list = []29_SOURCE_TEXT_CACHE: str = ""  # retained for revision quest generation30print("[StudyWithChampAI] Ready.")31 32 33def handle_process(file_obj, pasted_text: str, audio_obj, language: str):34    global _QUEST_CACHE, _SOURCE_TEXT_CACHE35    yield "Processing...", []36    try:37        quests = None38        if audio_obj is not None:39            yield "Transcribing voice input with Whisper...", []40            with open(audio_obj, "rb") as f:41                audio_bytes = f.read()42            quests = _orchestrator.process_voice(audio_bytes, language)43        elif file_obj is not None:44            yield "Processing file with MiniCPM-V...", []45            quests = _orchestrator.process_file(file_obj.name, language)46        elif pasted_text and pasted_text.strip():47            _SOURCE_TEXT_CACHE = pasted_text.strip()48            yield "Extracting concepts with MiniCPM-V...", []49            quests = _orchestrator.process_text(pasted_text.strip(), language)50        else:51            yield "No input provided. Upload a file, paste text, or record audio.", []; return52 53        _QUEST_CACHE = quests54        count = sum(len(q.questions) for q in quests)55        yield (f"Generated {len(quests)} quests with {count} questions "56               f"({count - len(quests)} regular + {len(quests)} boss battles). Go to **Quest Map**!",57               [q.name for q in quests])58    except Exception as exc:59        yield f"Error: {exc}", []60 61 62def handle_select_quest(quest_name: str):63    quest = next((q for q in _QUEST_CACHE if q.name == quest_name), None)64    if quest is None:65        return None, "Quest not found."66    session = QuizSession(quest_name=quest.name)67    session.questions = quest.questions68    return session, f"Quest '{quest_name}' ready! Go to Battle Mode."69 70 71def handle_answer(selected_option: str, session: QuizSession, question):72    if session is None or question is None:73        return "No active session.", "", "", "", "", gr.update(visible=False), gr.update(visible=True), session74    selected_idx = question.options.index(selected_option) if selected_option in question.options else -175    result = _engine.submit_next(session, selected_idx)76    tutor_hint = ""77    if not result.is_correct:78        tutor_hint = _orchestrator.get_tutor_hint(question, selected_option)79    boss_label = "๐Ÿ‘‘ BOSS BATTLE! " if result.was_boss else ""80    return (81        f"{boss_label}{'โœ… CORRECT!' if result.is_correct else 'โŒ WRONG!'}",82        f"+{result.xp_delta} XP" if result.xp_delta > 0 else "",83        result.streak_label,84        question.explanation,85        tutor_hint,86        gr.update(visible=True),87        gr.update(visible=False),88        session,89    )90 91 92def handle_translate(hint_text: str, target_lang: str):93    if not hint_text:94        return "_No hint to translate._"95    return _orchestrator.translate_hint(hint_text, target_lang)96 97 98def handle_session_complete(session: QuizSession):99    if session is None:100        return "No session.", "", "", "", ""101    result = _orchestrator.complete_quest(session)102    all_mastery = _mastery_store.all_mastery()103    mastery_lines = "\n".join(104        f"- **{t}**: {'โ–ˆ' * int(v * 10)}{'โ–‘' * (10 - int(v * 10))} {int(v*100)}%"105        for t, v in all_mastery.items())106    weak = result.get("weak_topics", [])107    weak_lines = ("\n".join(f"- {t} โ€” revision needed" for t in weak)108                  if weak else "_All topics strong!_ ๐Ÿ†")109    from quiz.scoring import compute_grade110    grade = compute_grade(session.score, len(session.questions))111    return (f"### {session.score}/{len(session.questions)} correct",112            f"### Grade: {grade}", f"### XP Earned: {session.xp_earned}",113            mastery_lines or "_No mastery data yet._", weak_lines)114 115 116def handle_revision_quest(session: QuizSession):117    """Generate an adaptive revision quest for weak topics and add it to the cache."""118    global _QUEST_CACHE119    weak = _mastery_store.weak_topics()120    if not weak:121        return "No weak topics found โ€” all strong!", [q.name for q in _QUEST_CACHE]122    try:123        revision = _orchestrator.generate_revision_quest(weak, source_text=_SOURCE_TEXT_CACHE)124        _QUEST_CACHE.append(revision)125        return (f"Revision quest unlocked: **{revision.name}**\nGo to Quest Map!",126                [q.name for q in _QUEST_CACHE])127    except Exception as exc:128        return f"Error generating revision quest: {exc}", [q.name for q in _QUEST_CACHE]129 130 131def build_app() -> gr.Blocks:132    with gr.Blocks(theme=build_champ_theme(), css=CHAMP_CSS, title="StudyWithChampAI") as demo:133        gr.Markdown("# StudyWithChampAI\n### Turn notes into quests. Turn studying into progression.")134 135        quest_names_state = gr.State([])136        session_state = gr.State(None)137        current_q_state = gr.State(None)138        q_idx_state = gr.State(0)139 140        with gr.Tab("Import Material"):141            gr.Markdown("### Upload PDF, image, voice, or paste text.\nMiniCPM-V reads and understands your material.")142            with gr.Row():143                file_input = gr.File(label="Upload File", file_types=[".pdf",".txt",".png",".jpg",".jpeg"])144                audio_input = gr.Audio(label="Record Voice", type="filepath", sources=["microphone"])145            text_input = gr.Textbox(label="Or paste notes here", lines=6, placeholder="Paste study material...")146            lang_dropdown = gr.Dropdown(choices=["English","Hindi","Bengali","Spanish","French"],147                                        value="English", label="Quiz Language")148            process_btn = gr.Button("Generate Quests โš”๏ธ", variant="primary", size="lg")149            status_md = gr.Markdown("_Ready._")150            process_btn.click(fn=handle_process,151                              inputs=[file_input, text_input, audio_input, lang_dropdown],152                              outputs=[status_md, quest_names_state])153 154        with gr.Tab("Quest Map"):155            gr.Markdown("### Select a quest to begin.")156            quest_radio = gr.Radio(choices=[], label="Available Quests", interactive=True)157            start_btn = gr.Button("Enter Battle โš”๏ธ", variant="primary")158            quest_status_md = gr.Markdown("")159            quest_names_state.change(fn=lambda n: gr.update(choices=n, visible=bool(n)),160                                     inputs=[quest_names_state], outputs=[quest_radio])161            start_btn.click(fn=handle_select_quest, inputs=[quest_radio],162                            outputs=[session_state, quest_status_md])163 164        with gr.Tab("Battle Mode"):165            gr.Markdown("### Answer to earn XP! Boss battle always awaits at quest end.")166            question_md = gr.Markdown("_Select a quest first._")167            progress_md = gr.Markdown("")168            answer_radio = gr.Radio(choices=[], label="Your Answer", interactive=True)169            submit_btn = gr.Button("Submit Answer", variant="primary")170 171            with gr.Group(visible=False) as feedback_group:172                result_md = gr.Markdown("")173                xp_md = gr.Markdown("")174                streak_md = gr.Markdown("")175                explanation_md = gr.Markdown("")176                tutor_md = gr.Markdown("")177                with gr.Row():178                    translate_lang = gr.Dropdown(choices=["Hindi","Bengali","Spanish","French"],179                                                 value="Hindi", label="Translate hint to", scale=1)180                    translate_btn = gr.Button("Translate ๐ŸŒ", scale=1)181                translated_md = gr.Markdown("")182                next_btn = gr.Button("Next Question โ†’")183 184            def load_question(session, idx):185                if session is None:186                    return "_No active session._", "", gr.update(choices=[], visible=False), gr.update(visible=False), None, idx187                if idx >= len(session.questions):188                    return "_Quest complete! Go to Results tab._", "", gr.update(choices=[], visible=False), gr.update(visible=False), None, idx189                q = session.questions[idx]190                header = "๐Ÿ‘‘ **BOSS BATTLE** ๐Ÿ‘‘\n\n" if q.is_boss else ""191                return (f"{header}**Q{idx+1}/{len(session.questions)}**\n\n{q.text}",192                        f"Progress: {idx}/{len(session.questions)} | XP: {session.xp_earned}",193                        gr.update(choices=q.options, visible=True, value=None),194                        gr.update(visible=True), q, idx)195 196            session_state.change(fn=load_question, inputs=[session_state, q_idx_state],197                                 outputs=[question_md, progress_md, answer_radio, submit_btn, current_q_state, q_idx_state])198            submit_btn.click(fn=handle_answer, inputs=[answer_radio, session_state, current_q_state],199                             outputs=[result_md, xp_md, streak_md, explanation_md, tutor_md,200                                      feedback_group, submit_btn, session_state])201            translate_btn.click(fn=handle_translate, inputs=[tutor_md, translate_lang],202                                outputs=[translated_md])203 204            def advance_question(session, idx):205                new_idx = idx + 1206                if session is None or new_idx >= len(session.questions):207                    return ("_Quest complete! Go to Results._", "",208                            gr.update(choices=[], visible=False), gr.update(visible=False),209                            gr.update(visible=False), None, new_idx)210                q = session.questions[new_idx]211                header = "๐Ÿ‘‘ **BOSS BATTLE** ๐Ÿ‘‘\n\n" if q.is_boss else ""212                return (f"{header}**Q{new_idx+1}/{len(session.questions)}**\n\n{q.text}",213                        f"Progress: {new_idx}/{len(session.questions)} | XP: {session.xp_earned}",214                        gr.update(choices=q.options, visible=True, value=None),215                        gr.update(visible=True), gr.update(visible=False), q, new_idx)216 217            next_btn.click(fn=advance_question, inputs=[session_state, q_idx_state],218                           outputs=[question_md, progress_md, answer_radio, submit_btn,219                                    feedback_group, current_q_state, q_idx_state])220 221        with gr.Tab("Results"):222            gr.Markdown("## Quest Complete! ๐Ÿ†")223            score_md = gr.Markdown("")224            grade_md = gr.Markdown("")225            xp_total_md = gr.Markdown("")226            mastery_md = gr.Markdown("### Mastery Map")227            weak_md = gr.Markdown("")228            with gr.Row():229                finish_btn = gr.Button("View Results", variant="primary")230                revision_btn = gr.Button("โš”๏ธ Generate Revision Quest", variant="secondary")231            revision_status_md = gr.Markdown("")232            finish_btn.click(fn=handle_session_complete, inputs=[session_state],233                             outputs=[score_md, grade_md, xp_total_md, mastery_md, weak_md])234            revision_btn.click(fn=handle_revision_quest, inputs=[session_state],235                               outputs=[revision_status_md, quest_names_state])236 237    return demo238 239 240if __name__ == "__main__":241    app = build_app()242    app.launch(share=False, server_name="0.0.0.0", server_port=7860)243