* feat(pipeline): fail loudly instead of completing with 0 clips
Introduce PipelineFailure(stage, message, hint). The simple pipeline adapter
now pre-flights the LLM provider (skipped in AUTOCLIP_LLM_CACHE_DIR replay),
and raises a stage-tagged failure when: no subtitle could be obtained, step1
gets no parseable outline (or every chunk's LLM call fails), the timeline is
empty, scoring keeps nothing, or ffmpeg produces zero clip files. Before, all
of these silently continued and the project ended up 'Completed · 0 clips'
(#100, #11, #24). A single failing chunk in step1 still continues; only
all-chunks-failed is fatal.
tasks/processing.py read result['message'] while the adapter returned
result['error'], so users only ever saw '处理失败'. Now reads either and
records the failing stage on the task.
ProjectResponse gains error_message (latest failed task's message; CLI runs
fall back to project_metadata.last_error) so the detail page, project card and
in-app feedback dialog show the actual reason.
Adds backend/tests/test_pipeline_failures.py (13 cases).
Co-authored-by: Kris K <zhouxiaoka@users.noreply.github.com>
* fix(upload): actually auto-start processing after local upload; desktop update_state no-op
/projects/upload referenced an undefined 'db' when checking for a running
import task. The NameError was swallowed by the surrounding except, so since
593cc62b (May) locally uploaded projects never started processing and sat in
'pending' until the user clicked start/retry.
Re-enabling that path exposed that process_import_task calls
self.update_state(), which in desktop mode tries to reach the Redis result
backend and dies with ConnectionRefused. DesktopAwareTask.update_state is now
a no-op in desktop mode; user-visible progress goes through simple_progress.
Co-authored-by: Kris K <zhouxiaoka@users.noreply.github.com>
* fix(db): stop sharing one SQLite connection across threads
For file-backed SQLite the engine used StaticPool, i.e. a single connection
for the whole process. In desktop mode the request thread, the import-task
thread and the pipeline thread each open their own Session on that one
connection, so their BEGIN/COMMIT/ROLLBACK interleave: one thread's
session.close() rolls back another thread's freshly INSERTed Task row
(ObjectDeletedError: row not present; tasks vanishing; progress stuck).
Use the default pool (one connection per Session) for file databases, enable
WAL + busy_timeout so readers and the writer don't block each other, and keep
StaticPool only for :memory:.
Co-authored-by: Kris K <zhouxiaoka@users.noreply.github.com>
* docs: changelog + handoff for fail-like-failure and the three latent bugs it uncovered
Co-authored-by: Kris K <zhouxiaoka@users.noreply.github.com>
* feat(llm): DashScope international endpoint (#45) + stop logging the API key
DashScopeProvider accepts base_url (or DASHSCOPE_BASE_URL) and switches to
OpenAI-compatible mode when one is set, so keys issued on alibabacloud.com
can hit dashscope-intl.aliyuncs.com per instance without touching the native
SDK's process-global URL. LLMManager keeps a separate dashscope_base_url so a
leftover OPENAI_BASE_URL in Docker .env cannot re-route Qwen. Settings page
gets a 中国站 / 国际站 segmented control on the Qwen card; /test-api forwards
the URL; compose / env.example / DOCKER.md document DASHSCOPE_BASE_URL.
Also drops the INFO log line that printed the full DashScope API key.
Co-authored-by: Kris K <zhouxiaoka@users.noreply.github.com>
* feat(pipeline): settings-page min score threshold actually reaches step3
resolve_min_score_threshold(): CLI MIN_SCORE_OVERRIDE > settings.json
processing.processing_min_score (hot-reloaded via LLMManager) > default 0.7.
Previously the settings page only mutated the API process's in-memory config
and every pipeline run used the 0.7 constant. chunk_size / max_clips are now
loaded into settings too for later steps to consume.
Co-authored-by: Kris K <zhouxiaoka@users.noreply.github.com>
* build(release): weekly release tooling
scripts/bump_version.py sets the version in tauri.conf.json / Cargo.toml /
pyproject.toml / desktop_config.py and rolls CHANGELOG [未发布] into a dated
section (--check verifies they agree). scripts/release_notes.py renders the
GitHub Release body from that CHANGELOG section plus platform install notes;
desktop-build.yml's release job now uses it via body_path. RELEASE_CHECKLIST.md
rewritten for a weekly cadence.
Co-authored-by: Kris K <zhouxiaoka@users.noreply.github.com>
* docs: changelog / handoff for dashscope intl, min_score plumbing and weekly release flow; fix 1.2.1 compare link
Co-authored-by: Kris K <zhouxiaoka@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Kris K <zhouxiaoka@users.noreply.github.com>
- docker-compose(.dev).yml: api and worker now receive LLM_PROVIDER, API_MODEL_NAME,
OPENAI_BASE_URL, API_*_API_KEY and AUTOCLIP_YT_SUBTITLE_LANGS from the host .env
(compose interpolation). Without this the .env that DOCKER.md tells users to write
never reached the containers
- ci docker-smoke: write a throwaway .env and assert LLMManager in both api and
worker resolves provider / model / base_url from it
- DOCKER.md: LLM section with Zhipu / DeepSeek / host Ollama examples
- CHANGELOG 1.2.1: entries for #92#93#94 and the un-persisted provider fix
- HANDOFF: status snapshot for main@992239a, remaining manual release steps,
v1.3 todo
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Kris K <zhouxiaoka@users.noreply.github.com>
* fix(docker): make the documented docker-compose path actually run
- .dockerignore: allow docker-entrypoint.sh / docker-dev-entrypoint.sh, which
Dockerfile COPYs; excluding them made every 'docker build' fail (#1#4#9#47#50#88)
- add .gitattributes forcing LF on shell scripts so Windows clones can start
the container (#73#88)
- base image python:3.9-slim -> python:3.11-slim; current yt-dlp no longer
supports 3.9 and only yields 360p via the android fallback (#88)
- celery worker in docker-compose / dev compose / start_autoclip.sh now
consumes the queues task_routes actually targets
(-Q celery,processing,video,notification,upload); without it tasks piled up
in 'processing' and were never executed (#88)
- the four compose services share one image tag so it is built once
- docs: Python 3.10+ requirement, Linux bind-mount permission note
Co-authored-by: Kris K <zhouxiaoka@users.noreply.github.com>
* fix(backend): remove machine-specific paths and the Redis probe that failed every Docker project
- task_submission_utils: the queue-depth log after a successful send_task used
redis.Redis(host='localhost') inside the try block, so under docker-compose
(Redis at redis:6379) every project was marked failed right after being
queued. Probe now uses REDIS_URL and only warns on failure (#88)
- youtube.py: yt-dlp was invoked via a hardcoded /Users/zhoukk venv path with
a hardcoded cwd, so /parse returned 500 on every other machine. Use
sys.executable -m yt_dlp with the data directory as cwd (#88)
- youtube.py: requesting five subtitle languages at once triggers HTTP 429 and
aborts the download; default to zh-Hans,zh,en, overridable with
AUTOCLIP_YT_SUBTITLE_LANGS (#88)
- llm_providers._build_full_input: JSON-serialise list/tuple input instead of
str() so the scoring step no longer breaks on list payloads (#53)
- fix_project_thumbnails.py / SettingsPage.tsx: drop remaining hardcoded
developer paths (the settings page was writing them into users'
settings.json)
- delete backend/api/v1/youtube_improved.py (unreferenced)
- add regression tests for the submission path and input serialisation
Co-authored-by: Kris K <zhouxiaoka@users.noreply.github.com>
* ci: add docker-compose smoke job
Builds the image, starts redis + api + worker, and asserts: API health,
yt-dlp importable on the image's Python, Redis reachable through REDIS_URL
from the app container, and the worker consuming every routed queue
(celery, processing, video, notification, upload). Guards the failure modes
from #88 that nothing in CI previously exercised.
Co-authored-by: Kris K <zhouxiaoka@users.noreply.github.com>
* chore: bump desktop version to 1.2.1 and stop hardcoding app_version in the backend
Cargo.toml was still 1.1.0 while tauri.conf.json said 1.2.0. Align both (and
Cargo.lock) at 1.2.1. The Rust launcher now passes CARGO_PKG_VERSION to the
backend as AUTOCLIP_APP_VERSION so /settings reports the real version instead
of a fixed "1.0.0".
Co-authored-by: Kris K <zhouxiaoka@users.noreply.github.com>
* docs: 1.2.1 changelog; refresh HANDOFF with current status, PR/issue triage and plan
CHANGELOG 1.2.0 had absorbed old unreleased web-side items wholesale; keep
them but label them as historical. Fix the compare/release links that still
pointed at your-username.
Co-authored-by: Kris K <zhouxiaoka@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Kris K <zhouxiaoka@users.noreply.github.com>