refactor: extract rag business module, keep ragserver as shell
This commit is contained in:
parent
77b8121ec9
commit
b76d1b85e8
59
build.sh
59
build.sh
@ -2,36 +2,34 @@
|
|||||||
set -e
|
set -e
|
||||||
cdir=$(cd "$(dirname "$0")"; pwd)
|
cdir=$(cd "$(dirname "$0")"; pwd)
|
||||||
|
|
||||||
# Environment
|
|
||||||
export SAGE_RBAC_DB=rag
|
export SAGE_RBAC_DB=rag
|
||||||
|
|
||||||
# Virtualenv
|
|
||||||
if [ ! -d py3 ]; then
|
if [ ! -d py3 ]; then
|
||||||
python3 -m venv py3
|
python3 -m venv py3
|
||||||
fi
|
fi
|
||||||
source py3/bin/activate
|
source py3/bin/activate
|
||||||
|
|
||||||
# Install build tools
|
|
||||||
pip install -q xls2ddl
|
pip install -q xls2ddl
|
||||||
|
|
||||||
# pkgs directory
|
|
||||||
mkdir -p pkgs logs files
|
mkdir -p pkgs logs files
|
||||||
|
|
||||||
# Clone and install foundation modules
|
# Foundation modules
|
||||||
for repo in apppublic sqlor ahserver rbac appbase bricks-for-python; do
|
for repo in apppublic sqlor ahserver rbac appbase bricks-for-python; do
|
||||||
[ -d pkgs/$repo ] || git clone git@git.opencomputing.cn:yumoqing/$repo.git pkgs/$repo
|
[ -d pkgs/$repo ] || git clone git@git.opencomputing.cn:yumoqing/$repo.git pkgs/$repo
|
||||||
pip install -q -e pkgs/$repo
|
pip install -q -e pkgs/$repo
|
||||||
done
|
done
|
||||||
|
|
||||||
# Clone and install rag-pipeline (core engine - add to PYTHONPATH, not pip install)
|
# Clone and install rag business module
|
||||||
[ -d pkgs/rag-pipeline ] || git clone git@git.opencomputing.cn:yumoqing/rag-pipeline.git pkgs/rag-pipeline
|
[ -d pkgs/rag ] || git clone git@git.opencomputing.cn:yumoqing/rag.git pkgs/rag
|
||||||
# rag-pipeline is imported directly, not via pip
|
pip install -q -e pkgs/rag
|
||||||
|
|
||||||
# Clone service modules (for reference, not imported)
|
# rag-pipeline (core engine - PYTHONPATH only)
|
||||||
|
[ -d pkgs/rag-pipeline ] || git clone git@git.opencomputing.cn:yumoqing/rag-pipeline.git pkgs/rag-pipeline
|
||||||
|
|
||||||
|
# Service modules (reference only)
|
||||||
for svc in clip_embedding vdb face-service graph-service ner-service; do
|
for svc in clip_embedding vdb face-service graph-service ner-service; do
|
||||||
[ -d pkgs/$svc ] || git clone git@git.opencomputing.cn:yumoqing/$svc.git pkgs/$svc 2>/dev/null || echo "Skip $svc (repo may not exist yet)"
|
[ -d pkgs/$svc ] || git clone git@git.opencomputing.cn:yumoqing/$svc.git pkgs/$svc 2>/dev/null || echo "Skip $svc"
|
||||||
done
|
done
|
||||||
# bge-reranker and speaker-id repos may not exist yet
|
|
||||||
[ -d pkgs/bge-reranker ] || git clone git@git.opencomputing.cn:yumoqing/bge-reranker.git pkgs/bge-reranker 2>/dev/null || echo "Skip bge-reranker"
|
[ -d pkgs/bge-reranker ] || git clone git@git.opencomputing.cn:yumoqing/bge-reranker.git pkgs/bge-reranker 2>/dev/null || echo "Skip bge-reranker"
|
||||||
[ -d pkgs/speaker-id ] || git clone git@git.opencomputing.cn:yumoqing/speaker-id.git pkgs/speaker-id 2>/dev/null || echo "Skip speaker-id"
|
[ -d pkgs/speaker-id ] || git clone git@git.opencomputing.cn:yumoqing/speaker-id.git pkgs/speaker-id 2>/dev/null || echo "Skip speaker-id"
|
||||||
|
|
||||||
@ -44,39 +42,20 @@ cd pkgs/bricks
|
|||||||
bash build.sh
|
bash build.sh
|
||||||
cd $cdir
|
cd $cdir
|
||||||
|
|
||||||
# Symlink bricks
|
# Symlink bricks + module wwwroots
|
||||||
ln -sf ../pkgs/bricks/dist wwwroot/bricks 2>/dev/null || true
|
ln -sf ../pkgs/bricks/dist wwwroot/bricks 2>/dev/null || true
|
||||||
|
ln -sf ../pkgs/rbac/wwwroot wwwroot/rbac 2>/dev/null || true
|
||||||
|
ln -sf ../pkgs/rag/wwwroot rag_wwwroot 2>/dev/null || true
|
||||||
|
|
||||||
# Install appbase and rbac
|
# Generate DDL from rag module models
|
||||||
pip install -q -e pkgs/appbase
|
if ls pkgs/rag/models/*.json >/dev/null 2>&1; then
|
||||||
pip install -q -e pkgs/rbac
|
cd pkgs/rag/models
|
||||||
|
json2ddl mysql . > /tmp/rag_ddl.sql
|
||||||
# Generate DDL from models
|
mysql -h db -utest -ptest123 rag < /tmp/rag_ddl.sql 2>/dev/null || echo "DDL apply skipped"
|
||||||
if ls models/*.json >/dev/null 2>&1; then
|
|
||||||
cd models
|
|
||||||
json2ddl mysql . > mysql.ddl.sql
|
|
||||||
mysql -h db -utest -ptest123 rag < mysql.ddl.sql 2>/dev/null || echo "DDL already applied or mysql not available"
|
|
||||||
cd $cdir
|
cd $cdir
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Generate CRUD from json/
|
# Sync password_key from Sage
|
||||||
if ls json/*.json >/dev/null 2>&1; then
|
|
||||||
xls2ui -m models -o wwwroot ragserver json/*.json 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Symlink module wwwroots
|
|
||||||
for m in rag-pipeline; do
|
|
||||||
[ -d pkgs/$m/wwwroot ] && ln -sf ../pkgs/$m/wwwroot wwwroot/$m 2>/dev/null || true
|
|
||||||
done
|
|
||||||
# Always symlink rbac wwwroot for login pages
|
|
||||||
ln -sf ../pkgs/rbac/wwwroot wwwroot/rbac 2>/dev/null || true
|
|
||||||
|
|
||||||
# Fix created_by in CRUD dspy files
|
|
||||||
if [ -d wwwroot/knowledge_bases_list ]; then
|
|
||||||
find wwwroot -name '*.dspy' -exec sed -i "s/ns\['created_by'\] = ''/ns['created_by'] = userorgid/" {} \; 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Sync password_key from Sage if available (only update key, preserve rest)
|
|
||||||
if [ -f /d/apitest/sage/conf/config.json ]; then
|
if [ -f /d/apitest/sage/conf/config.json ]; then
|
||||||
sage_key=$(python3 -c "import json; print(json.load(open('/d/apitest/sage/conf/config.json'))['password_key'])")
|
sage_key=$(python3 -c "import json; print(json.load(open('/d/apitest/sage/conf/config.json'))['password_key'])")
|
||||||
python3 -c "
|
python3 -c "
|
||||||
@ -85,7 +64,7 @@ c = json.load(open('$cdir/conf/config.json'))
|
|||||||
c['password_key'] = '$sage_key'
|
c['password_key'] = '$sage_key'
|
||||||
json.dump(c, open('$cdir/conf/config.json', 'w'), indent=4, ensure_ascii=False)
|
json.dump(c, open('$cdir/conf/config.json', 'w'), indent=4, ensure_ascii=False)
|
||||||
" 2>/dev/null
|
" 2>/dev/null
|
||||||
echo "password_key synced from Sage"
|
echo "password_key synced"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "=== RAG Server build complete ==="
|
echo "=== RAG Server build complete ==="
|
||||||
|
|||||||
544
init.py
544
init.py
@ -1,551 +1,25 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
"""
|
"""
|
||||||
RagServer DSPY Handlers - RAG 核心业务逻辑 + 文件上传/删除
|
RagServer Shell - 前端壳应用,业务逻辑由 rag 模块提供
|
||||||
"""
|
"""
|
||||||
from traceback import format_exc
|
|
||||||
from ahserver.serverenv import ServerEnv
|
from ahserver.serverenv import ServerEnv
|
||||||
from appPublic.registerfunction import RegisterFunction
|
from appPublic.registerfunction import RegisterFunction
|
||||||
from appPublic.log import debug, exception
|
import json
|
||||||
from sqlor.dbpools import get_sor_context
|
|
||||||
import json, os, uuid, time
|
|
||||||
|
|
||||||
|
|
||||||
async def status_handler(request, params_kw, *args, **kwargs):
|
async def status_handler(request, params_kw, *args, **kwargs):
|
||||||
return json.dumps({
|
return json.dumps({
|
||||||
"service": "ragserver", "version": "0.1.0",
|
"service": "ragserver",
|
||||||
"endpoints": ["/api/status", "/api/kb/list", "/api/doc/upload",
|
"version": "0.1.0",
|
||||||
"/api/doc/delete", "/api/search", "/api/engines",
|
"module": "rag"
|
||||||
"/api/dir/create", "/api/dir/delete", "/api/dir/list",
|
|
||||||
"/api/tag/create", "/api/tag/list", "/api/tag/delete",
|
|
||||||
"/api/tag/assign", "/api/tag/unassign", "/api/tag/media_tags",
|
|
||||||
"/api/tag/search"]
|
|
||||||
}, indent=2, ensure_ascii=False)
|
}, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
async def kb_list_handler(request, params_kw, *args, **kwargs):
|
|
||||||
env = request._run_ns
|
|
||||||
try:
|
|
||||||
userorgid = await env.get_userorgid()
|
|
||||||
async with get_sor_context(env, 'rag') as sor:
|
|
||||||
recs = await sor.R("knowledge_bases", {})
|
|
||||||
cards = []
|
|
||||||
for r in recs:
|
|
||||||
doc_recs = await sor.R("documents", {"kb_id": r.id})
|
|
||||||
doc_count = len(doc_recs)
|
|
||||||
total_size = r.total_size or 0
|
|
||||||
if total_size >= 1073741824:
|
|
||||||
size_str = f"{total_size/1073741824:.1f}GB"
|
|
||||||
elif total_size >= 1048576:
|
|
||||||
size_str = f"{total_size/1048576:.0f}MB"
|
|
||||||
elif total_size >= 1024:
|
|
||||||
size_str = f"{total_size/1024:.0f}KB"
|
|
||||||
else:
|
|
||||||
size_str = f"{total_size}B"
|
|
||||||
engine = r.embedding_engine or "CLIP"
|
|
||||||
card = {"widgettype":"VBox","options":{"cwidth":16,"cheight":12,"bgcolor":"#f0f7ff","padding":"16px","css":"card clickable","border":"1px solid #d0e4f7"},"subwidgets":[
|
|
||||||
{"widgettype":"Text","options":{"text":"📚 " + str(r.name),"cfontsize":16,"fontWeight":"bold"}},
|
|
||||||
{"widgettype":"Text","options":{"text":str(engine)+" · 1024维","cfontsize":12,"color":"#888"}},
|
|
||||||
{"widgettype":"HBox","options":{"spacing":"12px"},"subwidgets":[
|
|
||||||
{"widgettype":"Text","options":{"text":"📄 "+str(doc_count)+"文档","cfontsize":12,"color":"#666"}},
|
|
||||||
{"widgettype":"Text","options":{"text":"💾 "+size_str,"cfontsize":12,"color":"#666"}}]}],
|
|
||||||
"binds":[{"wid":"self","event":"click","actiontype":"urlwidget","target":"app.rag_main_content","mode":"replace","options":{"url":"/knowledge_bases_list/detail.ui","params":{"kb_id":str(r.id),"kb_name":str(r.name)}}}]}
|
|
||||||
cards.append(card)
|
|
||||||
if not cards:
|
|
||||||
cards.append({"widgettype":"Text","options":{"text":"暂无知识库","color":"#aaa","cfontsize":14,"halign":"center"}})
|
|
||||||
result = {"widgettype":"HBox","options":{"width":"100%","spacing":"12px","wrap":True},"subwidgets":cards}
|
|
||||||
return json.dumps(result, ensure_ascii=False)
|
|
||||||
except Exception as e:
|
|
||||||
exception(f"kb_list: {e}, {format_exc()}")
|
|
||||||
return json.dumps({"error": str(e)})
|
|
||||||
|
|
||||||
|
|
||||||
async def engines_handler(request, params_kw, *args, **kwargs):
|
|
||||||
env = request._run_ns
|
|
||||||
try:
|
|
||||||
userorgid = await env.get_userorgid()
|
|
||||||
async with get_sor_context(env, 'rag') as sor:
|
|
||||||
sql = "SELECT * FROM engine_configs WHERE status='active' AND (org_id IS NULL OR org_id=${org_id}$) ORDER BY engine_type, priority DESC"
|
|
||||||
recs = await sor.sqlExe(sql, {"org_id": userorgid})
|
|
||||||
rows = [dict(r) for r in recs]
|
|
||||||
return json.dumps({"status": "SUCCEEDED", "data": {"rows": rows, "total": len(rows)}}, ensure_ascii=False, default=str)
|
|
||||||
except Exception as e:
|
|
||||||
exception(f"engines: {e}, {format_exc()}")
|
|
||||||
return json.dumps({"error": str(e)})
|
|
||||||
|
|
||||||
|
|
||||||
async def search_handler(request, params_kw, *args, **kwargs):
|
|
||||||
env = request._run_ns
|
|
||||||
try:
|
|
||||||
query = params_kw.get("query", "")
|
|
||||||
kb_id = params_kw.get("kb_id", "")
|
|
||||||
top_k = int(params_kw.get("top_k", 5))
|
|
||||||
if not query:
|
|
||||||
return json.dumps({"error": "query required"})
|
|
||||||
try:
|
|
||||||
from pipeline import search as pipeline_search
|
|
||||||
result = pipeline_search(query, pipeline_name="kg-rag-standard",
|
|
||||||
collection=kb_id or "knowledge",
|
|
||||||
graph_name=kb_id or "knowledge",
|
|
||||||
top_k=top_k, llm_func=None)
|
|
||||||
return json.dumps(result, ensure_ascii=False)
|
|
||||||
except ImportError:
|
|
||||||
return json.dumps({"status": "FALLBACK", "message": "pipeline not available"})
|
|
||||||
except Exception as e:
|
|
||||||
exception(f"search: {e}, {format_exc()}")
|
|
||||||
return json.dumps({"error": str(e)})
|
|
||||||
|
|
||||||
|
|
||||||
async def doc_upload_handler(request, params_kw, *args, **kwargs):
|
|
||||||
"""文件上传 → 保存 → DB记录 → 触发RAG入库"""
|
|
||||||
env = request._run_ns
|
|
||||||
try:
|
|
||||||
userorgid = await env.get_userorgid()
|
|
||||||
kb_id = params_kw.get("kb_id", "")
|
|
||||||
file_name = params_kw.get("file_name", "upload.bin")
|
|
||||||
if not kb_id:
|
|
||||||
return json.dumps({"error": "kb_id required"})
|
|
||||||
|
|
||||||
# Read raw file data from request body
|
|
||||||
file_data = await request.read()
|
|
||||||
|
|
||||||
if not file_data:
|
|
||||||
return json.dumps({"error": "no file data"})
|
|
||||||
|
|
||||||
# Save file
|
|
||||||
doc_id = uuid.uuid4().hex[:16]
|
|
||||||
ext = os.path.splitext(file_name)[1] or ".bin"
|
|
||||||
saved_name = f"{doc_id}{ext}"
|
|
||||||
files_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "files")
|
|
||||||
os.makedirs(files_dir, exist_ok=True)
|
|
||||||
file_path = os.path.join(files_dir, saved_name)
|
|
||||||
with open(file_path, "wb") as f:
|
|
||||||
f.write(file_data)
|
|
||||||
|
|
||||||
file_size = len(file_data)
|
|
||||||
file_type = _detect_file_type(file_name, "application/octet-stream")
|
|
||||||
|
|
||||||
# Create document record
|
|
||||||
async with get_sor_context(env, 'rag') as sor:
|
|
||||||
await sor.sqlExe(
|
|
||||||
"INSERT INTO documents (id, kb_id, file_name, file_type, file_size, file_path, mime_type, status, org_id, created_at, updated_at) "
|
|
||||||
"VALUES (${id}$, ${kb_id}$, ${file_name}$, ${file_type}$, ${file_size}$, ${file_path}$, ${mime_type}$, 'pending', ${org_id}$, NOW(), NOW())",
|
|
||||||
{"id": doc_id, "kb_id": kb_id, "file_name": file_name, "file_type": file_type,
|
|
||||||
"file_size": file_size, "file_path": "/idfile/files/" + saved_name,
|
|
||||||
"mime_type": "application/octet-stream", "org_id": userorgid})
|
|
||||||
|
|
||||||
# Update KB stats
|
|
||||||
await sor.sqlExe(
|
|
||||||
"UPDATE knowledge_bases SET doc_count=doc_count+1, total_size=total_size+${size}$ WHERE id=${kb_id}$",
|
|
||||||
{"size": file_size, "kb_id": kb_id})
|
|
||||||
|
|
||||||
# Trigger async ingest for text-based files
|
|
||||||
ingest_result = None
|
|
||||||
if file_type == "text":
|
|
||||||
try:
|
|
||||||
text = file_data.decode("utf-8", errors="replace")
|
|
||||||
from pipeline import ingest as pipeline_ingest
|
|
||||||
ingest_result = pipeline_ingest(
|
|
||||||
text, pipeline_name="kg-rag-standard",
|
|
||||||
collection=kb_id, graph_name=kb_id, llm_func=None)
|
|
||||||
# Update status to done
|
|
||||||
async with get_sor_context(env, 'rag') as sor:
|
|
||||||
await sor.sqlExe(
|
|
||||||
"UPDATE documents SET status='done', chunk_count=${chunks}$ WHERE id=${id}$",
|
|
||||||
{"chunks": ingest_result.get("chunks", 0) if ingest_result else 0, "id": doc_id})
|
|
||||||
if ingest_result:
|
|
||||||
await sor.sqlExe(
|
|
||||||
"UPDATE knowledge_bases SET chunk_count=chunk_count+${n}$ WHERE id=${kb_id}$",
|
|
||||||
{"n": ingest_result.get("chunks", 0), "kb_id": kb_id})
|
|
||||||
except Exception as e:
|
|
||||||
exception(f"async ingest failed: {e}")
|
|
||||||
|
|
||||||
return json.dumps({
|
|
||||||
"status": "SUCCEEDED",
|
|
||||||
"doc_id": doc_id,
|
|
||||||
"file_name": file_name,
|
|
||||||
"file_size": file_size,
|
|
||||||
"file_type": file_type,
|
|
||||||
"ingest": ingest_result
|
|
||||||
}, ensure_ascii=False, default=str)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
exception(f"doc_upload: {e}, {format_exc()}")
|
|
||||||
return json.dumps({"error": str(e)})
|
|
||||||
|
|
||||||
|
|
||||||
async def doc_delete_handler(request, params_kw, *args, **kwargs):
|
|
||||||
"""文件删除 → 清理VDB → 清理图 → 清理DB → 删文件"""
|
|
||||||
env = request._run_ns
|
|
||||||
try:
|
|
||||||
doc_id = params_kw.get("doc_id", "")
|
|
||||||
if not doc_id:
|
|
||||||
return json.dumps({"error": "doc_id required"})
|
|
||||||
|
|
||||||
async with get_sor_context(env, 'rag') as sor:
|
|
||||||
# Get document info
|
|
||||||
recs = await sor.R("documents", {"id": doc_id})
|
|
||||||
if not recs:
|
|
||||||
return json.dumps({"error": "document not found"})
|
|
||||||
doc = recs[0]
|
|
||||||
|
|
||||||
# Get chunks to clean VDB
|
|
||||||
chunks = await sor.R("document_chunks", {"doc_id": doc_id})
|
|
||||||
|
|
||||||
# Delete from VDB
|
|
||||||
if chunks:
|
|
||||||
vector_ids = [c.vector_id for c in chunks if c.vector_id]
|
|
||||||
if vector_ids:
|
|
||||||
try:
|
|
||||||
_call_vdb("/v1/delete", {"colname": doc.kb_id, "ids": vector_ids})
|
|
||||||
except Exception as e:
|
|
||||||
exception(f"vdb delete failed: {e}")
|
|
||||||
|
|
||||||
# Delete from graph
|
|
||||||
entities = await sor.R("entities", {"kb_id": doc.kb_id})
|
|
||||||
if entities:
|
|
||||||
try:
|
|
||||||
_call_graph("/api/graph/save", {"graph": doc.kb_id})
|
|
||||||
except Exception as e:
|
|
||||||
exception(f"graph cleanup failed: {e}")
|
|
||||||
|
|
||||||
# Delete DB records
|
|
||||||
await sor.sqlExe("DELETE FROM document_chunks WHERE doc_id=${id}$", {"id": doc_id})
|
|
||||||
await sor.sqlExe("DELETE FROM entities WHERE kb_id=${kb_id}$", {"kb_id": doc.kb_id})
|
|
||||||
await sor.sqlExe("DELETE FROM entity_relations WHERE kb_id=${kb_id}$", {"kb_id": doc.kb_id})
|
|
||||||
await sor.sqlExe("DELETE FROM documents WHERE id=${id}$", {"id": doc_id})
|
|
||||||
|
|
||||||
# Update KB stats
|
|
||||||
await sor.sqlExe(
|
|
||||||
"UPDATE knowledge_bases SET doc_count=GREATEST(doc_count-1,0), total_size=GREATEST(total_size-${size}$,0), chunk_count=GREATEST(chunk_count-${n}$,0) WHERE id=${kb_id}$",
|
|
||||||
{"size": doc.file_size, "n": len(chunks), "kb_id": doc.kb_id})
|
|
||||||
|
|
||||||
# Delete file from disk
|
|
||||||
file_path = doc.file_path
|
|
||||||
if file_path and file_path.startswith("/idfile/"):
|
|
||||||
real_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "files",
|
|
||||||
os.path.basename(file_path))
|
|
||||||
if os.path.exists(real_path):
|
|
||||||
os.remove(real_path)
|
|
||||||
|
|
||||||
return json.dumps({"status": "SUCCEEDED", "doc_id": doc_id, "chunks_deleted": len(chunks)})
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
exception(f"doc_delete: {e}, {format_exc()}")
|
|
||||||
return json.dumps({"error": str(e)})
|
|
||||||
|
|
||||||
|
|
||||||
def _detect_file_type(name, mime):
|
|
||||||
"""Detect file type from name and MIME"""
|
|
||||||
ext = os.path.splitext(name)[1].lower()
|
|
||||||
if ext in ('.txt', '.md', '.json', '.csv', '.xml', '.html', '.py', '.js', '.css', '.yaml', '.yml'):
|
|
||||||
return "text"
|
|
||||||
if ext in ('.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg'):
|
|
||||||
return "image"
|
|
||||||
if ext in ('.mp3', '.wav', '.flac', '.ogg', '.m4a', '.aac'):
|
|
||||||
return "audio"
|
|
||||||
if ext in ('.mp4', '.avi', '.mov', '.mkv', '.webm'):
|
|
||||||
return "video"
|
|
||||||
if ext == '.pdf':
|
|
||||||
return "text"
|
|
||||||
return "other"
|
|
||||||
|
|
||||||
|
|
||||||
def _call_vdb(path, data, timeout=10):
|
|
||||||
"""Call VDB service"""
|
|
||||||
import urllib.request
|
|
||||||
url = f"http://localhost:8886{path}"
|
|
||||||
req = urllib.request.Request(url, data=json.dumps(data).encode(),
|
|
||||||
headers={"Content-Type": "application/json"})
|
|
||||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
||||||
return json.loads(resp.read())
|
|
||||||
|
|
||||||
|
|
||||||
def _call_graph(path, data, timeout=10):
|
|
||||||
"""Call Graph service"""
|
|
||||||
import urllib.request
|
|
||||||
url = f"http://localhost:9092{path}"
|
|
||||||
req = urllib.request.Request(url, data=json.dumps(data).encode(),
|
|
||||||
headers={"Content-Type": "application/json"})
|
|
||||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
||||||
return json.loads(resp.read())
|
|
||||||
|
|
||||||
|
|
||||||
async def dir_create_handler(request, params_kw, *args, **kwargs):
|
|
||||||
"""创建目录"""
|
|
||||||
env = request._run_ns
|
|
||||||
try:
|
|
||||||
kb_id = params_kw.get("kb_id", "")
|
|
||||||
parent_id = params_kw.get("parent_id", "")
|
|
||||||
dir_name = params_kw.get("dir_name", "")
|
|
||||||
if not kb_id or not dir_name:
|
|
||||||
return json.dumps({"error": "kb_id and dir_name required"})
|
|
||||||
async with get_sor_context(env, 'rag') as sor:
|
|
||||||
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}$, ${parent}$, NOW())",
|
|
||||||
{"id": dir_id, "kb_id": kb_id, "name": dir_name, "parent": parent_id})
|
|
||||||
return json.dumps({"status": "SUCCEEDED", "dir_id": dir_id})
|
|
||||||
except Exception as e:
|
|
||||||
exception(f"dir_create: {e}")
|
|
||||||
return json.dumps({"error": str(e)})
|
|
||||||
|
|
||||||
|
|
||||||
async def dir_delete_handler(request, params_kw, *args, **kwargs):
|
|
||||||
"""删除目录/文件"""
|
|
||||||
env = request._run_ns
|
|
||||||
try:
|
|
||||||
item_id = params_kw.get("item_id", "")
|
|
||||||
if not item_id:
|
|
||||||
return json.dumps({"error": "item_id required"})
|
|
||||||
async with get_sor_context(env, 'rag') as sor:
|
|
||||||
await sor.sqlExe("DELETE FROM document_chunks WHERE id=${id}$ OR doc_id=${id}$", {"id": item_id})
|
|
||||||
return json.dumps({"status": "SUCCEEDED"})
|
|
||||||
except Exception as e:
|
|
||||||
exception(f"dir_delete: {e}")
|
|
||||||
return json.dumps({"error": str(e)})
|
|
||||||
|
|
||||||
|
|
||||||
async def dir_list_handler(request, params_kw, *args, **kwargs):
|
|
||||||
"""列出知识库的目录树"""
|
|
||||||
env = request._run_ns
|
|
||||||
try:
|
|
||||||
kb_id = params_kw.get("kb_id", "")
|
|
||||||
if not kb_id:
|
|
||||||
return json.dumps({"error": "kb_id required"})
|
|
||||||
async with get_sor_context(env, 'rag') as sor:
|
|
||||||
# Get directories (chunk_type='directory')
|
|
||||||
dirs = await sor.sqlExe(
|
|
||||||
"SELECT id, content as label, description as parent_id FROM document_chunks WHERE kb_id=${kb_id}$ AND chunk_type='directory'",
|
|
||||||
{"kb_id": kb_id})
|
|
||||||
# Get files (documents table)
|
|
||||||
docs = await sor.sqlExe(
|
|
||||||
"SELECT id, file_name as label, '' as parent_id FROM documents WHERE kb_id=${kb_id}$",
|
|
||||||
{"kb_id": kb_id})
|
|
||||||
items = []
|
|
||||||
for d in dirs:
|
|
||||||
items.append({"id": d.id, "label": d.label, "parent_id": d.parent_id or "", "type": "dir"})
|
|
||||||
for d in docs:
|
|
||||||
items.append({"id": d.id, "label": d.label, "parent_id": "", "type": "file"})
|
|
||||||
return json.dumps({"status": "SUCCEEDED", "items": items})
|
|
||||||
except Exception as e:
|
|
||||||
exception(f"dir_list: {e}")
|
|
||||||
return json.dumps({"error": str(e)})
|
|
||||||
|
|
||||||
|
|
||||||
async def tag_create_handler(request, params_kw, *args, **kwargs):
|
|
||||||
"""创建标签"""
|
|
||||||
env = request._run_ns
|
|
||||||
try:
|
|
||||||
kb_id = params_kw.get("kb_id", "")
|
|
||||||
name = params_kw.get("name", "").strip()
|
|
||||||
color = params_kw.get("color", "#3b82f6")
|
|
||||||
if not kb_id or not name:
|
|
||||||
return json.dumps({"error": "kb_id and name required"})
|
|
||||||
userorgid = await env.get_userorgid()
|
|
||||||
async with get_sor_context(env, 'rag') as sor:
|
|
||||||
tag_id = uuid.uuid4().hex[:16]
|
|
||||||
await sor.sqlExe(
|
|
||||||
"INSERT INTO tags (id, kb_id, name, color, org_id, created_at) "
|
|
||||||
"VALUES (${id}$, ${kb_id}$, ${name}$, ${color}$, ${org_id}$, NOW())",
|
|
||||||
{"id": tag_id, "kb_id": kb_id, "name": name, "color": color, "org_id": userorgid})
|
|
||||||
return json.dumps({"status": "SUCCEEDED", "tag_id": tag_id, "name": name, "color": color})
|
|
||||||
except Exception as e:
|
|
||||||
exception(f"tag_create: {e}, {format_exc()}")
|
|
||||||
return json.dumps({"error": str(e)})
|
|
||||||
|
|
||||||
|
|
||||||
async def tag_list_handler(request, params_kw, *args, **kwargs):
|
|
||||||
"""列出知识库的所有标签"""
|
|
||||||
env = request._run_ns
|
|
||||||
try:
|
|
||||||
kb_id = params_kw.get("kb_id", "")
|
|
||||||
if not kb_id:
|
|
||||||
return json.dumps({"error": "kb_id required"})
|
|
||||||
userorgid = await env.get_userorgid()
|
|
||||||
async with get_sor_context(env, 'rag') as sor:
|
|
||||||
recs = await sor.R("tags", {"kb_id": kb_id, "org_id": userorgid})
|
|
||||||
tags = [{"id": r.id, "name": r.name, "color": r.color, "created_at": str(r.created_at)} for r in recs]
|
|
||||||
return json.dumps({"status": "SUCCEEDED", "tags": tags}, ensure_ascii=False, default=str)
|
|
||||||
except Exception as e:
|
|
||||||
exception(f"tag_list: {e}, {format_exc()}")
|
|
||||||
return json.dumps({"error": str(e)})
|
|
||||||
|
|
||||||
|
|
||||||
async def tag_delete_handler(request, params_kw, *args, **kwargs):
|
|
||||||
"""删除标签(级联删除关联)"""
|
|
||||||
env = request._run_ns
|
|
||||||
try:
|
|
||||||
tag_id = params_kw.get("tag_id", "")
|
|
||||||
if not tag_id:
|
|
||||||
return json.dumps({"error": "tag_id required"})
|
|
||||||
async with get_sor_context(env, 'rag') as sor:
|
|
||||||
await sor.sqlExe("DELETE FROM media_tags WHERE tag_id=${id}$", {"id": tag_id})
|
|
||||||
await sor.sqlExe("DELETE FROM tags WHERE id=${id}$", {"id": tag_id})
|
|
||||||
return json.dumps({"status": "SUCCEEDED", "tag_id": tag_id})
|
|
||||||
except Exception as e:
|
|
||||||
exception(f"tag_delete: {e}, {format_exc()}")
|
|
||||||
return json.dumps({"error": str(e)})
|
|
||||||
|
|
||||||
|
|
||||||
async def tag_assign_handler(request, params_kw, *args, **kwargs):
|
|
||||||
"""给媒体/人脸/声纹打标签"""
|
|
||||||
env = request._run_ns
|
|
||||||
try:
|
|
||||||
kb_id = params_kw.get("kb_id", "")
|
|
||||||
media_type = params_kw.get("media_type", "") # document / face / voice
|
|
||||||
media_id = params_kw.get("media_id", "")
|
|
||||||
tag_id = params_kw.get("tag_id", "")
|
|
||||||
if not all([kb_id, media_type, media_id, tag_id]):
|
|
||||||
return json.dumps({"error": "kb_id, media_type, media_id, tag_id required"})
|
|
||||||
if media_type not in ("document", "face", "voice"):
|
|
||||||
return json.dumps({"error": "media_type must be document/face/voice"})
|
|
||||||
async with get_sor_context(env, 'rag') as sor:
|
|
||||||
mt_id = uuid.uuid4().hex[:16]
|
|
||||||
await sor.sqlExe(
|
|
||||||
"INSERT INTO media_tags (id, kb_id, media_type, media_id, tag_id, created_at) "
|
|
||||||
"VALUES (${id}$, ${kb_id}$, ${type}$, ${mid}$, ${tid}$, NOW())",
|
|
||||||
{"id": mt_id, "kb_id": kb_id, "type": media_type, "mid": media_id, "tid": tag_id})
|
|
||||||
return json.dumps({"status": "SUCCEEDED", "media_tag_id": mt_id})
|
|
||||||
except Exception as e:
|
|
||||||
exception(f"tag_assign: {e}, {format_exc()}")
|
|
||||||
return json.dumps({"error": str(e)})
|
|
||||||
|
|
||||||
|
|
||||||
async def tag_unassign_handler(request, params_kw, *args, **kwargs):
|
|
||||||
"""取消标签关联"""
|
|
||||||
env = request._run_ns
|
|
||||||
try:
|
|
||||||
media_type = params_kw.get("media_type", "")
|
|
||||||
media_id = params_kw.get("media_id", "")
|
|
||||||
tag_id = params_kw.get("tag_id", "")
|
|
||||||
if not all([media_type, media_id, tag_id]):
|
|
||||||
return json.dumps({"error": "media_type, media_id, tag_id required"})
|
|
||||||
async with get_sor_context(env, 'rag') as sor:
|
|
||||||
await sor.sqlExe(
|
|
||||||
"DELETE FROM media_tags WHERE media_type=${type}$ AND media_id=${mid}$ AND tag_id=${tid}$",
|
|
||||||
{"type": media_type, "mid": media_id, "tid": tag_id})
|
|
||||||
return json.dumps({"status": "SUCCEEDED"})
|
|
||||||
except Exception as e:
|
|
||||||
exception(f"tag_unassign: {e}, {format_exc()}")
|
|
||||||
return json.dumps({"error": str(e)})
|
|
||||||
|
|
||||||
|
|
||||||
async def tag_media_tags_handler(request, params_kw, *args, **kwargs):
|
|
||||||
"""查询某媒体的所有标签"""
|
|
||||||
env = request._run_ns
|
|
||||||
try:
|
|
||||||
media_type = params_kw.get("media_type", "")
|
|
||||||
media_id = params_kw.get("media_id", "")
|
|
||||||
if not all([media_type, media_id]):
|
|
||||||
return json.dumps({"error": "media_type and media_id required"})
|
|
||||||
async with get_sor_context(env, 'rag') as sor:
|
|
||||||
recs = await sor.sqlExe(
|
|
||||||
"SELECT t.id, t.name, t.color FROM media_tags mt "
|
|
||||||
"JOIN tags t ON mt.tag_id=t.id "
|
|
||||||
"WHERE mt.media_type=${type}$ AND mt.media_id=${mid}$",
|
|
||||||
{"type": media_type, "mid": media_id})
|
|
||||||
tags = [{"id": r.id, "name": r.name, "color": r.color} for r in recs]
|
|
||||||
return json.dumps({"status": "SUCCEEDED", "tags": tags})
|
|
||||||
except Exception as e:
|
|
||||||
exception(f"tag_media_tags: {e}, {format_exc()}")
|
|
||||||
return json.dumps({"error": str(e)})
|
|
||||||
|
|
||||||
|
|
||||||
async def tag_search_handler(request, params_kw, *args, **kwargs):
|
|
||||||
"""组合标签检索:按 tag_ids 过滤,再语义检索"""
|
|
||||||
env = request._run_ns
|
|
||||||
try:
|
|
||||||
query = params_kw.get("query", "")
|
|
||||||
kb_id = params_kw.get("kb_id", "")
|
|
||||||
tag_ids_str = params_kw.get("tag_ids", "") # comma-separated tag IDs
|
|
||||||
top_k = int(params_kw.get("top_k", 5))
|
|
||||||
match_mode = params_kw.get("match_mode", "any") # any / all
|
|
||||||
if not kb_id:
|
|
||||||
return json.dumps({"error": "kb_id required"})
|
|
||||||
async with get_sor_context(env, 'rag') as sor:
|
|
||||||
if tag_ids_str:
|
|
||||||
tag_ids = [t.strip() for t in tag_ids_str.split(",") if t.strip()]
|
|
||||||
media_ids_by_tag = []
|
|
||||||
for tid in tag_ids:
|
|
||||||
recs = await sor.sqlExe(
|
|
||||||
"SELECT media_type, media_id FROM media_tags WHERE kb_id=${kb_id}$ AND tag_id=${tid}$",
|
|
||||||
{"kb_id": kb_id, "tid": tid})
|
|
||||||
mids = {(r.media_type, r.media_id) for r in recs}
|
|
||||||
media_ids_by_tag.append(mids)
|
|
||||||
if match_mode == "all":
|
|
||||||
matched = media_ids_by_tag[0]
|
|
||||||
for s in media_ids_by_tag[1:]:
|
|
||||||
matched = matched & s
|
|
||||||
else:
|
|
||||||
matched = set()
|
|
||||||
for s in media_ids_by_tag:
|
|
||||||
matched |= s
|
|
||||||
if not matched:
|
|
||||||
return json.dumps({"status": "SUCCEEDED", "results": [], "message": "no media match tags"})
|
|
||||||
doc_ids = [mid for mt, mid in matched if mt == "document"]
|
|
||||||
face_ids = [mid for mt, mid in matched if mt == "face"]
|
|
||||||
voice_ids = [mid for mt, mid in matched if mt == "voice"]
|
|
||||||
results = []
|
|
||||||
if doc_ids:
|
|
||||||
docs = await sor.sqlExe(
|
|
||||||
"SELECT id, file_name, file_type, file_size, status, created_at FROM documents WHERE id IN (${ids}$)",
|
|
||||||
{"ids": doc_ids})
|
|
||||||
for d in docs:
|
|
||||||
results.append({"type": "document", "id": d.id, "name": d.file_name, "file_type": d.file_type, "size": d.file_size, "status": d.status, "created_at": str(d.created_at)})
|
|
||||||
if face_ids:
|
|
||||||
faces = await sor.sqlExe(
|
|
||||||
"SELECT id, name, description, face_embedding_id, created_at FROM entities WHERE id IN (${ids}$) AND entity_type='person'",
|
|
||||||
{"ids": face_ids})
|
|
||||||
for f in faces:
|
|
||||||
results.append({"type": "face", "id": f.id, "name": f.name, "description": f.description, "created_at": str(f.created_at)})
|
|
||||||
if voice_ids:
|
|
||||||
voices = await sor.sqlExe(
|
|
||||||
"SELECT id, name, description, voice_embedding_id, created_at FROM entities WHERE id IN (${ids}$) AND entity_type='voice'",
|
|
||||||
{"ids": voice_ids})
|
|
||||||
for v in voices:
|
|
||||||
results.append({"type": "voice", "id": v.id, "name": v.name, "description": v.description, "created_at": str(v.created_at)})
|
|
||||||
if query:
|
|
||||||
import urllib.request
|
|
||||||
tag_filtered_docs = [r for r in results if r["type"] == "document"]
|
|
||||||
vdb_docs = []
|
|
||||||
for doc in tag_filtered_docs:
|
|
||||||
chunks = await sor.sqlExe(
|
|
||||||
"SELECT content FROM document_chunks WHERE doc_id=${id}$ LIMIT 3",
|
|
||||||
{"id": doc["id"]})
|
|
||||||
for c in chunks:
|
|
||||||
vdb_docs.append({"doc_id": doc["id"], "content": c.content})
|
|
||||||
results = {"documents": [{"id": r["id"], "name": r["name"], "file_type": r["file_type"]} for r in tag_filtered_docs],
|
|
||||||
"faces": [r for r in results if r["type"] == "face"],
|
|
||||||
"voices": [r for r in results if r["type"] == "voice"],
|
|
||||||
"chunks": vdb_docs,
|
|
||||||
"query": query,
|
|
||||||
"tag_ids": tag_ids,
|
|
||||||
"match_mode": match_mode}
|
|
||||||
return json.dumps({"status": "SUCCEEDED", "results": results, "tag_ids": tag_ids, "match_mode": match_mode}, ensure_ascii=False, default=str)
|
|
||||||
else:
|
|
||||||
return json.dumps({"status": "SUCCEEDED", "results": [], "message": "no tag_ids provided"})
|
|
||||||
except Exception as e:
|
|
||||||
exception(f"tag_search: {e}, {format_exc()}")
|
|
||||||
return json.dumps({"error": str(e)})
|
|
||||||
|
|
||||||
|
|
||||||
def init_ragserver():
|
def init_ragserver():
|
||||||
env = ServerEnv()
|
env = ServerEnv()
|
||||||
rf = RegisterFunction()
|
rf = RegisterFunction()
|
||||||
rf.register("status", status_handler)
|
rf.register("status", status_handler)
|
||||||
rf.register("kb_list", kb_list_handler)
|
|
||||||
rf.register("engines", engines_handler)
|
# 加载 rag 业务模块
|
||||||
rf.register("search", search_handler)
|
from rag.init import init_rag_module
|
||||||
rf.register("doc_upload", doc_upload_handler)
|
init_rag_module()
|
||||||
rf.register("doc_delete", doc_delete_handler)
|
|
||||||
rf.register("dir_create", dir_create_handler)
|
|
||||||
rf.register("dir_delete", dir_delete_handler)
|
|
||||||
rf.register("dir_list", dir_list_handler)
|
|
||||||
rf.register("tag_create", tag_create_handler)
|
|
||||||
rf.register("tag_list", tag_list_handler)
|
|
||||||
rf.register("tag_delete", tag_delete_handler)
|
|
||||||
rf.register("tag_assign", tag_assign_handler)
|
|
||||||
rf.register("tag_unassign", tag_unassign_handler)
|
|
||||||
rf.register("tag_media_tags", tag_media_tags_handler)
|
|
||||||
rf.register("tag_search", tag_search_handler)
|
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"tblname": "documents",
|
|
||||||
"params": {
|
|
||||||
"title": "文档管理",
|
|
||||||
"browserfields": [
|
|
||||||
{"field": "file_name", "title": "文件名", "width": "25%"},
|
|
||||||
{"field": "file_type", "title": "类型", "width": "10%"},
|
|
||||||
{"field": "file_size", "title": "大小", "width": "10%"},
|
|
||||||
{"field": "chunk_count", "title": "分片数", "width": "8%"},
|
|
||||||
{"field": "status", "title": "状态", "width": "10%"},
|
|
||||||
{"field": "kb_id", "title": "知识库", "width": "17%"},
|
|
||||||
{"field": "created_at", "title": "上传时间", "width": "20%"}
|
|
||||||
],
|
|
||||||
"editfields": [
|
|
||||||
{"field": "file_name", "uitype": "Text", "required": true},
|
|
||||||
{"field": "kb_id", "uitype": "Text", "required": true},
|
|
||||||
{"field": "file_type", "uitype": "Text"}
|
|
||||||
],
|
|
||||||
"searchfields": ["file_name"],
|
|
||||||
"sort": "created_at desc",
|
|
||||||
"data_filter": {
|
|
||||||
"org_id": "{{userorgid}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"tblname": "engine_configs",
|
|
||||||
"params": {
|
|
||||||
"title": "引擎配置",
|
|
||||||
"browserfields": [
|
|
||||||
{"field": "engine_type", "title": "引擎类型", "width": "15%"},
|
|
||||||
{"field": "engine_name", "title": "名称", "width": "15%"},
|
|
||||||
{"field": "endpoint_url", "title": "服务地址", "width": "25%"},
|
|
||||||
{"field": "model_name", "title": "模型", "width": "15%"},
|
|
||||||
{"field": "is_default", "title": "默认", "width": "8%"},
|
|
||||||
{"field": "status", "title": "状态", "width": "10%"},
|
|
||||||
{"field": "org_id", "title": "机构", "width": "12%"}
|
|
||||||
],
|
|
||||||
"editfields": [
|
|
||||||
{"field": "engine_type", "uitype": "Text", "required": true},
|
|
||||||
{"field": "engine_name", "uitype": "Text", "required": true},
|
|
||||||
{"field": "endpoint_url", "uitype": "Text"},
|
|
||||||
{"field": "api_key", "uitype": "Text"},
|
|
||||||
{"field": "model_name", "uitype": "Text"},
|
|
||||||
{"field": "is_default", "uitype": "Text"},
|
|
||||||
{"field": "config_json", "uitype": "Text"}
|
|
||||||
],
|
|
||||||
"searchfields": ["engine_type", "engine_name", "endpoint_url"],
|
|
||||||
"sort": "engine_type, priority desc"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"tblname": "knowledge_bases",
|
|
||||||
"params": {
|
|
||||||
"title": "知识库管理",
|
|
||||||
"browserfields": [
|
|
||||||
{"field": "name", "title": "名称", "width": "20%"},
|
|
||||||
{"field": "description", "title": "描述", "width": "30%"},
|
|
||||||
{"field": "doc_count", "title": "文档数", "width": "10%"},
|
|
||||||
{"field": "total_size", "title": "大小", "width": "10%"},
|
|
||||||
{"field": "embedding_engine", "title": "向量引擎", "width": "15%"},
|
|
||||||
{"field": "created_at", "title": "创建时间", "width": "15%"}
|
|
||||||
],
|
|
||||||
"editfields": [
|
|
||||||
{"field": "name", "uitype": "Text", "required": true},
|
|
||||||
{"field": "description", "uitype": "Text"},
|
|
||||||
{"field": "embedding_engine", "uitype": "Text", "default": "clip-vith14"},
|
|
||||||
{"field": "vdb_collection", "uitype": "Text"},
|
|
||||||
{"field": "graph_name", "uitype": "Text"}
|
|
||||||
],
|
|
||||||
"searchfields": ["name", "description"],
|
|
||||||
"sort": "created_at desc",
|
|
||||||
"data_filter": {
|
|
||||||
"org_id": "{{userorgid}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
{
|
|
||||||
"tblname": "subscriptions",
|
|
||||||
"params": {
|
|
||||||
"title": "订阅管理",
|
|
||||||
"browserfields": [
|
|
||||||
{"field": "org_id", "title": "机构", "width": "15%"},
|
|
||||||
{"field": "plan_name", "title": "套餐", "width": "15%"},
|
|
||||||
{"field": "disk_quota_bytes", "title": "磁盘配额", "width": "12%"},
|
|
||||||
{"field": "disk_used_bytes", "title": "已用磁盘", "width": "12%"},
|
|
||||||
{"field": "doc_used", "title": "文档数", "width": "10%"},
|
|
||||||
{"field": "start_date", "title": "开始", "width": "12%"},
|
|
||||||
{"field": "end_date", "title": "结束", "width": "12%"},
|
|
||||||
{"field": "status", "title": "状态", "width": "12%"}
|
|
||||||
],
|
|
||||||
"editfields": [
|
|
||||||
{"field": "org_id", "uitype": "Text", "required": true},
|
|
||||||
{"field": "plan_name", "uitype": "Text", "required": true},
|
|
||||||
{"field": "disk_quota_bytes", "uitype": "Text", "default": "1073741824"},
|
|
||||||
{"field": "doc_quota", "uitype": "Text"},
|
|
||||||
{"field": "kb_quota", "uitype": "Text"},
|
|
||||||
{"field": "api_call_quota", "uitype": "Text"},
|
|
||||||
{"field": "start_date", "uitype": "Text"},
|
|
||||||
{"field": "end_date", "uitype": "Text"}
|
|
||||||
],
|
|
||||||
"searchfields": ["org_id", "plan_name"],
|
|
||||||
"sort": "created_at desc"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"tblname": "tags",
|
|
||||||
"params": {
|
|
||||||
"title": "标签管理",
|
|
||||||
"browserfields": [
|
|
||||||
{"field": "name", "title": "标签名", "width": "25%"},
|
|
||||||
{"field": "color", "title": "颜色", "width": "15%"},
|
|
||||||
{"field": "kb_id", "title": "知识库", "width": "30%"},
|
|
||||||
{"field": "created_at", "title": "创建时间", "width": "30%"}
|
|
||||||
],
|
|
||||||
"editfields": [
|
|
||||||
{"field": "name", "uitype": "Text", "required": true, "label": "标签名"},
|
|
||||||
{"field": "kb_id", "uitype": "Text", "required": true, "label": "知识库ID"},
|
|
||||||
{"field": "color", "uitype": "Text", "default": "#3b82f6", "label": "颜色(#hex)"}
|
|
||||||
],
|
|
||||||
"searchfields": ["name"],
|
|
||||||
"sort": "created_at desc",
|
|
||||||
"data_filter": {
|
|
||||||
"org_id": "{{userorgid}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"summary": [
|
|
||||||
{
|
|
||||||
"name": "document_chunks",
|
|
||||||
"title": "文档分片",
|
|
||||||
"primary": ["id"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{"name": "id", "title": "ID", "type": "str", "length": 32},
|
|
||||||
{"name": "doc_id", "title": "文档ID", "type": "str", "length": 32},
|
|
||||||
{"name": "kb_id", "title": "知识库ID", "type": "str", "length": 32},
|
|
||||||
{"name": "chunk_index", "title": "分片序号", "type": "int", "default": 0},
|
|
||||||
{"name": "chunk_type", "title": "分片类型", "type": "str", "length": 32},
|
|
||||||
{"name": "content", "title": "文本内容", "type": "text", "length": 65535},
|
|
||||||
{"name": "description", "title": "描述", "type": "text", "length": 65535},
|
|
||||||
{"name": "vector_id", "title": "向量库ID", "type": "str", "length": 128},
|
|
||||||
{"name": "start_offset", "title": "起始偏移", "type": "int", "default": 0},
|
|
||||||
{"name": "end_offset", "title": "结束偏移", "type": "int", "default": 0},
|
|
||||||
{"name": "time_start", "title": "时间起始(秒)", "type": "float", "length": 10, "dec": 3},
|
|
||||||
{"name": "time_end", "title": "时间结束(秒)", "type": "float", "length": 10, "dec": 3},
|
|
||||||
{"name": "metadata", "title": "元数据JSON", "type": "text", "length": 2000},
|
|
||||||
{"name": "created_at", "title": "创建时间", "type": "datetime"}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
{"name": "idx_chunk_doc", "idxtype": "index", "idxfields": ["doc_id"]},
|
|
||||||
{"name": "idx_chunk_kb", "idxtype": "index", "idxfields": ["kb_id"]},
|
|
||||||
{"name": "idx_chunk_vector", "idxtype": "index", "idxfields": ["vector_id"]}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
{
|
|
||||||
"summary": [
|
|
||||||
{
|
|
||||||
"name": "documents",
|
|
||||||
"title": "文档",
|
|
||||||
"primary": ["id"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{"name": "id", "title": "ID", "type": "str", "length": 32},
|
|
||||||
{"name": "kb_id", "title": "知识库ID", "type": "str", "length": 32},
|
|
||||||
{"name": "file_name", "title": "文件名", "type": "str", "length": 255},
|
|
||||||
{"name": "file_type", "title": "文件类型", "type": "str", "length": 32},
|
|
||||||
{"name": "file_size", "title": "文件大小", "type": "int", "default": 0},
|
|
||||||
{"name": "file_path", "title": "文件路径", "type": "str", "length": 500},
|
|
||||||
{"name": "mime_type", "title": "MIME类型", "type": "str", "length": 128},
|
|
||||||
{"name": "chunk_count", "title": "分片数", "type": "int", "default": 0},
|
|
||||||
{"name": "embed_dim", "title": "向量维度", "type": "int", "default": 1024},
|
|
||||||
{"name": "metadata", "title": "元数据JSON", "type": "text", "length": 4000},
|
|
||||||
{"name": "status", "title": "状态", "type": "str", "length": 16, "default": "pending"},
|
|
||||||
{"name": "error_msg", "title": "错误信息", "type": "text", "length": 2000},
|
|
||||||
{"name": "org_id", "title": "所属机构", "type": "str", "length": 32},
|
|
||||||
{"name": "created_by", "title": "创建人", "type": "str", "length": 32},
|
|
||||||
{"name": "created_at", "title": "创建时间", "type": "datetime"},
|
|
||||||
{"name": "updated_at", "title": "更新时间", "type": "datetime"}
|
|
||||||
],
|
|
||||||
"codes": [
|
|
||||||
{"field": "file_type", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='file_type'"},
|
|
||||||
{"field": "status", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='doc_status'"},
|
|
||||||
{"field": "kb_id", "table": "knowledge_bases", "valuefield": "id", "textfield": "name"}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
{"name": "idx_doc_kb", "idxtype": "index", "idxfields": ["kb_id"]},
|
|
||||||
{"name": "idx_doc_org", "idxtype": "index", "idxfields": ["org_id"]},
|
|
||||||
{"name": "idx_doc_status", "idxtype": "index", "idxfields": ["status"]}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"summary": [
|
|
||||||
{
|
|
||||||
"name": "engine_configs",
|
|
||||||
"title": "引擎配置",
|
|
||||||
"primary": ["id"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{"name": "id", "title": "ID", "type": "str", "length": 32},
|
|
||||||
{"name": "org_id", "title": "机构ID", "type": "str", "length": 32, "nullable": "yes"},
|
|
||||||
{"name": "engine_type", "title": "引擎类型", "type": "str", "length": 32},
|
|
||||||
{"name": "engine_name", "title": "引擎名称", "type": "str", "length": 64},
|
|
||||||
{"name": "endpoint_url", "title": "服务地址", "type": "str", "length": 255},
|
|
||||||
{"name": "api_key", "title": "API密钥", "type": "str", "length": 255},
|
|
||||||
{"name": "model_name", "title": "模型名称", "type": "str", "length": 128},
|
|
||||||
{"name": "config_json", "title": "扩展配置JSON", "type": "text", "length": 4000},
|
|
||||||
{"name": "is_default", "title": "是否默认", "type": "int", "default": 0},
|
|
||||||
{"name": "priority", "title": "优先级", "type": "int", "default": 0},
|
|
||||||
{"name": "status", "title": "状态", "type": "str", "length": 16, "default": "active"},
|
|
||||||
{"name": "health_check_at", "title": "最近健康检查", "type": "datetime"},
|
|
||||||
{"name": "created_at", "title": "创建时间", "type": "datetime"},
|
|
||||||
{"name": "updated_at", "title": "更新时间", "type": "datetime"}
|
|
||||||
],
|
|
||||||
"codes": [
|
|
||||||
{"field": "engine_type", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='engine_type'"},
|
|
||||||
{"field": "status", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='engine_status'"}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
{"name": "idx_eng_type", "idxtype": "index", "idxfields": ["engine_type", "is_default"]},
|
|
||||||
{"name": "idx_eng_org", "idxtype": "index", "idxfields": ["org_id", "engine_type"]}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"summary": [
|
|
||||||
{
|
|
||||||
"name": "entities",
|
|
||||||
"title": "实体",
|
|
||||||
"primary": ["id"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{"name": "id", "title": "ID", "type": "str", "length": 32},
|
|
||||||
{"name": "kb_id", "title": "知识库ID", "type": "str", "length": 32},
|
|
||||||
{"name": "name", "title": "实体名", "type": "str", "length": 255},
|
|
||||||
{"name": "entity_type", "title": "实体类型", "type": "str", "length": 64},
|
|
||||||
{"name": "description", "title": "描述", "type": "text", "length": 2000},
|
|
||||||
{"name": "graph_node_id", "title": "图节点ID", "type": "str", "length": 128},
|
|
||||||
{"name": "source_doc_ids", "title": "来源文档ID列表", "type": "text", "length": 2000},
|
|
||||||
{"name": "face_embedding_id", "title": "人脸特征ID", "type": "str", "length": 128},
|
|
||||||
{"name": "voice_embedding_id", "title": "声纹特征ID", "type": "str", "length": 128},
|
|
||||||
{"name": "metadata", "title": "元数据JSON", "type": "text", "length": 2000},
|
|
||||||
{"name": "org_id", "title": "所属机构", "type": "str", "length": 32},
|
|
||||||
{"name": "created_at", "title": "创建时间", "type": "datetime"},
|
|
||||||
{"name": "updated_at", "title": "更新时间", "type": "datetime"}
|
|
||||||
],
|
|
||||||
"codes": [
|
|
||||||
{"field": "entity_type", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='entity_type'"},
|
|
||||||
{"field": "kb_id", "table": "knowledge_bases", "valuefield": "id", "textfield": "name"}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
{"name": "idx_entity_kb", "idxtype": "index", "idxfields": ["kb_id"]},
|
|
||||||
{"name": "idx_entity_name", "idxtype": "index", "idxfields": ["kb_id", "name"]},
|
|
||||||
{"name": "idx_entity_type", "idxtype": "index", "idxfields": ["kb_id", "entity_type"]},
|
|
||||||
{"name": "idx_entity_face", "idxtype": "index", "idxfields": ["face_embedding_id"]},
|
|
||||||
{"name": "idx_entity_voice", "idxtype": "index", "idxfields": ["voice_embedding_id"]}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"summary": [
|
|
||||||
{
|
|
||||||
"name": "entity_relations",
|
|
||||||
"title": "实体关系",
|
|
||||||
"primary": ["id"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{"name": "id", "title": "ID", "type": "str", "length": 32},
|
|
||||||
{"name": "kb_id", "title": "知识库ID", "type": "str", "length": 32},
|
|
||||||
{"name": "subject_id", "title": "主体实体ID", "type": "str", "length": 32},
|
|
||||||
{"name": "object_id", "title": "客体实体ID", "type": "str", "length": 32},
|
|
||||||
{"name": "relation_type", "title": "关系类型", "type": "str", "length": 64},
|
|
||||||
{"name": "description", "title": "关系描述", "type": "text", "length": 2000},
|
|
||||||
{"name": "graph_edge_id", "title": "图边ID", "type": "str", "length": 128},
|
|
||||||
{"name": "source_chunk_ids", "title": "来源分片ID列表", "type": "text", "length": 2000},
|
|
||||||
{"name": "org_id", "title": "所属机构", "type": "str", "length": 32},
|
|
||||||
{"name": "created_at", "title": "创建时间", "type": "datetime"}
|
|
||||||
],
|
|
||||||
"codes": [
|
|
||||||
{"field": "kb_id", "table": "knowledge_bases", "valuefield": "id", "textfield": "name"}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
{"name": "idx_rel_kb", "idxtype": "index", "idxfields": ["kb_id"]},
|
|
||||||
{"name": "idx_rel_subject", "idxtype": "index", "idxfields": ["subject_id"]},
|
|
||||||
{"name": "idx_rel_object", "idxtype": "index", "idxfields": ["object_id"]}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"summary": [
|
|
||||||
{
|
|
||||||
"name": "knowledge_bases",
|
|
||||||
"title": "知识库",
|
|
||||||
"primary": ["id"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{"name": "id", "title": "ID", "type": "str", "length": 32},
|
|
||||||
{"name": "name", "title": "名称", "type": "str", "length": 100},
|
|
||||||
{"name": "description", "title": "描述", "type": "text", "length": 2000},
|
|
||||||
{"name": "org_id", "title": "所属机构", "type": "str", "length": 32},
|
|
||||||
{"name": "embedding_engine", "title": "向量化引擎", "type": "str", "length": 64, "default": "clip-vith14"},
|
|
||||||
{"name": "vdb_collection", "title": "向量库集合名", "type": "str", "length": 128},
|
|
||||||
{"name": "graph_name", "title": "图名称", "type": "str", "length": 128},
|
|
||||||
{"name": "doc_count", "title": "文档数", "type": "int", "default": 0},
|
|
||||||
{"name": "chunk_count", "title": "分片数", "type": "int", "default": 0},
|
|
||||||
{"name": "total_size", "title": "总大小(字节)", "type": "int", "default": 0},
|
|
||||||
{"name": "status", "title": "状态", "type": "str", "length": 16, "default": "active"},
|
|
||||||
{"name": "created_by", "title": "创建人", "type": "str", "length": 32},
|
|
||||||
{"name": "created_at", "title": "创建时间", "type": "datetime"},
|
|
||||||
{"name": "updated_at", "title": "更新时间", "type": "datetime"}
|
|
||||||
],
|
|
||||||
"codes": [
|
|
||||||
{"field": "status", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='kb_status'"}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
{"name": "idx_kb_org", "idxtype": "index", "idxfields": ["org_id"]},
|
|
||||||
{"name": "idx_kb_name", "idxtype": "unique", "idxfields": ["org_id", "name"]}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
{
|
|
||||||
"summary": [
|
|
||||||
{
|
|
||||||
"name": "media_tags",
|
|
||||||
"title": "媒体标签关联",
|
|
||||||
"primary": ["id"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{"name": "id", "title": "ID", "type": "str", "length": 32},
|
|
||||||
{"name": "kb_id", "title": "知识库ID", "type": "str", "length": 32},
|
|
||||||
{"name": "media_type", "title": "媒体类型", "type": "str", "length": 32},
|
|
||||||
{"name": "media_id", "title": "媒体ID", "type": "str", "length": 32},
|
|
||||||
{"name": "tag_id", "title": "标签ID", "type": "str", "length": 32},
|
|
||||||
{"name": "created_at", "title": "创建时间", "type": "datetime"}
|
|
||||||
],
|
|
||||||
"codes": [
|
|
||||||
{"field": "media_type", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='media_type'"},
|
|
||||||
{"field": "tag_id", "table": "tags", "valuefield": "id", "textfield": "name"},
|
|
||||||
{"field": "kb_id", "table": "knowledge_bases", "valuefield": "id", "textfield": "name"}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
{"name": "idx_mt_media", "idxtype": "index", "idxfields": ["media_type", "media_id"]},
|
|
||||||
{"name": "idx_mt_tag", "idxtype": "index", "idxfields": ["tag_id"]},
|
|
||||||
{"name": "idx_mt_kb", "idxtype": "index", "idxfields": ["kb_id"]},
|
|
||||||
{"name": "idx_mt_unique", "idxtype": "unique", "idxfields": ["media_type", "media_id", "tag_id"]}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
{
|
|
||||||
"summary": [
|
|
||||||
{
|
|
||||||
"name": "subscriptions",
|
|
||||||
"title": "租户订阅",
|
|
||||||
"primary": ["id"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{"name": "id", "title": "ID", "type": "str", "length": 32},
|
|
||||||
{"name": "org_id", "title": "机构ID", "type": "str", "length": 32},
|
|
||||||
{"name": "plan_name", "title": "套餐名称", "type": "str", "length": 64},
|
|
||||||
{"name": "disk_quota_bytes", "title": "磁盘配额(字节)", "type": "int", "default": 1073741824},
|
|
||||||
{"name": "disk_used_bytes", "title": "已用磁盘(字节)", "type": "int", "default": 0},
|
|
||||||
{"name": "doc_quota", "title": "文档数配额", "type": "int", "default": 1000},
|
|
||||||
{"name": "doc_used", "title": "已用文档数", "type": "int", "default": 0},
|
|
||||||
{"name": "kb_quota", "title": "知识库数配额", "type": "int", "default": 10},
|
|
||||||
{"name": "kb_used", "title": "已用知识库数", "type": "int", "default": 0},
|
|
||||||
{"name": "api_call_quota", "title": "API调用配额(月)", "type": "int", "default": 10000},
|
|
||||||
{"name": "api_call_used", "title": "已用API调用(月)", "type": "int", "default": 0},
|
|
||||||
{"name": "start_date", "title": "开始日期", "type": "date"},
|
|
||||||
{"name": "end_date", "title": "结束日期", "type": "date"},
|
|
||||||
{"name": "status", "title": "状态", "type": "str", "length": 16, "default": "active"},
|
|
||||||
{"name": "created_at", "title": "创建时间", "type": "datetime"},
|
|
||||||
{"name": "updated_at", "title": "更新时间", "type": "datetime"}
|
|
||||||
],
|
|
||||||
"codes": [
|
|
||||||
{"field": "status", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='sub_status'"}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
{"name": "idx_sub_org", "idxtype": "unique", "idxfields": ["org_id"]},
|
|
||||||
{"name": "idx_sub_status", "idxtype": "index", "idxfields": ["status"]}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"summary": [
|
|
||||||
{
|
|
||||||
"name": "tags",
|
|
||||||
"title": "标签",
|
|
||||||
"primary": ["id"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{"name": "id", "title": "ID", "type": "str", "length": 32},
|
|
||||||
{"name": "kb_id", "title": "知识库ID", "type": "str", "length": 32},
|
|
||||||
{"name": "name", "title": "标签名", "type": "str", "length": 64},
|
|
||||||
{"name": "color", "title": "颜色", "type": "str", "length": 16, "default": "#3b82f6"},
|
|
||||||
{"name": "org_id", "title": "所属机构", "type": "str", "length": 32},
|
|
||||||
{"name": "created_at", "title": "创建时间", "type": "datetime"}
|
|
||||||
],
|
|
||||||
"codes": [
|
|
||||||
{"field": "kb_id", "table": "knowledge_bases", "valuefield": "id", "textfield": "name"}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
{"name": "idx_tag_kb", "idxtype": "index", "idxfields": ["kb_id"]},
|
|
||||||
{"name": "idx_tag_name", "idxtype": "unique", "idxfields": ["kb_id", "name"]}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"summary": [
|
|
||||||
{
|
|
||||||
"name": "usage_logs",
|
|
||||||
"title": "用量日志",
|
|
||||||
"primary": ["id"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{"name": "id", "title": "ID", "type": "str", "length": 32},
|
|
||||||
{"name": "org_id", "title": "机构ID", "type": "str", "length": 32},
|
|
||||||
{"name": "kb_id", "title": "知识库ID", "type": "str", "length": 32},
|
|
||||||
{"name": "operation", "title": "操作类型", "type": "str", "length": 32},
|
|
||||||
{"name": "bytes_delta", "title": "磁盘变化(字节)", "type": "int", "default": 0},
|
|
||||||
{"name": "bytes_total", "title": "累计磁盘", "type": "int", "default": 0},
|
|
||||||
{"name": "api_calls", "title": "API调用数", "type": "int", "default": 1},
|
|
||||||
{"name": "engine_type", "title": "引擎类型", "type": "str", "length": 32},
|
|
||||||
{"name": "tokens_used", "title": "Token消耗", "type": "int", "default": 0},
|
|
||||||
{"name": "cost_estimate", "title": "预估费用", "type": "float", "length": 20, "dec": 4},
|
|
||||||
{"name": "detail", "title": "详情JSON", "type": "text", "length": 2000},
|
|
||||||
{"name": "created_at", "title": "创建时间", "type": "datetime"}
|
|
||||||
],
|
|
||||||
"codes": [
|
|
||||||
{"field": "operation", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='usage_op'"}
|
|
||||||
],
|
|
||||||
"indexes": [
|
|
||||||
{"name": "idx_usage_org", "idxtype": "index", "idxfields": ["org_id", "created_at"]},
|
|
||||||
{"name": "idx_usage_kb", "idxtype": "index", "idxfields": ["kb_id"]}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"cheight": 40,
|
|
||||||
"width": "100%",
|
|
||||||
"padding": "16px",
|
|
||||||
"spacing": "12px"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "HBox",
|
|
||||||
"options": {
|
|
||||||
"width": "100%",
|
|
||||||
"justifyContent": "space-between",
|
|
||||||
"alignItems": "center"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {"spacing": "4px"},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "文档管理", "cfontsize": 22, "fontWeight": "bold", "color": "#333"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "上传文本、图片、音频、视频文件", "cfontsize": 14, "color": "#888"}}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Button",
|
|
||||||
"options": {
|
|
||||||
"label": "📁 上传文件",
|
|
||||||
"bgcolor": "#50b86c",
|
|
||||||
"color": "#fff"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"width": "100%",
|
|
||||||
"cheight": 8,
|
|
||||||
"bgcolor": "#f8f9fa",
|
|
||||||
"padding": "24px",
|
|
||||||
"css": "card",
|
|
||||||
"border": "2px dashed #d0d0d0",
|
|
||||||
"alignItems": "center",
|
|
||||||
"justifyContent": "center"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "📂", "cfontsize": 32}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "拖拽文件到此处上传", "cfontsize": 16, "color": "#888"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "支持: TXT, PDF, JPG, PNG, MP3, WAV, MP4, AVI", "cfontsize": 12, "color": "#aaa"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "单个文件最大 500MB · 自动向量化入库", "cfontsize": 12, "color": "#aaa"}}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"text": "暂无文档",
|
|
||||||
"color": "#aaa",
|
|
||||||
"cfontsize": 14,
|
|
||||||
"halign": "center"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,148 +0,0 @@
|
|||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"cheight": 40,
|
|
||||||
"width": "100%",
|
|
||||||
"padding": "16px",
|
|
||||||
"spacing": "16px"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"text": "引擎配置",
|
|
||||||
"cfontsize": 22,
|
|
||||||
"fontWeight": "bold",
|
|
||||||
"color": "#333"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"text": "管理多媒体RAG的各层引擎,支持租户级自定义覆盖",
|
|
||||||
"color": "#888",
|
|
||||||
"cfontsize": 14
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "HBox",
|
|
||||||
"options": {
|
|
||||||
"width": "100%",
|
|
||||||
"spacing": "12px",
|
|
||||||
"wrap": true
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"cwidth": 20,
|
|
||||||
"cheight": 10,
|
|
||||||
"bgcolor": "#f8f9fa",
|
|
||||||
"padding": "12px",
|
|
||||||
"css": "card"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "🔤 向量化引擎", "cfontsize": 16, "fontWeight": "bold"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "CLIP ViT-H-14", "cfontsize": 14, "color": "#4a90d9"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "1024维 · 图文跨模态", "cfontsize": 12, "color": "#888"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "状态: ✅ 运行中", "cfontsize": 12, "color": "#50b86c"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "端口: 9086", "cfontsize": 12, "color": "#aaa"}}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"cwidth": 20,
|
|
||||||
"cheight": 10,
|
|
||||||
"bgcolor": "#f8f9fa",
|
|
||||||
"padding": "12px",
|
|
||||||
"css": "card"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "🗄️ 向量数据库", "cfontsize": 16, "fontWeight": "bold"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "Milvus Lite", "cfontsize": 14, "color": "#4a90d9"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "COSINE度量 · HNSW索引", "cfontsize": 12, "color": "#888"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "状态: ✅ 运行中", "cfontsize": 12, "color": "#50b86c"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "端口: 8886", "cfontsize": 12, "color": "#aaa"}}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"cwidth": 20,
|
|
||||||
"cheight": 10,
|
|
||||||
"bgcolor": "#f8f9fa",
|
|
||||||
"padding": "12px",
|
|
||||||
"css": "card"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "📊 重排引擎", "cfontsize": 16, "fontWeight": "bold"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "BGE Reranker v2-m3", "cfontsize": 14, "color": "#4a90d9"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "Cross-encoder精排", "cfontsize": 12, "color": "#888"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "状态: ✅ 运行中", "cfontsize": 12, "color": "#50b86c"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "端口: 9090", "cfontsize": 12, "color": "#aaa"}}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"cwidth": 20,
|
|
||||||
"cheight": 10,
|
|
||||||
"bgcolor": "#f8f9fa",
|
|
||||||
"padding": "12px",
|
|
||||||
"css": "card"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "🕸️ 知识图谱", "cfontsize": 16, "fontWeight": "bold"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "NetworkX", "cfontsize": 14, "color": "#4a90d9"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "实体关系 · 邻居查询", "cfontsize": 12, "color": "#888"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "状态: ✅ 运行中", "cfontsize": 12, "color": "#50b86c"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "端口: 9092", "cfontsize": 12, "color": "#aaa"}}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"cwidth": 20,
|
|
||||||
"cheight": 10,
|
|
||||||
"bgcolor": "#f8f9fa",
|
|
||||||
"padding": "12px",
|
|
||||||
"css": "card"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "🏷️ 实体识别", "cfontsize": 16, "fontWeight": "bold"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "GLiNER Multitask", "cfontsize": 14, "color": "#4a90d9"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "零样本NER · 中文支持", "cfontsize": 12, "color": "#888"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "状态: ✅ 运行中", "cfontsize": 12, "color": "#50b86c"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "端口: 9093", "cfontsize": 12, "color": "#aaa"}}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"cwidth": 20,
|
|
||||||
"cheight": 10,
|
|
||||||
"bgcolor": "#f8f9fa",
|
|
||||||
"padding": "12px",
|
|
||||||
"css": "card"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "👤 人脸识别", "cfontsize": 16, "fontWeight": "bold"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "InsightFace buffalo_l", "cfontsize": 14, "color": "#4a90d9"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "检测+识别 · 512维", "cfontsize": 12, "color": "#888"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "状态: ✅ 运行中", "cfontsize": 12, "color": "#50b86c"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "端口: 9091", "cfontsize": 12, "color": "#aaa"}}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"text": "💡 引擎默认全局配置,租户可在订阅设置中覆盖为私有实例",
|
|
||||||
"color": "#aaa",
|
|
||||||
"cfontsize": 12
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
ns = params_kw.copy()
|
|
||||||
db = DBPools()
|
|
||||||
dbname = get_module_dbname('rag')
|
|
||||||
async with db.sqlorContext(dbname) as sor:
|
|
||||||
import uuid
|
|
||||||
env = request._run_ns
|
|
||||||
userorgid = await env.get_userorgid()
|
|
||||||
kb_id = uuid.uuid4().hex[:12]
|
|
||||||
name = ns.get("name", "")
|
|
||||||
desc = ns.get("description", "")
|
|
||||||
await sor.sqlExe(
|
|
||||||
"INSERT INTO knowledge_bases (id, name, description, org_id, embedding_engine, vdb_collection, doc_count, total_size, chunk_count, status, created_at) "
|
|
||||||
"VALUES (${id}$, ${name}$, ${desc}$, ${org_id}$, 'CLIP ViT-H-14', 'rag_collection', 0, 0, 0, 'active', NOW())",
|
|
||||||
{"id": kb_id, "name": name, "desc": desc, "org_id": userorgid})
|
|
||||||
return {
|
|
||||||
"widgettype": "urlwidget",
|
|
||||||
"options": {"url": entire_url('/knowledge_bases_list/index.ui')}
|
|
||||||
}
|
|
||||||
return {"error": "failed"}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
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 {"widgettype": "Message", "options": {"user_data": {"id": ns.get("id","")}}}
|
|
||||||
return {"error": "failed"}
|
|
||||||
@ -1,92 +0,0 @@
|
|||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {"cheight": 40, "width": "100%", "padding": "0", "spacing": 0},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "HBox",
|
|
||||||
"options": {"width": "100%", "bgcolor": "#f8f9fa", "padding": "10px 16px", "alignItems": "center", "borderBottom": "1px solid #e0e0e0"},
|
|
||||||
"subwidgets": [
|
|
||||||
{"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')}}"}}
|
|
||||||
]},
|
|
||||||
{"widgettype": "Text", "options": {"text": " / ", "cfontsize": 14, "color": "#ccc"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "📚 {{params_kw.kb_name or '知识库'}}", "cfontsize": 16, "fontWeight": "bold", "color": "#333"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "", "css": "filler"}}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "HBox",
|
|
||||||
"options": {"css": "filler", "width": "100%", "spacing": 0},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {"cwidth": 16, "bgcolor": "#fafafa", "borderRight": "1px solid #e0e0e0", "spacing": 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",
|
|
||||||
"options": {
|
|
||||||
"title": "目录",
|
|
||||||
"id": "dir_tree",
|
|
||||||
"parentField": "parentid",
|
|
||||||
"idField": "id",
|
|
||||||
"textField": "label",
|
|
||||||
"dataurl": "{{entire_url('./get_tree_data.dspy')}}?kb_id={{params_kw.kb_id}}",
|
|
||||||
"newdata_params": {"kb_id": "{{params_kw.kb_id}}"},
|
|
||||||
"editable": {
|
|
||||||
"fields": [
|
|
||||||
{"name": "name", "title": "名称", "type": "str", "length": 255, "uitype": "str", "label": "名称"}
|
|
||||||
],
|
|
||||||
"add_url": "{{entire_url('./new_tree_item.dspy')}}",
|
|
||||||
"update_url": "{{entire_url('./update_tree_item.dspy')}}",
|
|
||||||
"delete_url": "{{entire_url('./delete_tree_item.dspy')}}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "selected",
|
|
||||||
"actiontype": "script",
|
|
||||||
"script": "window._rag_current_folder = event.params.id ? event.params.id : ''"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {"id": "detail_content", "css": "filler", "padding": "16px", "spacing": "12px"},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {"id": "drop_zone", "width": "100%", "cheight": 8, "bgcolor": "#f8f9fa", "padding": "16px", "css": "card", "border": "2px dashed #3b82f6", "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": 12, "color": "#aaa"}},
|
|
||||||
{
|
|
||||||
"widgettype": "Button",
|
|
||||||
"options": {"label": "选择文件", "bgcolor": "#3b82f6", "color": "#fff", "marginTop": "8px"},
|
|
||||||
"binds": [{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "click",
|
|
||||||
"actiontype": "script",
|
|
||||||
"script": "var i=document.createElement('input');i.type='file';i.multiple=true;i.onchange=function(){var u=new URL(window.location.href);var kb=u.searchParams.get('kb_id')||'';var fd=window._rag_current_folder||'';for(var j=0;j<i.files.length;j++){var f=i.files[j];var x=new XMLHttpRequest();x.open('POST','/api/doc/upload?kb_id='+encodeURIComponent(kb)+'&file_name='+encodeURIComponent(f.name)+'&folder='+encodeURIComponent(fd));x.onload=function(){if(x.status===200){var st=document.getElementById('upload_status');if(st)st.innerText='上传成功: '+f.name;else location.reload()}};x.send(f)}};i.click()"
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
{"widgettype": "Text", "options": {"id": "upload_status", "cfontsize": 12, "color": "#50b86c", "marginTop": "8px", "text": ""}}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{"widgettype": "Text", "options": {"text": "暂无文件", "cfontsize": 13, "color": "#aaa", "halign": "center"}}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
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' AND (description IS NULL OR description='') ORDER BY content",
|
|
||||||
{"kb_id": kb_id})
|
|
||||||
return [dict(r) for r in recs]
|
|
||||||
else:
|
|
||||||
recs = await sor.sqlExe(
|
|
||||||
"SELECT id, description as parentid, content as label FROM document_chunks WHERE kb_id=${kb_id}$ AND chunk_type='directory' AND description=${id}$ ORDER BY content",
|
|
||||||
{"kb_id": kb_id, "id": id})
|
|
||||||
return [dict(r) for r in recs]
|
|
||||||
return []
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"cheight": 40,
|
|
||||||
"width": "100%",
|
|
||||||
"padding": "16px",
|
|
||||||
"spacing": "16px"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "HBox",
|
|
||||||
"options": {"width": "100%", "justifyContent": "space-between", "alignItems": "center"},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "知识库", "cfontsize": 22, "fontWeight": "bold", "color": "#333"}},
|
|
||||||
{"widgettype": "Button", "options": {"label": "+ 新建知识库", "bgcolor": "#4a90d9", "color": "#fff"}, "binds": [
|
|
||||||
{"wid": "self", "event": "click", "actiontype": "urlwidget",
|
|
||||||
"target": "Popup",
|
|
||||||
"popup_options": {"auto_dismiss": true},
|
|
||||||
"options": {"url": "{{entire_url('/knowledge_bases_list/new_kb_form.ui')}}"}}
|
|
||||||
]}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {"width": "100%", "bgcolor": "#f8f9fa", "padding": "16px", "css": "card", "spacing": "4px"},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "HBox", "options": {"width": "100%", "justifyContent": "space-between"}, "subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "💾 存储容量", "cfontsize": 14, "fontWeight": "bold", "color": "#333"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "已用 0MB / 总计 100MB", "cfontsize": 12, "color": "#888"}}
|
|
||||||
]},
|
|
||||||
{"widgettype": "VBox", "options": {"width": "100%", "cheight": 0.5, "bgcolor": "#e0e0e0", "css": ""}, "subwidgets": [
|
|
||||||
{"widgettype": "VBox", "options": {"cwidth": 0, "cheight": 0.5, "bgcolor": "#4a90d9", "css": ""}}
|
|
||||||
]}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "urlwidget",
|
|
||||||
"options": {
|
|
||||||
"url": "{{entire_url('/api/kb/list')}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {"padding": "16px", "spacing": "12px"},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "新建知识库", "cfontsize": 18, "fontWeight": "bold"}},
|
|
||||||
{
|
|
||||||
"widgettype": "Form",
|
|
||||||
"id": "kb_form",
|
|
||||||
"options": {
|
|
||||||
"cols": 1,
|
|
||||||
"fields": [
|
|
||||||
{"name": "name", "label": "名称", "uitype": "str", "required": true},
|
|
||||||
{"name": "description", "label": "描述", "uitype": "text"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "kb_form",
|
|
||||||
"event": "submit",
|
|
||||||
"actiontype": "urlwidget",
|
|
||||||
"target": "app.rag_main_content",
|
|
||||||
"mode": "replace",
|
|
||||||
"options": {
|
|
||||||
"url": "{{entire_url('./create_kb.dspy')}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
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]
|
|
||||||
name = ns.get("name", "")
|
|
||||||
parent_id = ns.get("parentid", "")
|
|
||||||
kb_id = ns.get("kb_id", "")
|
|
||||||
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": kb_id, "name": name, "parentid": parent_id})
|
|
||||||
return {"widgettype": "Message", "options": {"user_data": {"id": dir_id, "label": name, "parentid": parent_id}}}
|
|
||||||
return {"error": "failed"}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
ns = params_kw.copy()
|
|
||||||
db = DBPools()
|
|
||||||
dbname = get_module_dbname('rag')
|
|
||||||
async with db.sqlorContext(dbname) as sor:
|
|
||||||
id = ns.get("id", "")
|
|
||||||
name = ns.get("name", "")
|
|
||||||
await sor.sqlExe("UPDATE document_chunks SET content=${name}$ WHERE id=${id}$",
|
|
||||||
{"name": name, "id": id})
|
|
||||||
return {"widgettype": "Message", "options": {"user_data": {"id": id, "label": name}}}
|
|
||||||
return {"error": "failed"}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
ns = params_kw.copy()
|
|
||||||
kb_id = ns.get('kb_id', '')
|
|
||||||
|
|
||||||
import json
|
|
||||||
body = json.dumps({
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {"spacing": "16px", "padding": "0"},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "📤 上传文件", "cfontsize": 18, "fontWeight": "bold", "color": "#333"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "知识库: " + kb_id, "cfontsize": 12, "color": "#888"}},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {"id": "upload_zone", "width": "100%", "cheight": 10, "bgcolor": "#f8f9fa", "padding": "24px", "css": "card", "border": "2px dashed #3b82f6", "alignItems": "center", "justifyContent": "center"},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "📂", "cfontsize": 40}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "拖拽文件或点击按钮上传", "cfontsize": 15, "color": "#888"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "支持: TXT,PDF,JPG,PNG,MP3,WAV,MP4", "cfontsize": 12, "color": "#aaa"}},
|
|
||||||
{
|
|
||||||
"widgettype": "Button",
|
|
||||||
"options": {"label": "选择文件上传", "bgcolor": "#3b82f6", "color": "#fff", "marginTop": "12px"}
|
|
||||||
},
|
|
||||||
{"widgettype": "Text", "options": {"text": "", "id": "upload_status", "cfontsize": 12, "color": "#50b86c", "marginTop": "8px"}}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Button",
|
|
||||||
"options": {"label": "← 返回文件管理", "bgcolor": "#e0e0e0", "color": "#666"},
|
|
||||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.rag_main_content", "mode": "replace", "options": {"url": entire_url('/knowledge_bases_list/index.ui')}}]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
return json.loads(body)
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
// File drop upload handler - prevents browser default, uploads to API
|
|
||||||
(function(){
|
|
||||||
var kb_id = '{{params_kw.kb_id}}';
|
|
||||||
document.addEventListener('dragover', function(e){ e.preventDefault(); e.stopPropagation(); });
|
|
||||||
document.addEventListener('drop', function(e){
|
|
||||||
e.preventDefault(); e.stopPropagation();
|
|
||||||
var files = e.dataTransfer.files;
|
|
||||||
if (!files.length) return;
|
|
||||||
var st = document.getElementById('status_text');
|
|
||||||
if (st) st.innerHTML = '上传中...';
|
|
||||||
for (var i=0; i<files.length; i++){
|
|
||||||
(function(f){
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.open('POST', '/api/doc/upload?kb_id='+kb_id+'&file_name='+encodeURIComponent(f.name));
|
|
||||||
xhr.onload = function(){
|
|
||||||
if (xhr.status === 200){
|
|
||||||
if (st) st.innerHTML = '上传成功: '+f.name;
|
|
||||||
} else {
|
|
||||||
if (st) st.innerHTML = '上传失败: HTTP '+xhr.status;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhr.send(f);
|
|
||||||
})(files[i]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
@ -1,121 +0,0 @@
|
|||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"cheight": 40,
|
|
||||||
"width": "100%",
|
|
||||||
"padding": "16px",
|
|
||||||
"spacing": "16px"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"text": "订阅管理",
|
|
||||||
"cfontsize": 22,
|
|
||||||
"fontWeight": "bold",
|
|
||||||
"color": "#333"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"text": "选择适合您的套餐,按磁盘容量计费,随时升级",
|
|
||||||
"color": "#888",
|
|
||||||
"cfontsize": 14
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "HBox",
|
|
||||||
"options": {
|
|
||||||
"width": "100%",
|
|
||||||
"spacing": "16px",
|
|
||||||
"justifyContent": "center"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"cwidth": 18,
|
|
||||||
"bgcolor": "#ffffff",
|
|
||||||
"padding": "20px",
|
|
||||||
"css": "card",
|
|
||||||
"border": "2px solid #e0e0e0"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "Free", "cfontsize": 20, "fontWeight": "bold", "color": "#333"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "免费试用", "cfontsize": 14, "color": "#888"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": ""}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "¥0/月", "cfontsize": 28, "fontWeight": "bold", "color": "#4a90d9"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": ""}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "✓ 1GB 磁盘容量", "cfontsize": 13, "color": "#666"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "✓ 100 文档", "cfontsize": 13, "color": "#666"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "✓ 3 知识库", "cfontsize": 13, "color": "#666"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "✓ 基础向量化引擎", "cfontsize": 13, "color": "#666"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "✗ 自定义引擎", "cfontsize": 13, "color": "#ccc"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "✗ 知识图谱", "cfontsize": 13, "color": "#ccc"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": ""}},
|
|
||||||
{"widgettype": "Button", "options": {"label": "当前套餐", "bgcolor": "#e0e0e0", "color": "#888"}}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"cwidth": 18,
|
|
||||||
"bgcolor": "#f0f7ff",
|
|
||||||
"padding": "20px",
|
|
||||||
"css": "card",
|
|
||||||
"border": "2px solid #4a90d9"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "Pro", "cfontsize": 20, "fontWeight": "bold", "color": "#4a90d9"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "推荐", "cfontsize": 14, "color": "#4a90d9"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": ""}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "¥299/月", "cfontsize": 28, "fontWeight": "bold", "color": "#4a90d9"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": ""}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "✓ 50GB 磁盘容量", "cfontsize": 13, "color": "#666"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "✓ 5000 文档", "cfontsize": 13, "color": "#666"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "✓ 20 知识库", "cfontsize": 13, "color": "#666"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "✓ 全引擎支持", "cfontsize": 13, "color": "#666"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "✓ 自定义引擎配置", "cfontsize": 13, "color": "#666"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "✓ 知识图谱+人脸识别", "cfontsize": 13, "color": "#666"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": ""}},
|
|
||||||
{"widgettype": "Button", "options": {"label": "升级到 Pro", "bgcolor": "#4a90d9", "color": "#fff"}}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"cwidth": 18,
|
|
||||||
"bgcolor": "#ffffff",
|
|
||||||
"padding": "20px",
|
|
||||||
"css": "card",
|
|
||||||
"border": "2px solid #e0e0e0"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "Enterprise", "cfontsize": 20, "fontWeight": "bold", "color": "#333"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "企业定制", "cfontsize": 14, "color": "#888"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": ""}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "联系我们", "cfontsize": 28, "fontWeight": "bold", "color": "#333"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": ""}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "✓ 无限磁盘容量", "cfontsize": 13, "color": "#666"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "✓ 无限文档", "cfontsize": 13, "color": "#666"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "✓ 无限知识库", "cfontsize": 13, "color": "#666"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "✓ 私有化部署", "cfontsize": 13, "color": "#666"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "✓ 专属GPU实例", "cfontsize": 13, "color": "#666"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": "✓ 7×24技术支持", "cfontsize": 13, "color": "#666"}},
|
|
||||||
{"widgettype": "Text", "options": {"text": ""}},
|
|
||||||
{"widgettype": "Button", "options": {"label": "联系销售", "bgcolor": "#333", "color": "#fff"}}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"text": "💡 当前: Free 套餐 · 已用 0B/1GB · 0/100 文档 · 0/3 知识库",
|
|
||||||
"color": "#aaa",
|
|
||||||
"cfontsize": 12
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user