free-short-video 5.7.4 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -4
- package/core/api/error_collector.py +16 -0
- package/core/artifacts.py +247 -1
- package/core/config.py +5 -0
- package/core/dependency_graph.py +522 -0
- package/core/pipelines/__init__.py +177 -1
- package/core/pipelines/anchor_video.py +19 -1
- package/core/pipelines/creative/pipeline.py +46 -5
- package/core/pipelines/creative/steps_audio.py +1 -1
- package/core/pipelines/creative/steps_script.py +18 -6
- package/core/pipelines/manuscript_video.py +8 -0
- package/core/pipelines/multi_scene.py +10 -1
- package/core/pipelines/poetry_video.py +14 -0
- package/core/task_manager.py +8 -0
- package/models/task.py +30 -0
- package/package.json +1 -1
- package/start.bat +37 -16
- package/static/assets/index-CCQTBSQr.js +54 -0
- package/static/assets/index-Didd6fOX.css +1 -0
- package/static/index.html +2 -2
- package/web/deps.py +20 -0
- package/web/routes/config_routes.py +2 -0
- package/web/routes/task_creation_routes.py +61 -1
- package/web/routes/task_routes.py +233 -0
- package/web/routes/video_routes.py +267 -1
- package/static/assets/index-C-Z5weAF.css +0 -1
- package/static/assets/index-Da44GBbk.js +0 -30
package/README.md
CHANGED
|
@@ -1,15 +1,40 @@
|
|
|
1
1
|
---
|
|
2
2
|
|
|
3
|
-
# What's New in
|
|
3
|
+
# What's New in v6.1.0
|
|
4
4
|
|
|
5
5
|
## What's New
|
|
6
6
|
|
|
7
|
-
###
|
|
7
|
+
### Retry-First Guidance on Task Failure
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
The failed-task panel now gives you a clear action path instead of a dead-end error line:
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
- **Retry Task button** — resumes the task from the failed step via the existing checkpoint resume (no new backend endpoint, no full resubmit).
|
|
12
|
+
- **Retry count tracking** — retries are counted per task and persist across refreshes; the count resets when the task succeeds.
|
|
13
|
+
- **Transient-failure copy** — the panel explains that most failures come from temporary rate limiting / network timeouts / model-service fluctuation and are worth retrying before reporting.
|
|
14
|
+
|
|
15
|
+
### One-Click Issue Feedback with Progressive Expansion
|
|
16
|
+
|
|
17
|
+
When retries don't help, the feedback area helps you report efficiently:
|
|
18
|
+
|
|
19
|
+
- **Progressive expansion** — the feedback area stays a low-key hint line under the retry guidance; after **2 failed retries** it auto-expands, switching the copy to "still failing after N retries, please report with diagnostic info." You can open it manually at any time (your choice is remembered per task).
|
|
20
|
+
- **Deterministic-failure pre-screen** — parameter/content errors (HTTP 400, content policy, invalid API key) are detected from the error text, skip the retry guidance, and expand the feedback area directly with a "retrying may not help" hint and a de-emphasized retry button.
|
|
21
|
+
- **Diagnostic info preview** — a collapsible structured report: app version, task type, mode, failed step, error message, retry count, and key generation configs (no prompt text included).
|
|
22
|
+
- **Copy diagnostic info** — one click copies the full report for pasting anywhere.
|
|
23
|
+
- **Check the FAQ first** — a direct link to the official FAQ page for self-service troubleshooting.
|
|
24
|
+
- **Open a GitHub Issue** — jumps to the project issue tracker with title and body pre-filled from the diagnostic report (long bodies are safely truncated); a bug-report issue template is available in the repo.
|
|
25
|
+
|
|
26
|
+
### Diagnostics Endpoint (Phase 2)
|
|
12
27
|
|
|
28
|
+
- **`GET /api/tasks/{id}/diagnostics`** — returns a task summary plus the model-call error details associated with that task (exact task match first, time-window fallback), sourced from the existing `error_logs/` collector.
|
|
29
|
+
- **Task-linked error collection** — model errors are now written with their `task_id`, so a failure can be traced back to the exact API calls that caused it.
|
|
30
|
+
- **Privacy by design** — the endpoint never returns prompt text, response bodies, or system prompts; error messages are truncated. The feedback report is fully client-side and **nothing is uploaded automatically**.
|
|
31
|
+
|
|
32
|
+
### Other Features & Improvements
|
|
33
|
+
|
|
34
|
+
- **App version in diagnostics** — `APP_VERSION` is now exposed via `GET /api/config`, so every report carries the exact version that failed (kept in sync by the release process).
|
|
35
|
+
- **Official FAQ updated** — new entries explain what to do when generation fails (retry first, then report with diagnostic info) and how to get help / report issues; synced across the official site and wiki.
|
|
36
|
+
|
|
37
|
+
---
|
|
13
38
|
|
|
14
39
|
---
|
|
15
40
|
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import json
|
|
19
19
|
import logging
|
|
20
20
|
import os
|
|
21
|
+
from contextvars import ContextVar
|
|
21
22
|
from datetime import datetime
|
|
22
23
|
from pathlib import Path
|
|
23
24
|
from typing import Optional
|
|
@@ -28,6 +29,10 @@ logger = logging.getLogger(__name__)
|
|
|
28
29
|
|
|
29
30
|
_WORKSPACE_ROOT: Optional[Path] = None
|
|
30
31
|
|
|
32
|
+
# v6.1 二期:当前任务 ID 上下文(BasePipeline 执行入口 set,record 时落盘 task_id)。
|
|
33
|
+
# 用 contextvar 而非逐层传参,将改动面收敛到 API 模块内部(PRD FR9)。
|
|
34
|
+
_TASK_ID_CTX: ContextVar[str] = ContextVar("error_collector_task_id", default="")
|
|
35
|
+
|
|
31
36
|
|
|
32
37
|
def set_workspace_root(path: str) -> None:
|
|
33
38
|
"""设置错误日志存储的根目录(通常为激活的工作空间路径)。
|
|
@@ -66,6 +71,15 @@ def _get_log_dir() -> Path:
|
|
|
66
71
|
return log_dir
|
|
67
72
|
|
|
68
73
|
|
|
74
|
+
def set_error_task_id(task_id: str) -> None:
|
|
75
|
+
"""设置当前上下文的 task_id(v6.1 二期,诊断关联用)。
|
|
76
|
+
|
|
77
|
+
由流水线执行入口(``web/deps.run_pipeline``)调用,使后续 ``collect_error``
|
|
78
|
+
落盘时带上 task_id。通过 contextvar 传递,避免逐层向 API 模块传参。
|
|
79
|
+
"""
|
|
80
|
+
_TASK_ID_CTX.set(task_id or "")
|
|
81
|
+
|
|
82
|
+
|
|
69
83
|
def _extract_from_http_error(exc: Exception) -> tuple[Optional[int], str, str]:
|
|
70
84
|
"""从 HTTPError 异常中提取状态码、响应体、以及 API 级错误详情。
|
|
71
85
|
|
|
@@ -147,6 +161,8 @@ def collect_error(
|
|
|
147
161
|
# 限制字段长度,避免日志文件过大
|
|
148
162
|
error_data = {
|
|
149
163
|
"timestamp": now.isoformat(),
|
|
164
|
+
# v6.1 二期:任务关联(诊断端点精确匹配用;无则留空串)
|
|
165
|
+
"task_id": _TASK_ID_CTX.get(),
|
|
150
166
|
"model_type": model_type,
|
|
151
167
|
"api_method": api_method,
|
|
152
168
|
"prompt": prompt[:5000] if prompt else "",
|
package/core/artifacts.py
CHANGED
|
@@ -25,6 +25,7 @@ from models.task import (
|
|
|
25
25
|
BaseTaskState,
|
|
26
26
|
CreativeVideoTask,
|
|
27
27
|
ManuscriptVideoTask,
|
|
28
|
+
PoetryVideoTask,
|
|
28
29
|
StepStatus,
|
|
29
30
|
)
|
|
30
31
|
|
|
@@ -115,6 +116,16 @@ _ANCHOR_STEPS_MODEL = [
|
|
|
115
116
|
("step_clip_generation", "clip_gen"),
|
|
116
117
|
]
|
|
117
118
|
|
|
119
|
+
# Poetry 步骤序列(v6.0 P3:与 multi_scene 模板方法步骤对齐)
|
|
120
|
+
_POETRY_STEPS = [
|
|
121
|
+
("step_build_scenes", "build_scenes"),
|
|
122
|
+
("step_reference_images", "reference_images"),
|
|
123
|
+
("step_video_generation", "video_gen"),
|
|
124
|
+
("step_audio", "audio"),
|
|
125
|
+
("step_subtitle", "subtitle"),
|
|
126
|
+
("step_concatenation", "concatenate"),
|
|
127
|
+
]
|
|
128
|
+
|
|
118
129
|
|
|
119
130
|
# ═══════════════════════════════════════════════════════════════
|
|
120
131
|
# 产物定义(每种模式的产物模板)
|
|
@@ -183,6 +194,31 @@ def _manuscript_artifact_defs() -> list[dict]:
|
|
|
183
194
|
]
|
|
184
195
|
|
|
185
196
|
|
|
197
|
+
def _poetry_artifact_defs() -> list[dict]:
|
|
198
|
+
"""Poetry 模式的产物定义模板(v6.0 P3)。
|
|
199
|
+
|
|
200
|
+
诗词视频逐场景产物:scene_{i}/video.mp4、scene_{i}/narration.mp3、
|
|
201
|
+
scene_{i}/subtitle.srt。无参考图阶段(空实现跳过)。
|
|
202
|
+
"""
|
|
203
|
+
return [
|
|
204
|
+
# 场景级:视频 / 配音 / 字幕
|
|
205
|
+
{"type": "video", "step_key": "video_gen", "label": "artVideo",
|
|
206
|
+
"category": "video", "scope": "scene", "file": "scene_{i}/video.mp4",
|
|
207
|
+
"scene_fields": ["video_file", "video_id"],
|
|
208
|
+
"extra_files": ["scene_{i}/task.json", "scene_{i}/curl.sh"]},
|
|
209
|
+
{"type": "audio", "step_key": "audio", "label": "artAudio",
|
|
210
|
+
"category": "audio", "scope": "scene", "file": "scene_{i}/narration.mp3",
|
|
211
|
+
"scene_fields": ["narration_audio"]},
|
|
212
|
+
{"type": "subtitle", "step_key": "subtitle", "label": "artSubtitle",
|
|
213
|
+
"category": "subtitle", "scope": "scene", "file": "scene_{i}/subtitle.srt",
|
|
214
|
+
"scene_fields": ["subtitle_srt"]},
|
|
215
|
+
# 任务级:成片
|
|
216
|
+
{"type": "final_video", "step_key": "concatenate", "label": "artFinalVideo",
|
|
217
|
+
"category": "video", "scope": "task", "file": "final_video.mp4",
|
|
218
|
+
"fields": ["final_video_file"]},
|
|
219
|
+
]
|
|
220
|
+
|
|
221
|
+
|
|
186
222
|
def _anchor_artifact_defs(is_model_mode: bool) -> list[dict]:
|
|
187
223
|
"""Anchor 模式的产物定义模板。"""
|
|
188
224
|
artifacts = [
|
|
@@ -228,6 +264,8 @@ def _get_steps_for_state(state: BaseTaskState) -> list[tuple[Optional[str], str]
|
|
|
228
264
|
if state.audio_source == "model":
|
|
229
265
|
return _ANCHOR_STEPS_MODEL
|
|
230
266
|
return _ANCHOR_STEPS_POST_STITCH
|
|
267
|
+
elif isinstance(state, PoetryVideoTask):
|
|
268
|
+
return _POETRY_STEPS
|
|
231
269
|
return []
|
|
232
270
|
|
|
233
271
|
|
|
@@ -254,6 +292,8 @@ _SCHEMA_HINTS = {
|
|
|
254
292
|
"anchor_image": "数字人形象 PNG。可替换后影响数字人视频。",
|
|
255
293
|
"clip_prompts": "数字人视频 prompt JSON(prompts.json)。",
|
|
256
294
|
"clip": "数字人循环视频 MP4。",
|
|
295
|
+
"narration_audio": "诗词场景朗诵音频 MP3(scene_{i}/narration.mp3)。逐场景修改后重跑该场景字幕与成片。",
|
|
296
|
+
"subtitle_srt": "诗词场景字幕 SRT(scene_{i}/subtitle.srt)。",
|
|
257
297
|
}
|
|
258
298
|
|
|
259
299
|
|
|
@@ -270,6 +310,8 @@ def _get_artifact_defs(state: BaseTaskState) -> list[dict]:
|
|
|
270
310
|
return _manuscript_artifact_defs()
|
|
271
311
|
elif isinstance(state, AnchorVideoTask):
|
|
272
312
|
return _anchor_artifact_defs(state.audio_source == "model")
|
|
313
|
+
elif isinstance(state, PoetryVideoTask):
|
|
314
|
+
return _poetry_artifact_defs()
|
|
273
315
|
return []
|
|
274
316
|
|
|
275
317
|
|
|
@@ -324,6 +366,8 @@ def list_artifacts(state: BaseTaskState, task_dir: str) -> list[ArtifactDescript
|
|
|
324
366
|
scope_count = len(state.paragraphs)
|
|
325
367
|
elif isinstance(state, AnchorVideoTask):
|
|
326
368
|
scope_count = len(state.paragraphs) if state.paragraphs else 0
|
|
369
|
+
elif isinstance(state, PoetryVideoTask):
|
|
370
|
+
scope_count = len(state.scenes)
|
|
327
371
|
else:
|
|
328
372
|
scope_count = 0
|
|
329
373
|
|
|
@@ -611,6 +655,25 @@ def apply_cascade_plan(state: BaseTaskState, plan: CascadePlan) -> dict:
|
|
|
611
655
|
state.status = StepStatus.PENDING
|
|
612
656
|
update_kwargs["status"] = StepStatus.PENDING
|
|
613
657
|
|
|
658
|
+
# 6. v6.1:删除前置环节产物后,重置受影响检查点的"已批准"状态
|
|
659
|
+
# 级联重置了某检查点对应步骤 → 该检查点不再视为已确认,
|
|
660
|
+
# 恢复执行时 _maybe_pause 会重新在此暂停等待用户(否则会静默跳过)。
|
|
661
|
+
mc = getattr(state, "manual_config", None)
|
|
662
|
+
if mc is not None:
|
|
663
|
+
approved = list(mc.approved_checkpoints or [])
|
|
664
|
+
removed: list[str] = []
|
|
665
|
+
for cp in list(approved):
|
|
666
|
+
step_field = _checkpoint_to_step_field(cp, state)
|
|
667
|
+
if step_field and step_field in plan.steps_to_reset:
|
|
668
|
+
approved.remove(cp)
|
|
669
|
+
removed.append(cp)
|
|
670
|
+
if removed:
|
|
671
|
+
mc.approved_checkpoints = approved
|
|
672
|
+
update_kwargs["manual_config"] = mc
|
|
673
|
+
logger.info(
|
|
674
|
+
"[Artifacts] Cascade reset un-approved checkpoints: %s", removed
|
|
675
|
+
)
|
|
676
|
+
|
|
614
677
|
return update_kwargs
|
|
615
678
|
|
|
616
679
|
|
|
@@ -698,7 +761,177 @@ def sweep_stale_tasks(age_days: int = 7,
|
|
|
698
761
|
# ═══════════════════════════════════════════════════════════════
|
|
699
762
|
|
|
700
763
|
# 清单自身文件名(扫描文件树时排除)
|
|
701
|
-
_MANIFEST_FILES = {"manifest.json", "MANIFEST.md", "task_state.json"}
|
|
764
|
+
_MANIFEST_FILES = {"manifest.json", "MANIFEST.md", "task_state.json", "checkpoint.json"}
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
# ═══════════════════════════════════════════════════════════════
|
|
768
|
+
# v6.0 检查点分组(PRD §4.3 产物矩阵)
|
|
769
|
+
# ═══════════════════════════════════════════════════════════════
|
|
770
|
+
|
|
771
|
+
# 产物 type → 检查点名(与 dependency_graph._TYPE_TO_CHECKPOINT 语义一致)
|
|
772
|
+
# creative:细粒度检查点(每个有产物的环节独立,v6.1)
|
|
773
|
+
_ARTIFACT_TO_CHECKPOINT_FINE: dict[str, str] = {
|
|
774
|
+
"image_analysis": "image_analysis",
|
|
775
|
+
"story": "story",
|
|
776
|
+
"script": "script",
|
|
777
|
+
"character_ref": "character_ref",
|
|
778
|
+
"end_frame_prompts": "end_frame_prompts",
|
|
779
|
+
"end_frame": "end_frame_gen",
|
|
780
|
+
"video": "videos",
|
|
781
|
+
"audio": "audio",
|
|
782
|
+
"subtitle": "subtitle",
|
|
783
|
+
"final_video": "final",
|
|
784
|
+
}
|
|
785
|
+
# 非 creative(manuscript/poetry/anchor):粗粒度合并检查点
|
|
786
|
+
_ARTIFACT_TO_CHECKPOINT_COARSE: dict[str, str] = {
|
|
787
|
+
"story": "scenes",
|
|
788
|
+
"script": "scenes",
|
|
789
|
+
"end_frame_prompts": "scenes",
|
|
790
|
+
"character_ref": "references",
|
|
791
|
+
"end_frame": "references",
|
|
792
|
+
"video": "videos",
|
|
793
|
+
"audio": "audio",
|
|
794
|
+
"subtitle": "subtitle",
|
|
795
|
+
"final_video": "final",
|
|
796
|
+
"scene_prompts": "scenes",
|
|
797
|
+
"anchor_image": "references",
|
|
798
|
+
"clip_prompts": "scenes",
|
|
799
|
+
"clip": "videos",
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
# 检查点展示顺序(creative 细粒度;其余粗粒度)
|
|
803
|
+
_CHECKPOINT_ORDER_FINE = [
|
|
804
|
+
"image_analysis", "story", "script", "character_ref",
|
|
805
|
+
"end_frame_prompts", "end_frame_gen", "videos", "audio", "subtitle", "final",
|
|
806
|
+
]
|
|
807
|
+
_CHECKPOINT_ORDER = ["scenes", "references", "videos", "audio", "subtitle", "final"]
|
|
808
|
+
|
|
809
|
+
|
|
810
|
+
def _checkpoint_order_for(state: BaseTaskState) -> list[str]:
|
|
811
|
+
"""按任务类型返回检查点展示顺序。"""
|
|
812
|
+
if isinstance(state, CreativeVideoTask):
|
|
813
|
+
return _CHECKPOINT_ORDER_FINE
|
|
814
|
+
return _CHECKPOINT_ORDER
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
def checkpoint_for_artifact(artifact_id: str, task_type: Optional[str] = None) -> str:
|
|
818
|
+
"""返回产物 id 所属检查点名(未知产物 → "other")。
|
|
819
|
+
|
|
820
|
+
Args:
|
|
821
|
+
artifact_id: 产物 id(含任务类型前缀,如 ``creative:script``)。
|
|
822
|
+
task_type: 任务类型(缺省时从产物 id 前缀解析)。
|
|
823
|
+
"""
|
|
824
|
+
parts = artifact_id.split(":")
|
|
825
|
+
if len(parts) >= 2:
|
|
826
|
+
t = task_type or parts[0]
|
|
827
|
+
mapping = _ARTIFACT_TO_CHECKPOINT_FINE if t == "creative" else _ARTIFACT_TO_CHECKPOINT_COARSE
|
|
828
|
+
return mapping.get(parts[1], "other")
|
|
829
|
+
return "other"
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
def build_checkpoint_manifest(state: BaseTaskState, task_dir: str) -> dict:
|
|
833
|
+
"""构建检查点级产物清单(checkpoint.json 数据)。
|
|
834
|
+
|
|
835
|
+
按检查点分组展示产物(PRD §4.3 / §4.8),供手动模式检查点等待页
|
|
836
|
+
与外部 Agent 使用。每组含该检查点的产物列表(复用 manifest 的产物条目)。
|
|
837
|
+
|
|
838
|
+
Returns:
|
|
839
|
+
{
|
|
840
|
+
"format_version": "1.0",
|
|
841
|
+
"task_id": ...,
|
|
842
|
+
"task_type": ...,
|
|
843
|
+
"current_checkpoint": ...,
|
|
844
|
+
"checkpoints": {
|
|
845
|
+
"scenes": { "artifacts": [ ...产物条目... ], "status": "completed|pending" },
|
|
846
|
+
"videos": { "artifacts": [ ... ], "status": ... },
|
|
847
|
+
...
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
"""
|
|
851
|
+
task_dir = safe_join(get_working_dir(), task_dir)
|
|
852
|
+
manifest = build_manifest(state, task_dir)
|
|
853
|
+
order = _checkpoint_order_for(state)
|
|
854
|
+
|
|
855
|
+
groups: dict[str, dict] = {cp: {"artifacts": []} for cp in order}
|
|
856
|
+
for a in manifest.get("artifacts", []):
|
|
857
|
+
cp = checkpoint_for_artifact(a["artifact_id"], state.task_type.value)
|
|
858
|
+
if cp in groups:
|
|
859
|
+
groups[cp]["artifacts"].append(a)
|
|
860
|
+
|
|
861
|
+
# 状态:检查点对应的 step 字段状态(复用 step 状态)
|
|
862
|
+
for cp in order:
|
|
863
|
+
step_field = _checkpoint_to_step_field(cp, state)
|
|
864
|
+
status = "pending"
|
|
865
|
+
if step_field:
|
|
866
|
+
val = getattr(state, step_field, None)
|
|
867
|
+
if val is not None:
|
|
868
|
+
status = val.value
|
|
869
|
+
groups[cp]["status"] = status
|
|
870
|
+
|
|
871
|
+
current = ""
|
|
872
|
+
manual_cfg = getattr(state, "manual_config", None)
|
|
873
|
+
if manual_cfg is not None:
|
|
874
|
+
current = manual_cfg.current_checkpoint or ""
|
|
875
|
+
|
|
876
|
+
return {
|
|
877
|
+
"format_version": "1.0",
|
|
878
|
+
"task_id": state.task_id,
|
|
879
|
+
"task_type": state.task_type.value,
|
|
880
|
+
"current_checkpoint": current,
|
|
881
|
+
"checkpoints": groups,
|
|
882
|
+
"files": manifest.get("files", []),
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
def _checkpoint_to_step_field(checkpoint: str, state: BaseTaskState) -> Optional[str]:
|
|
887
|
+
"""检查点名 → 步骤字段名(按任务类型)。"""
|
|
888
|
+
if isinstance(state, CreativeVideoTask):
|
|
889
|
+
mapping = {
|
|
890
|
+
"image_analysis": "step_image_analysis",
|
|
891
|
+
"story": "step_story",
|
|
892
|
+
"script": "step_script",
|
|
893
|
+
"character_ref": "step_character_ref",
|
|
894
|
+
"end_frame_prompts": "step_end_frame_prompts",
|
|
895
|
+
"end_frame_gen": "step_end_frame_generation",
|
|
896
|
+
"videos": "step_video_generation",
|
|
897
|
+
"audio": "step_audio",
|
|
898
|
+
"subtitle": "step_subtitle",
|
|
899
|
+
"final": "step_concatenation",
|
|
900
|
+
}
|
|
901
|
+
return mapping.get(checkpoint)
|
|
902
|
+
if isinstance(state, ManuscriptVideoTask):
|
|
903
|
+
mapping = {
|
|
904
|
+
"scenes": "step_scene_prompts",
|
|
905
|
+
"videos": "step_video_generation",
|
|
906
|
+
"audio": "step_audio",
|
|
907
|
+
"subtitle": "step_subtitle",
|
|
908
|
+
"final": "step_concatenation",
|
|
909
|
+
}
|
|
910
|
+
return mapping.get(checkpoint)
|
|
911
|
+
if isinstance(state, AnchorVideoTask):
|
|
912
|
+
mapping = {
|
|
913
|
+
"scenes": "step_generate_anchor",
|
|
914
|
+
"references": "step_generate_anchor",
|
|
915
|
+
"videos": "step_clip_generation",
|
|
916
|
+
"audio": "step_audio",
|
|
917
|
+
"subtitle": "step_subtitle",
|
|
918
|
+
"final": "step_concatenation",
|
|
919
|
+
}
|
|
920
|
+
return mapping.get(checkpoint)
|
|
921
|
+
return None
|
|
922
|
+
|
|
923
|
+
|
|
924
|
+
def write_checkpoint_manifest(state: BaseTaskState, task_dir: str) -> str:
|
|
925
|
+
"""将检查点清单落盘为 ``checkpoint.json``,返回路径;失败返回空串。"""
|
|
926
|
+
try:
|
|
927
|
+
manifest = build_checkpoint_manifest(state, task_dir)
|
|
928
|
+
path = os.path.join(task_dir, "checkpoint.json")
|
|
929
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
930
|
+
json.dump(manifest, f, ensure_ascii=False, indent=2)
|
|
931
|
+
return path
|
|
932
|
+
except Exception as e:
|
|
933
|
+
logger.warning("[Artifacts] failed to write checkpoint.json: %s", e)
|
|
934
|
+
return ""
|
|
702
935
|
|
|
703
936
|
|
|
704
937
|
def _scan_task_files(task_dir: str) -> list[dict]:
|
|
@@ -759,11 +992,24 @@ def build_manifest(state: BaseTaskState, task_dir: str) -> dict:
|
|
|
759
992
|
),
|
|
760
993
|
})
|
|
761
994
|
|
|
995
|
+
# v6.0 手动模式:暴露执行模式与当前检查点(供前端判断暂停态 / 渲染依赖图)
|
|
996
|
+
manual_cfg = getattr(state, "manual_config", None)
|
|
997
|
+
current_checkpoint = ""
|
|
998
|
+
current_mode = "auto"
|
|
999
|
+
if manual_cfg is not None:
|
|
1000
|
+
current_mode = "manual" if manual_cfg.enabled else "auto"
|
|
1001
|
+
current_checkpoint = manual_cfg.current_checkpoint or ""
|
|
1002
|
+
|
|
762
1003
|
return {
|
|
763
1004
|
"format_version": "1.0",
|
|
764
1005
|
"task_id": state.task_id,
|
|
765
1006
|
"task_type": state.task_type.value,
|
|
766
1007
|
"task_status": state.status.value if state.status else "pending",
|
|
1008
|
+
"current_mode": current_mode,
|
|
1009
|
+
"current_checkpoint": current_checkpoint,
|
|
1010
|
+
"manual_config": (
|
|
1011
|
+
manual_cfg.model_dump() if manual_cfg is not None else {}
|
|
1012
|
+
),
|
|
767
1013
|
"dir_name": os.path.basename(task_dir.rstrip(os.sep)),
|
|
768
1014
|
"working_dir": task_dir,
|
|
769
1015
|
"artifacts": artifacts,
|
package/core/config.py
CHANGED
|
@@ -15,6 +15,11 @@ logger = logging.getLogger(__name__)
|
|
|
15
15
|
CONFIG_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ".agnes_config")
|
|
16
16
|
CONFIG_FILE = os.path.join(CONFIG_DIR, "config.json")
|
|
17
17
|
|
|
18
|
+
# ═══════════════════════════════════════════════════
|
|
19
|
+
# 应用版本号(v6.1 新增:发版时同步更新,见 docs/dev/release_process.md)
|
|
20
|
+
# ═══════════════════════════════════════════════════
|
|
21
|
+
APP_VERSION = "6.1.0"
|
|
22
|
+
|
|
18
23
|
# 未配置 API Key 时的统一报错文案(含免费获取与在线体验兜底,全站路由共用)
|
|
19
24
|
API_KEY_MISSING_MSG = (
|
|
20
25
|
"请先配置 API Key。免费获取:https://platform.agnes-ai.com | "
|