diff --git a/.gitignore b/.gitignore index c18dd8d..99f8b75 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,8 @@ __pycache__/ +# CRUD definition directories (auto-generated by Sage platform) +wwwroot/llm/ +wwwroot/llm_api_map/ +wwwroot/llmcatelog_list/ +wwwroot/llmusage/ +wwwroot/llmusage_accounting_failed/ +wwwroot/llmusage_history/ diff --git a/scripts/load_path.py b/scripts/load_path.py index 2c50749..69320aa 100644 --- a/scripts/load_path.py +++ b/scripts/load_path.py @@ -13,17 +13,19 @@ import subprocess import os import sys + def find_sage_root(): candidates = [ os.path.expanduser("~/repos/sage"), os.path.expanduser("~/sage"), - os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))), + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), ] for c in candidates: if os.path.isdir(os.path.join(c, "py3")) and os.path.isdir(os.path.join(c, "wwwroot")): return c return None + SAGE_ROOT = find_sage_root() if not SAGE_ROOT: print("ERROR: Cannot find Sage root directory") @@ -38,96 +40,65 @@ MOD = "llmage" # 权限路径定义 — 每次新增页面或API时同步更新 # ============================================================ -# any — 无需登录(仅静态资源和菜单) +# any — 无需登录(菜单、静态资源) PATHS_ANY = [ f"/{MOD}/menu.ui", - f"/{MOD}/imgs", - f"/{MOD}/imgs/kdb.svg", - f"/{MOD}/list_catelog_models.dspy", + f"/{MOD}/imgs/%", ] -# logined — 需要认证的页面和 API +# logined — 所有已登录用户 PATHS_LOGINED = [ - # Module entry + # 模块入口 f"/{MOD}", + f"/{MOD}/index.ui", - # Top-level pages and APIs - f"/{MOD}/llmcost.dspy", - f"/{MOD}/llminference.dspy", + # 顶层 .ui 页面 f"/{MOD}/llm_dialog.ui", f"/{MOD}/show_same_catelog_llm.ui", - f"/{MOD}/model_estimate.dspy", f"/{MOD}/show_llms.ui", - f"/{MOD}/llmcheck.dspy", f"/{MOD}/show_llms_by_providers.ui", - f"/{MOD}/list_paging_catelog_llms.dspy", + f"/{MOD}/failed_accounting.ui", + f"/{MOD}/llmcatelog_list.ui", - # llmusage CRUD directory - f"/{MOD}/llmusage", - f"/{MOD}/llmusage/update_llmusage.dspy", - f"/{MOD}/llmusage/delete_llmusage.dspy", - f"/{MOD}/llmusage/add_llmusage.dspy", - f"/{MOD}/llmusage/index.ui", - f"/{MOD}/llmusage/get_llmusage.dspy", + # 顶层 .dspy(非 api/ 目录) + f"/{MOD}/%.dspy", - # llmcatelog CRUD directory - f"/{MOD}/llmcatelog", - f"/{MOD}/llmcatelog/add_llmcatelog.dspy", - f"/{MOD}/llmcatelog/get_llmcatelog.dspy", - f"/{MOD}/llmcatelog/delete_llmcatelog.dspy", - f"/{MOD}/llmcatelog/index.ui", - f"/{MOD}/llmcatelog/update_llmcatelog.dspy", + # api/ 目录 — 所有 .dspy 通配 + f"/{MOD}/api/%", - # llm CRUD directory - f"/{MOD}/llm", - f"/{MOD}/llm/update_llm.dspy", - f"/{MOD}/llm/delete_llm.dspy", - f"/{MOD}/llm/index.ui", - f"/{MOD}/llm/get_llm.dspy", - f"/{MOD}/llm/add_llm.dspy", + # CRUD 子目录 — 通配(每个子目录下的所有文件) + f"/{MOD}/llm/%", + f"/{MOD}/llmcatelog/%", + f"/{MOD}/llmcatelog_list/%", + f"/{MOD}/llmusage/%", + f"/{MOD}/llmusage_accounting_failed/%", + f"/{MOD}/llmusage_history/%", + f"/{MOD}/llm_api_map/%", - # API endpoints - f"/{MOD}/api/llm_list.dspy", - f"/{MOD}/api/llm_create.dspy", - f"/{MOD}/api/llm_update.dspy", - f"/{MOD}/api/llm_delete.dspy", - f"/{MOD}/api/get_organizations.dspy", - f"/{MOD}/api/get_upapps.dspy", - f"/{MOD}/api/llm_api_map_list.dspy", - f"/{MOD}/api/llm_api_map_create.dspy", - f"/{MOD}/api/llm_api_map_delete.dspy", - f"/{MOD}/api/llm_api_map_options.dspy", - f"/{MOD}/api/uapi_options.dspy", - f"/{MOD}/api/failed_accounting_list.dspy", - f"/{MOD}/api/llmusage_accounting_failed_create.dspy", - f"/{MOD}/api/llmusage_accounting_failed_update.dspy", - f"/{MOD}/api/llmusage_accounting_failed_delete.dspy", - f"/{MOD}/api/llmusage_create.dspy", - f"/{MOD}/api/llmusage_update.dspy", - f"/{MOD}/api/llmusage_delete.dspy", + # v1 API 目录 + f"/{MOD}/v1/%", - # v1 API endpoints - f"/{MOD}/v1/chat/completions", - f"/{MOD}/v1/chat/completions/index.dspy", - f"/{MOD}/v1/models", - f"/{MOD}/v1/models/index.dspy", - f"/{MOD}/v1/tasks", - f"/{MOD}/v1/tasks/index.dspy", - f"/{MOD}/v1/video/generations", - f"/{MOD}/v1/video/generations/index.dspy", - f"/{MOD}/v1/image/generations", - f"/{MOD}/v1/image/generations/index.dspy", + # 其他子目录 + f"/{MOD}/list_llmcatelogs/%", + f"/{MOD}/list_llms/%", + f"/{MOD}/openai/%", + f"/{MOD}/t2t/%", + f"/{MOD}/tasks/%", + f"/{MOD}/upload_asset/%", + f"/{MOD}/video/%", ] # ============================================================ # 执行注册 # ============================================================ + def run_set_perm(role, path): cmd = [PYTHON, SET_PERM_SCRIPT, role, path] result = subprocess.run(cmd, capture_output=True, text=True) return result.returncode == 0 + def register_role_paths(role, paths): count = 0 for p in paths: @@ -136,6 +107,7 @@ def register_role_paths(role, paths): print(f" {role}: {count}/{len(paths)} paths registered") return count + def main(): print(f"Sage root: {SAGE_ROOT}") total = 0 @@ -144,5 +116,6 @@ def main(): print(f"\nDone. Total {total} permission entries registered.") print("NOTE: Restart Sage after permission changes to reload RBAC cache.") + if __name__ == "__main__": main() diff --git a/wwwroot/index.ui b/wwwroot/index.ui index a5afa71..93cb53d 100644 --- a/wwwroot/index.ui +++ b/wwwroot/index.ui @@ -11,15 +11,13 @@ "options": { "width": "100%", "alignItems": "center", - "padding": "16px 24px", - "marginBottom": "0" + "marginBottom": "24px" }, "subwidgets": [ { "widgettype": "Title2", "options": { - "text": "LLM 模型管理", - "fontWeight": "700" + "text": "LLM 模型管理" } }, { @@ -28,139 +26,181 @@ { "widgettype": "Text", "options": { - "text": "模型配置、目录分类与调用监控", - "fontSize": "14px" + "text": "模型类型、模型配置与记账失败记录", + "cfontsize": 1.2 } } ] }, { - "widgettype": "ResponsableBox", + "widgettype": "VScrollPanel", "options": { - "gap": "12px", - "minWidth": "200px", - "padding": "0 24px 16px 24px" + "css": "filler" }, "subwidgets": [ { "widgettype": "VBox", "options": { - "css": "card", - "padding": "16px 24px", - "cursor": "pointer" + "spacing": 24 }, - "binds": [ - { - "wid": "self", - "event": "click", - "actiontype": "urlwidget", - "target": "app.llmage_content", - "options": { - "url": "{{entire_url('/llmage/llmcatelog_list.ui')}}" - }, - "mode": "replace" - } - ], "subwidgets": [ { - "widgettype": "Title5", + "widgettype": "ResponsableBox", "options": { - "text": "模型类型管理", - "fontWeight": "600" - } - }, - { - "widgettype": "Text", - "options": { - "text": "管理模型的分类目录和类型定义", - "fontSize": "12px" - } + "gap": "16px", + "minWidth": "250px" + }, + "subwidgets": [ + { + "widgettype": "VBox", + "options": { + "css": "card", + "cheight": 12, + "cwidth": 23, + "padding": "24px", + "cursor": "pointer", + "borderRadius": "8px" + }, + "binds": [ + { + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.llmage_content", + "options": { + "url": "{{entire_url('/llmage/llmcatelog_list.ui')}}" + }, + "mode": "replace" + } + ], + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "40px", + "height": "40px" + } + }, + { + "widgettype": "Title4", + "options": { + "text": "模型类型管理", + "marginTop": "12px" + } + }, + { + "widgettype": "Text", + "options": { + "text": "管理模型的分类和类型", + "cfontsize": 1.2 + } + } + ] + }, + { + "widgettype": "VBox", + "options": { + "css": "card", + "cheight": 12, + "cwidth": 23, + "padding": "24px", + "cursor": "pointer", + "borderRadius": "8px" + }, + "binds": [ + { + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.llmage_content", + "options": { + "url": "{{entire_url('/llmage/llm')}}" + }, + "mode": "replace" + } + ], + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "40px", + "height": "40px" + } + }, + { + "widgettype": "Title4", + "options": { + "text": "模型管理", + "marginTop": "12px" + } + }, + { + "widgettype": "Text", + "options": { + "text": "管理 LLM 模型配置", + "cfontsize": 1.2 + } + } + ] + }, + { + "widgettype": "VBox", + "options": { + "css": "card", + "cheight": 12, + "cwidth": 23, + "padding": "24px", + "cursor": "pointer", + "borderRadius": "8px" + }, + "binds": [ + { + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.llmage_content", + "options": { + "url": "{{entire_url('/llmage/failed_accounting.ui')}}" + }, + "mode": "replace" + } + ], + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "40px", + "height": "40px" + } + }, + { + "widgettype": "Title4", + "options": { + "text": "记账失败记录", + "marginTop": "12px" + } + }, + { + "widgettype": "Text", + "options": { + "text": "查看和检索记账失败的记录", + "cfontsize": 1.2 + } + } + ] + } + ] } ] }, { "widgettype": "VBox", - "options": { - "css": "card", - "padding": "16px 24px", - "cursor": "pointer" - }, - "binds": [ - { - "wid": "self", - "event": "click", - "actiontype": "urlwidget", - "target": "app.llmage_content", - "options": { - "url": "{{entire_url('/llmage/llm')}}" - }, - "mode": "replace" - } - ], - "subwidgets": [ - { - "widgettype": "Title5", - "options": { - "text": "模型配置", - "fontWeight": "600" - } - }, - { - "widgettype": "Text", - "options": { - "text": "管理 LLM 模型的API配置与供应商映射", - "fontSize": "12px" - } - } - ] - }, - { - "widgettype": "VBox", - "options": { - "css": "card", - "padding": "16px 24px", - "cursor": "pointer" - }, - "binds": [ - { - "wid": "self", - "event": "click", - "actiontype": "urlwidget", - "target": "app.llmage_content", - "options": { - "url": "{{entire_url('/llmage/failed_accounting.ui')}}" - }, - "mode": "replace" - } - ], - "subwidgets": [ - { - "widgettype": "Title5", - "options": { - "text": "记账失败记录", - "fontWeight": "600" - } - }, - { - "widgettype": "Text", - "options": { - "text": "查看和检索调用计费失败记录", - "fontSize": "12px" - } - } - ] + "id": "llmage_content" } ] - }, - { - "widgettype": "VBox", - "id": "llmage_content", - "css": "filler", - "options": { - "width": "100%", - "padding": "0 24px", - "overflowY": "auto" - } } ] } diff --git a/wwwroot/llmcost.dspy b/wwwroot/llmcost.dspy index 87b77aa..45cbdfc 100644 --- a/wwwroot/llmcost.dspy +++ b/wwwroot/llmcost.dspy @@ -1,4 +1,4 @@ -debug(f'{params_kw=}') +debug_params('params_kw', params_kw) ns = params_kw.copy() if not ns.page: ns.page = 1 diff --git a/wwwroot/llminference.dspy b/wwwroot/llminference.dspy index bd32ebf..fcc6a46 100644 --- a/wwwroot/llminference.dspy +++ b/wwwroot/llminference.dspy @@ -1,4 +1,4 @@ -debug(f'{params_kw=}') +debug_params('params_kw', params_kw) if params_kw.off_peak: off_peak = params_kw.off_peak if off_peak in [True, "Y" "y", 1, "1"]: @@ -25,7 +25,7 @@ if kdbids: ret = await rfexe('fusedsearch', request, params) data.update(ret) params_kw.prompt = await tmpl_engine.renders(tmpl, data) - debug(f'{params=}rag return {data}, {params_kw.prompt=}') + debug_params('rag', {'query': params.get('query',''), 'prompt_len': len(str(params_kw.prompt))}) env = DictObject(**globals()) return await inference(request, env=env) diff --git a/wwwroot/model_estimate.dspy b/wwwroot/model_estimate.dspy index 0c04aed..f6a4917 100644 --- a/wwwroot/model_estimate.dspy +++ b/wwwroot/model_estimate.dspy @@ -1,5 +1,5 @@ -debug(f'model_estimate.dspy:{params_kw=}') +debug_params('model_estimate', params_kw) db = DBPools() dbname = get_module_dbname('llmage') async with db.sqlorContext(dbname) as sor: diff --git a/wwwroot/t2t/index.dspy b/wwwroot/t2t/index.dspy index 58ad527..61b7a2a 100644 --- a/wwwroot/t2t/index.dspy +++ b/wwwroot/t2t/index.dspy @@ -1,4 +1,4 @@ -debug(f'{params_kw=}') +debug_params('params_kw', params_kw) lctype='文生文' if params_kw.off_peak: off_peak = params_kw.off_peak diff --git a/wwwroot/v1/chat/completions/index.dspy b/wwwroot/v1/chat/completions/index.dspy index 6850c23..46b0ddc 100644 --- a/wwwroot/v1/chat/completions/index.dspy +++ b/wwwroot/v1/chat/completions/index.dspy @@ -9,7 +9,7 @@ async def gen(): async for l in f(): yield l -debug(f'{params_kw=}') +debug_params('params_kw', params_kw) lctype='文生文' if params_kw.off_peak: off_peak = params_kw.off_peak @@ -25,7 +25,7 @@ if userid is None: return openai_403() if not params_kw.prompt and not params_kw.messages: - debug(f'not params_kw.prompt and not params_kw.messages,{params_kw=}') + debug(f'missing prompt and messages, model={params_kw.model}') d = return_error('Missing need data(prompt or messages)') return json_response(d, status=400) env = request._run_ns diff --git a/wwwroot/v1/image/generations/index.dspy b/wwwroot/v1/image/generations/index.dspy index 067c014..a1d68cb 100644 --- a/wwwroot/v1/image/generations/index.dspy +++ b/wwwroot/v1/image/generations/index.dspy @@ -23,7 +23,7 @@ from appPublic.uniqueID import getID from appPublic.timeUtils import curDateString, timestampstr from sqlor.dbpools import get_sor_context -debug(f'{params_kw=}') +debug_params('params_kw', params_kw) userid = await get_user() userorgid = await get_userorgid() diff --git a/wwwroot/v1/video/generations/index.dspy b/wwwroot/v1/video/generations/index.dspy index 29a94c6..e81ff42 100644 --- a/wwwroot/v1/video/generations/index.dspy +++ b/wwwroot/v1/video/generations/index.dspy @@ -31,7 +31,7 @@ from appPublic.uniqueID import getID from appPublic.timeUtils import curDateString, timestampstr from sqlor.dbpools import get_sor_context -debug(f'{params_kw=}') +debug_params('params_kw', params_kw) userid = await get_user() userorgid = await get_userorgid() diff --git a/wwwroot/vidu_inference.dspy b/wwwroot/vidu_inference.dspy index cf0f6da..28d0a73 100644 --- a/wwwroot/vidu_inference.dspy +++ b/wwwroot/vidu_inference.dspy @@ -1,4 +1,4 @@ -debug(f'{params_kw=}') +debug_params('params_kw', params_kw) if params_kw.off_peak: off_peak = params_kw.off_peak if off_peak in [True, "Y" "y", 1, "1"]: