feat(ui): 模型治理卡片全部弹窗化(card_popup白名单)+模型注册弹窗加自动配置(auto_config_popup:API/定价链接或正文→内部助手全链)与上线检查(online_check_popup:选模型→真实调用+记账检查+状态复核→告知结果);删下方llm_content展示容器(2026-09-07用户定夺)

This commit is contained in:
yumoqing 2026-09-07 13:24:31 +08:00
parent 34ef256fe6
commit bd732bf35f
6 changed files with 360 additions and 61 deletions

View File

@ -97,6 +97,27 @@
且 r2v 判定加「输出为视频」限定防误吸文本输出模型);
`_MEDIA_INPUT_CAPS` 加 m2t模板生成按输入媒体处理
## 治理页面弹窗化2026-09-07 用户定夺)
`wwwroot/index.ui` 顶部统计条llm_dashboard_widget+ 7 张功能卡片。
**每张卡片点击后弹窗处理PopupWindow 85%×85%),不再在页面下方 llm_content 容器展示**
- `api/card_popup.dspy`target 白名单llm_vendor/llm_model/llm_api_profile/llm_org_policy/
llm_org_quota/llm_user_quota/llm_usage→ 弹窗内嵌对应 CRUD 页 urlwidget
非白名单 target 返回「无效的功能入口」提示(不拼 URL
- 模型注册弹窗target=llm_model顶部额外两个按钮
- **🤖 自动配置** → `api/auto_config_popup.dspy`:输入 API 文档与定价信息的链接或正文,
「开始自动配置」按钮 script 组装指令后驱动弹窗内嵌 AgentIO
pipeline_id=platform_general弹窗级独立 session_idspec 锚定会话隔离天然成立),
内部助手全链完成(抓取/提取/落库/定价/测试/记账检查)后汇报模型状态。
- **✅ 上线检查** → `api/online_check_popup.dspy`:模型下拉
`api/get_llm_online_check_models.dspy`value=模型名test_model_call 按名调用)+
「上线检查」按钮 → 内部助手按模型配置后的测试过程处理test_model_call 真实调用 →
check_model_accounting 记账检查 → platform_llm_status 状态复核),最后明确告知通过/不通过。
- 驱动 AgentIO 的机制:`io.inputw.textw.setValue(指令)` + `io.inputw.input_finished()`
AgentInput 原生方法 dispatch('inputed') → AgentIO POST 流),零自定义 JS 文件。
- 权限:新 dspy 都在 `/pipeline-llm/**` logined 通配内,无需新增 RBAC
内部助手工具本身有 owner 角色代码层硬门禁pipeline-platform
## API 文档端点2026-09-05
`/pipeline-llm/api/v1/docs/`RBAC 授权 any公开文档照 sage /bricks/docs 先例):

View File

@ -0,0 +1,64 @@
# auto_config_popup.dspy — 模型自动配置弹窗2026-09-07 用户定夺)
# 输入 API 文档与定价信息的链接或正文内部助手platform_general自动完成
# 抓取→提取→落库→定价→测试→记账检查全链,结束后汇报模型状态。
import json as _json
import random
uid = await get_user()
if not uid:
return _json.dumps({
"widgettype": "PopupWindow",
"options": {"title": "提示", "cwidth": 30, "cheight": 8, "auto_open": True},
"subwidgets": [{"widgettype": "Text", "options": {"text": "请先登录", "padding": "20px"}}]
}, ensure_ascii=False)
# 每个弹窗实例独立会话spec 锚定按 pipeline_id:user_id:session_id 隔离,
# extract→apply 的会话级缓存在同一弹窗内天然可用)
session_id = 'llmauto' + str(random.randint(100000, 999999))
chat_url = entire_url('/pipeline_core/api/agent_chat.dspy') + '?session_id=' + session_id + '&pipeline_id=platform_general'
# 开始自动配置读两个输入框UiText.resultValue组装指令驱动 AgentIO 发送
# input_finished 是 AgentInput 原生方法dispatch('inputed') → AgentIO 走 POST 流)
start_script = (
"var io=bricks.getWidgetById('llm_auto_io',bricks.app);if(!io||!io.inputw)return;"
"var aw=bricks.getWidgetById('llm_auto_api',bricks.app);"
"var pw=bricks.getWidgetById('llm_auto_pricing',bricks.app);"
"var api=aw&&aw.resultValue?String(aw.resultValue()||'').trim():'';"
"var pricing=pw&&pw.resultValue?String(pw.resultValue()||'').trim():'';"
"if(!api){new bricks.Message({title:'缺少输入',message:'请填写 API 文档链接或粘贴文档正文'});return;}"
"var p='请自动配置模型。API 文档(链接或正文)如下:\\n'+api;"
"if(pricing){p+='\\n定价信息链接或正文若 API 文档已含定价则以文档为准)如下:\\n'+pricing;}"
"p+='\\n请按模型自动配置全链工作流一口气完成抓取/提取/落库/定价/测试/记账检查),"
"结束后汇报模型状态(配置/定价/测试/记账四段结果+遗留事项)。';"
"io.inputw.textw.setValue(p);io.inputw.input_finished();")
resp = {
"widgettype": "PopupWindow",
"id": "llm_auto_cfg_pw",
"options": {"title": "🤖 模型自动配置(内部助手)", "width": "85%", "height": "85%", "auto_open": True},
"subwidgets": [{
"widgettype": "VBox",
"options": {"css": "filler", "width": "100%", "height": "100%", "padding": "12px", "gap": "8px"},
"subwidgets": [
{"widgettype": "Text", "options": {
"text": "粘贴 API 文档链接或直接粘贴文档正文;定价可单独给出(链接或正文),未给则从 API 文档提取。点击「开始自动配置」后由内部助手完成全链并汇报模型状态。",
"cfontsize": 0.9, "color": "#64748b", "halign": "left"}},
{"widgettype": "Text", "options": {"text": "API 文档(链接或正文)", "cfontsize": 0.95, "halign": "left"}},
{"widgettype": "UiText", "id": "llm_auto_api",
"options": {"name": "llm_auto_api", "placeholder": "https://help.aliyun.com/... 或直接粘贴文档正文"}},
{"widgettype": "Text", "options": {"text": "定价信息(链接或正文,可空)", "cfontsize": 0.95, "halign": "left"}},
{"widgettype": "UiText", "id": "llm_auto_pricing",
"options": {"name": "llm_auto_pricing", "placeholder": "定价页链接或价格表正文(元/千token、元/秒等,须来自文档原文)"}},
{"widgettype": "Button", "id": "llm_auto_start",
"options": {"label": "开始自动配置", "css": "small"},
"binds": [{"wid": "self", "event": "click", "actiontype": "script",
"target": "self", "script": start_script}]},
{"widgettype": "AgentIO", "id": "llm_auto_io",
"options": {"css": "filler", "url": chat_url,
"model_dataurl": entire_url('/pipeline_core/api/agent_model_options.dspy') + '?session_id=' + session_id + '&pipeline_id=platform_general',
"model_cwidth": 14,
"placeholder": "也可以直接对内部助手说话,例如:通读这份文档并配置模型 <URL> ..."}}
]}]
}
return _json.dumps(resp, ensure_ascii=False)

View File

@ -0,0 +1,76 @@
# card_popup.dspy — 模型治理功能卡片弹窗2026-09-07 用户定夺:卡片点击一律弹窗处理,不在页面下方展示)
# 参数target=白名单内的 CRUD 目录名;模型注册弹窗额外带「自动配置/上线检查」工具按钮
import json as _json
uid = await get_user()
if not uid:
return _json.dumps({
"widgettype": "PopupWindow",
"options": {"title": "提示", "cwidth": 30, "cheight": 8, "auto_open": True},
"subwidgets": [{"widgettype": "Text", "options": {"text": "请先登录", "padding": "20px"}}]
}, ensure_ascii=False)
target = ((params_kw or {}).get('target', '') or '').strip()
titles = {
'llm_vendor': '供应商与端点',
'llm_model': '模型注册',
'llm_api_profile': '适配模板',
'llm_org_policy': '组织容错策略',
'llm_org_quota': '组织限额限流',
'llm_user_quota': '个人限额限流',
'llm_usage': '用量流水',
}
if target not in titles:
return _json.dumps({
"widgettype": "PopupWindow",
"options": {"title": "提示", "cwidth": 40, "cheight": 10, "auto_open": True},
"subwidgets": [{"widgettype": "Text", "options": {
"text": "无效的功能入口:" + target, "padding": "20px"}}]
}, ensure_ascii=False)
subwidgets = []
if target == 'llm_model':
# 模型注册工具栏:自动配置(内部助手全链)+ 上线检查(真实调用测试+记账检查)
auto_url = entire_url('/pipeline-llm/api/auto_config_popup.dspy')
check_url = entire_url('/pipeline-llm/api/online_check_popup.dspy')
auto_script = (
"var old=bricks.getWidgetById('llm_auto_cfg_pw',bricks.app);"
"if(old&&old.destroy){old.destroy();}"
"var r=await fetch(" + _json.dumps(auto_url) + ");"
"var d=await r.json();if(d&&d.widgettype){bricks.widgetBuild(d,bricks.app);}")
check_script = (
"var old=bricks.getWidgetById('llm_online_chk_pw',bricks.app);"
"if(old&&old.destroy){old.destroy();}"
"var r=await fetch(" + _json.dumps(check_url) + ");"
"var d=await r.json();if(d&&d.widgettype){bricks.widgetBuild(d,bricks.app);}")
subwidgets.append({
"widgettype": "HBox",
"options": {"gap": "8px", "padding": "0 0 10px 0", "alignItems": "center"},
"subwidgets": [
{"widgettype": "Button",
"options": {"label": "🤖 自动配置", "css": "small"},
"binds": [{"wid": "self", "event": "click", "actiontype": "script",
"target": "self", "script": auto_script}]},
{"widgettype": "Button",
"options": {"label": "✅ 上线检查", "css": "small"},
"binds": [{"wid": "self", "event": "click", "actiontype": "script",
"target": "self", "script": check_script}]},
{"widgettype": "Text",
"options": {"text": "自动配置:粘贴 API/定价文档链接或正文,内部助手全链完成并汇报模型状态;上线检查:选择模型后真实调用测试+记账检查",
"cfontsize": 0.85, "color": "#94a3b8", "halign": "left"}},
]})
subwidgets.append({
"widgettype": "urlwidget",
"options": {"url": entire_url('/pipeline-llm/' + target)}})
resp = {
"widgettype": "PopupWindow",
"id": "llm_card_pw",
"options": {"title": titles[target], "width": "85%", "height": "85%", "auto_open": True},
"subwidgets": [{
"widgettype": "VBox",
"options": {"css": "filler", "width": "100%", "height": "100%", "padding": "12px"},
"subwidgets": subwidgets}]
}
return _json.dumps(resp, ensure_ascii=False)

View File

@ -0,0 +1,15 @@
# get_llm_online_check_models.dspy — 上线检查用模型下拉value=模型名test_model_call 按名调用)
dbname = get_module_dbname('pipeline_llm')
try:
async with get_sor_context(request._run_ns, dbname) as sor:
recs = await sor.sqlExe(
"SELECT name, capability, status FROM llm_model WHERE status='active' ORDER BY name", {})
await sor.sqlExe("COMMIT", {})
except Exception as e:
return {"success": False, "message": str(e), "data": []}
out = []
for r in (recs or []):
n = getattr(r, 'name', '') or ''
c = getattr(r, 'capability', '') or ''
out.append({"value": n, "text": (n + '' + c + '') if c else n})
return out

View File

@ -0,0 +1,64 @@
# online_check_popup.dspy — 模型上线检查弹窗2026-09-07 用户定夺)
# 选择一个模型 → 点击上线检查 → 内部助手按模型配置后的测试过程处理:
# test_model_call 真实调用(完整治理链)→ check_model_accounting 记账检查
# created 等 60~120 秒复查)→ platform_llm_status 复核状态 → 给出最终结论。
import json as _json
import random
uid = await get_user()
if not uid:
return _json.dumps({
"widgettype": "PopupWindow",
"options": {"title": "提示", "cwidth": 30, "cheight": 8, "auto_open": True},
"subwidgets": [{"widgettype": "Text", "options": {"text": "请先登录", "padding": "20px"}}]
}, ensure_ascii=False)
session_id = 'llmchk' + str(random.randint(100000, 999999))
chat_url = entire_url('/pipeline_core/api/agent_chat.dspy') + '?session_id=' + session_id + '&pipeline_id=platform_general'
models_url = entire_url('/pipeline-llm/api/get_llm_online_check_models.dspy')
# 上线检查读模型下拉UiCode.resultValue = 模型名),下达标准检查指令
check_script = (
"var io=bricks.getWidgetById('llm_chk_io',bricks.app);if(!io||!io.inputw)return;"
"var sel=bricks.getWidgetById('llm_chk_model',bricks.app);"
"var m=sel&&sel.resultValue?String(sel.resultValue()||'').trim():'';"
"if(!m){new bricks.Message({title:'缺少输入',message:'请先选择要检查的模型'});return;}"
"var p='请对模型「'+m+'」执行上线检查,按模型配置后的测试过程处理:"
"1) test_model_call 真实调用测试(生成类模型缺业务参数时先问我,不要编造);"
"2) check_model_accounting 用返回的 task_ref 检查记账created 则等 60~120 秒复查一次);"
"3) platform_llm_status 复核模型/定价/供应商状态;"
"4) 最后明确告诉我检查结果:通过 / 不通过 + 各环节明细 + 遗留事项,不要粉饰。';"
"io.inputw.textw.setValue(p);io.inputw.input_finished();")
resp = {
"widgettype": "PopupWindow",
"id": "llm_online_chk_pw",
"options": {"title": "✅ 模型上线检查(内部助手)", "width": "85%", "height": "85%", "auto_open": True},
"subwidgets": [{
"widgettype": "VBox",
"options": {"css": "filler", "width": "100%", "height": "100%", "padding": "12px", "gap": "8px"},
"subwidgets": [
{"widgettype": "Text", "options": {
"text": "选择模型后点击「上线检查」:内部助手执行真实调用测试(完整治理链)→ 记账检查(独立复算金额)→ 状态复核,最后给出通过/不通过结论。",
"cfontsize": 0.9, "color": "#64748b", "halign": "left"}},
{"widgettype": "HBox",
"options": {"gap": "8px", "alignItems": "center"},
"subwidgets": [
{"widgettype": "Text", "options": {"text": "模型:", "cfontsize": 0.95, "halign": "left"}},
{"widgettype": "UiCode", "id": "llm_chk_model",
"options": {"name": "llm_chk_model", "dataurl": models_url,
"valueField": "value", "textField": "text",
"nullable": True, "cwidth": 30}},
{"widgettype": "Button", "id": "llm_chk_start",
"options": {"label": "上线检查", "css": "small"},
"binds": [{"wid": "self", "event": "click", "actiontype": "script",
"target": "self", "script": check_script}]}]},
{"widgettype": "AgentIO", "id": "llm_chk_io",
"options": {"css": "filler", "url": chat_url,
"model_dataurl": entire_url('/pipeline_core/api/agent_model_options.dspy') + '?session_id=' + session_id + '&pipeline_id=platform_general',
"model_cwidth": 14,
"placeholder": "也可以直接对内部助手说话,例如:对模型 xxx 做上线检查"}}
]}]
}
return _json.dumps(resp, ensure_ascii=False)

View File

@ -26,7 +26,7 @@
{
"widgettype": "Text",
"options": {
"text": "供应商/账号/模型 · 容错策略 · 限额限流 · 双维度记账",
"text": "供应商/账号/模型 · 容错策略 · 限额限流 · 双维度记账 · 点击卡片弹窗处理",
"cfontsize": 1.2
}
}
@ -69,17 +69,26 @@
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.llm_content",
"options": {
"url": "{{entire_url('/pipeline-llm/llm_vendor')}}"
},
"mode": "replace"
"actiontype": "script",
"target": "self",
"script": "var old=bricks.getWidgetById('llm_card_pw',bricks.app);if(old&&old.destroy){old.destroy();}var r=await fetch('{{entire_url('/pipeline-llm/api/card_popup.dspy')}}?target=llm_vendor');var d=await r.json();if(d&&d.widgettype){bricks.widgetBuild(d,bricks.app);}else{bricks.show_message({title:'打开失败',message:'未能加载功能窗口'});}"
}
],
"subwidgets": [
{"widgettype": "Title4", "options": {"text": "供应商与端点", "marginBottom": "8px"}},
{"widgettype": "Text", "options": {"text": "供应商协议 + 全球端点目录(国内/国际,配置一次全账号共用)", "cfontsize": 1.2}}
{
"widgettype": "Title4",
"options": {
"text": "供应商与端点",
"marginBottom": "8px"
}
},
{
"widgettype": "Text",
"options": {
"text": "供应商协议 + 全球端点目录(国内/国际,配置一次全账号共用)",
"cfontsize": 1.2
}
}
]
},
{
@ -94,17 +103,26 @@
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.llm_content",
"options": {
"url": "{{entire_url('/pipeline-llm/llm_model')}}"
},
"mode": "replace"
"actiontype": "script",
"target": "self",
"script": "var old=bricks.getWidgetById('llm_card_pw',bricks.app);if(old&&old.destroy){old.destroy();}var r=await fetch('{{entire_url('/pipeline-llm/api/card_popup.dspy')}}?target=llm_model');var d=await r.json();if(d&&d.widgettype){bricks.widgetBuild(d,bricks.app);}else{bricks.show_message({title:'打开失败',message:'未能加载功能窗口'});}"
}
],
"subwidgets": [
{"widgettype": "Title4", "options": {"text": "模型注册", "marginBottom": "8px"}},
{"widgettype": "Text", "options": {"text": "能力分类t2t/…、挂定价项目ppid、成本/售价", "cfontsize": 1.2}}
{
"widgettype": "Title4",
"options": {
"text": "模型注册",
"marginBottom": "8px"
}
},
{
"widgettype": "Text",
"options": {
"text": "能力分类t2t/…、挂定价项目ppid、成本/售价 · 🤖自动配置 · ✅上线检查",
"cfontsize": 1.2
}
}
]
},
{
@ -119,17 +137,26 @@
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.llm_content",
"options": {
"url": "{{entire_url('/pipeline-llm/llm_api_profile')}}"
},
"mode": "replace"
"actiontype": "script",
"target": "self",
"script": "var old=bricks.getWidgetById('llm_card_pw',bricks.app);if(old&&old.destroy){old.destroy();}var r=await fetch('{{entire_url('/pipeline-llm/api/card_popup.dspy')}}?target=llm_api_profile');var d=await r.json();if(d&&d.widgettype){bricks.widgetBuild(d,bricks.app);}else{bricks.show_message({title:'打开失败',message:'未能加载功能窗口'});}"
}
],
"subwidgets": [
{"widgettype": "Title4", "options": {"text": "适配模板", "marginBottom": "8px"}},
{"widgettype": "Text", "options": {"text": "协议×能力形态的请求/响应模板(按形态去重,不按模型开)", "cfontsize": 1.2}}
{
"widgettype": "Title4",
"options": {
"text": "适配模板",
"marginBottom": "8px"
}
},
{
"widgettype": "Text",
"options": {
"text": "协议×能力形态的请求/响应模板(按形态去重,不按模型开)",
"cfontsize": 1.2
}
}
]
},
{
@ -144,17 +171,26 @@
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.llm_content",
"options": {
"url": "{{entire_url('/pipeline-llm/llm_org_policy')}}"
},
"mode": "replace"
"actiontype": "script",
"target": "self",
"script": "var old=bricks.getWidgetById('llm_card_pw',bricks.app);if(old&&old.destroy){old.destroy();}var r=await fetch('{{entire_url('/pipeline-llm/api/card_popup.dspy')}}?target=llm_org_policy');var d=await r.json();if(d&&d.widgettype){bricks.widgetBuild(d,bricks.app);}else{bricks.show_message({title:'打开失败',message:'未能加载功能窗口'});}"
}
],
"subwidgets": [
{"widgettype": "Title4", "options": {"text": "组织容错策略", "marginBottom": "8px"}},
{"widgettype": "Text", "options": {"text": "组织级主模型 + 备模型链 + 端点偏好prefer/must", "cfontsize": 1.2}}
{
"widgettype": "Title4",
"options": {
"text": "组织容错策略",
"marginBottom": "8px"
}
},
{
"widgettype": "Text",
"options": {
"text": "组织级主模型 + 备模型链 + 端点偏好prefer/must",
"cfontsize": 1.2
}
}
]
},
{
@ -169,17 +205,26 @@
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.llm_content",
"options": {
"url": "{{entire_url('/pipeline-llm/llm_org_quota')}}"
},
"mode": "replace"
"actiontype": "script",
"target": "self",
"script": "var old=bricks.getWidgetById('llm_card_pw',bricks.app);if(old&&old.destroy){old.destroy();}var r=await fetch('{{entire_url('/pipeline-llm/api/card_popup.dspy')}}?target=llm_org_quota');var d=await r.json();if(d&&d.widgettype){bricks.widgetBuild(d,bricks.app);}else{bricks.show_message({title:'打开失败',message:'未能加载功能窗口'});}"
}
],
"subwidgets": [
{"widgettype": "Title4", "options": {"text": "组织限额限流", "marginBottom": "8px"}},
{"widgettype": "Text", "options": {"text": "组织池充值/余额 + 组织 QPM 上限", "cfontsize": 1.2}}
{
"widgettype": "Title4",
"options": {
"text": "组织限额限流",
"marginBottom": "8px"
}
},
{
"widgettype": "Text",
"options": {
"text": "组织池充值/余额 + 组织 QPM 上限",
"cfontsize": 1.2
}
}
]
},
{
@ -194,17 +239,26 @@
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.llm_content",
"options": {
"url": "{{entire_url('/pipeline-llm/llm_user_quota')}}"
},
"mode": "replace"
"actiontype": "script",
"target": "self",
"script": "var old=bricks.getWidgetById('llm_card_pw',bricks.app);if(old&&old.destroy){old.destroy();}var r=await fetch('{{entire_url('/pipeline-llm/api/card_popup.dspy')}}?target=llm_user_quota');var d=await r.json();if(d&&d.widgettype){bricks.widgetBuild(d,bricks.app);}else{bricks.show_message({title:'打开失败',message:'未能加载功能窗口'});}"
}
],
"subwidgets": [
{"widgettype": "Title4", "options": {"text": "个人限额限流", "marginBottom": "8px"}},
{"widgettype": "Text", "options": {"text": "个人额度上限 + 个人 QPM共享组织池模式", "cfontsize": 1.2}}
{
"widgettype": "Title4",
"options": {
"text": "个人限额限流",
"marginBottom": "8px"
}
},
{
"widgettype": "Text",
"options": {
"text": "个人额度上限 + 个人 QPM共享组织池模式",
"cfontsize": 1.2
}
}
]
},
{
@ -219,26 +273,31 @@
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.llm_content",
"options": {
"url": "{{entire_url('/pipeline-llm/llm_usage')}}"
},
"mode": "replace"
"actiontype": "script",
"target": "self",
"script": "var old=bricks.getWidgetById('llm_card_pw',bricks.app);if(old&&old.destroy){old.destroy();}var r=await fetch('{{entire_url('/pipeline-llm/api/card_popup.dspy')}}?target=llm_usage');var d=await r.json();if(d&&d.widgettype){bricks.widgetBuild(d,bricks.app);}else{bricks.show_message({title:'打开失败',message:'未能加载功能窗口'});}"
}
],
"subwidgets": [
{"widgettype": "Title4", "options": {"text": "用量流水", "marginBottom": "8px"}},
{"widgettype": "Text", "options": {"text": "双维度记账:成本侧(账号)+ 消费侧(组织/个人)", "cfontsize": 1.2}}
{
"widgettype": "Title4",
"options": {
"text": "用量流水",
"marginBottom": "8px"
}
},
{
"widgettype": "Text",
"options": {
"text": "双维度记账:成本侧(账号)+ 消费侧(组织/个人)",
"cfontsize": 1.2
}
}
]
}
]
}
]
},
{
"widgettype": "VBox",
"id": "llm_content"
}
]
}