Spaces:
Sleeping
Sleeping
| /** | |
| * Sidebar — learner profile form with preset quick-load, | |
| * custom profile option, and auto-mode toggle. | |
| */ | |
| import React from "react"; | |
| import styles from "./Sidebar.module.css"; | |
| const ALL_TAGS = [ | |
| "python", "ml", "deployment", "kubernetes", "docker", | |
| "data-science", "pandas", "numpy", "deep-learning", "pytorch", | |
| "neural-networks", "mlops", "ci-cd", "monitoring", "nlp", | |
| "transformers", "bert", "llm", "prompt-engineering", "ai", | |
| "data-engineering", "spark", "big-data", "git", "github", | |
| "collaboration", "fastapi", "api", "backend", "aws", "sagemaker", | |
| ]; | |
| const STYLES = ["visual", "reading", "hands-on"]; | |
| const DIFFICULTIES = ["Beginner", "Intermediate", "Advanced"]; | |
| export default function Sidebar({ | |
| open, | |
| onToggle, | |
| profile, | |
| onChange, | |
| presets, | |
| activePreset, | |
| onPreset, | |
| onSubmit, | |
| loading, | |
| autoMode, | |
| onAutoModeChange, | |
| }) { | |
| const set = (key, val) => { | |
| const updated = { ...profile, [key]: val }; | |
| // When user edits fields on a preset, switch to custom user_id | |
| if (activePreset >= 0) { | |
| updated.user_id = "custom"; | |
| } | |
| onChange(updated); | |
| }; | |
| const toggleTag = (tag) => { | |
| const tags = profile.interest_tags || []; | |
| set( | |
| "interest_tags", | |
| tags.includes(tag) ? tags.filter((t) => t !== tag) : [...tags, tag] | |
| ); | |
| }; | |
| const toggleViewed = (id) => { | |
| const ids = profile.viewed_content_ids || []; | |
| set( | |
| "viewed_content_ids", | |
| ids.includes(id) ? ids.filter((i) => i !== id) : [...ids, id] | |
| ); | |
| }; | |
| return ( | |
| <> | |
| <button | |
| className={styles.toggle} | |
| onClick={onToggle} | |
| aria-label="Toggle sidebar" | |
| > | |
| {open ? "\u2190" : "\u2192"} | |
| </button> | |
| <aside className={`${styles.sidebar} ${open ? styles.open : ""}`}> | |
| <div className={styles.inner}> | |
| <h2 className={styles.heading}>Learner Profile</h2> | |
| {/* Auto-mode toggle */} | |
| <button | |
| className={`${styles.autoToggle} ${autoMode ? styles.autoOn : ""}`} | |
| onClick={() => onAutoModeChange(!autoMode)} | |
| > | |
| <span className={styles.autoIcon}>{autoMode ? "\u26A1" : "\u25CB"}</span> | |
| <span>Auto-mode</span> | |
| <span className={styles.autoStatus}>{autoMode ? "ON" : "OFF"}</span> | |
| </button> | |
| {/* Presets + Custom */} | |
| <div className={styles.presets}> | |
| {presets.map((p, i) => ( | |
| <button | |
| key={i} | |
| className={`${styles.presetBtn} ${ | |
| activePreset === i ? styles.presetActive : "" | |
| }`} | |
| onClick={() => onPreset(i)} | |
| > | |
| {p.label} | |
| </button> | |
| ))} | |
| <button | |
| className={`${styles.presetBtn} ${styles.customBtn} ${ | |
| activePreset < 0 ? styles.presetActive : "" | |
| }`} | |
| onClick={() => onPreset(-1)} | |
| > | |
| + Custom Profile | |
| </button> | |
| </div> | |
| <div className={styles.divider} /> | |
| {/* Name */} | |
| <label className={styles.label}>Name</label> | |
| <input | |
| className={styles.input} | |
| value={profile.name || ""} | |
| onChange={(e) => set("name", e.target.value)} | |
| placeholder="Your name" | |
| /> | |
| {/* Goal */} | |
| <label className={styles.label}>Learning goal</label> | |
| <textarea | |
| className={styles.textarea} | |
| value={profile.goal || ""} | |
| onChange={(e) => set("goal", e.target.value)} | |
| rows={3} | |
| placeholder="e.g. Learn ML deployment..." | |
| /> | |
| {/* Learning style */} | |
| <label className={styles.label}>Learning style</label> | |
| <div className={styles.chips}> | |
| {STYLES.map((s) => ( | |
| <button | |
| key={s} | |
| className={`${styles.chip} ${ | |
| profile.learning_style === s ? styles.chipActive : "" | |
| }`} | |
| onClick={() => set("learning_style", s)} | |
| > | |
| {s} | |
| </button> | |
| ))} | |
| </div> | |
| {/* Difficulty */} | |
| <label className={styles.label}>Preferred difficulty</label> | |
| <div className={styles.chips}> | |
| {DIFFICULTIES.map((d) => ( | |
| <button | |
| key={d} | |
| className={`${styles.chip} ${ | |
| profile.preferred_difficulty === d ? styles.chipActive : "" | |
| }`} | |
| onClick={() => set("preferred_difficulty", d)} | |
| > | |
| {d} | |
| </button> | |
| ))} | |
| </div> | |
| {/* Time */} | |
| <label className={styles.label}> | |
| Time per day: <strong>{profile.time_per_day || 60} min</strong> | |
| </label> | |
| <input | |
| type="range" | |
| min="15" | |
| max="120" | |
| step="5" | |
| className={styles.range} | |
| value={profile.time_per_day || 60} | |
| onChange={(e) => set("time_per_day", Number(e.target.value))} | |
| /> | |
| {/* Interest tags */} | |
| <label className={styles.label}>Interest tags</label> | |
| <div className={styles.tags}> | |
| {ALL_TAGS.map((tag) => ( | |
| <button | |
| key={tag} | |
| className={`${styles.tag} ${ | |
| (profile.interest_tags || []).includes(tag) | |
| ? styles.tagActive | |
| : "" | |
| }`} | |
| onClick={() => toggleTag(tag)} | |
| > | |
| {tag} | |
| </button> | |
| ))} | |
| </div> | |
| {/* Viewed IDs */} | |
| <label className={styles.label}>Already viewed IDs</label> | |
| <div className={styles.chips}> | |
| {Array.from({ length: 10 }, (_, i) => i + 1).map((id) => ( | |
| <button | |
| key={id} | |
| className={`${styles.chip} ${styles.chipSmall} ${ | |
| (profile.viewed_content_ids || []).includes(id) | |
| ? styles.chipActive | |
| : "" | |
| }`} | |
| onClick={() => toggleViewed(id)} | |
| > | |
| {id} | |
| </button> | |
| ))} | |
| </div> | |
| <div className={styles.divider} /> | |
| {/* Submit */} | |
| <button | |
| className={styles.submit} | |
| onClick={onSubmit} | |
| disabled={loading} | |
| > | |
| {loading ? ( | |
| <span className={styles.spinner} /> | |
| ) : ( | |
| "Get Recommendations" | |
| )} | |
| </button> | |
| </div> | |
| </aside> | |
| </> | |
| ); | |
| } | |