mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-07 04:40:00 +08:00
♻️ refactor: 统一图片渲染架构并引入通用UI组件系统 (#2019)
检查bot是否运行正常 / bot check (push) Has been cancelled
CodeQL Code Security Analysis / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Has been cancelled
CodeQL Code Security Analysis / Analyze (${{ matrix.language }}) (none, python) (push) Has been cancelled
Sequential Lint and Type Check / ruff-call (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Force Sync to Aliyun / sync (push) Has been cancelled
Update Version / update-version (push) Has been cancelled
Sequential Lint and Type Check / pyright-call (push) Has been cancelled
检查bot是否运行正常 / bot check (push) Has been cancelled
CodeQL Code Security Analysis / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Has been cancelled
CodeQL Code Security Analysis / Analyze (${{ matrix.language }}) (none, python) (push) Has been cancelled
Sequential Lint and Type Check / ruff-call (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Force Sync to Aliyun / sync (push) Has been cancelled
Update Version / update-version (push) Has been cancelled
Sequential Lint and Type Check / pyright-call (push) Has been cancelled
* ♻️ refactor: 统一图片渲染架构并引入通用UI组件系统 🎨 **渲染服务重构** - 统一图片渲染入口,引入主题系统支持 - 优化Jinja2环境管理,支持主题覆盖和插件命名空间 - 新增UI缓存机制和主题重载功能 ✨ **通用UI组件系统** - 新增 zhenxun.ui 模块,提供数据模型和构建器 - 引入BaseBuilder基类,支持链式调用 - 新增多种UI构建器:InfoCard, Markdown, Table, Chart, Layout等 - 新增通用组件:Divider, Badge, ProgressBar, UserInfoBlock 🔄 **插件迁移** - 迁移9个内置插件至新渲染系统 - 移除各插件中分散的图片生成工具 - 优化数据处理和渲染逻辑 💥 **Breaking Changes** - 移除旧的图片渲染接口和模板路径 - TEMPLATE_PATH 更名为 THEMES_PATH - 插件需适配新的RendererService和zhenxun.ui模块 * ✅ test(check): 更新自检插件测试中的渲染服务模拟 * ♻️ refactor(renderer): 将缓存文件名哈希算法切换到 SHA256 * ♻️ refactor(shop): 移除商店HTML图片生成模块 * 🚨 auto fix by pre-commit hooks --------- Co-authored-by: webjoin111 <455457521@qq.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
webjoin111
pre-commit-ci[bot]
parent
d5e5fac02d
commit
11524bcb04
@@ -2,8 +2,8 @@ from typing import Any
|
||||
|
||||
from zhenxun.services.llm.core import KeyStatus
|
||||
from zhenxun.services.llm.types import ModelModality
|
||||
from zhenxun.utils._build_image import BuildImage
|
||||
from zhenxun.utils._image_template import ImageTemplate, Markdown, RowStyle
|
||||
from zhenxun.ui import MarkdownBuilder, TableBuilder
|
||||
from zhenxun.ui.models import StatusBadgeCell, TextCell
|
||||
|
||||
|
||||
def _format_seconds(seconds: int) -> str:
|
||||
@@ -27,35 +27,40 @@ class Presenters:
|
||||
@staticmethod
|
||||
async def format_model_list_as_image(
|
||||
models: list[dict[str, Any]], show_all: bool
|
||||
) -> BuildImage:
|
||||
) -> bytes:
|
||||
"""将模型列表格式化为表格图片"""
|
||||
title = "📋 LLM模型列表" + (" (所有已配置模型)" if show_all else " (仅可用)")
|
||||
title = "LLM模型列表" + (" (所有已配置模型)" if show_all else " (仅可用)")
|
||||
|
||||
if not models:
|
||||
return await BuildImage.build_text_image(
|
||||
f"{title}\n\n当前没有配置任何LLM模型。"
|
||||
)
|
||||
builder = TableBuilder(
|
||||
title=title, tip="当前没有配置任何LLM模型。"
|
||||
).set_headers(["提供商", "模型名称", "API类型", "状态"])
|
||||
return await builder.build()
|
||||
|
||||
column_name = ["提供商", "模型名称", "API类型", "状态"]
|
||||
data_list = []
|
||||
for model in models:
|
||||
status_text = "✅ 可用" if model.get("is_available", True) else "❌ 不可用"
|
||||
is_available = model.get("is_available", True)
|
||||
status_cell = StatusBadgeCell(
|
||||
text="可用" if is_available else "不可用",
|
||||
status_type="ok" if is_available else "error",
|
||||
)
|
||||
embed_tag = " (Embed)" if model.get("is_embedding_model", False) else ""
|
||||
data_list.append(
|
||||
[
|
||||
model.get("provider_name", "N/A"),
|
||||
f"{model.get('model_name', 'N/A')}{embed_tag}",
|
||||
model.get("api_type", "N/A"),
|
||||
status_text,
|
||||
TextCell(content=model.get("provider_name", "N/A")),
|
||||
TextCell(content=f"{model.get('model_name', 'N/A')}{embed_tag}"),
|
||||
TextCell(content=model.get("api_type", "N/A")),
|
||||
status_cell,
|
||||
]
|
||||
)
|
||||
|
||||
return await ImageTemplate.table_page(
|
||||
head_text=title,
|
||||
tip_text="使用 `llm info <Provider/ModelName>` 查看详情",
|
||||
column_name=column_name,
|
||||
data_list=data_list,
|
||||
builder = TableBuilder(
|
||||
title=title, tip="使用 `llm info <Provider/ModelName>` 查看详情"
|
||||
)
|
||||
builder.set_headers(column_name)
|
||||
builder.add_rows(data_list)
|
||||
return await builder.build(use_cache=True)
|
||||
|
||||
@staticmethod
|
||||
async def format_model_details_as_markdown_image(details: dict[str, Any]) -> bytes:
|
||||
@@ -76,77 +81,33 @@ class Presenters:
|
||||
if caps.is_embedding_model:
|
||||
cap_list.append("文本嵌入")
|
||||
|
||||
md = Markdown()
|
||||
md.head(f"🔎 模型详情: {provider.name}/{model.model_name}", level=1)
|
||||
md.text("---")
|
||||
md.head("提供商信息", level=2)
|
||||
md.list(
|
||||
[
|
||||
f"**名称**: {provider.name}",
|
||||
f"**API 类型**: {provider.api_type}",
|
||||
f"**API Base**: {provider.api_base or '默认'}",
|
||||
]
|
||||
)
|
||||
md.head("模型详情", level=2)
|
||||
builder = MarkdownBuilder()
|
||||
builder.head(f"🔎 模型详情: {provider.name}/{model.model_name}", 1)
|
||||
builder.text("---")
|
||||
builder.head("提供商信息", 2)
|
||||
builder.text(f"- **名称**: {provider.name}")
|
||||
builder.text(f"- **API 类型**: {provider.api_type}")
|
||||
builder.text(f"- **API Base**: {provider.api_base or '默认'}")
|
||||
|
||||
builder.head("模型详情", 2)
|
||||
|
||||
temp_value = model.temperature or provider.temperature or "未设置"
|
||||
token_value = model.max_tokens or provider.max_tokens or "未设置"
|
||||
|
||||
md.list(
|
||||
[
|
||||
f"**名称**: {model.model_name}",
|
||||
f"**默认温度**: {temp_value}",
|
||||
f"**最大Token**: {token_value}",
|
||||
f"**核心能力**: {', '.join(cap_list) or '纯文本'}",
|
||||
]
|
||||
)
|
||||
builder.text(f"- **名称**: {model.model_name}")
|
||||
builder.text(f"- **默认温度**: {temp_value}")
|
||||
builder.text(f"- **最大Token**: {token_value}")
|
||||
builder.text(f"- **核心能力**: {', '.join(cap_list) or '纯文本'}")
|
||||
|
||||
return await md.build()
|
||||
return await builder.with_style("light").build()
|
||||
|
||||
@staticmethod
|
||||
async def format_key_status_as_image(
|
||||
provider_name: str, sorted_stats: list[dict[str, Any]]
|
||||
) -> BuildImage:
|
||||
) -> bytes:
|
||||
"""将已排序的、详细的API Key状态格式化为表格图片"""
|
||||
title = f"🔑 '{provider_name}' API Key 状态"
|
||||
|
||||
if not sorted_stats:
|
||||
return await BuildImage.build_text_image(
|
||||
f"{title}\n\n该提供商没有配置API Keys。"
|
||||
)
|
||||
|
||||
def _status_row_style(column: str, text: str) -> RowStyle:
|
||||
style = RowStyle()
|
||||
if column == "状态":
|
||||
if "✅ 健康" in text:
|
||||
style.font_color = "#67C23A"
|
||||
elif "⚠️ 告警" in text:
|
||||
style.font_color = "#E6A23C"
|
||||
elif "❌ 错误" in text or "🚫" in text:
|
||||
style.font_color = "#F56C6C"
|
||||
elif "❄️ 冷却中" in text:
|
||||
style.font_color = "#409EFF"
|
||||
elif column == "成功率":
|
||||
try:
|
||||
if text != "N/A":
|
||||
rate = float(text.replace("%", ""))
|
||||
if rate < 80:
|
||||
style.font_color = "#F56C6C"
|
||||
elif rate < 95:
|
||||
style.font_color = "#E6A23C"
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
return style
|
||||
|
||||
column_name = [
|
||||
"Key (部分)",
|
||||
"状态",
|
||||
"总调用",
|
||||
"成功率",
|
||||
"平均延迟(s)",
|
||||
"上次错误",
|
||||
"建议操作",
|
||||
]
|
||||
data_list = []
|
||||
|
||||
for key_info in sorted_stats:
|
||||
@@ -155,15 +116,19 @@ class Presenters:
|
||||
if status_enum == KeyStatus.COOLDOWN:
|
||||
cooldown_seconds = int(key_info["cooldown_seconds_left"])
|
||||
formatted_time = _format_seconds(cooldown_seconds)
|
||||
status_text = f"❄️ 冷却中({formatted_time})"
|
||||
status_cell = StatusBadgeCell(
|
||||
text=f"冷却中({formatted_time})", status_type="info"
|
||||
)
|
||||
else:
|
||||
status_text = {
|
||||
KeyStatus.DISABLED: "🚫 永久禁用",
|
||||
KeyStatus.ERROR: "❌ 错误",
|
||||
KeyStatus.WARNING: "⚠️ 告警",
|
||||
KeyStatus.HEALTHY: "✅ 健康",
|
||||
KeyStatus.UNUSED: "⚪️ 未使用",
|
||||
}.get(status_enum, "❔ 未知")
|
||||
status_map = {
|
||||
KeyStatus.DISABLED: ("永久禁用", "error"),
|
||||
KeyStatus.ERROR: ("错误", "error"),
|
||||
KeyStatus.WARNING: ("告警", "warning"),
|
||||
KeyStatus.HEALTHY: ("健康", "ok"),
|
||||
KeyStatus.UNUSED: ("未使用", "info"),
|
||||
}
|
||||
text, status_type = status_map.get(status_enum, ("未知", "info"))
|
||||
status_cell = StatusBadgeCell(text=text, status_type=status_type) # type: ignore
|
||||
|
||||
total_calls = key_info["total_calls"]
|
||||
total_calls_text = (
|
||||
@@ -174,6 +139,13 @@ class Presenters:
|
||||
|
||||
success_rate = key_info["success_rate"]
|
||||
success_rate_text = f"{success_rate:.1f}%" if total_calls > 0 else "N/A"
|
||||
rate_color = None
|
||||
if total_calls > 0:
|
||||
if success_rate < 80:
|
||||
rate_color = "#F56C6C"
|
||||
elif success_rate < 95:
|
||||
rate_color = "#E6A23C"
|
||||
success_rate_cell = TextCell(content=success_rate_text, color=rate_color)
|
||||
|
||||
avg_latency = key_info["avg_latency"]
|
||||
avg_latency_text = f"{avg_latency / 1000:.2f}" if avg_latency > 0 else "N/A"
|
||||
@@ -184,21 +156,29 @@ class Presenters:
|
||||
|
||||
data_list.append(
|
||||
[
|
||||
key_info["key_id"],
|
||||
status_text,
|
||||
total_calls_text,
|
||||
success_rate_text,
|
||||
avg_latency_text,
|
||||
last_error,
|
||||
key_info["suggested_action"],
|
||||
TextCell(content=key_info["key_id"]),
|
||||
status_cell,
|
||||
TextCell(content=total_calls_text),
|
||||
success_rate_cell,
|
||||
TextCell(content=avg_latency_text),
|
||||
TextCell(content=last_error),
|
||||
TextCell(content=key_info["suggested_action"]),
|
||||
]
|
||||
)
|
||||
|
||||
return await ImageTemplate.table_page(
|
||||
head_text=title,
|
||||
tip_text="使用 `llm reset-key <Provider>` 重置Key状态",
|
||||
column_name=column_name,
|
||||
data_list=data_list,
|
||||
text_style=_status_row_style,
|
||||
column_space=15,
|
||||
builder = TableBuilder(
|
||||
title=title, tip="使用 `llm reset-key <Provider>` 重置Key状态"
|
||||
)
|
||||
builder.set_headers(
|
||||
[
|
||||
"Key (部分)",
|
||||
"状态",
|
||||
"总调用",
|
||||
"成功率",
|
||||
"平均延迟(s)",
|
||||
"上次错误",
|
||||
"建议操作",
|
||||
]
|
||||
)
|
||||
builder.add_rows(data_list)
|
||||
return await builder.build(use_cache=False)
|
||||
|
||||
Reference in New Issue
Block a user