From 1b74b5bf10eba182f18ac91bc27f507f7e273275 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Sun, 17 May 2026 00:46:31 +0800 Subject: [PATCH] feat: add llm catalog (type) management module - Add CRUD API for llmcatelog (list/create/update/delete) - Add llmcatelog.ui as main entry for catalog management - Add llmcatelog_list.ui as DataViewer interface - Add index.ui as module navigation page - Update json/llmcatelog.json with editable section --- json/llmcatelog.json | 15 ++- wwwroot/api/llmcatelog_create.dspy | 29 +++++ wwwroot/api/llmcatelog_delete.dspy | 33 ++++++ wwwroot/api/llmcatelog_list.dspy | 28 +++++ wwwroot/api/llmcatelog_update.dspy | 31 +++++ wwwroot/index.ui | 177 +++++++++++++++++++++++++++++ wwwroot/llmcatelog.ui | 16 +++ wwwroot/llmcatelog_list.ui | 16 +++ 8 files changed, 339 insertions(+), 6 deletions(-) create mode 100644 wwwroot/api/llmcatelog_create.dspy create mode 100644 wwwroot/api/llmcatelog_delete.dspy create mode 100644 wwwroot/api/llmcatelog_list.dspy create mode 100644 wwwroot/api/llmcatelog_update.dspy create mode 100644 wwwroot/index.ui create mode 100644 wwwroot/llmcatelog.ui create mode 100644 wwwroot/llmcatelog_list.ui diff --git a/json/llmcatelog.json b/json/llmcatelog.json index 2104fb5..8964728 100644 --- a/json/llmcatelog.json +++ b/json/llmcatelog.json @@ -1,15 +1,18 @@ { "tblname": "llmcatelog", - "title":"模型类目", + "alias": "llmcatelog_list", + "title": "模型类型管理", "params": { - "sortby":"name", + "sortby": ["name"], + "editable": { + "new_data_url": "{{entire_url('../api/llmcatelog_create.dspy')}}", + "update_data_url": "{{entire_url('../api/llmcatelog_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/llmcatelog_delete.dspy')}}" + }, "browserfields": { "exclouded": ["id"], "alters": {} }, - "editexclouded": [ - "id" - ], - "record_toolbar": null + "editexclouded": ["id"] } } diff --git a/wwwroot/api/llmcatelog_create.dspy b/wwwroot/api/llmcatelog_create.dspy new file mode 100644 index 0000000..2d01e1f --- /dev/null +++ b/wwwroot/api/llmcatelog_create.dspy @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +import json + +result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid', 'type': 'error'}} + +try: + dbname = get_module_dbname('llmage') + name = params_kw.get('name', '').strip() + description = params_kw.get('description', '').strip() + + if not name: + result['options'] = {'title': '错误', 'message': '类型名不能为空', 'type': 'error'} + else: + async with DBPools().sqlorContext(dbname) as sor: + # 检查名称是否已存在 + check_sql = "select id from llmcatelog where name=${name}$" + exists = await sor.sqlExe(check_sql, {'name': name}) + if exists: + result['options'] = {'title': '提示', 'message': f'类型名「{name}」已存在', 'type': 'warning'} + else: + new_id = getID() + insert_sql = "insert into llmcatelog (id, name, description) values (${id}$, ${name}$, ${description}$)" + await sor.sqlExe(insert_sql, {'id': new_id, 'name': name, 'description': description}) + result = {'widgettype': 'Message', 'options': {'title': '成功', 'message': f'类型「{name}」已创建', 'type': 'info'}} + +except Exception as e: + result['options'] = {'title': '错误', 'message': str(e), 'type': 'error'} + +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/llmcatelog_delete.dspy b/wwwroot/api/llmcatelog_delete.dspy new file mode 100644 index 0000000..f167612 --- /dev/null +++ b/wwwroot/api/llmcatelog_delete.dspy @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +import json + +result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid', 'type': 'error'}} + +try: + dbname = get_module_dbname('llmage') + id = params_kw.get('id', '').strip() + + if not id: + result['options'] = {'title': '错误', 'message': '记录ID不能为空', 'type': 'error'} + else: + async with DBPools().sqlorContext(dbname) as sor: + # 检查是否有模型关联此类型 + check_sql = "select count(*) as cnt from llm_catalog_rel where llmcatelogid=${id}$" + rel_count = await sor.sqlExe(check_sql, {'id': id}) + cnt = rel_count[0]['cnt'] if rel_count else 0 + if cnt > 0: + result['options'] = {'title': '提示', 'message': f'该类型下还有 {cnt} 个模型关联,无法删除', 'type': 'warning'} + else: + # 获取类型名用于提示 + name_sql = "select name from llmcatelog where id=${id}$" + name_rows = await sor.sqlExe(name_sql, {'id': id}) + type_name = name_rows[0]['name'] if name_rows else id + + delete_sql = "delete from llmcatelog where id=${id}$" + await sor.sqlExe(delete_sql, {'id': id}) + result = {'widgettype': 'Message', 'options': {'title': '成功', 'message': f'类型「{type_name}」已删除', 'type': 'info'}} + +except Exception as e: + result['options'] = {'title': '错误', 'message': str(e), 'type': 'error'} + +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/llmcatelog_list.dspy b/wwwroot/api/llmcatelog_list.dspy new file mode 100644 index 0000000..8bed755 --- /dev/null +++ b/wwwroot/api/llmcatelog_list.dspy @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +import json + +result = {'success': False, 'rows': [], 'total': 0} + +try: + dbname = get_module_dbname('llmage') + page = int(params_kw.get('page', 1)) + rows_per_page = int(params_kw.get('rows', 20)) + offset = (page - 1) * rows_per_page + + async with DBPools().sqlorContext(dbname) as sor: + # 获取总数 + count_sql = "select count(*) as cnt from llmcatelog" + count_rows = await sor.sqlExe(count_sql, {}) + total = count_rows[0]['cnt'] if count_rows else 0 + + # 获取分页数据 + data_sql = "select id, name, description from llmcatelog order by name limit ${limit}$ offset ${offset}$" + rows = await sor.sqlExe(data_sql, {'limit': rows_per_page, 'offset': offset}) + result['rows'] = [dict(r) for r in (rows or [])] + result['total'] = total + result['success'] = True + +except Exception as e: + result['error'] = str(e) + +return json.dumps(result, ensure_ascii=False, default=str) diff --git a/wwwroot/api/llmcatelog_update.dspy b/wwwroot/api/llmcatelog_update.dspy new file mode 100644 index 0000000..487c2c0 --- /dev/null +++ b/wwwroot/api/llmcatelog_update.dspy @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +import json + +result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid', 'type': 'error'}} + +try: + dbname = get_module_dbname('llmage') + id = params_kw.get('id', '').strip() + name = params_kw.get('name', '').strip() + description = params_kw.get('description', '').strip() + + if not id: + result['options'] = {'title': '错误', 'message': '记录ID不能为空', 'type': 'error'} + elif not name: + result['options'] = {'title': '错误', 'message': '类型名不能为空', 'type': 'error'} + else: + async with DBPools().sqlorContext(dbname) as sor: + # 检查名称是否已被其他记录使用 + check_sql = "select id from llmcatelog where name=${name}$ and id != ${id}$" + exists = await sor.sqlExe(check_sql, {'name': name, 'id': id}) + if exists: + result['options'] = {'title': '提示', 'message': f'类型名「{name}」已被其他记录使用', 'type': 'warning'} + else: + update_sql = "update llmcatelog set name=${name}$, description=${description}$ where id=${id}$" + await sor.sqlExe(update_sql, {'name': name, 'description': description, 'id': id}) + result = {'widgettype': 'Message', 'options': {'title': '成功', 'message': f'类型「{name}」已更新', 'type': 'info'}} + +except Exception as e: + result['options'] = {'title': '错误', 'message': str(e), 'type': 'error'} + +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/index.ui b/wwwroot/index.ui new file mode 100644 index 0000000..d00ca1b --- /dev/null +++ b/wwwroot/index.ui @@ -0,0 +1,177 @@ +{ + "widgettype": "VBox", + "options": { + "width": "100%", + "height": "100%", + "padding": "20px", + "spacing": 16 + }, + "subwidgets": [ + { + "widgettype": "Title2", + "options": { + "text": "LLM 模型管理", + "halign": "left" + } + }, + { + "widgettype": "ResponsableBox", + "options": { + "gap": "16px", + "minWidth": "250px" + }, + "subwidgets": [ + { + "widgettype": "VBox", + "options": { + "backgroundColor": "#1e3a5f", + "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": "模型类型管理", + "color": "#ffffff", + "marginTop": "12px" + } + }, + { + "widgettype": "Text", + "options": { + "text": "管理模型的分类和类型", + "color": "#90caf9", + "fontSize": "14px" + } + } + ] + }, + { + "widgettype": "VBox", + "options": { + "backgroundColor": "#1e3a5f", + "padding": "24px", + "cursor": "pointer", + "borderRadius": "8px" + }, + "binds": [ + { + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.llmage_content", + "options": { + "url": "{{entire_url('/llmage/llm_catalog_rel_manage.ui')}}" + }, + "mode": "replace" + } + ], + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "40px", + "height": "40px" + } + }, + { + "widgettype": "Title4", + "options": { + "text": "模型-类型关联", + "color": "#ffffff", + "marginTop": "12px" + } + }, + { + "widgettype": "Text", + "options": { + "text": "管理模型与类型的多对多关系", + "color": "#ce93d8", + "fontSize": "14px" + } + } + ] + }, + { + "widgettype": "VBox", + "options": { + "backgroundColor": "#1e3a5f", + "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": "模型管理", + "color": "#ffffff", + "marginTop": "12px" + } + }, + { + "widgettype": "Text", + "options": { + "text": "管理 LLM 模型配置", + "color": "#4caf50", + "fontSize": "14px" + } + } + ] + } + ] + }, + { + "widgettype": "VBox", + "id": "llmage_content", + "options": { + "width": "100%", + "flex": "1", + "marginTop": "20px" + } + } + ] +} diff --git a/wwwroot/llmcatelog.ui b/wwwroot/llmcatelog.ui new file mode 100644 index 0000000..c929319 --- /dev/null +++ b/wwwroot/llmcatelog.ui @@ -0,0 +1,16 @@ +{ + "widgettype": "DataViewer", + "options": { + "url": "{{entire_url('/llmage/api/llmcatelog_list.dspy')}}", + "title": "模型类型管理", + "pageSize": 20, + "new_data_url": "{{entire_url('/llmage/api/llmcatelog_create.dspy')}}", + "update_data_url": "{{entire_url('/llmage/api/llmcatelog_update.dspy')}}", + "delete_data_url": "{{entire_url('/llmage/api/llmcatelog_delete.dspy')}}", + "fields": [ + {"name": "id", "title": "ID", "hidden": true}, + {"name": "name", "title": "类型名", "width": "200px", "editable": true}, + {"name": "description", "title": "类型说明", "width": "50%", "editable": true} + ] + } +} diff --git a/wwwroot/llmcatelog_list.ui b/wwwroot/llmcatelog_list.ui new file mode 100644 index 0000000..c929319 --- /dev/null +++ b/wwwroot/llmcatelog_list.ui @@ -0,0 +1,16 @@ +{ + "widgettype": "DataViewer", + "options": { + "url": "{{entire_url('/llmage/api/llmcatelog_list.dspy')}}", + "title": "模型类型管理", + "pageSize": 20, + "new_data_url": "{{entire_url('/llmage/api/llmcatelog_create.dspy')}}", + "update_data_url": "{{entire_url('/llmage/api/llmcatelog_update.dspy')}}", + "delete_data_url": "{{entire_url('/llmage/api/llmcatelog_delete.dspy')}}", + "fields": [ + {"name": "id", "title": "ID", "hidden": true}, + {"name": "name", "title": "类型名", "width": "200px", "editable": true}, + {"name": "description", "title": "类型说明", "width": "50%", "editable": true} + ] + } +}