diff --git a/scripts/load_path.py b/scripts/load_path.py index ea625b6..a9a7f66 100644 --- a/scripts/load_path.py +++ b/scripts/load_path.py @@ -57,7 +57,14 @@ PATHS_LOGINED = [ f"/{MOD}/api_doc.md", f"/{MOD}/llm_dialog.ui", f"/{MOD}/llm_launch_check.ui", - f"/{MOD}/llm_launch_check_page.dspy", + f"/{MOD}/check_model_record.dspy", + f"/{MOD}/check_date_status.dspy", + f"/{MOD}/check_upapp.dspy", + f"/{MOD}/check_uapi.dspy", + f"/{MOD}/check_uapiio.dspy", + f"/{MOD}/check_llm_api_map.dspy", + f"/{MOD}/check_pricing_program.dspy", + f"/{MOD}/check_pricing_data.dspy", f"/{MOD}/show_same_catelog_llm.ui", f"/{MOD}/show_llms.ui", f"/{MOD}/show_llms_by_providers.ui", diff --git a/wwwroot/check_date_status.dspy b/wwwroot/check_date_status.dspy new file mode 100644 index 0000000..0215732 --- /dev/null +++ b/wwwroot/check_date_status.dspy @@ -0,0 +1,31 @@ +llmid = params_kw.get('llmid', '') + +if not llmid: + return json.dumps({ + "widgettype": "Text", + "options": {"text": "❌ 日期与状态: 缺少llmid参数", "i18n": false} + }, ensure_ascii=False) + +async with get_sor_context(request._run_ns, 'llmage') as sor: + recs = await sor.sqlExe( + "select * from llm where id=${llmid}$", {'llmid': llmid}) + +if not recs: + return json.dumps({ + "widgettype": "Text", + "options": {"text": "❌ 日期与状态: 模型不存在", "i18n": false} + }, ensure_ascii=False) + +llm = recs[0] +date_ok = bool(llm.enabled_date and llm.expired_date) +status_ok = llm.status == 'published' + +if date_ok and status_ok: + text = f"✅ 日期与状态: 启用:{llm.enabled_date} 失效:{llm.expired_date} 状态:{llm.status}" +else: + text = f"❌ 日期与状态: 启用:{llm.enabled_date} 失效:{llm.expired_date} 状态:{llm.status}" + +return json.dumps({ + "widgettype": "Text", + "options": {"text": text, "i18n": false} +}, ensure_ascii=False) diff --git a/wwwroot/check_llm_api_map.dspy b/wwwroot/check_llm_api_map.dspy new file mode 100644 index 0000000..fa7c6c9 --- /dev/null +++ b/wwwroot/check_llm_api_map.dspy @@ -0,0 +1,22 @@ +llmid = params_kw.get('llmid', '') + +if not llmid: + return json.dumps({ + "widgettype": "Text", + "options": {"text": "❌ 能力映射(llm_api_map): 缺少llmid参数", "i18n": false} + }, ensure_ascii=False) + +async with get_sor_context(request._run_ns, 'llmage') as sor: + maps = await sor.sqlExe( + "select * from llm_api_map where llmid=${llmid}$", {'llmid': llmid}) + +if maps: + ppids = [m.ppid for m in maps if m.ppid] + text = f"✅ 能力映射(llm_api_map): {len(maps)}条记录, {len(ppids)}个有定价" +else: + text = "❌ 能力映射(llm_api_map): 无映射记录" + +return json.dumps({ + "widgettype": "Text", + "options": {"text": text, "i18n": false} +}, ensure_ascii=False) diff --git a/wwwroot/check_model_record.dspy b/wwwroot/check_model_record.dspy new file mode 100644 index 0000000..984d5c9 --- /dev/null +++ b/wwwroot/check_model_record.dspy @@ -0,0 +1,22 @@ +llmid = params_kw.get('llmid', '') + +if not llmid: + return json.dumps({ + "widgettype": "Text", + "options": {"text": "❌ 模型记录: 缺少llmid参数", "i18n": false} + }, ensure_ascii=False) + +async with get_sor_context(request._run_ns, 'llmage') as sor: + recs = await sor.sqlExe( + "select * from llm where id=${llmid}$", {'llmid': llmid}) + +if recs: + llm = recs[0] + text = f"✅ 模型记录: {llm.name} ({llm.model})" +else: + text = f"❌ 模型记录: llm id={llmid} 不存在" + +return json.dumps({ + "widgettype": "Text", + "options": {"text": text, "i18n": false} +}, ensure_ascii=False) diff --git a/wwwroot/check_pricing_data.dspy b/wwwroot/check_pricing_data.dspy new file mode 100644 index 0000000..93d5e7c --- /dev/null +++ b/wwwroot/check_pricing_data.dspy @@ -0,0 +1,36 @@ +llmid = params_kw.get('llmid', '') + +if not llmid: + return json.dumps({ + "widgettype": "Text", + "options": {"text": "❌ 定价数据(pricingdata): 缺少llmid参数", "i18n": false} + }, ensure_ascii=False) + +async with get_sor_context(request._run_ns, 'llmage') as sor: + maps = await sor.sqlExe( + "select * 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: + text = "❌ 定价数据(pricingdata): 无定价项目" +else: + ppid = ppids[0] + async with get_sor_context(request._run_ns, 'pricing') as psor: + # First check if pricing_program exists + pregs = await psor.sqlExe( + "select * from pricing_program where id=${ppid}$", {'ppid': ppid}) + if not pregs: + text = "❌ 定价数据(pricingdata): 依赖定价项目未通过" + else: + datas = await psor.sqlExe( + "select count(*) as cnt from pricingdata where ppid=${ppid}$", {'ppid': ppid}) + cnt = datas[0].cnt if datas else 0 + if cnt > 0: + text = f"✅ 定价数据(pricingdata): {cnt}条记录" + else: + text = "❌ 定价数据(pricingdata): 无定价数据" + +return json.dumps({ + "widgettype": "Text", + "options": {"text": text, "i18n": false} +}, ensure_ascii=False) diff --git a/wwwroot/check_pricing_program.dspy b/wwwroot/check_pricing_program.dspy new file mode 100644 index 0000000..aab1c5f --- /dev/null +++ b/wwwroot/check_pricing_program.dspy @@ -0,0 +1,29 @@ +llmid = params_kw.get('llmid', '') + +if not llmid: + return json.dumps({ + "widgettype": "Text", + "options": {"text": "❌ 定价项目(pricing_program): 缺少llmid参数", "i18n": false} + }, ensure_ascii=False) + +async with get_sor_context(request._run_ns, 'llmage') as sor: + maps = await sor.sqlExe( + "select * 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: + text = "❌ 定价项目(pricing_program): llm_api_map中无ppid" +else: + ppid = ppids[0] + async with get_sor_context(request._run_ns, 'pricing') as psor: + pregs = await psor.sqlExe( + "select * from pricing_program where id=${ppid}$", {'ppid': ppid}) + if pregs: + text = f"✅ 定价项目(pricing_program): {pregs[0].name} (id={ppid})" + else: + text = f"❌ 定价项目(pricing_program): ppid={ppid} 未找到" + +return json.dumps({ + "widgettype": "Text", + "options": {"text": text, "i18n": false} +}, ensure_ascii=False) diff --git a/wwwroot/check_uapi.dspy b/wwwroot/check_uapi.dspy new file mode 100644 index 0000000..43b0511 --- /dev/null +++ b/wwwroot/check_uapi.dspy @@ -0,0 +1,30 @@ +llmid = params_kw.get('llmid', '') + +if not llmid: + return json.dumps({ + "widgettype": "Text", + "options": {"text": "❌ API映射(uapi): 缺少llmid参数", "i18n": false} + }, ensure_ascii=False) + +async with get_sor_context(request._run_ns, 'llmage') as sor: + recs = await sor.sqlExe(""" + select a.*, e.ioid, e.stream + from llm a + join llm_api_map m on a.id = m.llmid + join upapp c on a.upappid = c.id + join uapi e on c.id = e.upappid and m.apiname = e.name + where a.id=${llmid}$""", {'llmid': llmid}) + +if recs: + text = f"✅ API映射(uapi): ioid={recs[0].ioid}, stream={recs[0].stream}" +else: + # Get apiname from llm + async with get_sor_context(request._run_ns, 'llmage') as sor: + llm_recs = await sor.sqlExe("select apiname from llm where id=${llmid}$", {'llmid': llmid}) + apiname = llm_recs[0].apiname if llm_recs else 'N/A' + text = f"❌ API映射(uapi): apiname={apiname} 在upapp中未找到" + +return json.dumps({ + "widgettype": "Text", + "options": {"text": text, "i18n": false} +}, ensure_ascii=False) diff --git a/wwwroot/check_uapiio.dspy b/wwwroot/check_uapiio.dspy new file mode 100644 index 0000000..8d89f40 --- /dev/null +++ b/wwwroot/check_uapiio.dspy @@ -0,0 +1,33 @@ +llmid = params_kw.get('llmid', '') + +if not llmid: + return json.dumps({ + "widgettype": "Text", + "options": {"text": "❌ IO定义(uapiio): 缺少llmid参数", "i18n": false} + }, ensure_ascii=False) + +async with get_sor_context(request._run_ns, 'llmage') as sor: + # First get ioid from uapi + recs = await sor.sqlExe(""" + select e.ioid + from llm a + join llm_api_map m on a.id = m.llmid + join upapp c on a.upappid = c.id + join uapi e on c.id = e.upappid and m.apiname = e.name + where a.id=${llmid}$""", {'llmid': llmid}) + + if not recs: + text = "❌ IO定义(uapiio): 依赖 uapi 未通过" + else: + ioid = recs[0].ioid + recs2 = await sor.sqlExe( + "select * from uapiio where id=${ioid}$", {'ioid': ioid}) + if recs2: + text = f"✅ IO定义(uapiio): uapiio id={ioid}" + else: + text = f"❌ IO定义(uapiio): ioid={ioid} 未找到" + +return json.dumps({ + "widgettype": "Text", + "options": {"text": text, "i18n": false} +}, ensure_ascii=False) diff --git a/wwwroot/check_upapp.dspy b/wwwroot/check_upapp.dspy new file mode 100644 index 0000000..16a4680 --- /dev/null +++ b/wwwroot/check_upapp.dspy @@ -0,0 +1,27 @@ +llmid = params_kw.get('llmid', '') + +if not llmid: + return json.dumps({ + "widgettype": "Text", + "options": {"text": "❌ 上位系统(upapp): 缺少llmid参数", "i18n": false} + }, ensure_ascii=False) + +async with get_sor_context(request._run_ns, 'llmage') as sor: + recs = await sor.sqlExe( + "select a.* from llm a, upapp b where a.id=${llmid}$ and a.upappid=b.id", + {'llmid': llmid}) + + if recs: + llm = recs[0] + text = f"✅ 上位系统(upapp): upappid={llm.upappid}" + else: + # Get llm info to show upappid + llm_recs = await sor.sqlExe( + "select upappid from llm where id=${llmid}$", {'llmid': llmid}) + upappid = llm_recs[0].upappid if llm_recs else '未知' + text = f"❌ 上位系统(upapp): upappid={upappid} 未找到关联" + +return json.dumps({ + "widgettype": "Text", + "options": {"text": text, "i18n": false} +}, ensure_ascii=False) diff --git a/wwwroot/llm_launch_check.ui b/wwwroot/llm_launch_check.ui index 7ded590..ecd2116 100644 --- a/wwwroot/llm_launch_check.ui +++ b/wwwroot/llm_launch_check.ui @@ -28,15 +28,64 @@ "options": { "name": "checks_list", "spacing": 5 - } + }, + "subwidgets": [ + { + "widgettype": "urlwidget", + "options": { + "url": "{{entire_url('./check_model_record.dspy')}}?llmid={{llmid}}" + } + }, + { + "widgettype": "urlwidget", + "options": { + "url": "{{entire_url('./check_date_status.dspy')}}?llmid={{llmid}}" + } + }, + { + "widgettype": "urlwidget", + "options": { + "url": "{{entire_url('./check_upapp.dspy')}}?llmid={{llmid}}" + } + }, + { + "widgettype": "urlwidget", + "options": { + "url": "{{entire_url('./check_uapi.dspy')}}?llmid={{llmid}}" + } + }, + { + "widgettype": "urlwidget", + "options": { + "url": "{{entire_url('./check_uapiio.dspy')}}?llmid={{llmid}}" + } + }, + { + "widgettype": "urlwidget", + "options": { + "url": "{{entire_url('./check_llm_api_map.dspy')}}?llmid={{llmid}}" + } + }, + { + "widgettype": "urlwidget", + "options": { + "url": "{{entire_url('./check_pricing_program.dspy')}}?llmid={{llmid}}" + } + }, + { + "widgettype": "urlwidget", + "options": { + "url": "{{entire_url('./check_pricing_data.dspy')}}?llmid={{llmid}}" + } + } + ] }, { "widgettype": "Button", "options": { "name": "test_btn", - "text": "体验一次", - "i18n": false, - "disabled": true + "label": "体验一次", + "i18n": false } }, { @@ -50,16 +99,15 @@ { "widgettype": "Button", "options": { - "name": "check_charging_btn", - "text": "检查计费", - "i18n": false, - "disabled": true + "name": "charge_btn", + "label": "检查计费", + "i18n": false } }, { "widgettype": "Text", "options": { - "name": "charging_result", + "name": "charge_result", "text": "", "i18n": false } @@ -67,11 +115,22 @@ ], "binds": [ { - "wid": "self", - "event": "load", - "actiontype": "script", - "target": "self", - "script": "var resp = await fetch('{{entire_url('./api/llm_launch_check_api.dspy')}}?llmid={{llmid}}&action=check'); var data = await resp.json(); var statusEl = this.getWidgetByName('check_status'); var listEl = this.getWidgetByName('checks_list'); if(data.error) { statusEl.set_text('错误: ' + data.error); return; } var html = ''; var allOk = true; (data.checks||[]).forEach(function(c){ var icon = c.passed ? '✅' : '❌'; if(!c.passed) allOk = false; html += '
' + icon + ' ' + c.name + ': ' + (c.detail||'') + '
'; }); if(listEl && listEl.dom_element) listEl.dom_element.innerHTML = html; if(allOk) { statusEl.set_text('全部通过 ✅'); var testBtn = this.getWidgetByName('test_btn'); if(testBtn) { testBtn.dom_element.disabled = false; testBtn.dom_element.onclick = async function(){ var r2 = await fetch('{{entire_url('./api/llm_launch_check_api.dspy')}}?llmid={{llmid}}&action=inference'); var d2 = await r2.json(); var tr = this.getWidgetByName('test_result'); if(d2.success) tr.set_text('回复: ' + d2.response); else tr.set_text('失败: ' + d2.error); }.bind(this); } } else { statusEl.set_text('存在问题 ❌ 请检查下方详情'); }" + "wid": "test_btn", + "event": "click", + "actiontype": "urldata", + "target": "test_result", + "options": { + "url": "{{entire_url('./api/llm_launch_check_api.dspy')}}?llmid={{llmid}}&action=inference" + } + }, + { + "wid": "charge_btn", + "event": "click", + "actiontype": "urldata", + "target": "charge_result", + "options": { + "url": "{{entire_url('./api/llm_launch_check_api.dspy')}}?llmid={{llmid}}&action=check_charging&usages={% raw %}{{"prompt_tokens":1000,"completion_tokens":500}}{% endraw %}" + } } ] } diff --git a/wwwroot/llm_launch_check_page.dspy b/wwwroot/llm_launch_check_page.dspy deleted file mode 100644 index bc85671..0000000 --- a/wwwroot/llm_launch_check_page.dspy +++ /dev/null @@ -1,170 +0,0 @@ -# llm_launch_check_page.dspy -# 服务端渲染模型上线检查页面,输出bricks widget JSON -# 所有检查在服务端async执行,结果直接嵌入widget - -try: - llmid = params_kw.get('id', '') - - if not llmid: - return json.dumps({ - "widgettype": "VBox", - "options": {"width": "100%", "height": "100%", "spacing": 10}, - "subwidgets": [ - {"widgettype": "Title", "options": {"text": "模型上线检查", "level": 2}}, - {"widgettype": "Text", "options": {"text": "缺少模型ID参数", "i18n": False}} - ] - }, ensure_ascii=False) - - # --- 服务端执行所有检查 --- - checks = [] - all_passed = True - llm_name = '' - llm_model = '' - - async def add_check(name, passed, detail=''): - global all_passed - checks.append({'name': name, 'passed': passed, 'detail': detail}) - if not passed: - all_passed = False - - async with get_sor_context(request._run_ns, 'llmage') as sor: - recs = await sor.sqlExe( - "select * from llm where id=${llmid}$", {'llmid': llmid}) - if not recs: - await add_check('模型记录', False, f'llm id={llmid} 不存在') - else: - llm = recs[0] - llm_name = llm.name or '' - llm_model = llm.model or '' - await add_check('模型记录', True, f'{llm_name} ({llm_model})') - - date_ok = bool(llm.enabled_date and llm.expired_date) - status_ok = llm.status == 'published' - await add_check('日期与状态', date_ok and status_ok, - f"启用:{llm.enabled_date} 失效:{llm.expired_date} 状态:{llm.status}") - - recs2 = await sor.sqlExe( - "select a.* from llm a, upapp b where a.id=${llmid}$ and a.upappid=b.id", - {'llmid': llmid}) - await add_check('上位系统(upapp)', bool(recs2), - f'upappid={llm.upappid}' + ('' if recs2 else ' 未找到关联')) - - recs3 = await sor.sqlExe(""" - select a.*, e.ioid, e.stream - from llm a - join llm_api_map m on a.id = m.llmid - join upapp c on a.upappid = c.id - join uapi e on c.id = e.upappid and m.apiname = e.name - where a.id=${llmid}$""", {'llmid': llmid}) - if recs3: - await add_check('API映射(uapi)', True, f'ioid={recs3[0].ioid}, stream={recs3[0].stream}') - ioid = recs3[0].ioid - recs4 = await sor.sqlExe( - "select * from uapiio where id=${ioid}$", {'ioid': ioid}) - await add_check('IO定义(uapiio)', bool(recs4), - f'uapiio id={ioid}' if recs4 else f'ioid={ioid} 未找到') - else: - await add_check('API映射(uapi)', False, f'apiname={getattr(llm, "apiname", "N/A")} 在upapp中未找到') - await add_check('IO定义(uapiio)', False, '依赖 uapi 未通过') - - maps = await sor.sqlExe( - "select * from llm_api_map where llmid=${llmid}$", {'llmid': llmid}) - ppids = [] - if maps: - ppids = [m.ppid for m in maps if m.ppid] - await add_check('能力映射(llm_api_map)', True, f'{len(maps)}条记录, {len(ppids)}个有定价') - else: - await add_check('能力映射(llm_api_map)', False, '无映射记录') - - if ppids: - ppid = ppids[0] - async with get_sor_context(request._run_ns, 'pricing') as psor: - pregs = await psor.sqlExe( - "select * from pricing_program where id=${ppid}$", {'ppid': ppid}) - if pregs: - await add_check('定价项目(pricing_program)', True, f'{pregs[0].name} (id={ppid})') - datas = await psor.sqlExe( - "select count(*) as cnt from pricingdata where ppid=${ppid}$", {'ppid': ppid}) - cnt = datas[0].cnt if datas else 0 - await add_check('定价数据(pricingdata)', cnt > 0, - f'{cnt}条记录' if cnt > 0 else '无定价数据') - else: - await add_check('定价项目(pricing_program)', False, f'ppid={ppid} 未找到') - await add_check('定价数据(pricingdata)', False, '依赖定价项目未通过') - else: - await add_check('定价项目(pricing_program)', False, 'llm_api_map中无ppid') - await add_check('定价数据(pricingdata)', False, '无定价项目') - - # --- 构建widget JSON --- - api_url = entire_url('./api/llm_launch_check_api.dspy') - status_text = '全部通过 ✅' if all_passed else '存在问题 ❌ 请检查下方详情' - - # 检查项列表:每项一行Text widget - check_widgets = [] - for c in checks: - icon = '✅' if c['passed'] else '❌' - text = f"{icon} {c['name']}: {c['detail']}" - check_widgets.append({ - "widgettype": "Text", - "options": {"text": text, "i18n": False} - }) - - # 按钮和结果显示区 - subwidgets = [ - {"widgettype": "Title", "options": {"text": f"模型上线检查 — {llm_name}", "level": 2}}, - {"widgettype": "Text", "options": {"text": status_text, "i18n": False}}, - ] + check_widgets - - if all_passed: - subwidgets.append({ - "widgettype": "Button", - "options": {"name": "test_btn", "label": "体验一次", "i18n": False}, - "binds": [{ - "wid": "self", - "event": "click", - "actiontype": "urldata", - "target": "test_result", - "options": { - "url": f"{api_url}?llmid={llmid}&action=inference" - } - }] - }) - subwidgets.append({ - "widgettype": "Text", - "options": {"name": "test_result", "text": "", "i18n": False} - }) - subwidgets.append({ - "widgettype": "Button", - "options": {"name": "charge_btn", "label": "检查计费", "i18n": False}, - "binds": [{ - "wid": "self", - "event": "click", - "actiontype": "urldata", - "target": "charge_result", - "options": { - "url": f"{api_url}?llmid={llmid}&action=check_charging&usages=" + json.dumps({"prompt_tokens": 1000, "completion_tokens": 500}) - } - }] - }) - subwidgets.append({ - "widgettype": "Text", - "options": {"name": "charge_result", "text": "", "i18n": False} - }) - - widget = { - "widgettype": "VBox", - "options": {"width": "100%", "height": "100%", "spacing": 10}, - "subwidgets": subwidgets - } - - return json.dumps(widget, ensure_ascii=False) - -except Exception as e: - return json.dumps({ - "widgettype": "VBox", - "options": {"width": "100%", "height": "100%", "spacing": 10}, - "subwidgets": [ - {"widgettype": "Title", "options": {"text": "模型上线检查", "level": 2}}, - {"widgettype": "Text", "options": {"text": f"执行错误: {format_exc()}", "i18n": False}} - ] - }, ensure_ascii=False)