# llm_wallet_todo_popup.dspy — 钱包余额不足待办详情弹窗(2026-09-11 用户定夺) # 平台待办 source='llm_wallet' 的「查看内容并处理」入口。 # 入参: id=llm_wallet_ 或 account_id=<账号id> # 展示账号钱包状态 + 充值表单(金额/备注)→ POST llm_account_recharge.dspy; # 充值到账余额回升 → 待办由状态派生自动消失(provider 不再返回)。 import json as _json uid = await get_user() if not uid: return {"widgettype": "Message", "options": {"title": "未登录", "message": "请先登录后查看待办"}} pk = params_kw or {} acc_id = str(pk.get('account_id') or '').strip() if not acc_id: raw = str(pk.get('id') or '').strip() if raw.startswith('llm_wallet_'): acc_id = raw[len('llm_wallet_'):] else: acc_id = raw if not acc_id: return {"widgettype": "Message", "options": {"title": "打开失败", "message": "缺少账号 id"}} user_org = str(await get_userorgid() or '0') dbname = get_module_dbname('pipeline-llm') async with DBPools().sqlorContext(dbname) as sor: recs = await sor.sqlExe( "SELECT a.id, a.name, a.balance, a.total_recharge, a.status, a.org_id, " "v.name AS vendor_name, COALESCE(NULLIF(a.org_id,''), v.org_id, '0') AS owner_org " "FROM llm_account a LEFT JOIN llm_vendor v ON v.id=a.vendor_id " "WHERE a.id=${i}$ LIMIT 1", {"i": acc_id}) await sor.sqlExe("COMMIT", {}) trecs = await sor.sqlExe( "SELECT params_value FROM params WHERE params_name='llm_wallet_balance_threshold' LIMIT 1", {}) await sor.sqlExe("COMMIT", {}) if not recs: return {"widgettype": "Message", "options": {"title": "打开失败", "message": "账号不存在或已删除"}} row = recs[0] owner_org = str(row['owner_org'] or '0') # 机构隔离:平台业主('0')可看全部;否则只看本属主机构的账号 if user_org != '0' and owner_org not in ('', user_org): return {"widgettype": "Message", "options": {"title": "无权访问", "message": "该账号属于其他机构"}} balance = float(row['balance'] or 0) threshold = 5.0 try: if trecs and str(trecs[0]['params_value'] or '').strip(): threshold = float(str(trecs[0]['params_value']).strip()) except Exception: pass acc_name = str(row['name'] or acc_id) vendor_name = str(row['vendor_name'] or '') head_md = ['## 供应商账号钱包', '', '**账号**:' + (('【%s】' % vendor_name) if vendor_name else '') + acc_name, '**当前余额**:%.4f 元 | **门禁阈值**:%.2f 元 | **累计充值**:%.4f 元' % (balance, threshold, float(row['total_recharge'] or 0)), '**账号状态**:' + str(row['status'] or ''), ''] if balance < threshold: head_md += ['> ⚠️ 余额低于门禁阈值——该账号已从模型调用轮询集剔除,' '调用其下模型将顺延到其他账号;全部账号不足时调用报可行动错误。', '> 充值到账后待办自动消失、账号自动恢复轮询。', ''] else: head_md += ['> ✅ 余额已恢复到阈值以上,账号在轮询集中正常可用,本待办即将自动关闭。', ''] recharge_url = entire_url('/pipeline-llm/api/llm_account_recharge.dspy') def _read_input_js(wid): return ("var cw=bricks.getWidgetById(" + _json.dumps(wid) + ",bricks.app);var cv='';" "if(cw){cv=(typeof cw.resultValue==='function')?cw.resultValue():" "((cw.dom_element&&cw.dom_element.value)||'');}" "cv=(cv===null||cv===undefined)?'':String(cv).trim();") _tail = ("var pw=bricks.getWidgetById('llm_wallet_todo_pw',bricks.app);" "if(pw&&pw.destroy){pw.destroy();}" "if(window.refreshTodo){window.refreshTodo();}" "if(d&&d.success){var mo=new bricks.Message({title:'充值成功',message:d.message||'钱包已充值'});mo.open();}" "else{var mf=new bricks.Message({title:'充值失败',message:(d&&(d.message||d.error))||'充值失败'});mf.open();}") do_js = (_read_input_js('llm_wallet_amt') + "var amt=parseFloat(cv);" + "if(isNaN(amt)||amt<=0){new bricks.Message({title:'请填写金额',message:'充值金额必须是大于 0 的数字'}).open();return;}" + _read_input_js('llm_wallet_note') + "var r=await fetch(" + _json.dumps(recharge_url) + ",{method:'POST'," "headers:{'Content-Type':'application/json'}," "body:JSON.stringify({account_id:" + _json.dumps(acc_id) + ",amount:amt,note:cv})});" "var d=await r.json();" + _tail) sub = [ {"widgettype": "MdWidget", "options": {"mdtext": '\n'.join(head_md), "width": "100%"}}, {"widgettype": "HBox", "options": {"width": "100%", "gap": "8px", "alignItems": "center"}, "subwidgets": [ {"widgettype": "Text", "options": {"text": "充值金额(元):", "cfontsize": 0.9, "color": "#334155"}}, {"widgettype": "UiText", "id": "llm_wallet_amt", "options": {"name": "llm_wallet_amt", "placeholder": "如 100.00", "width": "160px"}}, {"widgettype": "UiText", "id": "llm_wallet_note", "options": {"name": "llm_wallet_note", "placeholder": "备注(可选,如银行流水号)", "flex": "1 1 auto"}}, {"widgettype": "Button", "options": {"label": "💰 确认充值", "css": "primary small"}, "binds": [{"wid": "self", "event": "click", "actiontype": "script", "target": "self", "script": do_js}]}]}] return { "widgettype": "PopupWindow", "id": "llm_wallet_todo_pw", "options": {"title": "钱包充值 — " + acc_name, "width": "52%", "height": "46%", "auto_open": True, "resizable": True}, "subwidgets": [{ "widgettype": "VScrollPanel", "options": {"css": "filler", "width": "100%", "height": "100%", "padding": "14px"}, "subwidgets": sub}] }