Skip to content

Commit 3ff005d

Browse files
committed
feat: gate official transcript behind a config flag, add UI checkbox
USE_OFFICIAL_TRANSCRIPT env var (default false) controls whether the pipeline probes the iCourse transcript API before running ASR. single_run.yml accepts use_official_transcript input; the frontend exposes it as a small checkbox next to the 单次运行 button.
1 parent 43d71ba commit 3ff005d

6 files changed

Lines changed: 40 additions & 21 deletions

File tree

.github/workflows/single_run.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ on:
77
description: "Comma-separated course IDs to process (e.g. 30004,30005)"
88
required: true
99
type: string
10+
use_official_transcript:
11+
description: "Use official iCourse transcripts instead of ASR"
12+
required: false
13+
default: false
14+
type: boolean
1015

1116
permissions:
1217
contents: write
@@ -157,6 +162,7 @@ jobs:
157162
StuId: ${{ secrets.STUID }}
158163
UISPsw: ${{ secrets.UISPSW }}
159164
COURSE_IDS: ${{ inputs.course_ids }}
165+
USE_OFFICIAL_TRANSCRIPT: ${{ inputs.use_official_transcript && '1' || '' }}
160166
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
161167
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
162168
SMTP_EMAIL: ${{ secrets.SMTP_EMAIL }}

frontend/index.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,13 @@ <h3 class="text-sm font-semibold text-gray-700">单次运行</h3>
705705
<div x-show="subsSaving" class="spinner !w-4 !h-4 !border-2 !border-white/30 !border-t-white"></div>
706706
<span x-text="subsSaving ? '加密上传中…' : '保存订阅到 Secret'"></span>
707707
</button>
708+
<label class="flex items-center gap-1.5 cursor-pointer select-none"
709+
:class="singleRunIds.length === 0 ? 'opacity-40' : ''">
710+
<input type="checkbox" x-model="singleRunUseOfficial"
711+
:disabled="singleRunIds.length === 0"
712+
class="w-3.5 h-3.5 rounded border-gray-300 text-emerald-500 focus:ring-emerald-400">
713+
<span class="text-xs text-gray-500">官方字幕</span>
714+
</label>
708715
<button @click="runSingleRunWorkflow()" :disabled="singleRunTriggering || singleRunIds.length === 0"
709716
class="px-5 py-2.5 bg-emerald-500 text-white rounded-lg text-sm font-medium hover:bg-emerald-600 disabled:opacity-50 flex items-center gap-2">
710717
<div x-show="singleRunTriggering" class="spinner !w-4 !h-4 !border-2 !border-white/30 !border-t-white"></div>

frontend/js/app.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ document.addEventListener("alpine:init", () => {
282282
_subsFilterTimer: null, _deptFilterTimer: null,
283283
subsSaving: false, subsError: "",
284284
singleRunTriggering: false,
285+
singleRunUseOfficial: false,
285286
/* Per-browser pinned-courses set, lazily synced to localStorage. */
286287
starred: _loadStarred(),
287288

@@ -983,7 +984,7 @@ document.addEventListener("alpine:init", () => {
983984
// than overloading the scheduled check workflow.
984985
await ICS.github.triggerSingleRunWorkflow(
985986
this.repoOwner, this.repoName, "main", creds.token,
986-
this.singleRunIds,
987+
this.singleRunIds, this.singleRunUseOfficial,
987988
);
988989
this._toast(
989990
"已触发单次运行,处理 " + this.singleRunIds.length + " 门课。请到 Actions 查看进度",

frontend/js/github.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -266,22 +266,19 @@ async function _setCourseIdsSecret(owner, repo, token, courseIds) {
266266
return value;
267267
}
268268

269-
async function _triggerSingleRunWorkflow(owner, repo, ref, token, courseIds) {
270-
// Fires the Single Run workflow (single_run.yml) via workflow_dispatch.
271-
// The workflow takes a ``course_ids`` input (comma-separated list) and
272-
// processes ONLY those courses without touching the persisted
273-
// ``COURSE_IDS`` secret used by the daily check workflow.
274-
// PAT needs ``Actions: Read and write``.
269+
async function _triggerSingleRunWorkflow(owner, repo, ref, token, courseIds, useOfficial) {
275270
const url = `${_GH_API}/repos/${owner}/${repo}/actions/workflows/single_run.yml/dispatches`;
276271
const ids = (Array.isArray(courseIds) ? courseIds : [])
277272
.map(String).map((s) => s.trim()).filter(Boolean).join(",");
278273
if (!ids) throw new Error("单次运行列表为空");
274+
const inputs = { course_ids: ids };
275+
if (useOfficial) inputs.use_official_transcript = "true";
279276
const res = await fetch(url, {
280277
method: "POST",
281278
headers: { ..._ghHeaders(token), "Content-Type": "application/json" },
282279
body: JSON.stringify({
283280
ref: ref || "main",
284-
inputs: { course_ids: ids },
281+
inputs: inputs,
285282
}),
286283
});
287284
if (res.status === 204) return;

src/pipeline/lecture_runner.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from src.ai import bucketer
3737
from src.pipeline.ppt_pipeline import PPTPipeline
3838
from src.ai.transcriber import IncompleteAudioError, NoAudioStreamError
39+
from src.runtime import config
3940

4041
if TYPE_CHECKING:
4142
from src.data.database import Database
@@ -202,19 +203,20 @@ def _get_transcript(self, existing: dict | None, course_id: str,
202203
)
203204
return existing["transcript"], None
204205

205-
# Try official transcript before firing up ASR.
206-
try:
207-
official = self._client.get_transcript_segments(sub_id)
208-
if self._official_transcript_usable(official):
209-
text = " ".join(s["text"] for s in official)
210-
self._reporter.info(
211-
f" Using official transcript "
212-
f"({len(text)} chars, {len(official)} segments)"
213-
)
214-
self._db.update_transcript(sub_id, text)
215-
return text, official
216-
except Exception:
217-
pass # fall through to ASR
206+
# Try official transcript before firing up ASR (config-gated).
207+
if config.USE_OFFICIAL_TRANSCRIPT:
208+
try:
209+
official = self._client.get_transcript_segments(sub_id)
210+
if self._official_transcript_usable(official):
211+
text = " ".join(s["text"] for s in official)
212+
self._reporter.info(
213+
f" Using official transcript "
214+
f"({len(text)} chars, {len(official)} segments)"
215+
)
216+
self._db.update_transcript(sub_id, text)
217+
return text, official
218+
except Exception:
219+
pass # fall through to ASR
218220

219221
# Pull the audio handle. ``schedule`` is idempotent — usually the
220222
# previous lecture already kicked it off (Phase C), but for the

src/runtime/config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,12 @@ def resolve_model_providers() -> list[dict]:
164164
os.environ.get("VIDEO_DOWNLOAD_CONCURRENCY", "2")
165165
)
166166

167+
# 是否优先使用 iCourse 官方字幕(跳过 ASR 转录)。默认关闭。
168+
USE_OFFICIAL_TRANSCRIPT = (
169+
os.environ.get("USE_OFFICIAL_TRANSCRIPT", "").strip().lower()
170+
in ("1", "true", "yes")
171+
)
172+
167173
# 监控的课程 ID 列表
168174
COURSE_IDS = [
169175
c.strip()

0 commit comments

Comments
 (0)