feat(llmage): model pricing query page — select model, display pricing table

This commit is contained in:
yumoqing 2026-07-17 16:08:08 +08:00
parent 6ab53eca2b
commit 233b5db481
4 changed files with 79 additions and 0 deletions

View File

@ -69,6 +69,8 @@ PATHS_LOGINED = [
f"/{MOD}/show_llms.ui",
f"/{MOD}/show_llms_by_providers.ui",
f"/{MOD}/model_plaza.ui",
f"/{MOD}/model_pricing.dspy",
f"/{MOD}/model_pricing.ui",
f"/{MOD}/failed_accounting.ui",
f"/{MOD}/llmcatelog_list.ui",
@ -101,6 +103,7 @@ PATHS_LOGINED = [
f"/{MOD}/api/get_search_apiname.dspy",
f"/{MOD}/api/get_search_providerid.dspy",
f"/{MOD}/api/get_search_upappid.dspy",
f"/{MOD}/api/get_search_model.dspy",
f"/{MOD}/api/get_upapps.dspy",
f"/{MOD}/api/llm_launch_check_api.dspy",
f"/{MOD}/api/llm_api_map_create.dspy",

View File

@ -0,0 +1,7 @@
async with get_sor_context(request._run_ns, 'llmage') as sor:
recs = await sor.sqlExe(
"SELECT id, name FROM llm ORDER BY name", {})
result = [{'value': '', 'text': '全部'}]
for r in recs:
result.append({'value': r.id, 'text': r.name})
return result

View File

@ -0,0 +1,17 @@
llmid = params_kw.get('llmid', '')
if not llmid:
return json.dumps({'status': 'error', 'data': {'message': '请选择模型'}}, ensure_ascii=False)
async with get_sor_context(request._run_ns, 'llmage') as sor:
maps = await sor.sqlExe(
"select ppid from llm_api_map where llmid=${llmid}$", {'llmid': llmid})
ppids = [m.ppid for m in maps if m.ppid] if maps else []
if not ppids:
return json.dumps({'status': 'error', 'data': {'message': '该模型未配置定价'}}, ensure_ascii=False)
try:
data = await get_pricing_display(ppids[0])
return json.dumps({'status': 'ok', 'data': data}, ensure_ascii=False, default=str)
except Exception as e:
return json.dumps({'status': 'error', 'data': {'message': str(e)}}, ensure_ascii=False)

52
wwwroot/model_pricing.ui Normal file
View File

@ -0,0 +1,52 @@
{
"widgettype": "VBox",
"id": "model_pricing_page",
"options": {
"width": "100%",
"height": "100%",
"padding": "20px",
"gap": "16px"
},
"subwidgets": [
{
"widgettype": "Title3",
"options": {
"text": "模型定价查询"
}
},
{
"widgettype": "InlineForm",
"id": "pricing_form",
"options": {
"fields": [
{
"name": "llmid",
"label": "选择模型",
"uitype": "code",
"required": true,
"dataurl": "{{entire_url('/llmage/api/get_search_model.dspy')}}",
"valueField": "value",
"textField": "text"
}
],
"submit_label": "查询"
},
"binds": [
{
"wid": "self",
"event": "submit",
"actiontype": "script",
"script": "var v=this.values||{};if(!v.llmid)return;var url='{{entire_url('/llmage/model_pricing.dspy')}}?llmid='+encodeURIComponent(v.llmid);fetch(url,{credentials:'include'}).then(function(r){return r.json()}).then(function(d){var box=bricks.getWidgetById('pricing_result');if(!box)return;if(d.status!=='ok'){box.clear();box.add({widgettype:'Text',options:{text:d.data.message,color:'#ef4444',fontSize:'14px'}});return;}var items=d.data.items||[];var rows=[];for(var i=0;i<items.length;i++){var it=items[i];var fl=it.filter_labels||{};var label=[];for(var k in fl)label.push(fl[k]);var pfs=it.price_factors||[];for(var j=0;j<pfs.length;j++){var pf=pfs[j];rows.push({model:label.join(' '),factor:pf.label||pf.factor,unit:pf.unit_label||(pf.unit_price+'/'+pf.unit),price:pf.unit_price});}}var tbl=bricks.getWidgetById('pricing_table');if(tbl){tbl.setData({total:rows.length,rows:rows})}else{box.clear();box.add({widgettype:'Tabular',id:'pricing_table',options:{width:'100%',editable:false,row_options:{fields:[{name:'model',title:'模型/规格',cwidth:20},{name:'factor',title:'计费因子',cwidth:14},{name:'unit',title:'单价',cwidth:18},{name:'price',title:'价格',cwidth:10,type:'float'}]}},data:{total:rows.length,rows:rows}});}}).catch(function(e){var box=bricks.getWidgetById('pricing_result');if(box){box.clear();box.add({widgettype:'Text',options:{text:'查询失败: '+e.toString(),color:'#ef4444'}})}})"
}
]
},
{
"widgettype": "VBox",
"id": "pricing_result",
"options": {
"width": "100%",
"flex": "1"
}
}
]
}