From f81d569e35eaf7f0b2b982abd0193238a979c9a4 Mon Sep 17 00:00:00 2001 From: yuval Date: Thu, 28 May 2026 12:25:43 +0300 Subject: [PATCH 1/5] feat: add detail levels, cover-all-slides rule, and slides command - learn.md: route slides arg to slides.md; add detail 1/2/3 and slides to learner command table - teaching.md: every slide must be represented; detail 1/2/3 modes; ask for detail level at session start - slides.md (new): verbatim slide reading via tts-scripts; launches interactive HTML viewer with TTS auto-play per slide; exercises written fresh by tutor Co-Authored-By: Claude Sonnet 4.6 --- .claude/commands/learn.md | 5 +++ .claude/commands/learn/slides.md | 70 ++++++++++++++++++++++++++++++ .claude/commands/learn/teaching.md | 46 +++++++++++++++----- 3 files changed, 110 insertions(+), 11 deletions(-) create mode 100644 .claude/commands/learn/slides.md diff --git a/.claude/commands/learn.md b/.claude/commands/learn.md index b91f113..f2fbff2 100644 --- a/.claude/commands/learn.md +++ b/.claude/commands/learn.md @@ -66,6 +66,7 @@ Create `~/skill-tutor-tutorials/learner_profile.md` with their answers. |-----------|--------| | $ARGUMENTS = lesson number (e.g. `3.2`) | Read `.claude/commands/learn/teaching.md` | | $ARGUMENTS = "status" | Read `.claude/commands/learn/status.md` | +| $ARGUMENTS = "slides" or "slides [lesson]" | Read `.claude/commands/learn/slides.md` | | $ARGUMENTS empty | Read `.claude/commands/learn/resume.md` | | "quiz me" trigger | Read `.claude/commands/learn/quiz.md` | | "stop" trigger | Read `.claude/commands/learn/progress.md` | @@ -85,3 +86,7 @@ Create `~/skill-tutor-tutorials/learner_profile.md` with their answers. | stop | Read `.claude/commands/learn/progress.md`, then summarize | | read aloud | Use TTS helper | | settings | Show current settings | +| detail 1 | Switch to detail level 1 (very brief summaries) | +| detail 2 | Switch to detail level 2 (slightly compressed — default) | +| detail 3 | Switch to detail level 3 (full depth, may add bullet points) | +| slides | Read `.claude/commands/learn/slides.md` — verbatim slide reading mode | diff --git a/.claude/commands/learn/slides.md b/.claude/commands/learn/slides.md new file mode 100644 index 0000000..fb37cdb --- /dev/null +++ b/.claude/commands/learn/slides.md @@ -0,0 +1,70 @@ +# Slides Module + +*Loaded when the learner types `/learn slides` or "slides" during a session. Switches to verbatim slide reading mode.* + +Respond in `session.language` throughout. + +--- + +## Overview + +Slides Mode reads the course script verbatim — no Journey Format, no reformulation. The learner hears the slides exactly as written, translated to `session.language` if needed. Exercises are still written by the tutor (not taken from the exercises file verbatim). + +--- + +## Step 1 — Resolve Lesson + +If a lesson number was specified (e.g. `/learn slides 3.1`), load that lesson's script file. + +If no lesson number was given, use the current lesson already loaded in the session. If no lesson is active, ask the learner which lesson they want. + +Resolve the script path using the same priority as `teaching.md`: +1. Check `./lessons/` at project root +2. Fallback to `course.path` from settings + +Split the script by `[מעבר שקף]`. + +--- + +## Step 2 — Announce Mode + +Tell the learner: +> "Switching to Slides Mode. I'll read each slide verbatim. Say **next** to advance, **stop slides** to return to teaching mode, or **exercises** to get the exercises for this lesson." + +--- + +## Step 3 — Read Slides Verbatim + +For each slide: +1. Display the slide content as-is (translated to `session.language` if the script is in a different language) +2. Open the slide image in the browser using PowerShell: + - Slide images are at: `C:\Users\yuval\ai-track\courses\ai-engineer\lessons\[module]\[lesson]\digital-course-screenshots\slide-NN.png` + - Use zero-padded numbers: slide-01.png, slide-02.png, etc. + - Run this PowerShell to open the image: + ```powershell + Start-Process "SLIDE_IMAGE_PATH" + ``` +3. Run TTS from the per-slide TTS script file if it exists: + - TTS scripts are at: `C:\Users\yuval\ai-track\courses\ai-engineer\lessons\[module]\[lesson]\digital-course-tts-scripts\slide-NN.txt` + - Read the file content and speak it using the TTS helper + - If no TTS script file exists, speak the slide text directly +4. Wait for the learner to say **next** before advancing + +**Do not** apply Journey Format. **Do not** ask thinking questions between slides. + +--- + +## Step 4 — Exercises + +When the learner says **exercises**: +- Do NOT read from the exercises file verbatim +- Write fresh, interactive exercises based on the slide content covered so far +- Follow the exercise design rule: most exercises completable through Claude directly; external tool exercises only when the lesson topic is that specific tool + +--- + +## Step 5 — Exit Slides Mode + +When the learner says **stop slides** or **teaching mode**: +- Return to the teaching module (`teaching.md`) at the slide where they left off +- Resume in the previously set `session.detail_level` diff --git a/.claude/commands/learn/teaching.md b/.claude/commands/learn/teaching.md index 2191398..0cae808 100644 --- a/.claude/commands/learn/teaching.md +++ b/.claude/commands/learn/teaching.md @@ -33,29 +33,53 @@ Greet the learner. State the lesson topic and number of sections. **If a progress file exists for this lesson:** Tell the learner their previous score and ask if they want to restart or jump to a quiz on what was already covered. -**Otherwise:** Ask what they already know about the topic, if anything. +**Otherwise:** Ask what they already know about the topic, if anything. Also ask which detail level they prefer: +- **detail 1** — very brief, one or two sentences per slide +- **detail 2** — slightly compressed (default) +- **detail 3** — full depth, may add extra bullet points -Use their answer plus `learner_profile.md` to calibrate depth. Offer to skip sections they clearly already know. +Store the chosen level as `session.detail_level` (default: 2). + +Use their answers plus `learner_profile.md` to calibrate depth. Offer to skip sections they clearly already know. *(Speak greeting if TTS enabled)* --- -## Step 4 — Teach Each Section +## Step 4 — Cover Every Slide + +**IMPORTANT: Every slide (section split by `[מעבר שקף]`) MUST be represented in the lesson output.** Never silently skip a slide. At minimum, include one sentence summarizing it. + +For every slide, apply the **Journey Format** scaled by `session.detail_level`: + +### Detail Level 1 — Very Brief +- 1–2 sentences max per slide +- State the core idea only +- No question, no context example +- Good for fast review passes -For every section, use the **Journey Format**: +### Detail Level 2 — Standard (default) +Use the full Journey Format: +1. **The problem** — one sentence +2. **The insight** — 2–3 sentences in your own words, not copied from the script +3. **In your context** — one concrete connection to the learner's project or a real example +4. **Question** — one thinking question; wait for an answer before continuing -1. **The problem** — What problem does this section solve? Why is it hard without it? (One sentence) -2. **The insight** — What do experts understand that beginners don't? (2–3 sentences, in your own words — not copied from the script) -3. **In your context** — How does this connect to the learner's project from their profile, or a relevant real-world example -4. **Question** — Ask one thinking question. Not trivia. Wait for an answer before continuing. +Each block: max 5 sentences total. + +### Detail Level 3 — Full Depth +Use the full Journey Format plus: +- Expand the insight to 4–5 sentences +- Add bullet points for key facts, numbers, or comparisons from the slide +- May include one extra "did you know" point not in the script if genuinely relevant +- Still ask a question and wait for an answer + +**The learner can switch levels at any time** by saying "detail 1", "detail 2", or "detail 3". **Responding to answers:** -- Correct → brief acknowledgment + "Shall we continue?" +- Correct → brief acknowledgment + continue - Partial/wrong → one hint → let them try again → then explain -Each teaching block: max 5 sentences. The learner should write more than you. - *(Speak each teaching block if TTS enabled — strip markdown before speaking)* --- From 8fd40390415829524fed4a227004c624a37ebc27 Mon Sep 17 00:00:00 2001 From: yuval Date: Thu, 28 May 2026 12:32:17 +0300 Subject: [PATCH 2/5] feat: consolidate slide viewer scripts into repo, update slides.md paths - .claude/scripts/generate-slideshow.ps1: generates interactive HTML viewer for any lesson - .claude/scripts/slide-server.ps1: localhost:7823 TTS + slide state server (preloads scripts, Hebrew voice first) - slides.md: updated Step 3 to reference new script paths inside this repo - CLAUDE.md: added Slides row to modules table, noted .claude/scripts/ folder Co-Authored-By: Claude Sonnet 4.6 --- .claude/commands/learn/slides.md | 39 +++-- .claude/scripts/generate-slideshow.ps1 | 207 +++++++++++++++++++++++++ .claude/scripts/slide-server.ps1 | 109 +++++++++++++ CLAUDE.md | 3 + 4 files changed, 344 insertions(+), 14 deletions(-) create mode 100644 .claude/scripts/generate-slideshow.ps1 create mode 100644 .claude/scripts/slide-server.ps1 diff --git a/.claude/commands/learn/slides.md b/.claude/commands/learn/slides.md index fb37cdb..cd3fd3e 100644 --- a/.claude/commands/learn/slides.md +++ b/.claude/commands/learn/slides.md @@ -33,22 +33,33 @@ Tell the learner: --- -## Step 3 — Read Slides Verbatim - -For each slide: -1. Display the slide content as-is (translated to `session.language` if the script is in a different language) -2. Open the slide image in the browser using PowerShell: - - Slide images are at: `C:\Users\yuval\ai-track\courses\ai-engineer\lessons\[module]\[lesson]\digital-course-screenshots\slide-NN.png` - - Use zero-padded numbers: slide-01.png, slide-02.png, etc. - - Run this PowerShell to open the image: +## Step 3 — Launch Viewer + +On entry to Slides Mode, start the slide server and generate the HTML viewer: + +1. **Start the server** (if not already running on port 7823): + ```powershell + Start-Process powershell -ArgumentList "-NoProfile -File `"C:\Users\yuval\Tov-learn\.claude\scripts\slide-server.ps1`"" -WindowStyle Normal + Start-Sleep -Seconds 2 + ``` + +2. **Resolve the lesson's absolute path** on disk: + - Slide images are at: `C:\Users\yuval\ai-track\courses\ai-engineer\lessons\[module]\[lesson]\digital-course-screenshots\` + - TTS scripts are at: `C:\Users\yuval\ai-track\courses\ai-engineer\lessons\[module]\[lesson]\digital-course-tts-scripts\` + +3. **Generate and open the viewer**: ```powershell - Start-Process "SLIDE_IMAGE_PATH" + & "C:\Users\yuval\Tov-learn\.claude\scripts\generate-slideshow.ps1" -LessonPath "ABSOLUTE_LESSON_PATH" + Start-Process "$env:TEMP\tov_slideshow.html" ``` -3. Run TTS from the per-slide TTS script file if it exists: - - TTS scripts are at: `C:\Users\yuval\ai-track\courses\ai-engineer\lessons\[module]\[lesson]\digital-course-tts-scripts\slide-NN.txt` - - Read the file content and speak it using the TTS helper - - If no TTS script file exists, speak the slide text directly -4. Wait for the learner to say **next** before advancing + +4. **For each slide**, display the text verbatim (translated to `session.language`), then advance the server to that slide so TTS plays automatically: + ```powershell + "SLIDE_NUMBER" | Set-Content "$env:TEMP\tov_current_slide.txt" + Invoke-RestMethod -Uri "http://localhost:7823/" -Method POST -Body "SLIDE_NUMBER" | Out-Null + ``` + +5. Wait for the learner to say **next** before advancing. **Do not** apply Journey Format. **Do not** ask thinking questions between slides. diff --git a/.claude/scripts/generate-slideshow.ps1 b/.claude/scripts/generate-slideshow.ps1 new file mode 100644 index 0000000..bfb9ce5 --- /dev/null +++ b/.claude/scripts/generate-slideshow.ps1 @@ -0,0 +1,207 @@ +# Generates tov_slideshow.html for a given lesson +# Usage: .\generate-slideshow.ps1 -LessonPath "ABSOLUTE\PATH\TO\LESSON" +# The LessonPath must contain digital-course-screenshots\ and digital-course-tts-scripts\ + +param( + [string]$LessonPath +) + +$slidesDir = Join-Path $LessonPath "digital-course-screenshots" +$slides = Get-ChildItem "$slidesDir\slide-*.png" | Sort-Object Name + +if ($slides.Count -eq 0) { + Write-Error "No slides found in $slidesDir" + exit 1 +} + +# Build slide tags +$slideImgTags = "" +$i = 1 +foreach ($slide in $slides) { + $path = $slide.FullName -replace '\\', '/' + $display = if ($i -eq 1) { "block" } else { "none" } + $slideImgTags += " `n" + $i++ +} + +$total = $slides.Count + +# Write lesson path for the TTS server to preload +[System.IO.File]::WriteAllText("$env:TEMP\tov_current_lesson.txt", $LessonPath, [System.Text.Encoding]::UTF8) +[System.IO.File]::WriteAllText("$env:TEMP\tov_current_slide.txt", "1", [System.Text.Encoding]::UTF8) + +$html = @" + + + + +Tov-learn + + + + + +
+ +$slideImgTags +
+
+ +
+ +
+
+ +
+
+ + + + +"@ + +$outPath = "$env:TEMP\tov_slideshow.html" +[System.IO.File]::WriteAllText($outPath, $html, (New-Object System.Text.UTF8Encoding($true))) +Write-Host $outPath diff --git a/.claude/scripts/slide-server.ps1 b/.claude/scripts/slide-server.ps1 new file mode 100644 index 0000000..c774753 --- /dev/null +++ b/.claude/scripts/slide-server.ps1 @@ -0,0 +1,109 @@ +# Tov-learn Slide Server — localhost:7823 +# GET / → returns current slide number +# POST / {num} → advance to slide N + speak TTS +# POST / stop → kill current TTS only + +param([int]$Port = 7823) + +$slideFile = "$env:TEMP\tov_current_slide.txt" +$lessonFile = "$env:TEMP\tov_current_lesson.txt" +$ttsTextFile = "$env:TEMP\tov_tts_text.txt" +$ttsRunFile = "$env:TEMP\tov_tts_run.ps1" + +if (!(Test-Path $slideFile)) { "1" | Set-Content $slideFile } + +$script:ttsProcess = $null +$script:loadedLesson = "" +$script:ttsScripts = @{} # [int]slideNum → [string]text + +function Load-Lesson { + param([string]$lessonPath) + if ($lessonPath -eq $script:loadedLesson) { return } + $script:ttsScripts = @{} + $ttsDir = Join-Path $lessonPath "digital-course-tts-scripts" + if (Test-Path $ttsDir) { + Get-ChildItem "$ttsDir\slide-*.txt" | ForEach-Object { + $num = [int]($_.BaseName -replace 'slide-', '') + $script:ttsScripts[$num] = (Get-Content $_.FullName -Raw -Encoding UTF8).Trim() + } + Write-Host "Preloaded $($script:ttsScripts.Count) TTS scripts from $lessonPath" + } + $script:loadedLesson = $lessonPath +} + +function Kill-TTS { + if ($script:ttsProcess -and !$script:ttsProcess.HasExited) { + try { $script:ttsProcess.Kill() } catch {} + $script:ttsProcess = $null + } +} + +function Speak-Slide { + param([int]$num) + Kill-TTS + + # Reload lesson if changed + if (Test-Path $lessonFile) { + $lesson = (Get-Content $lessonFile -Raw -Encoding UTF8).Trim() + Load-Lesson $lesson + } + + $text = $script:ttsScripts[$num] + if (!$text) { return } + + # Write TTS text with BOM so runner reads Hebrew correctly + [System.IO.File]::WriteAllText($ttsTextFile, $text, (New-Object System.Text.UTF8Encoding($true))) + + # Build runner script — backtick-dollar escapes PS vars in the here-string + $runner = @" +Add-Type -AssemblyName System.Speech +`$s = New-Object System.Speech.Synthesis.SpeechSynthesizer +`$hv = `$s.GetInstalledVoices() | Where-Object { `$_.VoiceInfo.Culture.Name -like 'he-*' } | Select-Object -First 1 +if (`$hv) { `$s.SelectVoice(`$hv.VoiceInfo.Name) } +`$t = [System.IO.File]::ReadAllText('$ttsTextFile', [System.Text.Encoding]::UTF8) +`$s.Speak(`$t) +"@ + [System.IO.File]::WriteAllText($ttsRunFile, $runner, (New-Object System.Text.UTF8Encoding($true))) + $script:ttsProcess = Start-Process powershell -ArgumentList "-NoProfile -WindowStyle Hidden -File `"$ttsRunFile`"" -PassThru +} + +# Pre-load current lesson if already set +if (Test-Path $lessonFile) { + $lesson = (Get-Content $lessonFile -Raw -Encoding UTF8).Trim() + Load-Lesson $lesson +} + +$listener = New-Object System.Net.HttpListener +$listener.Prefixes.Add("http://localhost:$Port/") +$listener.Start() +Write-Host "Slide server running on http://localhost:$Port" + +while ($listener.IsListening) { + try { + $ctx = $listener.GetContext() + $res = $ctx.Response + $res.Headers.Add("Access-Control-Allow-Origin", "*") + $res.Headers.Add("Cache-Control", "no-cache") + + if ($ctx.Request.HttpMethod -eq "POST") { + $reader = New-Object System.IO.StreamReader($ctx.Request.InputStream) + $body = $reader.ReadToEnd().Trim() + + if ($body -eq "stop") { + Kill-TTS + } else { + $num = [int]$body + "$num" | Set-Content $slideFile + Speak-Slide $num + } + $buf = [System.Text.Encoding]::UTF8.GetBytes("ok") + } else { + $cur = if (Test-Path $slideFile) { (Get-Content $slideFile).Trim() } else { "1" } + $buf = [System.Text.Encoding]::UTF8.GetBytes($cur) + } + + $res.ContentLength64 = $buf.Length + $res.OutputStream.Write($buf, 0, $buf.Length) + $res.OutputStream.Close() + } catch { } +} diff --git a/CLAUDE.md b/CLAUDE.md index 880c110..320a9cd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,6 +18,9 @@ | Progress | `.claude/commands/learn/progress.md` | שמירת tutorials, knowledge map | | Status | `.claude/commands/learn/status.md` | HTML dashboard — all lessons, scores, due reviews | | Project Analysis | `.claude/commands/learn/project-analysis.md` | סריקת קוד, ראיון ארכיטקט, מפת HTML | +| Slides | `.claude/commands/learn/slides.md` | קריאה מילה במילה מסקריפטים, viewer אינטראקטיבי, TTS אוטומטי | + +סקריפטים תומכים נמצאים ב-`.claude/scripts/` (PowerShell — slide server, HTML generator). **כלל פיתוח:** כל מודול עצמאי — לא מניחים שמודול אחר כבר נטען. מידע שמודול צריך — הוא קורא בעצמו. TTS helper מוגדר ב-`learn.md` ונטען לפני כל מודול. From 20590d4a4dd9cfee131acc35b9048e8c61f2f3e8 Mon Sep 17 00:00:00 2001 From: yuval Date: Thu, 28 May 2026 12:36:00 +0300 Subject: [PATCH 3/5] =?UTF-8?q?=EF=BB=BFfix:=20make=20TTS=20auto-fire=20ma?= =?UTF-8?q?ndatory=20on=20every=20slide?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extracted per-slide TTS POST into its own Step 4 with explicit MANDATORY label. Previously buried inside a combined step causing it to be missed. Co-Authored-By: Claude Sonnet 4.6 --- .claude/commands/learn/slides.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/.claude/commands/learn/slides.md b/.claude/commands/learn/slides.md index cd3fd3e..daf9438 100644 --- a/.claude/commands/learn/slides.md +++ b/.claude/commands/learn/slides.md @@ -53,13 +53,21 @@ On entry to Slides Mode, start the slide server and generate the HTML viewer: Start-Process "$env:TEMP\tov_slideshow.html" ``` -4. **For each slide**, display the text verbatim (translated to `session.language`), then advance the server to that slide so TTS plays automatically: - ```powershell - "SLIDE_NUMBER" | Set-Content "$env:TEMP\tov_current_slide.txt" - Invoke-RestMethod -Uri "http://localhost:7823/" -Method POST -Body "SLIDE_NUMBER" | Out-Null - ``` +--- + +## Step 4 — Per-Slide Loop + +Repeat this sequence for every slide, starting at slide 1: + +**a. Display text** — show the verbatim slide content (translated to `session.language`). + +**b. Fire TTS — MANDATORY, do not skip.** Immediately after displaying the text, run: +```powershell +Invoke-RestMethod -Uri "http://localhost:7823/" -Method POST -Body "SLIDE_NUMBER" | Out-Null +``` +Replace `SLIDE_NUMBER` with the current slide number (integer). This tells the server to speak the slide and sync the viewer. TTS fires automatically on every slide — the learner should never have to ask for it. -5. Wait for the learner to say **next** before advancing. +**c. Wait** for the learner to say **next** before advancing. **Do not** apply Journey Format. **Do not** ask thinking questions between slides. From 79edbd81a0bd146ba02456a6b25a2fce63bc22b4 Mon Sep 17 00:00:00 2001 From: yuval Date: Mon, 1 Jun 2026 17:47:38 +0300 Subject: [PATCH 4/5] =?UTF-8?q?=EF=BB=BFdocs:=20add=20changes.md=20summari?= =?UTF-8?q?zing=20yuval=5Fver=20additions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Covers detail levels 1/2/3, cover-every-slide rule, and new Slides Mode with interactive viewer and TTS server. Co-Authored-By: Claude Sonnet 4.6 --- changes.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 changes.md diff --git a/changes.md b/changes.md new file mode 100644 index 0000000..936573c --- /dev/null +++ b/changes.md @@ -0,0 +1,76 @@ +# Changes — yuval_ver branch + +## Overview + +Three additions to the `/learn` skill system, focused on giving the learner more control over how they experience course content. + +--- + +## 1. Detail Levels (`detail 1 / 2 / 3`) + +**What changed:** `teaching.md`, `learn.md` + +The learner can now type `detail 1`, `detail 2`, or `detail 3` at any point during a session to change how deeply Claude explains each slide. + +| Level | Behavior | +|-------|----------| +| `detail 1` | 1–2 sentences per slide — just the core idea, no follow-up question | +| `detail 2` | Full Journey Format, max 5 sentences — the default | +| `detail 3` | Journey Format + extended insight + bullet points with extra context | + +Claude now asks for the preferred detail level at the start of each session so learners don't have to discover this themselves. + +**Why:** Some learners are already familiar with parts of the material and want to move faster. Others want a deeper dive. This makes the tutor adapt to the learner rather than forcing everyone through the same pace. + +--- + +## 2. Cover Every Slide Rule + +**What changed:** `teaching.md` + +Claude is now required to cover every slide in the lesson — no skipping, no merging slides silently. If a slide is short or repetitive, it may summarize briefly (according to the active detail level), but it must represent every slide. + +**Why:** Previously Claude could skip slides it deemed redundant. Learners were missing content without knowing it. + +--- + +## 3. Slides Mode (`/learn slides` or `slides` command) + +**What changed:** `learn.md` (routing), new file `slides.md`, new scripts `slide-server.ps1` + `generate-slideshow.ps1` + +A new mode the learner can switch into at any time. Instead of Claude's teaching format, the learner sees and hears the actual course slides verbatim. + +**How it works:** +- Claude opens an interactive browser window showing the real slide images from the course +- The existing הבא / הקודם buttons in the slide images are made clickable via transparent overlays +- Extra controls are added: ⏸ השהה (pause TTS) and ⛶ מסך מלא (fullscreen) +- A local server (`localhost:7823`) keeps the browser viewer and Claude in sync — if Claude advances, the browser follows, and vice versa +- TTS reads the official course script for each slide automatically — no manual trigger needed +- Exercises in this mode are still written fresh by Claude (not pulled verbatim from the exercises file) + +**Learner commands in this mode:** + +| Command | Action | +|---------|--------| +| `next` | Advance to next slide | +| `stop slides` | Return to teaching mode at the current slide | +| `exercises` | Get fresh exercises based on slides covered so far | + +**Supporting scripts** (in `.claude/scripts/`): +- `slide-server.ps1` — preloads all TTS scripts at startup, speaks via Windows TTS on every slide change, handles pause/resume +- `generate-slideshow.ps1` — generates the HTML viewer for any lesson, positions click overlays over the existing nav buttons in the slide images + +**Why:** Some learners want to experience the slides the way they were designed — visually, with the recorded voiceover — rather than Claude's reformulation. This mode gives them that while keeping Claude available for exercises and Q&A. + +--- + +## File Map + +| File | Status | Purpose | +|------|--------|---------| +| `.claude/commands/learn.md` | Modified | Added slides routing + detail 1/2/3 and slides to learner commands | +| `.claude/commands/learn/teaching.md` | Modified | Cover-every-slide rule, detail levels, ask preference at session start | +| `.claude/commands/learn/slides.md` | New | Slides Mode — verbatim reading, viewer launch, TTS loop | +| `.claude/scripts/generate-slideshow.ps1` | New | Generates interactive HTML slide viewer | +| `.claude/scripts/slide-server.ps1` | New | Local TTS + slide state server on localhost:7823 | +| `CLAUDE.md` | Modified | Added Slides to modules table, noted scripts folder | From 673835fee2adfd8e55d1762d9d501e97dd64fc35 Mon Sep 17 00:00:00 2001 From: yuval Date: Tue, 2 Jun 2026 10:30:46 +0300 Subject: [PATCH 5/5] feat: auto-save progress on session end, Hebrew FAQ, pause fix - Add Stop hook (.claude/settings.json) that auto-saves lesson+slide on every session close - Add auto-save-progress.ps1: writes progress/lesson-X.Y.md silently without overwriting quiz scores - Fix TTS pause: split text by sentences so pause kicks in within ~5 seconds (was per-paragraph = up to 20s) - Fix resume button: now sends 'pause'/'resume' signals instead of killing+restarting TTS - Add FAQ.md in Hebrew covering Hebrew voice installation and SAPI registry bridge Co-Authored-By: Claude Sonnet 4.6 --- .claude/scripts/auto-save-progress.ps1 | 43 ++++++++++++++++ .claude/scripts/generate-slideshow.ps1 | 4 +- .claude/scripts/slide-server.ps1 | 34 +++++++++---- .claude/settings.json | 15 ++++++ FAQ.md | 70 ++++++++++++++++++++++++++ 5 files changed, 155 insertions(+), 11 deletions(-) create mode 100644 .claude/scripts/auto-save-progress.ps1 create mode 100644 .claude/settings.json create mode 100644 FAQ.md diff --git a/.claude/scripts/auto-save-progress.ps1 b/.claude/scripts/auto-save-progress.ps1 new file mode 100644 index 0000000..0de533a --- /dev/null +++ b/.claude/scripts/auto-save-progress.ps1 @@ -0,0 +1,43 @@ +# Auto-save progress when Claude session ends. +# Reads lesson/slide from TTS temp files and writes to skill-tutor-tutorials/progress/. +# Runs silently — no output on success. + +$lessonFile = "$env:TEMP\tov_current_lesson.txt" +$slideFile = "$env:TEMP\tov_current_slide.txt" +$progressDir = "$env:USERPROFILE\skill-tutor-tutorials\progress" + +if (!(Test-Path $lessonFile)) { exit 0 } +$lessonPath = (Get-Content $lessonFile -Raw -Encoding UTF8).Trim() +if (!$lessonPath) { exit 0 } + +$lessonFolder = Split-Path $lessonPath -Leaf +$lessonNum = if ($lessonFolder -match '^(\d+\.\d+)') { $Matches[1] } else { $lessonFolder } + +$slide = if (Test-Path $slideFile) { (Get-Content $slideFile -Raw).Trim() } else { "1" } +$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm" + +if (!(Test-Path $progressDir)) { New-Item -ItemType Directory -Path $progressDir -Force | Out-Null } + +$progressFile = "$progressDir\lesson-$lessonNum.md" + +# Read existing file to preserve quiz scores and notes +$existing = if (Test-Path $progressFile) { Get-Content $progressFile -Raw -Encoding UTF8 } else { "" } + +# Replace or prepend the auto-save block +$autoSaveBlock = @" + +**שיעור:** $lessonNum +**שקף אחרון:** $slide +**זמן:** $timestamp +**נתיב:** $lessonPath + + +"@ + +if ($existing -match '(?s).*?') { + $updated = $existing -replace '(?s).*?\r?\n?', $autoSaveBlock +} else { + $updated = $autoSaveBlock + $existing +} + +[System.IO.File]::WriteAllText($progressFile, $updated, (New-Object System.Text.UTF8Encoding($true))) diff --git a/.claude/scripts/generate-slideshow.ps1 b/.claude/scripts/generate-slideshow.ps1 index bfb9ce5..fbcf974 100644 --- a/.claude/scripts/generate-slideshow.ps1 +++ b/.claude/scripts/generate-slideshow.ps1 @@ -159,11 +159,11 @@ $slideImgTags paused = !paused; const btn = document.getElementById('pause-btn'); if (paused) { - fetch('http://localhost:7823/', { method: 'POST', body: 'stop' }).catch(() => {}); + fetch('http://localhost:7823/', { method: 'POST', body: 'pause' }).catch(() => {}); btn.innerHTML = '▶ המשך'; btn.classList.add('paused'); } else { - postSlide(current); + fetch('http://localhost:7823/', { method: 'POST', body: 'resume' }).catch(() => {}); btn.innerHTML = '⏸ השהה'; btn.classList.remove('paused'); } diff --git a/.claude/scripts/slide-server.ps1 b/.claude/scripts/slide-server.ps1 index c774753..f51799a 100644 --- a/.claude/scripts/slide-server.ps1 +++ b/.claude/scripts/slide-server.ps1 @@ -1,7 +1,9 @@ # Tov-learn Slide Server — localhost:7823 -# GET / → returns current slide number -# POST / {num} → advance to slide N + speak TTS -# POST / stop → kill current TTS only +# GET / → returns current slide number +# POST / {num} → advance to slide N + speak TTS +# POST / pause → pause current TTS in place +# POST / resume → resume from exact pause point +# POST / stop → stop current TTS param([int]$Port = 7823) @@ -9,12 +11,13 @@ $slideFile = "$env:TEMP\tov_current_slide.txt" $lessonFile = "$env:TEMP\tov_current_lesson.txt" $ttsTextFile = "$env:TEMP\tov_tts_text.txt" $ttsRunFile = "$env:TEMP\tov_tts_run.ps1" +$controlFile = "$env:TEMP\tov_tts_control.txt" if (!(Test-Path $slideFile)) { "1" | Set-Content $slideFile } $script:ttsProcess = $null $script:loadedLesson = "" -$script:ttsScripts = @{} # [int]slideNum → [string]text +$script:ttsScripts = @{} function Load-Lesson { param([string]$lessonPath) @@ -32,6 +35,8 @@ function Load-Lesson { } function Kill-TTS { + [System.IO.File]::WriteAllText($controlFile, "stop") + Start-Sleep -Milliseconds 200 if ($script:ttsProcess -and !$script:ttsProcess.HasExited) { try { $script:ttsProcess.Kill() } catch {} $script:ttsProcess = $null @@ -42,7 +47,6 @@ function Speak-Slide { param([int]$num) Kill-TTS - # Reload lesson if changed if (Test-Path $lessonFile) { $lesson = (Get-Content $lessonFile -Raw -Encoding UTF8).Trim() Load-Lesson $lesson @@ -51,23 +55,31 @@ function Speak-Slide { $text = $script:ttsScripts[$num] if (!$text) { return } - # Write TTS text with BOM so runner reads Hebrew correctly [System.IO.File]::WriteAllText($ttsTextFile, $text, (New-Object System.Text.UTF8Encoding($true))) + [System.IO.File]::WriteAllText($controlFile, "play") - # Build runner script — backtick-dollar escapes PS vars in the here-string $runner = @" Add-Type -AssemblyName System.Speech `$s = New-Object System.Speech.Synthesis.SpeechSynthesizer `$hv = `$s.GetInstalledVoices() | Where-Object { `$_.VoiceInfo.Culture.Name -like 'he-*' } | Select-Object -First 1 if (`$hv) { `$s.SelectVoice(`$hv.VoiceInfo.Name) } `$t = [System.IO.File]::ReadAllText('$ttsTextFile', [System.Text.Encoding]::UTF8) -`$s.Speak(`$t) +`$controlFile = '$controlFile' +`$chunks = [System.Text.RegularExpressions.Regex]::Split(`$t, '(?<=[.!?])\s+|\r?\n') | Where-Object { `$_.Trim() -ne '' } +foreach (`$chunk in `$chunks) { + while (`$true) { + `$ctrl = ([System.IO.File]::ReadAllText(`$controlFile)).Trim() + if (`$ctrl -eq 'stop') { exit } + if (`$ctrl -ne 'pause') { break } + Start-Sleep -Milliseconds 200 + } + `$s.Speak(`$chunk) +} "@ [System.IO.File]::WriteAllText($ttsRunFile, $runner, (New-Object System.Text.UTF8Encoding($true))) $script:ttsProcess = Start-Process powershell -ArgumentList "-NoProfile -WindowStyle Hidden -File `"$ttsRunFile`"" -PassThru } -# Pre-load current lesson if already set if (Test-Path $lessonFile) { $lesson = (Get-Content $lessonFile -Raw -Encoding UTF8).Trim() Load-Lesson $lesson @@ -91,6 +103,10 @@ while ($listener.IsListening) { if ($body -eq "stop") { Kill-TTS + } elseif ($body -eq "pause") { + [System.IO.File]::WriteAllText($controlFile, "pause") + } elseif ($body -eq "resume") { + [System.IO.File]::WriteAllText($controlFile, "resume") } else { $num = [int]$body "$num" | Set-Content $slideFile diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..9120472 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,15 @@ +{ + "hooks": { + "Stop": [ + { + "matcher": "", + "hooks": [ + { + "type": "command", + "command": "powershell -NoProfile -WindowStyle Hidden -File \"C:\\Users\\yuval\\Tov-learn\\.claude\\scripts\\auto-save-progress.ps1\"" + } + ] + } + ] + } +} diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 0000000..4b6528d --- /dev/null +++ b/FAQ.md @@ -0,0 +1,70 @@ +# שאלות נפוצות — Tov-learn + +--- + +## קול ו-TTS + +### איך מוסיפים קול בעברית למערכת? + +מערכת ה-TTS של Tov-learn משתמשת בקולות Windows SAPI. ברירת המחדל היא לחפש קול עברי אוטומטית — אם אין קול עברי מותקן, הדיקלום לא יעבוד. + +**שלב 1 — התקנת חבילת שפה עברית:** + +1. פתחו **הגדרות** ← **זמן ושפה** ← **שפה ואזור** +2. לחצו **הוסף שפה** וחפשו **עברית** +3. לאחר ההתקנה, לחצו על **עברית** ← **אפשרויות שפה** +4. מצאו את **המרת טקסט לדיבור** ולחצו **הורד** + +**שלב 2 — חיבור הקול ל-SAPI (נדרש פעם אחת):** + +Windows מתקין קולות חדשים תחת "OneCore" שאינו נגיש ישירות ל-SAPI. יש להריץ את הפקודה הבאה ב-PowerShell **כמנהל** (Admin): + +```powershell +$src = "HKLM:\SOFTWARE\Microsoft\Speech_OneCore\Voices\Tokens\MSTTS_V110_heIL_Asaf" +$dst = "HKLM:\SOFTWARE\Microsoft\Speech\Voices\Tokens\MSTTS_V110_heIL_Asaf" +function Copy-RegKey($srcPath, $dstPath) { + $srcKey = Get-Item $srcPath + New-Item -Path $dstPath -Force | Out-Null + foreach ($val in $srcKey.GetValueNames()) { + $data = $srcKey.GetValue($val, $null, "DoNotExpandEnvironmentNames") + $kind = $srcKey.GetValueKind($val) + Set-ItemProperty -Path $dstPath -Name $val -Value $data -Type $kind + } + foreach ($sub in $srcKey.GetSubKeyNames()) { + Copy-RegKey "$srcPath\$sub" "$dstPath\$sub" + } +} +Copy-RegKey $src $dst +Write-Host "הקול אסף חובר בהצלחה" +``` + +**שלב 3 — בדיקה:** + +```powershell +Add-Type -AssemblyName System.Speech +$s = New-Object System.Speech.Synthesis.SpeechSynthesizer +$s.GetInstalledVoices() | ForEach-Object { $_.VoiceInfo.Name + " | " + $_.VoiceInfo.Culture.Name } +``` + +אם מופיע `Microsoft Asaf | he-IL` — הכל מוכן. + +--- + +### אילו קולות נתמכים? + +| קול | שפה | סטטוס | +|-----|-----|--------| +| Microsoft Asaf | עברית (ישראל) | מומלץ | +| Microsoft David | אנגלית (ארה"ב) | ברירת מחדל אם אין עברית | + +המערכת תמיד מנסה לבחור קול עברי ראשון. אם לא נמצא — תדובר השפה הזמינה (ייתכן שלא יקרא עברית נכון). + +--- + +### ה-TTS מדלג על הטקסט העברי ומדבר רק מילים באנגלית — מה קורה? + +זה קורה כשאין קול עברי מותקן. הקול האנגלי קורא רק את האותיות הלטיניות ומדלג על כל האות העברי. + +**פתרון:** התקינו קול עברי לפי השלבים למעלה. + +---