diff --git a/README.md b/README.md index de75a8a..1a31f02 100644 --- a/README.md +++ b/README.md @@ -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_id,spec 锚定会话隔离天然成立), + 内部助手全链完成(抓取/提取/落库/定价/测试/记账检查)后汇报模型状态。 + - **✅ 上线检查** → `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 先例): diff --git a/wwwroot/api/auto_config_popup.dspy b/wwwroot/api/auto_config_popup.dspy new file mode 100644 index 0000000..535b9cb --- /dev/null +++ b/wwwroot/api/auto_config_popup.dspy @@ -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": "也可以直接对内部助手说话,例如:通读这份文档并配置模型 ..."}} + ]}] +} +return _json.dumps(resp, ensure_ascii=False) diff --git a/wwwroot/api/card_popup.dspy b/wwwroot/api/card_popup.dspy new file mode 100644 index 0000000..cd33411 --- /dev/null +++ b/wwwroot/api/card_popup.dspy @@ -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) diff --git a/wwwroot/api/get_llm_online_check_models.dspy b/wwwroot/api/get_llm_online_check_models.dspy new file mode 100644 index 0000000..3fa1edc --- /dev/null +++ b/wwwroot/api/get_llm_online_check_models.dspy @@ -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 diff --git a/wwwroot/api/online_check_popup.dspy b/wwwroot/api/online_check_popup.dspy new file mode 100644 index 0000000..6345022 --- /dev/null +++ b/wwwroot/api/online_check_popup.dspy @@ -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) diff --git a/wwwroot/index.ui b/wwwroot/index.ui index 2085e27..ce082a4 100644 --- a/wwwroot/index.ui +++ b/wwwroot/index.ui @@ -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" } ] }