Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
e45729d
refactor: unify LLM providers into MODEL_PROVIDERS list
LeafCreeper Apr 30, 2026
1625bb7
feat: migrate DB key derivation to v2 (sha256 + 100k PBKDF2)
LeafCreeper Apr 30, 2026
40f2441
feat: add PPT OCR + 10-min bucketed prompts (FireRed ASR2)
LeafCreeper Apr 30, 2026
c057ba2
feat: shard database into encrypted ~10MB chunks for incremental fetch
LeafCreeper Apr 30, 2026
09849ef
feat: three-state detail viewer + drop edit UI
LeafCreeper Apr 30, 2026
4787b1e
fix: deterministic shard encryption so frontend cache actually hits
LeafCreeper Apr 30, 2026
f5ca3f9
fix: reject PKCS7 false-positive decryptions with plaintext sanity va…
LeafCreeper Apr 30, 2026
fe2c8fb
fix: retain longest transcript across audio-retry attempts
LeafCreeper Apr 30, 2026
ca6d778
refactor: _check_session mutates client in place
LeafCreeper Apr 30, 2026
5531a4a
refactor: centralize SQLite schema in src/schema.py
LeafCreeper Apr 30, 2026
6c1f30e
feat: rewrite PPT pipeline — dHash dedup + multi-pattern invalid-page…
LeafCreeper Apr 30, 2026
fb0bc5c
feat: parallelize PPT downloads + OCR with ASR via thread pools
LeafCreeper Apr 30, 2026
34a255b
a lot of update!
LeafCreeper Apr 30, 2026
e55ceb0
personal use config updated
LeafCreeper Apr 30, 2026
30f6376
refactor: split src/ into api/ data/ ai/ runtime/ pipeline/ subpackages
LeafCreeper May 23, 2026
4240326
perf: scale ASR threads + cap OCR cores when ASR active + add OCR rat…
LeafCreeper May 23, 2026
7379250
feat: add scripts/test_prod_lecture.py + gate resummarize behind RESU…
LeafCreeper May 23, 2026
ef1a40a
feat: pluggable ASR backend (firered / sensevoice / zipformer)
LeafCreeper May 23, 2026
53fc5cc
chore: move scripts/test_prod_lecture.py off dev to the profiling branch
LeafCreeper May 23, 2026
76a2d7a
feat: default ASR to SenseVoice + cross-backend text postprocess
LeafCreeper May 23, 2026
af78be2
feat: preserve pre-v2 summary into a new old_summary column
LeafCreeper May 23, 2026
880d08e
feat(workflow): allow manual deploy of frontend from any branch
LeafCreeper May 23, 2026
b2384f9
feat: strip PowerPoint ribbon UI noise from OCR'd PPT text
LeafCreeper May 23, 2026
ed2cfaf
fix(sharder): migrate missing columns on old shards before INSERT
LeafCreeper May 23, 2026
4a91060
feat(workflow): enable RESUMMARIZE_OLD=1 in check.yml
LeafCreeper May 23, 2026
9e40efd
Update config.py
LeafCreeper May 23, 2026
da46aff
perf: cap OCR_MAX_TARGET at 2, down from 8
LeafCreeper May 23, 2026
6488efd
refactor: remove DynamicSemaphore/ResourceMonitor, simplify OCR concu…
LeafCreeper May 24, 2026
1547d2e
docs: update README with design discussion and V1→V2 changelog
LeafCreeper May 24, 2026
f62fa75
feat: parallel resummarize script (OCR + LLM across lectures)
LeafCreeper May 24, 2026
0d36286
feat: rewrite resummarize_parallel as dual OCR/LLM pipeline
LeafCreeper May 24, 2026
fc3e8a3
chore: commit remaining uncommitted changes
LeafCreeper May 24, 2026
45e563a
fix(workflow): update reset_data.yml git push to x-access-token format
LeafCreeper May 24, 2026
7b8b0ff
feat: reshard script + workflow — activity-aware sharding
LeafCreeper May 24, 2026
e678216
fix(reshard): split active courses into separate 3MB shards
LeafCreeper May 24, 2026
1736747
fix(dhash): fill-forward window dedup — queue semantics
LeafCreeper May 24, 2026
2b5e9f2
feat: post-OCR text subset dedup (3-gram directional containment)
LeafCreeper May 24, 2026
6adb7bf
fix(workflow): robust data branch fetch after force-push
LeafCreeper May 24, 2026
b3a6120
fix(workflow): use +refspec + ls-tree for force-pushed data branches
LeafCreeper May 24, 2026
337e218
chore: gitignore local encryption test artifacts
LeafCreeper May 24, 2026
cceb6d4
perf: defer OCR to after ASR to avoid CPU contention
LeafCreeper May 24, 2026
399a0a7
feat: add CPU+memory meter to progress lines
LeafCreeper May 24, 2026
6f74f0b
perf: pre-submit next lecture's OCR during LLM wait
LeafCreeper May 24, 2026
5418ffb
fix: drain in-flight pre-submitted OCR before ASR
LeafCreeper May 24, 2026
9b36646
feat: add resource-bound tag + network throughput to progress lines
LeafCreeper May 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 116 additions & 64 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
path: |
sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/
silero_vad.onnx
key: asr-models-sensevoice-2024-07-17
key: asr-models-sense-voice-2024-07-17

- name: Download ASR models
if: steps.cache-models.outputs.cache-hit != 'true'
Expand All @@ -56,46 +56,98 @@ jobs:
rm sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17.tar.bz2
wget -q https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/silero_vad.onnx

- name: Cache RapidOCR models
uses: actions/cache@v4
with:
path: ~/.cache/rapidocr
key: rapidocr-onnx-default-v1

- name: Fetch database from data branch
run: |
mkdir -p data
# Try new compressed format first, then fall back to legacy
if curl -fL -o data/icourse.db.gz.enc.tmp \
"https://raw.githubusercontent.com/${{ github.repository }}/data/data/icourse.db.gz.enc" 2>/dev/null; then
mv data/icourse.db.gz.enc.tmp data/icourse.db.gz.enc
echo "Loaded compressed database from data branch."
elif curl -fL -o data/icourse.db.enc.tmp \
"https://raw.githubusercontent.com/${{ github.repository }}/data/data/icourse.db.enc" 2>/dev/null; then
mv data/icourse.db.enc.tmp data/icourse.db.enc
echo "Loaded legacy (uncompressed) database from data branch."
# Force-fetch (+refspec) so non-fast-forward updates from
# force-pushed data branches are picked up (shallow clone
# otherwise refuses to overwrite the stale tracking ref).
git fetch --depth=1 origin "+refs/heads/data:refs/remotes/origin/data" 2>/dev/null \
|| { echo "No data branch, starting fresh."; exit 0; }
# Use ls-tree (reads tree directly) instead of cat-file
# (requires fetched blobs) in case the shallow clone hasn't
# fetched every blob object yet.
git ls-tree origin/data --name-only data/ | grep -q . \
|| { echo "No database found on data branch, starting fresh."; exit 0; }
# Determine format from tree listing (ls-tree reads tree
# objects directly without needing the blobs fetched).
files=$(git ls-tree origin/data --name-only data/ | tr '\n' ' ')
echo "Data branch files: $files"
if echo "$files" | grep -q "icourse-index.enc"; then
mkdir -p data/sharded/shards
git show origin/data:data/icourse-index.enc > data/sharded/icourse-index.enc
shard_paths=$(git ls-tree -r --name-only origin/data | grep '^data/shards/' || true)
for path in $shard_paths; do
base=$(basename "$path")
git show "origin/data:$path" > "data/sharded/shards/$base"
done
echo "Loaded $(ls data/sharded/shards 2>/dev/null | wc -l) shard(s)."
elif echo "$files" | grep -q "icourse.db.gz.enc"; then
git show origin/data:data/icourse.db.gz.enc > data/icourse.db.gz.enc
echo "Loaded legacy compressed database."
elif echo "$files" | grep -q "icourse.db.enc"; then
git show origin/data:data/icourse.db.enc > data/icourse.db.enc
echo "Loaded legacy uncompressed database."
else
echo "No database found on data branch, starting fresh."
fi

- name: Decrypt database
if: hashFiles('data/icourse.db.gz.enc') != '' || hashFiles('data/icourse.db.enc') != ''
- name: Decode database
if: hashFiles('data/sharded/icourse-index.enc', 'data/icourse.db.gz.enc', 'data/icourse.db.enc') != ''
env:
DB_KEY: ${{ secrets.STUID }}${{ secrets.UISPSW }}${{ secrets.DASHSCOPE_API_KEY }}${{ secrets.SMTP_PASSWORD }}
STUID: ${{ secrets.STUID }}
UISPSW: ${{ secrets.UISPSW }}
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}
run: |
if [ -f data/icourse.db.gz.enc ]; then
# New format: decrypt then decompress
if [ -f data/sharded/icourse-index.enc ]; then
# Sharded format — always v2.
python scripts/db_shard.py reassemble data/sharded data/icourse.db
echo "Reassembled sharded database."
exit 0
fi

# Legacy single-file path (one-time migration on first sharded run).
NEW_KEY=$(printf '%s' "ICSv2:${STUID}:${UISPSW}" | sha256sum | cut -d' ' -f1)
LEGACY_KEY="${STUID}${UISPSW}${DASHSCOPE_API_KEY}${SMTP_PASSWORD}"

decrypt_blob() {
local in_path="$1"
local out_path="$2"
if openssl enc -aes-256-cbc -d -pbkdf2 -iter 100000 \
-in "$in_path" -out "$out_path" \
-pass pass:"$NEW_KEY" 2>/dev/null; then
echo "v2"
return 0
fi
if openssl enc -aes-256-cbc -d -pbkdf2 \
-in data/icourse.db.gz.enc -out data/icourse.db.gz \
-pass env:DB_KEY 2>/dev/null && \
gzip -d data/icourse.db.gz; then
echo "Database decrypted and decompressed."
-in "$in_path" -out "$out_path" \
-pass pass:"$LEGACY_KEY" 2>/dev/null; then
echo "legacy"
return 0
fi
return 1
}

if [ -f data/icourse.db.gz.enc ]; then
if VER=$(decrypt_blob data/icourse.db.gz.enc data/icourse.db.gz); then
gzip -d data/icourse.db.gz
echo "Legacy compressed DB decrypted ($VER)."
else
echo "::warning::Decryption failed. Starting with fresh database."
echo "::warning::Decryption failed (both keys). Starting with fresh database."
rm -f data/icourse.db data/icourse.db.gz
fi
elif [ -f data/icourse.db.enc ]; then
# Legacy format: decrypt only
if openssl enc -aes-256-cbc -d -pbkdf2 \
-in data/icourse.db.enc -out data/icourse.db \
-pass env:DB_KEY 2>/dev/null; then
echo "Database decrypted (legacy format)."
if VER=$(decrypt_blob data/icourse.db.enc data/icourse.db); then
echo "Legacy uncompressed DB decrypted ($VER)."
else
echo "::warning::Decryption failed. Starting with fresh database."
echo "::warning::Decryption failed (both keys). Starting with fresh database."
rm -f data/icourse.db
fi
fi
Expand All @@ -115,7 +167,10 @@ jobs:
StuId: ${{ secrets.STUID }}
UISPsw: ${{ secrets.UISPSW }}
COURSE_IDS: ${{ secrets.COURSE_IDS }}
CRAWL_TERM: ${{ secrets.CRAWL_TERM }}
RESUMMARIZE_OLD: "1"
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
SMTP_EMAIL: ${{ secrets.SMTP_EMAIL }}
SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}
Expand All @@ -125,7 +180,10 @@ jobs:
- name: Deploy database to data branch
if: always()
env:
DB_KEY: ${{ secrets.STUID }}${{ secrets.UISPSW }}${{ secrets.DASHSCOPE_API_KEY }}${{ secrets.SMTP_PASSWORD }}
STUID: ${{ secrets.STUID }}
UISPSW: ${{ secrets.UISPSW }}
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ ! -f data/icourse.db ]; then
Expand All @@ -139,48 +197,42 @@ jobs:
exit 0
fi

# Re-fetch latest remote DB (may have been updated by concurrent workflow)
DEPLOY_DB=data/icourse.db
# Pull the latest remote shards (a concurrent run may have written
# new data) and merge our local changes in before re-sharding.
git fetch origin data --depth=1 2>/dev/null || echo "No data branch yet."
if git rev-parse origin/data >/dev/null 2>&1; then
# Try new compressed format, then legacy
git show origin/data:data/icourse.db.gz.enc > data/remote.gz.enc 2>/dev/null || true
git show origin/data:data/icourse.db.enc > data/remote.enc 2>/dev/null || true
# Remove empty files from failed git show
find data -name 'remote.*' -empty -delete 2>/dev/null || true
if [ -s data/remote.gz.enc ] && \
openssl enc -aes-256-cbc -d -pbkdf2 \
-in data/remote.gz.enc -out data/remote.db.gz \
-pass env:DB_KEY 2>/dev/null && \
gzip -d data/remote.db.gz; then
echo "Merging local changes into latest remote database..."
python scripts/merge_db.py data/icourse.db data/remote.db
DEPLOY_DB=data/remote.db
elif [ -s data/remote.enc ] && \
openssl enc -aes-256-cbc -d -pbkdf2 \
-in data/remote.enc -out data/remote.db \
-pass env:DB_KEY 2>/dev/null; then
echo "Merging local changes into latest remote database (legacy)..."
python scripts/merge_db.py data/icourse.db data/remote.db
DEPLOY_DB=data/remote.db
fi
DEPLOY_DB=data/icourse.db
if git rev-parse origin/data >/dev/null 2>&1 && \
git cat-file -e origin/data:data/icourse-index.enc 2>/dev/null; then
rm -rf data/remote_sharded
mkdir -p data/remote_sharded/shards
git show origin/data:data/icourse-index.enc > data/remote_sharded/icourse-index.enc
shard_paths=$(git ls-tree -r --name-only origin/data | grep '^data/shards/' || true)
for path in $shard_paths; do
base=$(basename "$path")
git show "origin/data:$path" > "data/remote_sharded/shards/$base"
done
python scripts/db_shard.py reassemble data/remote_sharded data/remote.db
echo "Merging local changes into latest remote..."
python scripts/merge_db.py data/icourse.db data/remote.db
DEPLOY_DB=data/remote.db
fi

# Compress then encrypt (new format)
gzip -c "$DEPLOY_DB" > data/icourse.db.gz
openssl enc -aes-256-cbc -salt -pbkdf2 \
-in data/icourse.db.gz -out data/icourse.db.gz.enc \
-pass env:DB_KEY
# Re-shard the merged DB and stage to a clean tree for force-push.
rm -rf data/sharded_out
mkdir -p data/sharded_out
python scripts/db_shard.py shard "$DEPLOY_DB" data/sharded_out

echo "Deploying to data branch..."
mkdir -p /tmp/db_deploy/data
cp data/icourse.db.gz.enc /tmp/db_deploy/data/
rm -rf /tmp/db_deploy
mkdir -p /tmp/db_deploy/data/shards
cp data/sharded_out/icourse-index.enc /tmp/db_deploy/data/
cp data/sharded_out/shards/*.db.gz.enc /tmp/db_deploy/data/shards/
cd /tmp/db_deploy

git init
git checkout -b data
git init -q
git checkout -q -b data
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add data/icourse.db.gz.enc
git commit -m "chore: update encrypted database"
git push --force "https://${{ github.actor }}:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git" data
git add data/
git commit -q -m "chore: update encrypted database (sharded v2)"
git remote add origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git"
git push --force origin data
11 changes: 11 additions & 0 deletions .github/workflows/deploy-frontend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ on:
branches: [main]
paths: ['frontend/**']
workflow_dispatch:
inputs:
branch:
description: "Branch to deploy frontend from (e.g. main, dev)"
required: true
default: "main"
type: string

permissions:
contents: read
Expand All @@ -22,7 +28,12 @@ jobs:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
# On manual dispatch we honour the ``branch`` input; for the auto
# push trigger this resolves to '' and actions/checkout falls back
# to the triggering ref (i.e. main per the push filter above).
- uses: actions/checkout@v4
with:
ref: ${{ inputs.branch || github.ref }}

- name: Setup Pages
uses: actions/configure-pages@v5
Expand Down
59 changes: 45 additions & 14 deletions .github/workflows/export.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,30 +52,61 @@ jobs:
- name: Fetch database from data branch
run: |
mkdir -p data
if curl -fL -o data/icourse.db.gz.enc.tmp \
"https://raw.githubusercontent.com/${{ github.repository }}/data/data/icourse.db.gz.enc" 2>/dev/null; then
mv data/icourse.db.gz.enc.tmp data/icourse.db.gz.enc
echo "Loaded compressed database."
elif curl -fL -o data/icourse.db.enc.tmp \
"https://raw.githubusercontent.com/${{ github.repository }}/data/data/icourse.db.enc" 2>/dev/null; then
mv data/icourse.db.enc.tmp data/icourse.db.enc
echo "Loaded legacy database."
git fetch origin data --depth=1
if git cat-file -e origin/data:data/icourse-index.enc 2>/dev/null; then
mkdir -p data/sharded/shards
git show origin/data:data/icourse-index.enc > data/sharded/icourse-index.enc
shard_paths=$(git ls-tree -r --name-only origin/data | grep '^data/shards/' || true)
for path in $shard_paths; do
base=$(basename "$path")
git show "origin/data:$path" > "data/sharded/shards/$base"
done
echo "Loaded $(ls data/sharded/shards 2>/dev/null | wc -l) shard(s)."
elif git cat-file -e origin/data:data/icourse.db.gz.enc 2>/dev/null; then
git show origin/data:data/icourse.db.gz.enc > data/icourse.db.gz.enc
echo "Loaded legacy compressed database."
elif git cat-file -e origin/data:data/icourse.db.enc 2>/dev/null; then
git show origin/data:data/icourse.db.enc > data/icourse.db.enc
echo "Loaded legacy uncompressed database."
else
echo "::error::No database found on data branch."
exit 1
fi

- name: Decrypt database
- name: Decode database
env:
DB_KEY: ${{ secrets.STUID }}${{ secrets.UISPSW }}${{ secrets.DASHSCOPE_API_KEY }}${{ secrets.SMTP_PASSWORD }}
STUID: ${{ secrets.STUID }}
UISPSW: ${{ secrets.UISPSW }}
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}
run: |
if [ -f data/icourse.db.gz.enc ]; then
if [ -f data/sharded/icourse-index.enc ]; then
python scripts/db_shard.py reassemble data/sharded data/icourse.db
echo "Reassembled sharded database."
exit 0
fi

NEW_KEY=$(printf '%s' "ICSv2:${STUID}:${UISPSW}" | sha256sum | cut -d' ' -f1)
LEGACY_KEY="${STUID}${UISPSW}${DASHSCOPE_API_KEY}${SMTP_PASSWORD}"

decrypt_blob() {
local in_path="$1"
local out_path="$2"
if openssl enc -aes-256-cbc -d -pbkdf2 -iter 100000 \
-in "$in_path" -out "$out_path" \
-pass pass:"$NEW_KEY" 2>/dev/null; then
return 0
fi
openssl enc -aes-256-cbc -d -pbkdf2 \
-in data/icourse.db.gz.enc -out data/icourse.db.gz -pass env:DB_KEY
-in "$in_path" -out "$out_path" \
-pass pass:"$LEGACY_KEY" 2>/dev/null
}

if [ -f data/icourse.db.gz.enc ]; then
decrypt_blob data/icourse.db.gz.enc data/icourse.db.gz
gzip -d data/icourse.db.gz
else
openssl enc -aes-256-cbc -d -pbkdf2 \
-in data/icourse.db.enc -out data/icourse.db -pass env:DB_KEY
decrypt_blob data/icourse.db.enc data/icourse.db
fi
echo "Database decrypted."

Expand Down
Loading
Loading