上线版: 产品表单统一+form嵌套修复+用户管理+部署+三套叙事

- 产品编辑入口统一走 ProductFormFull(卖点/风格/人群/品牌词全字段);
  修复开任务页 <form> 套 <form> 致"编辑产品"报错、改不了、跳回首个产品
- dashboard 入口卡片对齐实际路由: 系统管理(/config) 与 工作配置(/settings) 分开;
  settings ?tab=products 直达改用挂载后读 URL, 消除 hydration mismatch
- 新增用户管理(users API/admin service/改密页) + alembic 022/023/024
- 上线部署: Dockerfile / docker-compose.prod+https / nginx https / .env.example
- A8 三套正交叙事(痛点/场景/成分背书) + beige 调色去AI化 + 飞轮 text_import 高权重信号

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
yangqianqian
2026-06-30 18:08:13 +08:00
parent a77212781c
commit df1856d793
150 changed files with 8616 additions and 1765 deletions

View File

@@ -46,19 +46,37 @@ def decrypt_user_key(db, operator_id: int, workspace_id: int) -> str:
return decrypt_key(api_key_row.encrypted_key)
def build_clients_and_clear_key(plain_key: str):
def decrypt_codeproxy_key(db, operator_id: int, workspace_id: int) -> str | None:
"""
查用户录入的 codeproxy 备用站 key → Fernet 解密,返回 plain_key 或 None。
备用通道:用户没录则返回 None不抛错build_ai_clients 会回落 env
主生图流程绝不因没录备用 key 而中断apiports 主通道才是必需)。
"""
from app.models.workspace import UserApiKey
from app.utils.fernet_utils import decrypt_key
row = db.query(UserApiKey).filter(
UserApiKey.user_id == operator_id,
UserApiKey.workspace_id == workspace_id,
UserApiKey.provider == "codeproxy",
).first()
return decrypt_key(row.encrypted_key) if row else None
def build_clients_and_clear_key(plain_key: str, alt_key: str | None = None):
"""
Step3: 构建 AIClientsplain_key 传入后立即由调用方置 None。
alt_key用户录入的 codeproxy 备用 key可选同样由调用方查库解密后传入。
返回 clients 对象。
"""
from app.services.ai_engine.gemini_factory import build_ai_clients
return build_ai_clients(plain_key)
return build_ai_clients(plain_key, alt_key=alt_key)
def build_product_dict(product) -> dict:
"""把 ORM product 转成 AI 引擎所需的 dict不含任何 key"""
return {
"id": product.id,
"id": getattr(product, "id", None),
"name": product.name,
"category": product.category or "通用好物",
"selling_points": json.loads(product.selling_points or "[]"),
@@ -133,4 +151,7 @@ def load_flywheel_context(db, workspace_id: int, product_id: int, product_dict:
for e in recent
]
ctx = aggregate_preference_context(events_dicts, product_dict, workspace_id, product_id)
# 累积感知:补该产品累计信号总数(前端"飞轮已积累N条信号"),与展示端同口径
from app.services.flywheel_service import count_signals
ctx["signal_count"] = count_signals(db, workspace_id, product_id)
return ctx.get("prompt_fragment", ""), ctx