auto-save 2026-05-13 20:40 (~4)

This commit is contained in:
2026-05-13 20:40:23 +08:00
parent bbe98641f1
commit 66f2495296
4 changed files with 60 additions and 10 deletions

View File

@@ -2328,6 +2328,19 @@
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 1 项未提交变更 · 最近提交auto-save 2026-05-13 20:29 (~9)",
"files_changed": 1
},
{
"ts": "2026-05-13T20:34:52+08:00",
"type": "commit",
"message": "auto-save 2026-05-13 20:34 (~2)",
"hash": "bbe9864",
"files_changed": 2
},
{
"ts": "2026-05-13T12:39:30Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 4 项未提交变更 · 最近提交auto-save 2026-05-13 20:34 (~2)",
"files_changed": 4
}
]
}

View File

@@ -11,6 +11,11 @@ VIDEO_MODEL=seedance
VIDEO_MODEL_SEEDANCE=seedance
VIDEO_MODEL_KLING=kling
VIDEO_MODEL_VEO3=veo3
VIDEO_API_BASE_URL=
VIDEO_API_KEY=
VIDEO_CREATE_PATH=/videos
VIDEO_STATUS_PATH=/videos/{id}
VIDEO_CONTENT_PATH=/videos/{id}/content
VIDEO_DURATION_FIELD=seconds
# 工作目录

View File

@@ -36,6 +36,11 @@ VIDEO_MODEL_ALIASES = {
"kling": os.getenv("VIDEO_MODEL_KLING", "kling").strip() or "kling",
"veo3": os.getenv("VIDEO_MODEL_VEO3", "veo3").strip() or "veo3",
}
VIDEO_API_BASE_URL = os.getenv("VIDEO_API_BASE_URL", "").strip()
VIDEO_API_KEY = os.getenv("VIDEO_API_KEY", "").strip()
VIDEO_CREATE_PATH = os.getenv("VIDEO_CREATE_PATH", "/videos").strip() or "/videos"
VIDEO_STATUS_PATH = os.getenv("VIDEO_STATUS_PATH", "/videos/{id}").strip() or "/videos/{id}"
VIDEO_CONTENT_PATH = os.getenv("VIDEO_CONTENT_PATH", "/videos/{id}/content").strip() or "/videos/{id}/content"
VIDEO_DURATION_FIELD = os.getenv("VIDEO_DURATION_FIELD", "seconds").strip() or "seconds"
# OpenAI 客户端OpenAI 兼容网关,含 SKG ezlink
@@ -191,6 +196,32 @@ def public_api_base() -> str:
return (LLM_BASE_URL or "https://api.openai.com/v1").rstrip("/")
def video_api_base() -> str:
return (VIDEO_API_BASE_URL or LLM_BASE_URL or "https://api.openai.com/v1").rstrip("/")
def video_api_key() -> str:
return VIDEO_API_KEY or LLM_API_KEY
def video_path(template: str, **values: str) -> str:
path = template.format(**values)
return path if path.startswith("/") else f"/{path}"
def ensure_video_api_configured() -> None:
base = video_api_base()
# 已探测SKG ezlink 当前只开了 chat/images/videos 返回 404。
# 没有显式 VIDEO_API_BASE_URL 时,不再把这个 404 伪装成一次“生成失败”。
if not VIDEO_API_BASE_URL and "ai.skg.com/ezlink" in base:
raise HTTPException(
503,
"当前 SKG ezlink 网关未开通生视频 /videos 端点;请配置 VIDEO_API_BASE_URL/VIDEO_API_KEY 接 Seedance、Kling 或 Veo 3 的真实视频 API 后再生成。",
)
if not video_api_key():
raise HTTPException(503, "VIDEO_API_KEY 或 LLM_API_KEY 未配置,无法调用生视频 API")
def storyboard_ref_path(job_id: str, ref: dict | None) -> Path | None:
if not ref:
return None
@@ -758,6 +789,8 @@ def health() -> dict:
"rewrite": REWRITE_MODEL,
"video": VIDEO_MODEL,
"video_aliases": VIDEO_MODEL_ALIASES,
"video_base_url": video_api_base() if VIDEO_API_BASE_URL else "",
"video_configured": bool(VIDEO_API_BASE_URL and video_api_key()),
},
}
@@ -1641,7 +1674,7 @@ def download_generated_video(client, base: str, headers: dict, provider_id: str,
url = direct_url if direct_url.startswith("http") else f"{base}{direct_url if direct_url.startswith('/') else '/' + direct_url}"
r = client.get(url, headers=headers if url.startswith(base) else None)
else:
r = client.get(f"{base}/videos/{provider_id}/content", headers=headers)
r = client.get(f"{base}{video_path(VIDEO_CONTENT_PATH, id=provider_id)}", headers=headers)
r.raise_for_status()
out_mp4.write_bytes(r.content)
@@ -1652,8 +1685,8 @@ def render_storyboard_video(job_id: str, local_id: str, provider_id: str, ref_pa
out_dir = job_dir(job_id) / "storyboard_videos" / local_id
ref_img = out_dir / "reference.jpg"
out_mp4 = out_dir / "video.mp4"
base = public_api_base()
headers = {"Authorization": f"Bearer {LLM_API_KEY}"}
base = video_api_base()
headers = {"Authorization": f"Bearer {video_api_key()}"}
try:
prepare_video_reference(ref_path, ref_img)
@@ -1663,7 +1696,7 @@ def render_storyboard_video(job_id: str, local_id: str, provider_id: str, ref_pa
payload[VIDEO_DURATION_FIELD] = seconds
with ref_img.open("rb") as fh:
create = client.post(
f"{base}/videos",
f"{base}{video_path(VIDEO_CREATE_PATH)}",
headers=headers,
data=payload,
files={"input_reference": ("reference.jpg", fh, "image/jpeg")},
@@ -1679,7 +1712,7 @@ def render_storyboard_video(job_id: str, local_id: str, provider_id: str, ref_pa
deadline = time.time() + 420
while status in {"queued", "in_progress"} and time.time() < deadline:
time.sleep(8)
poll = client.get(f"{base}/videos/{video_api_id}", headers=headers)
poll = client.get(f"{base}{video_path(VIDEO_STATUS_PATH, id=video_api_id)}", headers=headers)
poll.raise_for_status()
pdata = poll.json()
status = normalize_video_status(pdata.get("status"))
@@ -1712,8 +1745,7 @@ def generate_storyboard_video(job_id: str, idx: int, req: GenerateStoryboardVide
frame = next((f for f in job.frames if f.index == idx), None)
if not frame:
raise HTTPException(404, "frame not found")
if not LLM_API_KEY:
raise HTTPException(500, "LLM_API_KEY 未配置,无法调用视频生成 API")
ensure_video_api_configured()
prompt = req.prompt.strip()
if not prompt:
raise HTTPException(400, "prompt required")

View File

@@ -790,7 +790,7 @@ api/main.py
<li>ASRSKG 网关 audio endpoint 404 或渠道不可用。</li>
<li>Translate本身 text 通,但产品流里依赖 ASR 段落。</li>
<li>Rewrite需要 SKG 产品信息模板和目标脚本结构。</li>
<li>Video Gen已接 OpenAI-compatible <code>/videos</code> 网关;前端可选 Seedance / Kling / Veo 3具体模型 ID 由 <code>VIDEO_MODEL_*</code> 环境变量映射</li>
<li>Video Gen当前 SKG ezlink 未开 <code>/videos</code>(实测 404<code>/models</code> 只列 <code>sora-2</code>);代码已支持通过 <code>VIDEO_API_BASE_URL</code>/<code>VIDEO_API_KEY</code> 显式接 Seedance / Kling / Veo 3 外部生视频 API未配置时会前置报错不再生成 5% 失败任务</li>
<li>Compose还没做本地 ffmpeg 字幕/TTS 合成。</li>
</ul>
</div>
@@ -838,8 +838,8 @@ api/main.py
</header>
<div class="body">
<p><strong>问题:</strong>4 图槽已经粘贴参考图后,用户要直接调用生视频 API而不是只生成 prompt 或图片任务。</p>
<p><strong>改动:</strong>分镜编排明细区增加 Seedance / Kling / Veo 3 模型选择和“调用模型生成视频”按钮;后端新增 <code>/jobs/{job_id}/frames/{idx}/storyboard/video</code>,提交 <code>/videos</code> 网关后轮询并保存 MP4<code>VideoGenNode</code> 读取 <code>job.generated_videos</code> 展示排队、生成中、失败和完成视频。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>api/.env.example</code><code>web/components/storyboard-workbench.tsx</code><code>web/components/nodes/index.tsx</code><code>web/app/page.tsx</code><code>web/lib/api.ts</code>。Sora 不再作为默认模型真实模型 ID 通过 <code>VIDEO_MODEL_SEEDANCE</code><code>VIDEO_MODEL_KLING</code><code>VIDEO_MODEL_VEO3</code> 配置。</p>
<p><strong>改动:</strong>分镜编排明细区增加 Seedance / Kling / Veo 3 模型选择和“调用模型生成视频”按钮;后端新增 <code>/jobs/{job_id}/frames/{idx}/storyboard/video</code>。若已配置真实 <code>VIDEO_API_BASE_URL</code>,则提交、轮询并保存 MP4若仍使用当前 SKG ezlink则前置返回 503避免继续创建 404 失败任务。<code>VideoGenNode</code> 读取 <code>job.generated_videos</code> 展示排队、生成中、失败和完成视频。</p>
<p><strong>影响:</strong><code>api/main.py</code><code>api/.env.example</code><code>web/components/storyboard-workbench.tsx</code><code>web/components/nodes/index.tsx</code><code>web/app/page.tsx</code><code>web/lib/api.ts</code>。Sora 不再作为默认模型真实模型 ID 通过 <code>VIDEO_MODEL_SEEDANCE</code><code>VIDEO_MODEL_KLING</code><code>VIDEO_MODEL_VEO3</code> 配置,真实视频 API 地址通过 <code>VIDEO_API_BASE_URL</code>/<code>VIDEO_API_KEY</code> 配置。</p>
</div>
</article>
<article class="change">