rag/wwwroot/knowledge_bases_list/delete_file.dspy
ymq 1c1ef65931 feat(rag): 机构rags目录+容量检查参数化+KB级角色权限
- 存储: 知识库文件存 {workspace_base}/{org}/rags/{kb}/ (机构目录下开设rags目录)
- 容量: rag_check_storage_quota 参数控制是否检查机构上限(默认不检查)
- 权限: rag_knowledge_bases 加 maintain_roles/search_roles; 空=同机构默认可用
  维护入口(upload/delete/rename/tree/tags)校验maintain; 检索入口(search/file_list/file_serve)校验search
- file_serve.dspy: 下载/预览走校验端点, 堵住/idfile/猜路径越权
- 机构隔离: 跨机构KB一律拒绝
2026-09-02 12:09:16 +08:00

122 lines
6.5 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()
doc_id = ns.get('doc_id', '')
kb_id = ns.get('kb_id', '')
folder_id = ns.get('folder_id', '')
folder_label = ns.get('folder_label', '全部文件')
# KB 维护角色校验(机构隔离 + 角色)
from rag.init import check_kb_perm
env = request._run_ns
db = DBPools()
async with db.sqlorContext(get_module_dbname('rag')) as sor:
_ok, _kb, _msg = await check_kb_perm(env, sor, kb_id, 'maintain')
if not _ok:
return json.dumps({"status": "error", "error": _msg}, ensure_ascii=False)
if not doc_id:
return json.dumps({"status": "error", "error": "doc_id required"}, ensure_ascii=False)
env = request._run_ns
userorgid = await env.get_userorgid()
async with get_sor_context(env, 'rag') as sor:
recs = await sor.sqlExe(
"SELECT file_path, file_size, kb_id FROM rag_documents WHERE id=${id}$",
{"id": doc_id})
if not recs:
return json.dumps({"status": "error", "error": "file not found"}, ensure_ascii=False)
doc = recs[0]
file_path = doc.file_path
file_size = doc.file_size
# Delete document + chunks
await sor.sqlExe("DELETE FROM rag_document_chunks WHERE doc_id=${doc_id}$", {"doc_id": doc_id})
await sor.sqlExe("DELETE FROM rag_documents WHERE id=${id}$", {"id": doc_id})
# Update KB stats
await sor.sqlExe(
"UPDATE rag_knowledge_bases SET doc_count=GREATEST(doc_count-1, 0), total_size=GREATEST(total_size-${size}$, 0) WHERE id=${kb_id}$",
{"size": file_size, "kb_id": kb_id})
# Delete physical file via env.realpath
if file_path and file_path.startswith("/idfile/"):
try:
real_path = env.realpath(file_path)
if os.path.exists(real_path):
os.remove(real_path)
except Exception:
pass
# Re-query and return refreshed file list
db = DBPools()
dbname = get_module_dbname('rag')
rows = []
async with db.sqlorContext(dbname) as sor:
if folder_id and folder_id != '__root__':
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='') 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 ''
})
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"
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:
del_url_base = entire_url('./delete_file.dspy')
for f in rows:
sc = "#50b86c"; st = "已完成"
if f["status"] == "pending": sc = "#e6a23c"; st = "处理中"
elif f["status"] == "error": sc = "#f56c6c"; st = "失败"
file_url = entire_url(f["file_path"])
durl = del_url_base + "?kb_id=" + kb_id + "&doc_id=" + f["id"] + "&folder_id=" + folder_id + "&folder_label=" + folder_label
row = {
"widgettype": "HBox",
"options": {"width": "100%", "padding": "8px 12px", "borderBottom": "1px solid #f0f0f0", "alignItems": "center"},
"subwidgets": [
{"widgettype": "Text", "options": {"text": "📄 " + f["file_name"], "cfontsize": 13, "color": "#333", "textAlign": "left", "css": "filler"}},
{"widgettype": "Text", "options": {"text": f["file_type"].upper(), "cfontsize": 11, "color": "#999", "marginRight": "8px", "cwidth": 5, "textAlign": "center"}},
{"widgettype": "Text", "options": {"text": fmt_size(f["file_size"]), "cfontsize": 11, "color": "#999", "marginRight": "8px", "cwidth": 8, "textAlign": "right"}},
{"widgettype": "Text", "options": {"text": st, "cfontsize": 11, "color": sc, "marginRight": "8px", "cwidth": 6, "textAlign": "center"}},
{"widgettype": "Text", "options": {"text": f["created_at"], "cfontsize": 11, "color": "#999", "marginRight": "8px", "cwidth": 12}},
{"widgettype": "HBox", "options": {"spacing": "4px"}, "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": "#f56c6c", "title": "删除"},
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "file_list_panel", "mode": "replace",
"options": {"url": durl}}]}
]}
]}
file_widgets.append(row)
result = {"widgettype": "VBox", "options": {"width": "100%", "spacing": "0"}, "subwidgets": file_widgets}
return result