133 lines
8.3 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

ns = params_kw.copy()
kb_id = ns.get('kb_id', '')
folder_id = ns.get('id', '')
folder_label = ns.get('label', '全部文件')
if folder_id == '__root__':
folder_id = ''
folder_label = '根目录'
db = DBPools()
dbname = get_module_dbname('rag')
# KB 检索角色校验(机构隔离 + 角色):无 search 权限不列文件
from rag.init import check_kb_perm
env = request._run_ns
async with db.sqlorContext(dbname) as sor:
_ok, _kb, _msg = await check_kb_perm(env, sor, kb_id, 'search')
if not _ok:
# 维护者也需要看到文件列表/下载文件(管理职责)
_ok2, _kb, _msg2 = await check_kb_perm(env, sor, kb_id, 'maintain')
if not _ok2:
return {"widgettype": "Text", "options": {"text": "⛔ " + _msg, "cfontsize": 13, "color": "#f56c6c", "halign": "center", "marginTop": "40px"}}
rows = []
try:
async with db.sqlorContext(dbname) as sor:
if folder_id:
recs = await sor.sqlExe(
"SELECT id, file_name, file_type, file_size, file_path, status, chunk_count, created_at FROM rag_documents WHERE kb_id=${kb_id}$ AND folder_id=${folder_id}$ ORDER BY created_at DESC",
{"kb_id": kb_id, "folder_id": folder_id})
else:
recs = await sor.sqlExe(
"SELECT id, file_name, file_type, file_size, file_path, status, chunk_count, created_at FROM rag_documents WHERE kb_id=${kb_id}$ AND (folder_id IS NULL OR folder_id IN ('','__root__')) ORDER BY created_at DESC",
{"kb_id": kb_id})
for r in recs:
rows.append({
"id": r.id,
"file_name": r.file_name,
"file_type": r.file_type or '',
"file_size": r.file_size or 0,
"file_path": r.file_path or '',
"status": r.status or '',
"chunk_count": r.chunk_count or 0,
"created_at": str(r.created_at)[:16] if r.created_at else ''
})
except:
return {"widgettype": "Text", "options": {"text": "加载文件列表失败", "cfontsize": 14, "color": "#e74c3c", "halign": "center", "marginTop": "40px"}}
def fmt_size(sz):
if sz < 1024: return str(sz) + "B"
elif sz < 1024*1024: return str(round(sz/1024, 1)) + "KB"
else: return str(round(sz/(1024*1024), 1)) + "MB"
def trunc_name(name, maxlen=28):
if len(name) > maxlen:
return name[:maxlen-3] + "..."
return name
header = {
"widgettype": "HBox",
"options": {"width": "100%", "padding": "10px 0", "borderBottom": "1px solid #e0e0e0", "alignItems": "center"},
"subwidgets": [
{"widgettype": "Text", "options": {"text": "📄 " + folder_label, "cfontsize": 14, "fontWeight": "bold", "color": "#333"}},
{"widgettype": "Text", "options": {"text": "" + str(len(rows)) + " 个文件)", "cfontsize": 12, "color": "#999", "marginLeft": "8px"}},
{"widgettype": "Text", "options": {"text": "", "css": "filler"}}
]
}
file_widgets = [header]
if not rows:
file_widgets.append({
"widgettype": "Text", "options": {"text": "暂无文件", "cfontsize": 13, "color": "#aaa", "halign": "center", "marginTop": "40px"}
})
else:
for f in rows:
sc = "#50b86c"; st = "完成"
if f["status"] == "pending": sc = "#e6a23c"; st = "处理中"
elif f["status"] == "error": sc = "#f56c6c"; st = "失败"
file_url = f["file_path"]
if not file_url.startswith("/idfile"):
file_url = "/idfile" + file_url
# 查看/下载统一走 file_serve.dspysearch 角色校验),不再直连 /idfile/
serve_url = "/rag/knowledge_bases_list/file_serve.dspy?_webbricks_=1&doc_id=" + f["id"]
view_script = ("fetch(" + json.dumps(serve_url) + ").then(function(r){return r.json()}).then(function(d){"
"if(d.error){alert(d.message||d.error);return}"
"var bin=atob(d.b64);var arr=new Uint8Array(bin.length);for(var i=0;i<bin.length;i++)arr[i]=bin.charCodeAt(i);"
"var u=URL.createObjectURL(new Blob([arr],{type:d.mime}));window.open(u,'_blank')})")
dl_script = ("fetch(" + json.dumps(serve_url) + ").then(function(r){return r.json()}).then(function(d){"
"if(d.error){alert(d.message||d.error);return}"
"var bin=atob(d.b64);var arr=new Uint8Array(bin.length);for(var i=0;i<bin.length;i++)arr[i]=bin.charCodeAt(i);"
"var u=URL.createObjectURL(new Blob([arr],{type:d.mime}));var a=document.createElement('a');a.href=u;a.download=d.file_name||'file';"
"document.body.appendChild(a);a.click();document.body.removeChild(a)})")
del_url = "/rag/knowledge_bases_list/delete_file.dspy?_webbricks_=1&kb_id=" + kb_id + "&doc_id=" + f["id"] + "&folder_id=" + folder_id + "&folder_label=" + folder_label
display_name = trunc_name(f["file_name"])
row = {
"widgettype": "HBox",
"options": {"width": "100%", "padding": "6px 12px", "borderBottom": "1px solid #f0f0f0", "alignItems": "center"},
"subwidgets": [
{"widgettype": "Text", "options": {
"text": "📄 " + display_name, "cfontsize": 13, "color": "#333",
"cwidth": 28, "halign": "left", "overflow": "hidden",
"title": f["file_name"]
}},
{"widgettype": "Text", "options": {"text": f["file_type"].upper(), "cfontsize": 11, "color": "#999", "cwidth": 5, "halign": "center"}},
{"widgettype": "Text", "options": {"text": fmt_size(f["file_size"]), "cfontsize": 11, "color": "#999", "cwidth": 8, "halign": "right"}},
{"widgettype": "Text", "options": {"text": st, "cfontsize": 11, "color": sc, "cwidth": 6, "halign": "center"}},
{"widgettype": "Text", "options": {"text": f["created_at"], "cfontsize": 11, "color": "#999", "cwidth": 13, "halign": "center"}},
{
"widgettype": "HBox", "options": {"spacing": "14px"},
"subwidgets": [
{"widgettype": "Text", "options": {"text": "👁", "cfontsize": 14, "css": "clickable", "color": "#4a90d9", "title": "查看"},
"binds": [{"wid": "self", "event": "click", "actiontype": "script", "target": "self",
"script": view_script}]},
{"widgettype": "Text", "options": {"text": "⬇", "cfontsize": 14, "css": "clickable", "color": "#50b86c", "title": "下载"},
"binds": [{"wid": "self", "event": "click", "actiontype": "script", "target": "self",
"script": dl_script}]},
{"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)})}})})"}]}
]}
]}
file_widgets.append(row)
result = {"widgettype": "VBox", "options": {"width": "100%", "spacing": "0"}, "subwidgets": file_widgets}
return result