feat: detail.ui face/voiceprint media card popups (DynamicColumn, image/audio playback) + tag buttons on cards and file list rows (tag_form.dspy -> add_tag.dspy, refresh media panel)
This commit is contained in:
parent
6878b58c95
commit
3cb591ed92
@ -62,6 +62,68 @@
|
||||
"text": "",
|
||||
"css": "filler"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {
|
||||
"label": "👤 人脸",
|
||||
"cfontsize": 12,
|
||||
"bgcolor": "#e3f2fd",
|
||||
"color": "#1565c0",
|
||||
"padding": "4px 12px",
|
||||
"borderRadius": "14px",
|
||||
"marginLeft": "8px"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "PopupWindow",
|
||||
"popup_options": {
|
||||
"title": "👤 人脸库",
|
||||
"width": "60%",
|
||||
"height": "80%",
|
||||
"movable": true,
|
||||
"resizable": true,
|
||||
"modal": true
|
||||
},
|
||||
"options": {
|
||||
"url": "{{entire_url('./media_cards.dspy')}}?kb_id={{params_kw.kb_id}}&kind=face"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {
|
||||
"label": "🎤 声纹",
|
||||
"cfontsize": 12,
|
||||
"bgcolor": "#f3e5f5",
|
||||
"color": "#7b1fa2",
|
||||
"padding": "4px 12px",
|
||||
"borderRadius": "14px",
|
||||
"marginLeft": "8px"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "PopupWindow",
|
||||
"popup_options": {
|
||||
"title": "🎤 声纹库",
|
||||
"width": "60%",
|
||||
"height": "80%",
|
||||
"movable": true,
|
||||
"resizable": true,
|
||||
"modal": true
|
||||
},
|
||||
"options": {
|
||||
"url": "{{entire_url('./media_cards.dspy')}}?kb_id={{params_kw.kb_id}}&kind=voice"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -84,25 +146,6 @@
|
||||
"flexShrink": "0"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"padding": "10px 12px",
|
||||
"bgcolor": "#f0f0f0",
|
||||
"borderBottom": "1px solid #e0e0e0"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "📁 目录结构",
|
||||
"cfontsize": 13,
|
||||
"fontWeight": "bold",
|
||||
"color": "#666"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "Tree",
|
||||
"id": "dir_tree",
|
||||
|
||||
@ -97,6 +97,11 @@ else:
|
||||
{"widgettype": "Text", "options": {"text": "⬇", "cfontsize": 14, "css": "clickable", "color": "#50b86c", "title": "下载"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "script", "target": "self",
|
||||
"script": "var a=document.createElement('a');a.href=" + json.dumps(file_url) + ";a.download='';document.body.appendChild(a);a.click();document.body.removeChild(a)"}]},
|
||||
{"widgettype": "Text", "options": {"text": "🏷", "cfontsize": 14, "css": "clickable", "color": "#1565c0", "title": "添加标签"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "PopupWindow",
|
||||
"popup_options": {"title": "为「" + display_name + "」添加标签", "width": "32%", "height": "auto",
|
||||
"movable": True, "resizable": True, "modal": True},
|
||||
"options": {"url": "./tag_form.dspy?doc_id=" + f["id"] + "&kb_id=" + kb_id}}]},
|
||||
{"widgettype": "Text", "options": {"text": "🗑", "cfontsize": 14, "css": "clickable", "color": "#f56c6c", "title": "删除"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "script", "target": "self",
|
||||
"script": "if(!confirm('确认删除 ' + decodeURIComponent(" + json.dumps(f["file_name"]) + ") + ' ? 不可恢复'))return;fetch(" + json.dumps(del_url) + ").then(function(r){return r.json()}).then(function(){var u='/rag/knowledge_bases_list/file_list.dspy?_webbricks_=1&kb_id='+encodeURIComponent(" + json.dumps(kb_id) + ")+'&id='+encodeURIComponent(" + json.dumps(folder_id) + ")+'&label='+encodeURIComponent(" + json.dumps(folder_label) + ");fetch(u).then(function(r){return r.json()}).then(function(d){var fp=bricks.getWidgetById('file_list_panel',bricks.app.root);if(fp){fp.clear_widgets();bricks.widgetBuild(d,fp).then(function(w){if(w)fp.add_widget(w)})}})})"}]}
|
||||
|
||||
110
wwwroot/knowledge_bases_list/media_cards.dspy
Normal file
110
wwwroot/knowledge_bases_list/media_cards.dspy
Normal file
@ -0,0 +1,110 @@
|
||||
import json
|
||||
|
||||
ns = params_kw.copy()
|
||||
kb_id = ns.get('kb_id', '')
|
||||
kind = ns.get('kind', 'face')
|
||||
|
||||
db = DBPools()
|
||||
dbname = get_module_dbname('rag')
|
||||
|
||||
rows = []
|
||||
try:
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
if kind == 'voice':
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT id, file_name, file_path, metadata, created_at FROM documents "
|
||||
"WHERE kb_id=${kb_id}$ AND (LOWER(file_name) LIKE '%.mp3' OR LOWER(file_name) LIKE '%.wav' "
|
||||
"OR LOWER(file_name) LIKE '%.m4a' OR LOWER(file_name) LIKE '%.aac' OR LOWER(file_name) LIKE '%.ogg' "
|
||||
"OR LOWER(file_name) LIKE '%.flac') ORDER BY created_at DESC",
|
||||
{"kb_id": kb_id})
|
||||
else:
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT id, file_name, file_path, metadata, created_at FROM documents "
|
||||
"WHERE kb_id=${kb_id}$ AND (LOWER(file_name) LIKE '%.jpg' OR LOWER(file_name) LIKE '%.jpeg' "
|
||||
"OR LOWER(file_name) LIKE '%.png' OR LOWER(file_name) LIKE '%.gif' OR LOWER(file_name) LIKE '%.webp' "
|
||||
"OR LOWER(file_name) LIKE '%.bmp') ORDER BY created_at DESC",
|
||||
{"kb_id": kb_id})
|
||||
for r in recs:
|
||||
rows.append({
|
||||
"id": r.id,
|
||||
"file_name": r.file_name or '',
|
||||
"file_path": r.file_path or '',
|
||||
"metadata": r.metadata or '{}',
|
||||
"created_at": str(r.created_at)[:16] if r.created_at else ''
|
||||
})
|
||||
except Exception:
|
||||
return {"widgettype": "Text", "options": {"text": "加载失败", "cfontsize": 14, "color": "#e74c3c", "halign": "center", "marginTop": "40px"}}
|
||||
|
||||
def safe_url(p):
|
||||
if not p:
|
||||
return ''
|
||||
if p.startswith('/idfile'):
|
||||
return p
|
||||
return '/idfile' + p
|
||||
|
||||
kind_cn = '人脸' if kind != 'voice' else '声纹'
|
||||
|
||||
cards = []
|
||||
for f in rows:
|
||||
doc_id = f["id"]
|
||||
fname = f["file_name"]
|
||||
media_url = safe_url(f["file_path"])
|
||||
meta = {}
|
||||
try:
|
||||
meta = json.loads(f["metadata"])
|
||||
except Exception:
|
||||
meta = {}
|
||||
tags = meta.get('tags', [])
|
||||
if not isinstance(tags, list):
|
||||
tags = []
|
||||
|
||||
if kind == 'voice':
|
||||
media_widget = {"widgettype": "Html", "options": {"html": "<audio controls preload=\"metadata\" style=\"width:100%\" src=\"" + media_url + "\"></audio>", "padding": "8px 12px"}}
|
||||
else:
|
||||
media_widget = {"widgettype": "Image", "options": {"url": media_url, "width": "100%", "cheight": 12, "objectFit": "contain", "bgcolor": "#f5f5f5"}}
|
||||
|
||||
if tags:
|
||||
tag_widgets = []
|
||||
for t in tags:
|
||||
tag_widgets.append({"widgettype": "Text", "options": {"text": "#" + str(t), "cfontsize": 10, "color": "#1565c0", "bgcolor": "#e3f2fd", "padding": "1px 8px", "borderRadius": "8px", "margin": "2px 4px 2px 0"}})
|
||||
tag_row = {"widgettype": "HBox", "options": {"wrap": True, "padding": "0 12px", "alignItems": "center"}, "subwidgets": tag_widgets}
|
||||
else:
|
||||
tag_row = {"widgettype": "Text", "options": {"text": "暂无标签", "cfontsize": 10, "color": "#bbb", "padding": "0 12px"}}
|
||||
|
||||
tag_url = "./tag_form.dspy?doc_id=" + doc_id + "&kb_id=" + kb_id + "&kind=" + kind
|
||||
|
||||
cards.append({
|
||||
"widgettype": "VBox",
|
||||
"options": {"css": "card", "cwidth": 20, "padding": "0", "spacing": "0", "overflow": "hidden"},
|
||||
"subwidgets": [
|
||||
media_widget,
|
||||
{"widgettype": "Text", "options": {"text": fname, "cfontsize": 12, "fontWeight": "bold", "color": "#333", "padding": "8px 12px 2px 12px", "wrap": True, "halign": "left"}},
|
||||
{"widgettype": "Text", "options": {"text": f["created_at"], "cfontsize": 10, "color": "#999", "padding": "0 12px", "halign": "left"}},
|
||||
tag_row,
|
||||
{"widgettype": "HBox", "options": {"padding": "8px 12px", "alignItems": "center"}, "subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": "", "css": "filler"}},
|
||||
{"widgettype": "Button", "options": {"label": "🏷 添加标签", "cfontsize": 11, "bgcolor": "#e3f2fd", "color": "#1565c0", "padding": "3px 10px", "borderRadius": "12px"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "PopupWindow",
|
||||
"popup_options": {"title": "为「" + fname + "」添加标签", "width": "34%", "height": "auto",
|
||||
"movable": True, "resizable": True, "modal": True},
|
||||
"options": {"url": tag_url}}]}
|
||||
]}
|
||||
]
|
||||
})
|
||||
|
||||
if not cards:
|
||||
cards.append({"widgettype": "Text", "options": {"text": "该知识库暂无" + kind_cn + "文件,请先在文件列表上传对应图片/音频", "cfontsize": 13, "color": "#aaa", "halign": "center", "marginTop": "60px"}})
|
||||
|
||||
result = {
|
||||
"widgettype": "VScrollPanel",
|
||||
"id": "media_card_panel",
|
||||
"options": {"css": "filler", "width": "100%", "height": "100%", "padding": "12px"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "DynamicColumn",
|
||||
"options": {"width": "100%", "col_cwidth": 22, "col_cgap": 1},
|
||||
"subwidgets": cards
|
||||
}
|
||||
]
|
||||
}
|
||||
return result
|
||||
82
wwwroot/knowledge_bases_list/tag_form.dspy
Normal file
82
wwwroot/knowledge_bases_list/tag_form.dspy
Normal file
@ -0,0 +1,82 @@
|
||||
import json
|
||||
|
||||
ns = params_kw.copy()
|
||||
doc_id = ns.get('doc_id', '')
|
||||
kb_id = ns.get('kb_id', '')
|
||||
kind = ns.get('kind', '')
|
||||
|
||||
db = DBPools()
|
||||
dbname = get_module_dbname('rag')
|
||||
|
||||
fname = ''
|
||||
cur_tags = []
|
||||
try:
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT file_name, metadata FROM documents WHERE id=${id}$ AND kb_id=${kb_id}$",
|
||||
{"id": doc_id, "kb_id": kb_id})
|
||||
if recs:
|
||||
fname = recs[0].file_name or ''
|
||||
try:
|
||||
meta = json.loads(recs[0].metadata or '{}')
|
||||
except Exception:
|
||||
meta = {}
|
||||
cur_tags = meta.get('tags', [])
|
||||
if not isinstance(cur_tags, list):
|
||||
cur_tags = []
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
subwidgets = []
|
||||
if not doc_id:
|
||||
subwidgets.append({"widgettype": "Text", "options": {"text": "缺少 doc_id 参数", "cfontsize": 13, "color": "#e74c3c", "padding": "20px"}})
|
||||
else:
|
||||
subwidgets.append({"widgettype": "Text", "options": {"text": "📄 " + (fname or doc_id), "cfontsize": 13, "fontWeight": "bold", "color": "#333", "padding": "0 0 8px 0"}})
|
||||
if cur_tags:
|
||||
tws = []
|
||||
for t in cur_tags:
|
||||
tws.append({"widgettype": "Text", "options": {"text": "#" + str(t), "cfontsize": 10, "color": "#1565c0", "bgcolor": "#e3f2fd", "padding": "1px 8px", "borderRadius": "8px", "margin": "2px 6px 2px 0"}})
|
||||
subwidgets.append({"widgettype": "HBox", "options": {"wrap": True, "alignItems": "center", "marginBottom": "8px"}, "subwidgets": tws})
|
||||
else:
|
||||
subwidgets.append({"widgettype": "Text", "options": {"text": "暂无标签", "cfontsize": 11, "color": "#aaa", "marginBottom": "8px"}})
|
||||
subwidgets.append({
|
||||
"widgettype": "Form",
|
||||
"id": "tag_input_form",
|
||||
"options": {
|
||||
"cols": 1,
|
||||
"fields": [
|
||||
{"name": "tag", "label": "标签名", "uitype": "str", "required": True}
|
||||
],
|
||||
"buttons": [{"name": "submit", "label": "✓ 添加标签", "icon": "save"}]
|
||||
}
|
||||
})
|
||||
|
||||
refresh_js = ''
|
||||
if kind:
|
||||
refresh_js = ("var mp=document.querySelector('#media_card_panel');"
|
||||
"if(mp&&mp.bricks_widget){fetch('/rag/knowledge_bases_list/media_cards.dspy?_webbricks_=1&kb_id="
|
||||
+ kb_id + "&kind=" + kind + "').then(function(r){return r.json()}).then(function(d){"
|
||||
"var mw=mp.bricks_widget;mw.clear_widgets();bricks.widgetBuild(d,mw).then(function(nw){if(nw)mw.add_widget(nw)})}).catch(function(){})};")
|
||||
|
||||
tag_script = ("var d=event&&event.params?event.params:{};if(!d||!d.tag){alert('请输入标签名');return};"
|
||||
"var u='/rag/knowledge_bases_list/add_tag.dspy?doc_id=" + doc_id + "&kb_id=" + kb_id + "'+'&tag='+encodeURIComponent(d.tag);"
|
||||
"fetch(u).then(function(r){return r.text()}).then(function(t){var d2={};try{d2=JSON.parse(t)}catch(e){};"
|
||||
"if(d2&&d2.status==='SUCCEEDED'){" + refresh_js +
|
||||
"var pw=null;var w=this;while(w){if(w instanceof bricks.PopupWindow||w instanceof bricks.Popup){pw=w;break};w=w.parent};"
|
||||
"if(pw){pw.dismiss();pw.destroy()}else{alert('标签已添加:'+d2.added)}}"
|
||||
"else{alert('添加失败:'+((d2&&d2.error)||t))}}.bind(this)).catch(function(){alert('网络错误')})")
|
||||
|
||||
result = {
|
||||
"widgettype": "VBox",
|
||||
"options": {"width": "100%", "padding": "16px", "spacing": "6px"},
|
||||
"subwidgets": subwidgets,
|
||||
"binds": [
|
||||
{
|
||||
"wid": "tag_input_form",
|
||||
"event": "submit",
|
||||
"actiontype": "script",
|
||||
"script": tag_script
|
||||
}
|
||||
]
|
||||
}
|
||||
return result
|
||||
Loading…
x
Reference in New Issue
Block a user