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') 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 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 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 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": "window.open(" + json.dumps(file_url) + ",'_blank')"}]}, {"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)})}})})"}]} ]} ]} file_widgets.append(row) result = {"widgettype": "VBox", "options": {"width": "100%", "spacing": "0"}, "subwidgets": file_widgets} return result