From 4d7075aeb8b87d2ea2729d76570169281d208f4d Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 13 Jul 2026 10:26:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E6=9C=89=20gpt=5Fimage=20?= =?UTF-8?q?=E6=9D=83=E9=99=90=E7=9A=84=E7=94=A8=E6=88=B7=E7=94=9F=E5=9B=BE?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=20GPT=20=E7=94=9F=E5=9B=BE(bump=200.58.16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /v1/image_models 的 is_default 改为 _default_image_variant:档位里有 gpt_image(pro/admin)→ 默认它,否则仍过滤后第一个(seedream) - _resolve_image_model 空串时按同一 helper 解析该用户默认,下拉默认选中 与实际起 run 挂的图像工具保持一致;用户仍可下拉切回豆包 Seedream - 无 user_id 内部路径(定时任务/messenger)行为不变 Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 4 ++++ PROGRESS.md | 1 + core/__init__.py | 2 +- web/app.py | 36 +++++++++++++++++++++++++++++------- web/static/js/state.js | 4 ++-- 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 685ccb6..c53c668 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ > 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。 > 工程口径的完整记录见 `PROGRESS.md` / git log。 +## 0.58.16 — 2026-07-13 + +- Pro 档用户的图像生成默认改为「GPT 生图」(顶栏下拉自动选中并实际生效);想用豆包 Seedream(支持改图、指定尺寸)随时在下拉切回即可。 + ## 0.58.15 — 2026-07-13 - 画流程图 / 架构图前会先让你点选出图方式:结构清晰的矢量图(文字准、可编辑、零成本)还是图像生成的视觉版(有质感,但文字标签易乱码、不可后期改字)。你点名了工具或本次对话已选过,则不再重复问。 diff --git a/PROGRESS.md b/PROGRESS.md index dd12dd7..9f71c62 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -23,6 +23,7 @@ ### 2026-07 +- **07-13 / 0.58.16**:**有 gpt_image 权限的用户生图默认 GPT 生图**:此前 `/v1/image_models` 的 `is_default` 恒标"档位过滤后第一个"(yaml 文件名排序豆包在前 → 人人默认 seedream)。改法:`web/app.py` 新增 `_default_image_variant(allowed)`——档位集合里有 `gpt_image`(pro 档 / admin)→ 默认它,否则仍第一个(seedream);`/v1/image_models` 的 `is_default` 与 `_resolve_image_model` 的空串解析(POST messages 时客户端没显式选 image_model)共用该 helper,保证下拉默认选中项和实际起 run 挂的图像工具永远一致。用户可在顶栏下拉自行切回豆包 Seedream(选中值随消息显式发送,不受默认影响)。前端零改动(chat.js 本就选 `is_default` 项);无 user_id 的内部路径(定时任务/messenger)行为不变(空串 → agent_builder yaml 第一个兜底)。 - **07-13 / 0.58.15**:**结构图 mermaid vs 生图:改为 ask_user 让用户明确点选**:用户反馈"画流程图/架构图时模型直奔 mermaid,想用生图(gpt image)没入口"。根因两处:①岔路引导只写在 imagegen SKILL.md 里,而 mermaid 路径根本不触发 load_skill,模型全程看不到岔路口;②SKILL.md 岔路段把"画个流程图/做个架构图"硬性划给 mermaid。改法(口径:结构图选型交给用户点选,不做信号猜测):`core/agent_builder.py` 新增 `_MEDIA_DIAGRAM_FORK_SEG`(挂 seedream 或 gpt_image 任一 variant 时追加进媒体段)——用户要流程图/架构图/技术路线/时序图时先 `ask_user` 二选一(mermaid 矢量:文字准可编辑零成本 vs 生图视觉版:有质感但中文标签易乱码/位图不可改字),免问例外=用户已点名工具/本对话已选过路线/paper·proposal skill 管线内部要求 mermaid;SKILL.md 岔路段同口径重写,并补"生图画结构图取舍"段(乱码/不可改字提前说清,prompt 按风格化示意少文字装配,想两全建议 mermaid 出结构+生图出底图),反模式条同步;SKILL_LIST.md imagegen 岔路段同步。纯 prompt 调整。 - **07-13 / 0.58.14**:**toolfail 聚集加时间分布,活跃/已安静一眼分**(0.58.11 的可观测性补强):此前聚集只有全窗口累计 count+last_at——修掉的问题旧记录仍挂满 7 天窗口,和"还在发生"的没视觉区别,只能人肉盯"最近时间"是否前进。`core/toolfail.py` 聚合时加 `daily`(从 now 往回按 24h 分桶,旧→新,非日历日)/ `count_24h`(=尾桶)/ `first_at`,排序改"近 24h 活跃在前(count_24h 降序),安静的按 count 降序沉底";兼补 naive timestamp 的 tzinfo 兜底。admin 页表格加"近24h"(>0 红)和"趋势"列(unicode block mini 条形图,悬浮看逐桶数值),近24h=0 的行整体淡化;告警邮件每条带"近24h N 次/已安静"。API 只加字段不删,向后兼容。冒烟:伪造行 patch session_scope 全绿(分桶/排序/naive ts/邮件格式)。修复部署后次日看板:对应行变灰+趋势尾格归零 = 修好;仍红 = 没修好。 - **07-13 / 0.58.13**:**look_at_image 引导口径改"慢、谨慎调"**:tool description 原写 "Cheap (usually < ¥0.01)"、system prompt 媒体段写"每次很便宜"——都在鼓励随手调,而实际痛点是慢(一次几十秒)。两处(`tools/look_at_image.py` description + `core/agent_builder.py` `_MEDIA_LOOK_SEG`)改为强调 SLOW / 谨慎调用 / 只在确实需要图内容时调 / 想问的一次在 `question` 里问全、绝不对同一张图反复看;"何时不调"加"图内容对当前任务可有可无"。纯 prompt 调整,无行为代码变化。 diff --git a/core/__init__.py b/core/__init__.py index f0cf515..0a85bcd 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,3 +1,3 @@ # zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。 # 改版本只动这一行。 -__version__ = "0.58.15" +__version__ = "0.58.16" diff --git a/web/app.py b/web/app.py index 660c1d0..36566bc 100644 --- a/web/app.py +++ b/web/app.py @@ -677,8 +677,8 @@ async def _transcribe_wecom_voice(media_id: str) -> str: def _list_media_variants(kind: str) -> list[tuple[str, dict]]: """扫 config/media/*.yaml 的 段 → [(variant_key, variant_cfg), ...]。 - 多 provider(doubao / unifyllm)合并列举,文件名排序保证豆包在前(→ is_default - 仍落 seedream)。variant key 约定跨文件唯一;万一撞名先读的生效(后者跳过)。 + 多 provider(doubao / unifyllm)合并列举,文件名排序保证豆包在前(默认 variant + 的选择逻辑另见 _default_image_variant)。variant key 约定跨文件唯一;万一撞名先读的生效(后者跳过)。 目录不存在或段空 / 仅注释 → 返 []。不要求对应 API key 已设 —— 仅纯元数据列举, UI 拉这个画下拉。真正调用时 agent_builder 那边再过 `ArkConfig.load()` (没 key → tool 不注册)。 @@ -708,15 +708,35 @@ def _list_image_variants() -> list[tuple[str, dict]]: return _list_media_variants("image") +def _default_image_variant(allowed: Optional[set[str]]) -> str: + """该用户(档位过滤后)的默认 image variant key;无可用 → ""。 + + 有 gpt_image 权限(pro 档 / admin)→ 默认 GPT 生图;否则落过滤后第一个 + (= 豆包 seedream,沿用 yaml 文件名排序)。用户仍可在顶栏下拉自行切换。 + /v1/image_models 的 is_default 与 _resolve_image_model 的空串解析共用这里, + 保证"下拉默认选中"和"实际起 run 用的"永远一致。 + """ + keys = [k for k, _ in _list_image_variants() if allowed is None or k in allowed] + if not keys: + return "" + return "gpt_image" if "gpt_image" in keys else keys[0] + + def _resolve_image_model(variant: str, user_id: Optional[UUID] = None) -> str: """校验 image_model variant key。 - 传空 → 返空(agent_builder fallback 到第一个 variant);传非空 → 必须存在 - 于 config/media/*.yaml 的 image 段,否则 400。user_id 非空 → 额外过档位门控。 + 传空 = 客户端未显式选 → 解析成该用户的默认 variant(与 /v1/image_models 的 + is_default 同源,见 _default_image_variant);无 user_id 的内部路径仍返空 + (agent_builder fallback 到第一个 variant)。传非空 → 必须存在于 + config/media/*.yaml 的 image 段,否则 400。user_id 非空 → 额外过档位门控。 """ name = (variant or "").strip() if not name: - return "" + if user_id is None: + return "" + from core.model_access import allowed_set + plan, role = _user_plan_role(user_id) + return _default_image_variant(allowed_set(plan, role)) variants = {k for k, _ in _list_image_variants()} if name not in variants: raise HTTPException(400, f"invalid image_model {name!r}; available: {sorted(variants)}") @@ -1900,11 +1920,13 @@ def create_app() -> FastAPI: """图像生成模型清单(扫 config/media/*.yaml image 段,跨 provider)。 前端顶栏第二个下拉拉这个;空列表 → 没配 image variant 或本档无授权,UI 隐藏下拉。 - 按用户档位过滤;`is_default` 标过滤后第一个 variant。开发期不缓存,改 YAML 立即生效。 + 按用户档位过滤;`is_default` 见 _default_image_variant(有 gpt_image 权限 → + 默认 GPT 生图,否则第一个)。开发期不缓存,改 YAML 立即生效。 """ from core.model_access import allowed_set plan, role = _user_plan_role(user_id) allowed = allowed_set(plan, role) + default_key = _default_image_variant(allowed) out: list[dict] = [] for key, cfg in _list_image_variants(): if allowed is not None and key not in allowed: @@ -1914,7 +1936,7 @@ def create_app() -> FastAPI: "display_name": cfg.get("display_name") or key, "model_id": cfg.get("model_id") or "", "price_cny_per_image": cfg.get("price_cny_per_image"), - "is_default": not out, # 过滤后第一个 + "is_default": key == default_key, }) return {"models": out} diff --git a/web/static/js/state.js b/web/static/js/state.js index 2536c27..be133fa 100644 --- a/web/static/js/state.js +++ b/web/static/js/state.js @@ -59,8 +59,8 @@ export const state = { models: [], // 图像生成模型清单(GET /v1/image_models;ARK_API_KEY 未设也会拿到 yaml 元数据) imageModels: [], - // 当前选中的图像生成 variant key(per-session,不入 DB);默认 = imageModels[0].variant - // (=yaml 第一个 = agent_builder fallback)。下次 send 消息时随 POST body 带给 backend。 + // 当前选中的图像生成 variant key(per-session,不入 DB);默认 = is_default 项 + // (后端按档位定:有 gpt_image 权限默认 GPT 生图,否则 seedream)。下次 send 消息时随 POST body 带给 backend。 imageModel: "", // 视频生成模型清单(GET /v1/video_models)+ 当前选中 variant。同 imageModels 范式。 videoModels: [],