clawed 2.1.0__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {clawed-2.1.0 → clawed-2.3.0}/PKG-INFO +8 -6
- {clawed-2.1.0 → clawed-2.3.0}/README.md +7 -5
- {clawed-2.1.0 → clawed-2.3.0}/clawed/__init__.py +1 -1
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/core.py +20 -1
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/memory/episodes.py +50 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/memory/loader.py +23 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/generate_lesson_bundle.py +93 -61
- clawed-2.3.0/clawed/agent_core/tools/search_my_materials.py +149 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/generate.py +7 -1
- {clawed-2.1.0 → clawed-2.3.0}/clawed/export_docx.py +209 -0
- clawed-2.3.0/clawed/export_handout.py +368 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/ingestor.py +249 -1
- {clawed-2.1.0 → clawed-2.3.0}/clawed/llm.py +78 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/memory_engine.py +180 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/models.py +117 -0
- clawed-2.3.0/clawed/prompts/admin_lesson_plan.txt +69 -0
- clawed-2.3.0/clawed/prompts/student_packet.txt +87 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/slide_images.py +54 -4
- {clawed-2.1.0 → clawed-2.3.0}/clawed/workspace.py +1 -0
- {clawed-2.1.0 → clawed-2.3.0}/pyproject.toml +1 -1
- clawed-2.1.0/clawed/agent_core/tools/search_my_materials.py +0 -97
- clawed-2.1.0/clawed/export_handout.py +0 -229
- {clawed-2.1.0 → clawed-2.3.0}/.gitignore +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/LICENSE +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/__main__.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/_legacy_gateway.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/__init__.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/approvals.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/autonomy.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/context.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/custom_tools.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/drive/__init__.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/drive/auth.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/drive/client.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/fake_llm.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/loop.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/memory/__init__.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/memory/curriculum.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/memory/curriculum_kb.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/memory/embeddings.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/memory/identity.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/memory/preferences.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/planner.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/prompt.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/scheduler.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/__init__.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/base.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/configure_profile.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/curriculum_map.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/drive_create_doc.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/drive_create_slides.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/drive_list.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/drive_organize.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/drive_read.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/drive_upload.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/export_document.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/gap_analysis.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/generate_assessment.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/generate_lesson.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/generate_materials.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/generate_unit.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/ingest_materials.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/parent_comm.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/read_heartbeat.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/read_workspace.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/request_approval.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/schedule_task.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/search_lessons.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/search_standards.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/student_insights.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/sub_packet.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/switch_model.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/agent_core/tools/update_soul.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/analytics.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/__init__.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/deps.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/__init__.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/chat.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/export.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/feedback.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/gateway_chat.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/generate.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/ingest.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/lessons.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/school.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/settings.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/routes/tools.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/server.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/static/app.js +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/static/style.css +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/static/widget.js +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/templates/analytics.html +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/templates/base.html +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/templates/dashboard.html +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/templates/generate.html +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/templates/index.html +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/templates/lesson.html +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/templates/profile.html +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/templates/settings.html +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/templates/stats.html +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/api/templates/students.html +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/assessment.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/asset_registry.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/auth/__init__.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/auth/google_auth.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/bot_state.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/chat.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/cli.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/cli_chat.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/__init__.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/_helpers.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/bot.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/config.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/config_llm.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/config_profile.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/export.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/generate_assessment.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/generate_unit.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/queue.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/schedule_cmd.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/sub.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/commands/workspace_cmd.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/config.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/corpus.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/curriculum_map.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/database.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/demo/__init__.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/demo/demo_assessment.json +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/demo/demo_lesson_science_g6.json +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/demo/demo_lesson_social_studies_g8.json +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/demo/demo_unit_plan.json +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/differentiation.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/doc_export.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/drive.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/evaluation.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/export_markdown.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/export_pdf.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/export_pptx.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/export_templates.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/export_theme.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/exporter.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/feedback.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/formats/__init__.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/formats/flipchart.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/formats/notebook.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/formats/xbk.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/gateway.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/gateway_response.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/generation.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/handlers/__init__.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/handlers/export.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/handlers/feedback.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/handlers/gaps.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/handlers/generate.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/handlers/ingest.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/handlers/misc.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/handlers/onboard.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/handlers/schedule.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/handlers/standards.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/improver.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/io.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/lesson.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/materials.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/mcp_server.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/model_router.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/onboarding.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/openclaw_plugin.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/parent_comm.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/persona.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/planner.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/504_accommodations.txt +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/assessment.txt +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/curriculum_gaps.txt +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/dbq_assessment.txt +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/differentiation.txt +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/formative_assessment.txt +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/iep_modification.txt +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/lesson_plan.txt +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/pacing_guide.txt +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/parent_note.txt +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/persona_extract.txt +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/quiz.txt +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/rubric.txt +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/sub_packet.txt +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/summative_assessment.txt +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/tiered_assignments.txt +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/unit_plan.txt +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/worksheet.txt +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/prompts/year_map.txt +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/quality.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/reading_report.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/router.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/sanitize.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/scheduler.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/school.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/search.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/__init__.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/art.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/base.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/computer_science.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/ela.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/foreign_language.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/history.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/library.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/math.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/music.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/physical_education.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/science.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/social_studies.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/skills/special_education.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/standards.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/state.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/state_standards.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/student_bot.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/student_cli.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/student_telegram_bot.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/sub_packet.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/task_queue.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/templates_lib.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/tools.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/transports/__init__.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/transports/cli.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/transports/openclaw.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/transports/student_telegram.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/transports/telegram.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/transports/web.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/tui.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/tui_chat.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/clawed/voice.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/eduagent/__init__.py +0 -0
- {clawed-2.1.0 → clawed-2.3.0}/eduagent/_compat.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clawed
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Claw-ED — personal AI teaching agent. Learns your voice, works while you sleep.
|
|
5
5
|
Project-URL: Homepage, https://github.com/SirhanMacx/Claw-ED
|
|
6
6
|
Project-URL: Documentation, https://github.com/SirhanMacx/Claw-ED#readme
|
|
@@ -79,15 +79,17 @@ Built on the OpenClaw agent framework. Open source. MIT license.
|
|
|
79
79
|
|
|
80
80
|
---
|
|
81
81
|
|
|
82
|
-
## What's new in v2.
|
|
82
|
+
## What's new in v2.3
|
|
83
83
|
|
|
84
|
-
**
|
|
84
|
+
**Three documents, not one.** Every lesson now generates three professional files in parallel:
|
|
85
85
|
|
|
86
|
-
**
|
|
86
|
+
1. **Student Packet** (4-6 page DOCX workbook) — Fill-in-the-blank guided notes, station sections with full primary source text and analysis questions, graphic organizer tables, exit ticket with sentence starters. This is what students hold in their hands.
|
|
87
|
+
2. **Admin Lesson Plan** (observation-ready DOCX) — Multi-column table with per-section teacher actions (scripted language), student actions, observer look-fors, and differentiation. Anticipated student responses and misconceptions with teacher corrections. Teacher content knowledge appendix.
|
|
88
|
+
3. **Slideshow** (PPTX) — Subject-themed slides with academic images from your own files first, then Library of Congress and Wikimedia. Vocabulary, source quotes, and section dividers on dedicated slides.
|
|
87
89
|
|
|
88
|
-
**Your files
|
|
90
|
+
**Your files are first-class.** Ingestion extracts images from your PPTX/DOCX files, catalogues YouTube links, and classifies every file by type. The agent tells you what you already have before generating.
|
|
89
91
|
|
|
90
|
-
**
|
|
92
|
+
**Pedagogical fingerprint.** "Teacher voice" means how you teach, not just how you sound. The persona captures source types, activity patterns, scaffolding moves, Do Now style, exit ticket format, and signature moves.
|
|
91
93
|
|
|
92
94
|
---
|
|
93
95
|
|
|
@@ -13,15 +13,17 @@ Built on the OpenClaw agent framework. Open source. MIT license.
|
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
## What's new in v2.
|
|
16
|
+
## What's new in v2.3
|
|
17
17
|
|
|
18
|
-
**
|
|
18
|
+
**Three documents, not one.** Every lesson now generates three professional files in parallel:
|
|
19
19
|
|
|
20
|
-
**
|
|
20
|
+
1. **Student Packet** (4-6 page DOCX workbook) — Fill-in-the-blank guided notes, station sections with full primary source text and analysis questions, graphic organizer tables, exit ticket with sentence starters. This is what students hold in their hands.
|
|
21
|
+
2. **Admin Lesson Plan** (observation-ready DOCX) — Multi-column table with per-section teacher actions (scripted language), student actions, observer look-fors, and differentiation. Anticipated student responses and misconceptions with teacher corrections. Teacher content knowledge appendix.
|
|
22
|
+
3. **Slideshow** (PPTX) — Subject-themed slides with academic images from your own files first, then Library of Congress and Wikimedia. Vocabulary, source quotes, and section dividers on dedicated slides.
|
|
21
23
|
|
|
22
|
-
**Your files
|
|
24
|
+
**Your files are first-class.** Ingestion extracts images from your PPTX/DOCX files, catalogues YouTube links, and classifies every file by type. The agent tells you what you already have before generating.
|
|
23
25
|
|
|
24
|
-
**
|
|
26
|
+
**Pedagogical fingerprint.** "Teacher voice" means how you teach, not just how you sound. The persona captures source types, activity patterns, scaffolding moves, Do Now style, exit ticket format, and signature moves.
|
|
25
27
|
|
|
26
28
|
---
|
|
27
29
|
|
|
@@ -17,7 +17,7 @@ if hasattr(sys.stderr, "reconfigure"):
|
|
|
17
17
|
except Exception:
|
|
18
18
|
pass
|
|
19
19
|
|
|
20
|
-
__version__ = "2.
|
|
20
|
+
__version__ = "2.3.0"
|
|
21
21
|
__author__ = "Jon Maccarello & Claw-ED contributors"
|
|
22
22
|
__description__ = "Personal AI teaching agent. Learns your voice, works while you sleep."
|
|
23
23
|
|
|
@@ -361,7 +361,18 @@ class Gateway:
|
|
|
361
361
|
soul_context=soul_context,
|
|
362
362
|
)
|
|
363
363
|
|
|
364
|
-
# 2c.
|
|
364
|
+
# 2c. Cross-session context threading — greet with continuity
|
|
365
|
+
last_session = memory_ctx.get("last_session_summary", "")
|
|
366
|
+
if last_session and not session_history:
|
|
367
|
+
system += (
|
|
368
|
+
"\n\n=== Last Session Context ===\n"
|
|
369
|
+
f"The teacher's last interaction was about: {last_session}\n"
|
|
370
|
+
"If this is a new conversation, greet them with continuity — e.g. "
|
|
371
|
+
'"Last time we worked on [topic]. Want to continue or start something new?"\n'
|
|
372
|
+
"=== End Last Session Context ===\n"
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
# 2d. Enhance prompt for multi-step planning requests
|
|
365
376
|
from clawed.agent_core.planner import build_planning_prompt, is_planning_request
|
|
366
377
|
|
|
367
378
|
if is_planning_request(message):
|
|
@@ -415,6 +426,14 @@ class Gateway:
|
|
|
415
426
|
except Exception:
|
|
416
427
|
pass
|
|
417
428
|
|
|
429
|
+
# 8. Maybe compress old episodes (runs every COMPRESSION_THRESHOLD episodes)
|
|
430
|
+
try:
|
|
431
|
+
from clawed.memory_engine import maybe_compress_episodes
|
|
432
|
+
|
|
433
|
+
maybe_compress_episodes(teacher_id)
|
|
434
|
+
except Exception:
|
|
435
|
+
pass
|
|
436
|
+
|
|
418
437
|
return result
|
|
419
438
|
|
|
420
439
|
# ------------------------------------------------------------------
|
|
@@ -96,3 +96,53 @@ class EpisodicMemory:
|
|
|
96
96
|
|
|
97
97
|
scored.sort(key=lambda x: x["similarity"], reverse=True)
|
|
98
98
|
return scored[:top_k]
|
|
99
|
+
|
|
100
|
+
def get_latest_episode(self, teacher_id: str) -> dict[str, Any] | None:
|
|
101
|
+
"""Return the most recent episode for a teacher, or None."""
|
|
102
|
+
with sqlite3.connect(self._db_path) as conn:
|
|
103
|
+
conn.row_factory = sqlite3.Row
|
|
104
|
+
row = conn.execute(
|
|
105
|
+
"SELECT text, metadata, created_at FROM episodes "
|
|
106
|
+
"WHERE teacher_id = ? ORDER BY created_at DESC LIMIT 1",
|
|
107
|
+
(teacher_id,),
|
|
108
|
+
).fetchone()
|
|
109
|
+
if not row:
|
|
110
|
+
return None
|
|
111
|
+
return {
|
|
112
|
+
"text": row["text"],
|
|
113
|
+
"metadata": json.loads(row["metadata"]),
|
|
114
|
+
"created_at": row["created_at"],
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
def count_episodes(self, teacher_id: str) -> int:
|
|
118
|
+
"""Return the total number of episodes stored for a teacher."""
|
|
119
|
+
with sqlite3.connect(self._db_path) as conn:
|
|
120
|
+
row = conn.execute(
|
|
121
|
+
"SELECT COUNT(*) FROM episodes WHERE teacher_id = ?",
|
|
122
|
+
(teacher_id,),
|
|
123
|
+
).fetchone()
|
|
124
|
+
return row[0] if row else 0
|
|
125
|
+
|
|
126
|
+
def get_all_episodes(
|
|
127
|
+
self,
|
|
128
|
+
teacher_id: str,
|
|
129
|
+
limit: int = 500,
|
|
130
|
+
offset: int = 0,
|
|
131
|
+
) -> list[dict[str, Any]]:
|
|
132
|
+
"""Return episodes in chronological order (oldest first)."""
|
|
133
|
+
with sqlite3.connect(self._db_path) as conn:
|
|
134
|
+
conn.row_factory = sqlite3.Row
|
|
135
|
+
rows = conn.execute(
|
|
136
|
+
"SELECT text, metadata, created_at FROM episodes "
|
|
137
|
+
"WHERE teacher_id = ? ORDER BY created_at ASC "
|
|
138
|
+
"LIMIT ? OFFSET ?",
|
|
139
|
+
(teacher_id, limit, offset),
|
|
140
|
+
).fetchall()
|
|
141
|
+
return [
|
|
142
|
+
{
|
|
143
|
+
"text": row["text"],
|
|
144
|
+
"metadata": json.loads(row["metadata"]),
|
|
145
|
+
"created_at": row["created_at"],
|
|
146
|
+
}
|
|
147
|
+
for row in rows
|
|
148
|
+
]
|
|
@@ -96,6 +96,28 @@ def load_memory_context(teacher_id: str, current_message: str) -> dict[str, Any]
|
|
|
96
96
|
except Exception as e:
|
|
97
97
|
logger.debug("Curriculum KB search failed: %s", e)
|
|
98
98
|
|
|
99
|
+
# Layer 6: Last session summary (cross-session context threading)
|
|
100
|
+
last_session_summary = ""
|
|
101
|
+
try:
|
|
102
|
+
from clawed.agent_core.memory.episodes import EpisodicMemory
|
|
103
|
+
|
|
104
|
+
mem = EpisodicMemory()
|
|
105
|
+
latest = mem.get_latest_episode(teacher_id)
|
|
106
|
+
if latest:
|
|
107
|
+
text = latest["text"]
|
|
108
|
+
# Extract teacher's message (first line is usually "Teacher: <msg>")
|
|
109
|
+
first_line = text.split("\n")[0].strip()
|
|
110
|
+
if first_line.startswith("Teacher: "):
|
|
111
|
+
topic = first_line[len("Teacher: "):]
|
|
112
|
+
else:
|
|
113
|
+
topic = first_line
|
|
114
|
+
# Truncate to a concise summary
|
|
115
|
+
if len(topic) > 150:
|
|
116
|
+
topic = topic[:147] + "..."
|
|
117
|
+
last_session_summary = topic
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logger.debug("Last session summary load failed: %s", e)
|
|
120
|
+
|
|
99
121
|
return {
|
|
100
122
|
"identity_summary": identity_summary,
|
|
101
123
|
"curriculum_summary": curriculum_summary,
|
|
@@ -104,4 +126,5 @@ def load_memory_context(teacher_id: str, current_message: str) -> dict[str, Any]
|
|
|
104
126
|
"preferences_summary": preferences_summary,
|
|
105
127
|
"autonomy_summary": autonomy_summary,
|
|
106
128
|
"curriculum_kb_context": curriculum_kb_context,
|
|
129
|
+
"last_session_summary": last_session_summary,
|
|
107
130
|
}
|
|
@@ -104,18 +104,32 @@ class GenerateLessonBundleTool:
|
|
|
104
104
|
topic=topic,
|
|
105
105
|
)
|
|
106
106
|
|
|
107
|
-
# ── Search
|
|
107
|
+
# ── Search for teacher's existing materials (assets + KB) ─────
|
|
108
108
|
kb_context = ""
|
|
109
109
|
kb_prompt_section = ""
|
|
110
|
+
|
|
111
|
+
# Asset-level search (complete files, YouTube links)
|
|
112
|
+
try:
|
|
113
|
+
from clawed.asset_registry import AssetRegistry
|
|
114
|
+
registry = AssetRegistry()
|
|
115
|
+
assets = registry.search_assets(context.teacher_id, topic, top_k=5)
|
|
116
|
+
yt_links = registry.get_youtube_links(context.teacher_id, topic, top_k=3)
|
|
117
|
+
if assets or yt_links:
|
|
118
|
+
kb_prompt_section = registry.format_asset_summary(assets, yt_links)
|
|
119
|
+
logger.info(
|
|
120
|
+
"Asset search found %d files, %d YouTube links for '%s'",
|
|
121
|
+
len(assets), len(yt_links), topic,
|
|
122
|
+
)
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.debug("Asset search failed: %s", e)
|
|
125
|
+
|
|
126
|
+
# KB chunk-level search (text excerpts)
|
|
110
127
|
try:
|
|
111
128
|
from clawed.agent_core.memory.curriculum_kb import CurriculumKB
|
|
112
129
|
kb = CurriculumKB()
|
|
113
130
|
kb_results = kb.search(context.teacher_id, topic, top_k=3)
|
|
114
131
|
if kb_results:
|
|
115
|
-
kb_parts = []
|
|
116
|
-
for r in kb_results:
|
|
117
|
-
if r.get("similarity", 0) > 0.1:
|
|
118
|
-
kb_parts.append(r)
|
|
132
|
+
kb_parts = [r for r in kb_results if r.get("similarity", 0) > 0.1]
|
|
119
133
|
if kb_parts:
|
|
120
134
|
kb_context = (
|
|
121
135
|
"\n\nRelevant materials from the teacher's files:\n"
|
|
@@ -124,20 +138,22 @@ class GenerateLessonBundleTool:
|
|
|
124
138
|
for r in kb_parts
|
|
125
139
|
)
|
|
126
140
|
)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
"The teacher has created content on this topic before. "
|
|
131
|
-
"Reference and build on their existing work:\n\n"
|
|
132
|
-
+ "\n\n".join(
|
|
133
|
-
f"From \"{r['doc_title']}\":\n{r['chunk_text'][:500]}"
|
|
134
|
-
for r in kb_parts
|
|
135
|
-
)
|
|
136
|
-
+ "\n\nUse these materials as a foundation. Reference the teacher's existing "
|
|
137
|
-
"lessons, reuse their graphic organizer formats, build on their approach. "
|
|
138
|
-
"If the teacher has taught this topic before, extend their work — don't "
|
|
139
|
-
"start from scratch."
|
|
141
|
+
chunk_section = "\n\n".join(
|
|
142
|
+
f"From \"{r['doc_title']}\":\n{r['chunk_text'][:500]}"
|
|
143
|
+
for r in kb_parts
|
|
140
144
|
)
|
|
145
|
+
if kb_prompt_section:
|
|
146
|
+
kb_prompt_section += "\n\n" + chunk_section
|
|
147
|
+
else:
|
|
148
|
+
kb_prompt_section = (
|
|
149
|
+
"Teacher's Existing Materials on This Topic\n"
|
|
150
|
+
"The teacher has created content on this topic before. "
|
|
151
|
+
"Reference and build on their existing work:\n\n"
|
|
152
|
+
+ chunk_section
|
|
153
|
+
+ "\n\nUse these materials as a foundation. "
|
|
154
|
+
"Reference the teacher's existing lessons, reuse their "
|
|
155
|
+
"graphic organizer formats, build on their approach."
|
|
156
|
+
)
|
|
141
157
|
logger.info("KB search found %d relevant chunks for '%s'", len(kb_parts), topic)
|
|
142
158
|
except Exception as e:
|
|
143
159
|
logger.debug("KB search failed: %s", e)
|
|
@@ -216,6 +232,44 @@ class GenerateLessonBundleTool:
|
|
|
216
232
|
except Exception:
|
|
217
233
|
pass # Review is best-effort, don't block on failure
|
|
218
234
|
|
|
235
|
+
# ── Generate student packet + admin plan in parallel ──────────
|
|
236
|
+
import asyncio
|
|
237
|
+
|
|
238
|
+
from clawed.llm import LLMClient
|
|
239
|
+
|
|
240
|
+
llm_client = LLMClient(config)
|
|
241
|
+
persona_ctx = persona.to_prompt_context()
|
|
242
|
+
student_packet = None
|
|
243
|
+
admin_plan = None
|
|
244
|
+
|
|
245
|
+
async def _gen_packet():
|
|
246
|
+
return await llm_client.generate_student_packet(
|
|
247
|
+
lesson_json_str, persona_context=persona_ctx,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
async def _gen_admin():
|
|
251
|
+
return await llm_client.generate_admin_plan(
|
|
252
|
+
lesson_json_str, persona_context=persona_ctx,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
results = await asyncio.gather(
|
|
257
|
+
_gen_packet(), _gen_admin(), return_exceptions=True,
|
|
258
|
+
)
|
|
259
|
+
if not isinstance(results[0], Exception):
|
|
260
|
+
student_packet = results[0]
|
|
261
|
+
logger.info("Student packet generated: %d stations, %d guided notes",
|
|
262
|
+
len(student_packet.stations), len(student_packet.guided_notes))
|
|
263
|
+
else:
|
|
264
|
+
logger.warning("Student packet generation failed: %s", results[0])
|
|
265
|
+
if not isinstance(results[1], Exception):
|
|
266
|
+
admin_plan = results[1]
|
|
267
|
+
logger.info("Admin lesson plan generated: %d sections", len(admin_plan.sections))
|
|
268
|
+
else:
|
|
269
|
+
logger.warning("Admin plan generation failed: %s", results[1])
|
|
270
|
+
except Exception as e:
|
|
271
|
+
logger.warning("Parallel generation failed: %s", e)
|
|
272
|
+
|
|
219
273
|
# ── Export all three files ────────────────────────────────────
|
|
220
274
|
output_dir = Path("clawed_output").resolve()
|
|
221
275
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -224,61 +278,39 @@ class GenerateLessonBundleTool:
|
|
|
224
278
|
side_effects: list[str] = []
|
|
225
279
|
errors: list[str] = []
|
|
226
280
|
|
|
227
|
-
# 1.
|
|
281
|
+
# 1. Admin lesson plan DOCX (or fallback to basic lesson plan)
|
|
228
282
|
try:
|
|
229
283
|
from clawed.export_docx import export_lesson_docx
|
|
230
284
|
|
|
231
|
-
docx_path = export_lesson_docx(
|
|
285
|
+
docx_path = export_lesson_docx(
|
|
286
|
+
lesson, persona, output_dir, admin_plan=admin_plan,
|
|
287
|
+
)
|
|
232
288
|
generated_files.append(docx_path)
|
|
233
|
-
|
|
289
|
+
label = "Admin lesson plan" if admin_plan else "Lesson plan"
|
|
290
|
+
side_effects.append(f"{label} DOCX: {docx_path.name}")
|
|
234
291
|
except Exception as e:
|
|
235
292
|
logger.error("Lesson DOCX export failed: %s", e)
|
|
236
293
|
errors.append(f"Lesson plan DOCX failed: {e}")
|
|
237
294
|
|
|
238
|
-
# 2.
|
|
295
|
+
# 2. Student packet DOCX (structured) or fallback to old handout
|
|
239
296
|
try:
|
|
240
|
-
import
|
|
297
|
+
from clawed.export_handout import export_student_packet_docx
|
|
241
298
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
subject=subject,
|
|
249
|
-
grade=grade,
|
|
250
|
-
)
|
|
251
|
-
# Parse handout JSON
|
|
252
|
-
handout_cleaned = handout_raw.strip()
|
|
253
|
-
if handout_cleaned.startswith("```"):
|
|
254
|
-
lines = handout_cleaned.split("\n")
|
|
255
|
-
lines = lines[1:]
|
|
256
|
-
if lines and lines[-1].strip() == "```":
|
|
257
|
-
lines = lines[:-1]
|
|
258
|
-
handout_cleaned = "\n".join(lines)
|
|
259
|
-
try:
|
|
260
|
-
handout_data = json.loads(handout_cleaned)
|
|
261
|
-
except json.JSONDecodeError:
|
|
262
|
-
handout_data = json_repair.loads(handout_cleaned)
|
|
263
|
-
|
|
264
|
-
from clawed.export_handout import export_handout_docx
|
|
265
|
-
|
|
266
|
-
handout_path = export_handout_docx(handout_data, subject=subject)
|
|
267
|
-
if handout_path:
|
|
268
|
-
generated_files.append(Path(handout_path))
|
|
269
|
-
side_effects.append(f"Student handout DOCX: {Path(handout_path).name}")
|
|
270
|
-
except Exception as handout_err:
|
|
271
|
-
logger.warning("LLM handout generation failed, falling back: %s", handout_err)
|
|
272
|
-
# Fallback to regex-based handout
|
|
273
|
-
try:
|
|
299
|
+
if student_packet:
|
|
300
|
+
packet_path = export_student_packet_docx(
|
|
301
|
+
student_packet, subject=subject, output_dir=output_dir,
|
|
302
|
+
)
|
|
303
|
+
else:
|
|
304
|
+
# Fallback: build minimal packet from lesson data
|
|
274
305
|
from clawed.export_docx import export_student_handout
|
|
306
|
+
packet_path = export_student_handout(lesson, persona, output_dir)
|
|
275
307
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
308
|
+
if packet_path:
|
|
309
|
+
generated_files.append(Path(packet_path))
|
|
310
|
+
side_effects.append(f"Student packet DOCX: {Path(packet_path).name}")
|
|
311
|
+
except Exception as e:
|
|
312
|
+
logger.warning("Student packet export failed: %s", e)
|
|
313
|
+
errors.append(f"Student packet failed: {e}")
|
|
282
314
|
|
|
283
315
|
# 3. Slideshow PPTX
|
|
284
316
|
try:
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Tool: search_my_materials — search the teacher's uploaded curriculum files.
|
|
2
|
+
|
|
3
|
+
This is the key tool that makes Claw-ED curriculum-aware. The agent calls
|
|
4
|
+
this before generating to find relevant prior work in the teacher's own
|
|
5
|
+
uploaded materials. Now includes asset-level awareness (slideshows, handouts,
|
|
6
|
+
YouTube links) alongside text chunk search.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from clawed.agent_core.context import AgentContext, ToolResult
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SearchMyMaterialsTool:
|
|
18
|
+
"""Search the teacher's curriculum knowledge base for relevant content."""
|
|
19
|
+
|
|
20
|
+
def schema(self) -> dict[str, Any]:
|
|
21
|
+
return {
|
|
22
|
+
"type": "function",
|
|
23
|
+
"function": {
|
|
24
|
+
"name": "search_my_materials",
|
|
25
|
+
"description": (
|
|
26
|
+
"Search the teacher's uploaded curriculum files for relevant "
|
|
27
|
+
"content. Use this BEFORE generating lessons, units, or materials "
|
|
28
|
+
"to ground your output in the teacher's own prior work. Returns "
|
|
29
|
+
"matching files (slideshows, handouts, assessments), YouTube links, "
|
|
30
|
+
"and text excerpts with source file attribution."
|
|
31
|
+
),
|
|
32
|
+
"parameters": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"properties": {
|
|
35
|
+
"query": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"description": (
|
|
38
|
+
"What to search for — a topic, concept, or question. "
|
|
39
|
+
"Example: 'Civil War causes', 'photosynthesis lab', "
|
|
40
|
+
"'fractions worksheet'"
|
|
41
|
+
),
|
|
42
|
+
},
|
|
43
|
+
"top_k": {
|
|
44
|
+
"type": "integer",
|
|
45
|
+
"description": "Maximum results to return (default 5)",
|
|
46
|
+
"default": 5,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
"required": ["query"],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async def execute(
|
|
55
|
+
self, params: dict[str, Any], context: AgentContext
|
|
56
|
+
) -> ToolResult:
|
|
57
|
+
query = params["query"]
|
|
58
|
+
top_k = params.get("top_k", 5)
|
|
59
|
+
teacher_id = context.teacher_id
|
|
60
|
+
|
|
61
|
+
lines: list[str] = []
|
|
62
|
+
|
|
63
|
+
# ── Asset-level search (files, YouTube links) ──────────────
|
|
64
|
+
try:
|
|
65
|
+
from clawed.asset_registry import AssetRegistry
|
|
66
|
+
registry = AssetRegistry()
|
|
67
|
+
assets = registry.search_assets(teacher_id, query, top_k=top_k)
|
|
68
|
+
yt_links = registry.get_youtube_links(teacher_id, query, top_k=3)
|
|
69
|
+
|
|
70
|
+
if assets:
|
|
71
|
+
lines.append("EXISTING MATERIALS:\n")
|
|
72
|
+
for i, a in enumerate(assets, 1):
|
|
73
|
+
type_label = a["material_type"].replace("_", " ").title()
|
|
74
|
+
extras: list[str] = []
|
|
75
|
+
if a.get("slide_count"):
|
|
76
|
+
extras.append(f"{a['slide_count']} slides")
|
|
77
|
+
if a.get("image_count"):
|
|
78
|
+
extras.append(f"{a['image_count']} images")
|
|
79
|
+
yt_raw = a.get("youtube_urls", [])
|
|
80
|
+
yt_list = json.loads(yt_raw) if isinstance(yt_raw, str) else yt_raw
|
|
81
|
+
yt_count = len(yt_list)
|
|
82
|
+
if yt_count:
|
|
83
|
+
extras.append(f"{yt_count} YouTube links")
|
|
84
|
+
extra_str = f" ({', '.join(extras)})" if extras else ""
|
|
85
|
+
lines.append(
|
|
86
|
+
f" {i}. [{type_label}] \"{a['title']}\"{extra_str}\n"
|
|
87
|
+
f" File: {a['filename']}\n"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if yt_links:
|
|
91
|
+
lines.append("YOUTUBE LINKS IN YOUR FILES:\n")
|
|
92
|
+
for link in yt_links:
|
|
93
|
+
lines.append(f" - {link['url']} (from \"{link['from_file']}\")\n")
|
|
94
|
+
|
|
95
|
+
except Exception:
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
# ── Chunk-level search (text excerpts) ─────────────────────
|
|
99
|
+
try:
|
|
100
|
+
from clawed.agent_core.memory.curriculum_kb import CurriculumKB
|
|
101
|
+
|
|
102
|
+
kb = CurriculumKB()
|
|
103
|
+
results = kb.search(teacher_id, query, top_k=top_k)
|
|
104
|
+
|
|
105
|
+
if not results and not lines:
|
|
106
|
+
stats = kb.stats(teacher_id)
|
|
107
|
+
if stats["doc_count"] == 0:
|
|
108
|
+
return ToolResult(
|
|
109
|
+
text="No curriculum files uploaded yet. Ask the teacher "
|
|
110
|
+
"to share their lesson plans, handouts, or other "
|
|
111
|
+
"teaching materials so you can reference them."
|
|
112
|
+
)
|
|
113
|
+
return ToolResult(
|
|
114
|
+
text=f"No matches found for '{query}' in "
|
|
115
|
+
f"{stats['doc_count']} uploaded documents."
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if results:
|
|
119
|
+
lines.append("RELEVANT EXCERPTS:\n")
|
|
120
|
+
for i, r in enumerate(results, 1):
|
|
121
|
+
source = r["doc_title"]
|
|
122
|
+
if r.get("source_path"):
|
|
123
|
+
fname = Path(r["source_path"]).name
|
|
124
|
+
source = f"{r['doc_title']} ({fname})"
|
|
125
|
+
sim_pct = int(r["similarity"] * 100)
|
|
126
|
+
text_preview = r["chunk_text"][:300]
|
|
127
|
+
if len(r["chunk_text"]) > 300:
|
|
128
|
+
text_preview += "..."
|
|
129
|
+
lines.append(
|
|
130
|
+
f" {i}. From '{source}' ({sim_pct}% match):\n"
|
|
131
|
+
f" {text_preview}\n"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
except Exception as e:
|
|
135
|
+
if not lines:
|
|
136
|
+
return ToolResult(text=f"Failed to search curriculum files: {e}")
|
|
137
|
+
|
|
138
|
+
if lines:
|
|
139
|
+
header = f"Found materials related to \"{query}\":\n\n"
|
|
140
|
+
lines.append(
|
|
141
|
+
"\nWould you like me to use these existing materials, "
|
|
142
|
+
"enhance them, or create something new?"
|
|
143
|
+
)
|
|
144
|
+
return ToolResult(
|
|
145
|
+
text=header + "\n".join(lines),
|
|
146
|
+
data={"query": query},
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return ToolResult(text=f"No materials found for '{query}'.")
|
|
@@ -155,20 +155,26 @@ def ingest(
|
|
|
155
155
|
except Exception:
|
|
156
156
|
pass
|
|
157
157
|
|
|
158
|
-
# Register assets
|
|
158
|
+
# Register assets with rich extraction (images, YouTube links, metadata)
|
|
159
159
|
asset_msg = ""
|
|
160
160
|
try:
|
|
161
161
|
from clawed.asset_registry import AssetRegistry
|
|
162
|
+
from clawed.ingestor import extract_rich
|
|
162
163
|
registry = AssetRegistry()
|
|
163
164
|
asset_count = 0
|
|
164
165
|
for doc in documents:
|
|
165
166
|
doc_type_val = doc.doc_type.value if hasattr(doc.doc_type, "value") else str(doc.doc_type)
|
|
167
|
+
# Try rich extraction for images/URLs from original file
|
|
168
|
+
extraction = None
|
|
169
|
+
if doc.source_path:
|
|
170
|
+
extraction = extract_rich(Path(doc.source_path))
|
|
166
171
|
asset_id = registry.register_asset(
|
|
167
172
|
teacher_id="default",
|
|
168
173
|
source_path=doc.source_path or "",
|
|
169
174
|
title=doc.title,
|
|
170
175
|
doc_type=doc_type_val,
|
|
171
176
|
text=doc.content,
|
|
177
|
+
extraction=extraction,
|
|
172
178
|
)
|
|
173
179
|
if asset_id:
|
|
174
180
|
asset_count += 1
|