fix(wechat/token/market): 修复微信对话时间、Token数据重置、技能市场复活、媒体内联

1. 微信Bot对话记录时间按北京时间渲染(ImBotHub/WechatHistory 的 formatTime 显式 +08:00 + Asia/Shanghai);
2. 新增 /app/api/chat/media/relay 媒体中转端点(白名单 agnES 域名, 经香港代理代取),
   改写 _wechat_send_media 用平台中转地址,微信/iLink 可内联播放图片/视频;
3. 删除 014_clean_test_data.sql:该迁移每次重启重跑并 DELETE token_usage_logs/
   messages/conversations/knowledge 等,导致 Token 驾驶舱数据每次重启被清空;
4. 002_init_data.sql 移除 market_apps 自动播种(原 WHERE COUNT=0 在用户删空后重启复活),
   技能市场交由用户后台维护,重启不再复活。
This commit is contained in:
QihuAI Design committed 2026-07-16 09:19:23 +08:00
1 parent da89a27bca
commit 82afe14a23
6 files changed
+85 -108

No files matched your search

+35 -1
View File
@@ -3,7 +3,7 @@
包含: Skill ID统一(MarketApp映射), 真实Skill执行, Agent工作流注入,
多轮上下文保持, BM25 KB检索, Function Calling循环增强
"""
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi import APIRouter, Depends, HTTPException, Query, Response
from sqlalchemy.orm import Session
from sqlalchemy import and_, desc, asc, func
from pydantic import BaseModel, Field
@@ -173,6 +173,40 @@ def _proxy_for_url(url: str) -> str | None:
return _outbound_proxy()
# ============================================================
# 媒体中转(relay)端点:让微信/iLink 能内联渲染 AGNES 生成的图片/视频
# 背景:AGNES 媒体 URL 位于境外(agnes-ai.com / agnes-ai.space),
# iLink 服务器(中国大陆)直连常被墙/超时,导致微信里只看到"链接"无法内联播放。
# 此端点由平台服务器(经香港代理可达 AGNES)代取媒体并以平台域名回传,
# 微信即可内联显示。仅允许白名单域名,杜绝 SSRF。
# ============================================================
_MEDIA_RELAY_ALLOWED = ("agnes-ai.com", "agnes-ai.space", "qihuyun.com")
@router.get("/media/relay")
async def media_relay(url: str = Query(...)):
"""中转 AGNES 媒体(图片/视频)到平台域名,供微信/iLink 内联渲染。
仅允许白名单域名;否则 400。返回的媒体流保持原 content-type。
"""
import urllib.parse as _up
host = (_up.urlparse(url).hostname or "").lower()
if not any(allowed in host for allowed in _MEDIA_RELAY_ALLOWED):
raise HTTPException(status_code=400, detail="unsupported media host")
try:
async with httpx.AsyncClient(timeout=180, proxy=_proxy_for_url(url), trust_env=False) as client:
r = await client.get(url)
if r.status_code != 200:
raise HTTPException(status_code=r.status_code, detail="upstream error")
content_type = r.headers.get("content-type", "application/octet-stream")
data = await r.aread()
return Response(content=data, media_type=content_type)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=502, detail=f"relay failed: {e}")
async def _optimize_media_prompt(raw_prompt: str, media_type: str, db=None) -> str:
"""使用平台文本模型对用户原始输入做「媒体提示词增强优化」。
+25 -12
View File
@@ -14,11 +14,13 @@ im_bot_user.py
"""
import base64
import json
import os
import random
import threading
import time
from datetime import datetime, timedelta, timezone
from typing import Optional
from urllib.parse import quote as _quote
import requests
from fastapi import APIRouter, Depends, HTTPException, Query
@@ -225,21 +227,31 @@ def _wechat_send_message(baseurl: str, bot_token: str, to_user_id: str, context_
[{"type": 1, "text_item": {"text": text}}])
def _platform_relay_url(raw_url: str) -> str:
"""把 AGNES 等境外媒体 URL 改写为平台中转地址,使微信/iLink 可内联渲染。
iLink 服务器(中国大陆)直连境外 AGNES 常被墙/超时,微信里只能看到"链接"。
改写为平台中转地址(平台服务器经香港代理可达 AGNES),微信即可内联显示图片/视频。
"""
access = os.environ.get("APP_ACCESS_URL", "http://210.16.161.218:12888").rstrip("/")
return f"{access}/app/api/chat/media/relay?url={_quote(raw_url, safe='')}"
def _wechat_send_media(baseurl: str, bot_token: str, to_user_id: str, context_token: str,
media_type: str, url: str):
"""发送媒体(图片/视频)并附带文本链接兜底,确保用户可在微信内直接查看。
"""发送媒体(图片/视频)并在微信内联渲染。
iLink 消息 item_list:
- type=2 图片:image_item: {url}
- type=5 视频:video_item: {url}
同时附带一条文本项(含直链),防止微信/iLink 不渲染外链时用户仍能复制查看。
问题:AGNES 媒体 URL 位于境外,iLink(中国大陆服务器)直连常被墙/超时,
微信里只能看到"链接"而无法内联播放。
解决:改写为平台中转地址(经香港代理可达 AGNES),微信即可内联显示图片/视频。
"""
relay_url = _platform_relay_url(url)
if media_type == "image":
media_item = {"type": 2, "image_item": {"url": url}}
caption = "🖼️ 图片已生成(若未直接显示,请点链接查看):" + url
media_item = {"type": 2, "image_item": {"url": relay_url}}
caption = "🖼️ 图片已生成(若未直接显示,请点此查看):" + relay_url
else:
media_item = {"type": 5, "video_item": {"url": url}}
caption = "🎬 视频已生成(若未直接播放,请点链接查看):" + url
media_item = {"type": 5, "video_item": {"url": relay_url}}
caption = "🎬 视频已生成(若未直接播放,请点此查看):" + relay_url
return _wechat_post_msg(baseurl, bot_token, to_user_id, context_token,
[media_item, {"type": 1, "text_item": {"text": caption}}])
@@ -420,11 +432,12 @@ def _wechat_receiver_loop(account_id: int):
media_type = reply.get("media_type") or "image"
media_url = reply.get("url") or ""
ok, ret, _ = _wechat_send_media(baseurl, bot_token, sender, ctx, media_type, media_url)
# 对话历史里存可渲染的媒体标签(AI 聊天 WeXin Bot 窗与历史页均可显示)
# 对话历史里存可渲染的媒体标签(经平台中转,AI 聊天 WeXin Bot 窗与历史页均可内联显示)
relay_url = _platform_relay_url(media_url)
reply_for_log = (
f'<img src="{media_url}" alt="生成图片"/>'
f'<img src="{relay_url}" alt="生成图片"/>'
if media_type == "image"
else f'<video src="{media_url}" controls></video>'
else f'<video src="{relay_url}" controls></video>'
)
else:
reply_text = reply if isinstance(reply, str) else str(reply)