diff --git a/src/components/micro_learning/MicroLearningSelector.jsx b/src/components/micro_learning/MicroLearningSelector.jsx index 4faf36c..55821d7 100644 --- a/src/components/micro_learning/MicroLearningSelector.jsx +++ b/src/components/micro_learning/MicroLearningSelector.jsx @@ -1,83 +1,123 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; +import { Loader, BookOpen, Target, Layers, MessageCircle } from 'lucide-react'; import { useMicroLearnings } from '../../hooks/useMicroLearnings'; import MicroLearningContainer from './MicroLearningContainer'; -import Button from '../ui/Button'; import Card from '../ui/Card'; -const TYPE_LABELS = { - 'concept_explainer': 'Concept Explainer', - 'scenario_quiz': 'Scenario Quiz', - 'flashcard_set': 'Flashcard Set', - 'reflection_prompt': 'Reflection Prompt' -}; - -const TYPE_DESCRIPTIONS = { - 'concept_explainer': 'Read a structured explanation to understand the concept.', - 'scenario_quiz': 'Apply your knowledge in a realistic workplace scenario.', - 'flashcard_set': 'Test your recall with a set of quick flashcards.', - 'reflection_prompt': 'Connect the topic to your own professional experience.' -}; +const TYPES = [ + { + key: 'concept_explainer', + label: 'Concept Explainer', + description: 'Read a structured explanation to understand the concept.', + icon: BookOpen, + }, + { + key: 'scenario_quiz', + label: 'Scenario Quiz', + description: 'Apply your knowledge in a realistic workplace scenario.', + icon: Target, + }, + { + key: 'flashcard_set', + label: 'Flashcard Set', + description: 'Test your recall with a set of quick flashcards.', + icon: Layers, + }, + { + key: 'reflection_prompt', + label: 'Reflection Prompt', + description: 'Connect the topic to your own professional experience.', + icon: MessageCircle, + }, +]; export default function MicroLearningSelector({ topicId, sessionWeek, onTopicCompleted }) { - const { getMicroLearningsByTopic } = useMicroLearnings(); - const [availableMLs, setAvailableMLs] = useState([]); + const { getOrGenerate } = useMicroLearnings(); const [selectedML, setSelectedML] = useState(null); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); - useEffect(() => { - const fetchMLs = async () => { - setLoading(true); - const data = await getMicroLearningsByTopic(topicId); - setAvailableMLs(data); + const handleSelection = async (type) => { + setLoading(true); + setError(null); + try { + const record = await getOrGenerate(topicId, type); + setSelectedML(record); + } catch (err) { + console.error('[MicroLearningSelector] Generation failed:', err); + setError(err.message || 'Failed to generate content. Please try again.'); + } finally { setLoading(false); - }; - if (topicId) { - fetchMLs(); } - }, [topicId]); - - const handleSelection = (ml) => { - setSelectedML(ml); }; - if (loading) return
Loading learning formats...
; - - if (availableMLs.length === 0) { - return
No micro learnings available for this topic yet.
; + // Loading state while AI generates + if (loading) { + return ( + + +

AI is generating your learning module…

+

This may take 10–30 seconds.

+
+ ); } + // Error state + if (error) { + return ( + +

Generation failed

+

{error}

+ +
+ ); + } + + // Render selected micro learning if (selectedML) { return (
- -
); } + // Type selection menu — always shows all 4 types return (

Choose a Learning Format

+

Select how you want to engage with this topic.

- {availableMLs.map((ml) => ( -
handleSelection(ml)} + {TYPES.map(({ key, label, description, icon: Icon }) => ( +
handleSelection(key)} > -

{TYPE_LABELS[ml.type] || ml.type}

-

{TYPE_DESCRIPTIONS[ml.type]}

+
+
+ +
+

{label}

+
+

{description}

))}
diff --git a/src/hooks/useMicroLearnings.js b/src/hooks/useMicroLearnings.js index f0eccc9..c55dfd6 100644 --- a/src/hooks/useMicroLearnings.js +++ b/src/hooks/useMicroLearnings.js @@ -1,17 +1,20 @@ -import { pb } from '../lib/pb'; +import { getOrGenerateMicroLearning, regenerateMicroLearning } from '../lib/microLearningService'; export function useMicroLearnings() { - const getMicroLearningsByTopic = async (topicId) => { - try { - const records = await pb.collection('micro_learnings').getFullList({ - filter: `topic_id = "${topicId}" && status = 'published'`, - }); - return records; - } catch (err) { - console.error("Error fetching micro learnings:", err); - return []; - } + /** + * Get or generate a micro learning for the given topic and type. + * Returns a PocketBase record with .content ready to render. + */ + const getOrGenerate = async (topicId, type) => { + return getOrGenerateMicroLearning(topicId, type); }; - return { getMicroLearningsByTopic }; + /** + * Force regeneration of a micro learning (deletes cached version first). + */ + const regenerate = async (topicId, type) => { + return regenerateMicroLearning(topicId, type); + }; + + return { getOrGenerate, regenerate }; } diff --git a/src/lib/llmTools.js b/src/lib/llmTools.js index 1a972c9..7278f27 100644 --- a/src/lib/llmTools.js +++ b/src/lib/llmTools.js @@ -341,3 +341,92 @@ export const ARTICLE_PATCH_TOOLS = [ REMOVE_SECTION_TOOL, REPLACE_TAKEAWAYS_TOOL, ]; + +// ── Micro Learning generation tools ─────────────────────────────────────────── + +export const EMIT_CONCEPT_EXPLAINER_TOOL = { + name: 'emit_concept_explainer', + description: 'Return a structured concept explanation with multiple sections. Each section moves from definition → importance → practical application. The final section must include a concrete workplace example.', + input_schema: { + type: 'object', + properties: { + sections: { + type: 'array', + items: { + type: 'object', + properties: { + title: { type: 'string', description: 'Section heading.' }, + content: { type: 'string', description: 'Section body in HTML. Use

,