feat: Tree widget with editable CRUD (add/delete/rename)
This commit is contained in:
parent
186e3659fb
commit
4086010b37
7
wwwroot/knowledge_bases_list/delete_tree_item.dspy
Normal file
7
wwwroot/knowledge_bases_list/delete_tree_item.dspy
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
ns = params_kw.copy()
|
||||||
|
db = DBPools()
|
||||||
|
dbname = get_module_dbname('rag')
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
await sor.sqlExe("DELETE FROM document_chunks WHERE id=${id}$", {"id": ns.get("id","")})
|
||||||
|
return {"status": "SUCCEEDED"}
|
||||||
|
return {"error": "failed"}
|
||||||
@ -1,138 +1,60 @@
|
|||||||
{
|
{
|
||||||
"widgettype": "VBox",
|
"widgettype": "VBox",
|
||||||
"options": {
|
"options": {"cheight": 40, "width": "100%", "padding": "0", "spacing": 0},
|
||||||
"cheight": 40,
|
|
||||||
"width": "100%",
|
|
||||||
"padding": "0",
|
|
||||||
"spacing": 0
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
"subwidgets": [
|
||||||
{
|
{
|
||||||
"widgettype": "HBox",
|
"widgettype": "HBox",
|
||||||
"options": {
|
"options": {"width": "100%", "bgcolor": "#f8f9fa", "padding": "10px 16px", "alignItems": "center", "borderBottom": "1px solid #e0e0e0"},
|
||||||
"width": "100%",
|
|
||||||
"bgcolor": "#f8f9fa",
|
|
||||||
"padding": "10px 16px",
|
|
||||||
"alignItems": "center",
|
|
||||||
"borderBottom": "1px solid #e0e0e0"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
"subwidgets": [
|
||||||
{"widgettype": "Text", "options": {"text": "← 知识库列表", "css": "clickable", "cfontsize": 14, "color": "#4a90d9"}, "binds": [
|
{"widgettype": "Text", "options": {"text": "← 知识库列表", "css": "clickable", "cfontsize": 14, "color": "#4a90d9"}, "binds": [
|
||||||
{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.rag_main_content", "mode": "replace", "options": {"url": "{{entire_url('/knowledge_bases_list/index.ui')}}"}}
|
{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.rag_main_content", "mode": "replace",
|
||||||
|
"options": {"url": "{{entire_url('/knowledge_bases_list/index.ui')}}"}}
|
||||||
]},
|
]},
|
||||||
{"widgettype": "Text", "options": {"text": " / ", "cfontsize": 14, "color": "#ccc"}},
|
{"widgettype": "Text", "options": {"text": " / ", "cfontsize": 14, "color": "#ccc"}},
|
||||||
{"widgettype": "Text", "options": {"text": "📚 产品文档", "cfontsize": 16, "fontWeight": "bold", "color": "#333"}},
|
{"widgettype": "Text", "options": {"text": "📚 产品文档", "cfontsize": 16, "fontWeight": "bold", "color": "#333"}},
|
||||||
{"widgettype": "Text", "options": {"text": "", "css": "filler"}},
|
{"widgettype": "Text", "options": {"text": "", "css": "filler"}}
|
||||||
{"widgettype": "Text", "options": {"text": "📄 12文档 · 📁 3目录 · 💾 256MB", "cfontsize": 12, "color": "#888"}}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"widgettype": "HBox",
|
"widgettype": "HBox",
|
||||||
"options": {
|
"options": {"css": "filler", "width": "100%", "spacing": 0},
|
||||||
"css": "filler",
|
|
||||||
"width": "100%",
|
|
||||||
"spacing": 0
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
"subwidgets": [
|
||||||
{
|
{
|
||||||
"widgettype": "VBox",
|
"widgettype": "VBox",
|
||||||
"options": {
|
"options": {"cwidth": 16, "bgcolor": "#fafafa", "borderRight": "1px solid #e0e0e0", "spacing": 0},
|
||||||
"cwidth": 15,
|
|
||||||
"bgcolor": "#fafafa",
|
|
||||||
"borderRight": "1px solid #e0e0e0",
|
|
||||||
"spacing": 0
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
"subwidgets": [
|
||||||
{
|
{
|
||||||
"widgettype": "HBox",
|
"widgettype": "HBox",
|
||||||
"options": {
|
"options": {"padding": "10px 12px", "bgcolor": "#f0f0f0", "borderBottom": "1px solid #e0e0e0"},
|
||||||
"padding": "10px 12px",
|
|
||||||
"bgcolor": "#f0f0f0",
|
|
||||||
"borderBottom": "1px solid #e0e0e0",
|
|
||||||
"justifyContent": "space-between",
|
|
||||||
"alignItems": "center"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
"subwidgets": [
|
||||||
{"widgettype": "Text", "options": {"text": "📁 目录结构", "cfontsize": 13, "fontWeight": "bold", "color": "#666"}},
|
{"widgettype": "Text", "options": {"text": "📁 目录结构", "cfontsize": 13, "fontWeight": "bold", "color": "#666"}}
|
||||||
{"widgettype": "Text", "options": {"text": "+ 新建", "cfontsize": 12, "color": "#4a90d9", "css": "clickable"}}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"widgettype": "Tree",
|
"widgettype": "Tree",
|
||||||
"options": {
|
"options": {
|
||||||
"items": [
|
"title": "目录",
|
||||||
{
|
"parentField": "parentid",
|
||||||
"label": "技术文档",
|
"idField": "id",
|
||||||
"id": "folder_tech",
|
"textField": "label",
|
||||||
"icon": "📂",
|
"dataurl": "{{entire_url('./get_tree_data.dspy')}}?kb_id=kb1",
|
||||||
"children": [
|
"editable": {
|
||||||
{"label": "API参考.pdf", "id": "file_api", "icon": "📄"},
|
"fields": [
|
||||||
{"label": "部署指南.md", "id": "file_deploy", "icon": "📄"},
|
{"name": "name", "title": "名称", "type": "str", "length": 255, "uitype": "str", "label": "名称"}
|
||||||
{"label": "接口规范", "id": "folder_api_spec", "icon": "📂", "children": [
|
],
|
||||||
{"label": "REST API.md", "id": "file_rest", "icon": "📄"},
|
"add_url": "{{entire_url('./new_tree_item.dspy')}}?kb_id=kb1",
|
||||||
{"label": "WebSocket.md", "id": "file_ws", "icon": "📄"}
|
"update_url": "{{entire_url('./update_tree_item.dspy')}}",
|
||||||
]}
|
"delete_url": "{{entire_url('./delete_tree_item.dspy')}}"
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "产品介绍",
|
|
||||||
"id": "folder_product",
|
|
||||||
"icon": "📂"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "培训材料",
|
|
||||||
"id": "folder_training",
|
|
||||||
"icon": "📂"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "selected",
|
|
||||||
"actiontype": "urlwidget",
|
|
||||||
"target": "app.detail_content",
|
|
||||||
"mode": "replace",
|
|
||||||
"options": {"url": "{{entire_url('/knowledge_bases_list/folder_content.ui')}}", "params": {"folder_id": "${item.id}", "folder_label": "${item.label}"}}
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"widgettype": "VBox",
|
"widgettype": "VBox",
|
||||||
"options": {
|
"options": {"id": "detail_content", "css": "filler", "padding": "16px", "spacing": "12px"},
|
||||||
"id": "detail_content",
|
|
||||||
"css": "filler",
|
|
||||||
"padding": "16px",
|
|
||||||
"spacing": "12px"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
"subwidgets": [
|
||||||
{
|
{"widgettype": "Text", "options": {"text": "📂 请选择左侧目录查看文件", "cfontsize": 14, "color": "#888"}}
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"width": "100%",
|
|
||||||
"cheight": 6,
|
|
||||||
"bgcolor": "#f8f9fa",
|
|
||||||
"padding": "16px",
|
|
||||||
"css": "card",
|
|
||||||
"border": "2px dashed #d0d0d0",
|
|
||||||
"alignItems": "center",
|
|
||||||
"justifyContent": "center"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "📂 拖拽文件到此处上传", "cfontsize": 15, "color": "#888"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "选中左侧目录后上传到该目录 · 支持 TXT/PDF/JPG/PNG/MP3/WAV/MP4", "cfontsize": 11, "color": "#aaa"}}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"text": "📂 全部分文件 · 请选择左侧目录查看具体内容",
|
|
||||||
"cfontsize": 13,
|
|
||||||
"color": "#aaa"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,63 +0,0 @@
|
|||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"width": "100%",
|
|
||||||
"spacing": "12px"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "HBox",
|
|
||||||
"options": {
|
|
||||||
"width": "100%",
|
|
||||||
"justifyContent": "space-between",
|
|
||||||
"alignItems": "center"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "HBox",
|
|
||||||
"options": {"spacing": "4px", "alignItems": "center"},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "📂", "cfontsize": 16}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "{{params_kw.folder_label or '全部文件'}}", "cfontsize": 16, "fontWeight": "bold", "color": "#333"}}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "HBox",
|
|
||||||
"options": {"spacing": "8px"},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "Button",
|
|
||||||
"options": {"label": "📂 新建子目录", "bgcolor": "#e8e8e8", "color": "#666"},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "tap",
|
|
||||||
"actiontype": "urlwidget",
|
|
||||||
"target": "app.detail_content",
|
|
||||||
"mode": "replace",
|
|
||||||
"options": {
|
|
||||||
"url": "{{entire_url('/knowledge_bases_list/new_dir_form.ui')}}",
|
|
||||||
"params": {
|
|
||||||
"kb_id": "{{params_kw.kb_id or ''}}",
|
|
||||||
"folder_id": "{{params_kw.folder_id or ''}}",
|
|
||||||
"folder_label": "{{params_kw.folder_label or ''}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"text": "当前目录暂无内容",
|
|
||||||
"color": "#aaa",
|
|
||||||
"cfontsize": 13,
|
|
||||||
"halign": "center"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
17
wwwroot/knowledge_bases_list/get_tree_data.dspy
Normal file
17
wwwroot/knowledge_bases_list/get_tree_data.dspy
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
ns = params_kw.copy()
|
||||||
|
id = ns.get('id')
|
||||||
|
kb_id = ns.get('kb_id', '')
|
||||||
|
db = DBPools()
|
||||||
|
dbname = get_module_dbname('rag')
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
if not id:
|
||||||
|
recs = await sor.sqlExe(
|
||||||
|
"SELECT id, '' as parentid, content as label FROM document_chunks WHERE kb_id=${kb_id}$ AND chunk_type='directory' ORDER BY content",
|
||||||
|
{"kb_id": kb_id})
|
||||||
|
return [dict(r) for r in recs]
|
||||||
|
else:
|
||||||
|
recs = await sor.sqlExe(
|
||||||
|
"SELECT id, '' as parentid, content as label FROM document_chunks WHERE kb_id=${kb_id}$ AND chunk_type='directory' AND parent_id=${id}$ ORDER BY content",
|
||||||
|
{"kb_id": kb_id, "id": id})
|
||||||
|
return [dict(r) for r in recs]
|
||||||
|
return []
|
||||||
@ -1,55 +0,0 @@
|
|||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {"spacing": "12px", "padding": "16px"},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "新建目录", "cfontsize": 18, "fontWeight": "bold"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "", "cheight": 0.5}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "目录名称"}},
|
|
||||||
{
|
|
||||||
"widgettype": "Input",
|
|
||||||
"options": {"id": "dir_name_input", "width": "100%"}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Button",
|
|
||||||
"options": {"label": "创建", "bgcolor": "#4a90d9", "color": "#fff"},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "tap",
|
|
||||||
"actiontype": "urlwidget",
|
|
||||||
"target": "app.detail_content",
|
|
||||||
"mode": "replace",
|
|
||||||
"options": {
|
|
||||||
"url": "{{entire_url('/knowledge_bases_list/tree_crud.dspy')}}",
|
|
||||||
"params": {
|
|
||||||
"action": "create_dir",
|
|
||||||
"kb_id": "{{params_kw.kb_id or ''}}",
|
|
||||||
"dir_name": "${app.dir_name_input.text}",
|
|
||||||
"parent_id": "{{params_kw.folder_id or ''}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Button",
|
|
||||||
"options": {"label": "取消", "bgcolor": "#e0e0e0", "color": "#666"},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "tap",
|
|
||||||
"actiontype": "urlwidget",
|
|
||||||
"target": "app.detail_content",
|
|
||||||
"mode": "replace",
|
|
||||||
"options": {
|
|
||||||
"url": "{{entire_url('/knowledge_bases_list/folder_content.ui')}}",
|
|
||||||
"params": {
|
|
||||||
"folder_id": "{{params_kw.folder_id or ''}}",
|
|
||||||
"folder_label": "{{params_kw.folder_label or ''}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
12
wwwroot/knowledge_bases_list/new_tree_item.dspy
Normal file
12
wwwroot/knowledge_bases_list/new_tree_item.dspy
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
ns = params_kw.copy()
|
||||||
|
db = DBPools()
|
||||||
|
dbname = get_module_dbname('rag')
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
import uuid
|
||||||
|
dir_id = uuid.uuid4().hex[:16]
|
||||||
|
await sor.sqlExe(
|
||||||
|
"INSERT INTO document_chunks (id, doc_id, kb_id, chunk_index, chunk_type, content, description, created_at) "
|
||||||
|
"VALUES (${id}$, '', ${kb_id}$, 0, 'directory', ${name}$, ${parentid}$, NOW())",
|
||||||
|
{"id": dir_id, "kb_id": ns.get("kb_id",""), "name": ns.get("name",""), "parentid": ns.get("parentid","")})
|
||||||
|
return {"status": "SUCCEEDED", "id": dir_id}
|
||||||
|
return {"error": "failed"}
|
||||||
8
wwwroot/knowledge_bases_list/update_tree_item.dspy
Normal file
8
wwwroot/knowledge_bases_list/update_tree_item.dspy
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
ns = params_kw.copy()
|
||||||
|
db = DBPools()
|
||||||
|
dbname = get_module_dbname('rag')
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
await sor.sqlExe("UPDATE document_chunks SET content=${name}$ WHERE id=${id}$",
|
||||||
|
{"name": ns.get("name",""), "id": ns.get("id","")})
|
||||||
|
return {"status": "SUCCEEDED"}
|
||||||
|
return {"error": "failed"}
|
||||||
Loading…
x
Reference in New Issue
Block a user