fix: add session_max_time/issue_time for Redis session persistence
This commit is contained in:
parent
94361d7da4
commit
51f0fd680b
@ -14,6 +14,8 @@
|
||||
}
|
||||
},
|
||||
"SAGE_RBAC_DB": "rag",
|
||||
"session_max_time": 3000,
|
||||
"session_issue_time": 2500,
|
||||
"session_redis": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 6379,
|
||||
|
||||
@ -14,6 +14,27 @@
|
||||
"url": "{{entire_url('/knowledge_bases_list/index.ui')}}",
|
||||
"target": "app.rag_main_content"
|
||||
},
|
||||
{
|
||||
"name": "tags",
|
||||
"label": "标签管理",
|
||||
"icon": "{{entire_url('/bricks/imgs/tag.png')}}",
|
||||
"url": "{{entire_url('/tags_list/index.ui')}}",
|
||||
"target": "app.rag_main_content"
|
||||
},
|
||||
{
|
||||
"name": "tag_assign",
|
||||
"label": "标签分配",
|
||||
"icon": "{{entire_url('/bricks/imgs/edit.svg')}}",
|
||||
"url": "{{entire_url('/tag_assign/index.dspy')}}",
|
||||
"target": "app.rag_main_content"
|
||||
},
|
||||
{
|
||||
"name": "tag_search",
|
||||
"label": "标签检索",
|
||||
"icon": "{{entire_url('/bricks/imgs/search.svg')}}",
|
||||
"url": "{{entire_url('/tag_search/index.dspy')}}",
|
||||
"target": "app.rag_main_content"
|
||||
},
|
||||
{
|
||||
"name": "engines",
|
||||
"label": "引擎配置",
|
||||
|
||||
157
wwwroot/tag_assign/index.dspy
Normal file
157
wwwroot/tag_assign/index.dspy
Normal file
@ -0,0 +1,157 @@
|
||||
ns = params_kw.copy()
|
||||
kb_id = ns.get('kb_id', '')
|
||||
media_type = ns.get('media_type', 'document')
|
||||
media_id = ns.get('media_id', '')
|
||||
action = ns.get('action', '')
|
||||
tag_id = ns.get('tag_id', '')
|
||||
|
||||
db = DBPools()
|
||||
dbname = get_module_dbname('rag')
|
||||
import json
|
||||
base_url = entire_url('/tag_assign/index.dspy')
|
||||
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
# Handle assign/unassign actions
|
||||
if action and media_id and tag_id:
|
||||
import uuid
|
||||
try:
|
||||
if action == 'assign':
|
||||
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})
|
||||
elif action == 'unassign':
|
||||
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})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Get knowledge bases
|
||||
kbs = await sor.R('knowledge_bases', {})
|
||||
|
||||
# Get media items
|
||||
media_items = []
|
||||
if kb_id:
|
||||
if media_type == 'document':
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT id, file_name, file_type FROM documents WHERE kb_id=${kb_id}$ ORDER BY created_at DESC LIMIT 100",
|
||||
{"kb_id": kb_id})
|
||||
media_items = [{"id": r.id, "name": r.file_name, "type": r.file_type} for r in recs]
|
||||
elif media_type == 'face':
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT id, name, description FROM entities WHERE kb_id=${kb_id}$ AND entity_type='person' AND face_embedding_id IS NOT NULL ORDER BY created_at DESC LIMIT 100",
|
||||
{"kb_id": kb_id})
|
||||
media_items = [{"id": r.id, "name": r.name or '人脸#'+r.id[:8], "type": "face"} for r in recs]
|
||||
elif media_type == 'voice':
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT id, name, description FROM entities WHERE kb_id=${kb_id}$ AND entity_type='voice' AND voice_embedding_id IS NOT NULL ORDER BY created_at DESC LIMIT 100",
|
||||
{"kb_id": kb_id})
|
||||
media_items = [{"id": r.id, "name": r.name or '声纹#'+r.id[:8], "type": "voice"} for r in recs]
|
||||
|
||||
# Get current tags for selected media
|
||||
current_tags = []
|
||||
if media_id:
|
||||
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})
|
||||
current_tags = [{"id": r.id, "name": r.name, "color": r.color} for r in recs]
|
||||
|
||||
# Get all available tags
|
||||
all_tags = []
|
||||
if kb_id:
|
||||
tag_recs = await sor.R('tags', {'kb_id': kb_id})
|
||||
all_tags = [{"id": t.id, "name": t.name, "color": t.color} for t in tag_recs]
|
||||
|
||||
current_tag_ids = {t['id'] for t in current_tags}
|
||||
|
||||
# Build available tag buttons
|
||||
tag_buttons = []
|
||||
for t in all_tags:
|
||||
is_assigned = t['id'] in current_tag_ids
|
||||
if is_assigned:
|
||||
act = 'unassign'
|
||||
lbl = '✓ ' + t['name']
|
||||
bg = t['color']
|
||||
clr = '#fff'
|
||||
else:
|
||||
act = 'assign'
|
||||
lbl = '+ ' + t['name']
|
||||
bg = '#e5e7eb'
|
||||
clr = '#666'
|
||||
tag_url = base_url + '?kb_id=' + kb_id + '&media_type=' + media_type + '&media_id=' + media_id + '&action=' + act + '&tag_id=' + t['id']
|
||||
tag_buttons.append({
|
||||
"widgettype": "Button",
|
||||
"options": {"label": lbl, "bgcolor": bg, "color": clr, "cfontsize": 12, "padding": "4px 12px", "marginRight": "6px", "marginBottom": "6px"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.rag_main_content", "mode": "replace", "options": {"url": tag_url}}]
|
||||
})
|
||||
|
||||
# Build media list buttons
|
||||
media_buttons = []
|
||||
for m in media_items:
|
||||
murl = base_url + '?kb_id=' + kb_id + '&media_type=' + media_type + '&media_id=' + m['id']
|
||||
is_current = m['id'] == media_id
|
||||
media_buttons.append({
|
||||
"widgettype": "Button",
|
||||
"options": {
|
||||
"label": ('📄' if media_type=='document' else ('👤' if media_type=='face' else '🎤')) + ' ' + m['name'],
|
||||
"bgcolor": "#dbeafe" if is_current else "#f9fafb",
|
||||
"color": "#1e40af" if is_current else "#666",
|
||||
"cfontsize": 12,
|
||||
"padding": "6px 12px",
|
||||
"marginBottom": "4px",
|
||||
"halign": "left"
|
||||
},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.rag_main_content", "mode": "replace", "options": {"url": murl}}]
|
||||
})
|
||||
|
||||
# KB buttons
|
||||
kb_buttons = []
|
||||
for k in kbs:
|
||||
kb_buttons.append({
|
||||
"widgettype": "Button",
|
||||
"options": {"label": k.name, "bgcolor": "#3b82f6" if k.id==kb_id else "#e5e7eb", "color": "#fff" if k.id==kb_id else "#666", "cfontsize": 12, "padding": "6px 14px", "marginRight": "6px", "marginBottom": "4px"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.rag_main_content", "mode": "replace",
|
||||
"options": {"url": base_url + '?kb_id=' + k.id + '&media_type=' + media_type}}]}
|
||||
)
|
||||
|
||||
# Type tabs
|
||||
type_tabs = []
|
||||
for mt, mt_label in [('document', '📄 文档'), ('face', '👤 人脸'), ('voice', '🎤 声纹')]:
|
||||
type_tabs.append({
|
||||
"widgettype": "Button",
|
||||
"options": {"label": mt_label, "bgcolor": "#3b82f6" if mt==media_type else "#e5e7eb", "color": "#fff" if mt==media_type else "#666", "cfontsize": 12, "padding": "6px 16px", "marginRight": "4px"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.rag_main_content", "mode": "replace",
|
||||
"options": {"url": base_url + '?kb_id=' + kb_id + '&media_type=' + mt}}]}
|
||||
)
|
||||
|
||||
result = {
|
||||
"widgettype": "VBox",
|
||||
"options": {"cheight": 40, "width": "100%", "padding": "16px", "spacing": "12px"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": "🏷️ 标签分配", "cfontsize": 20, "fontWeight": "bold", "color": "#333"}},
|
||||
{"widgettype": "Text", "options": {"text": "为文档、人脸、声纹设置标签", "cfontsize": 13, "color": "#888"}},
|
||||
# KB
|
||||
{"widgettype": "Text", "options": {"text": "知识库:", "cfontsize": 13, "color": "#555", "marginTop": "8px"}},
|
||||
{"widgettype": "HBox", "options": {"spacing": "4px", "wrap": True}, "subwidgets": kb_buttons},
|
||||
# Media type
|
||||
{"widgettype": "Text", "options": {"text": "媒体类型:", "cfontsize": 13, "color": "#555", "marginTop": "4px"}},
|
||||
{"widgettype": "HBox", "options": {"spacing": "4px"}, "subwidgets": type_tabs},
|
||||
# Media list
|
||||
{"widgettype": "Text", "options": {"text": '选择' + ('文档' if media_type=='document' else ('人脸' if media_type=='face' else '声纹')) + ':', "cfontsize": 13, "color": "#555", "marginTop": "8px"}},
|
||||
{"widgettype": "VBox", "options": {"cheight": 12, "bgcolor": "#fafafa", "padding": "8px", "border": "1px solid #e0e0e0", "overflowY": "auto"}, "subwidgets": media_buttons},
|
||||
# Current tags
|
||||
{"widgettype": "Text", "options": {"text": "当前标签:", "cfontsize": 13, "color": "#555", "marginTop": "8px"}},
|
||||
{"widgettype": "HBox", "options": {"spacing": "4px", "wrap": True}, "subwidgets": [
|
||||
{"widgettype": "Button", "options": {"label": t['name'], "bgcolor": t['color'], "color": "#fff", "cfontsize": 11, "padding": "3px 10px", "marginRight": "4px", "marginBottom": "4px"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.rag_main_content", "mode": "replace",
|
||||
"options": {"url": base_url + '?kb_id=' + kb_id + '&media_type=' + media_type + '&media_id=' + media_id + '&action=unassign&tag_id=' + t['id']}}]}
|
||||
for t in current_tags] if current_tags else [{"widgettype": "Text", "options": {"text": "(无)", "color": "#aaa", "cfontsize": 12}}]},
|
||||
# Available tags
|
||||
{"widgettype": "Text", "options": {"text": "可用标签 (点击切换):", "cfontsize": 13, "color": "#555", "marginTop": "4px"}},
|
||||
{"widgettype": "HBox", "options": {"spacing": "4px", "wrap": True}, "subwidgets": tag_buttons} if media_id else [
|
||||
{"widgettype": "Text", "options": {"text": "请先选择媒体文件", "color": "#aaa", "cfontsize": 12, "padding": "12px 0"}}
|
||||
]
|
||||
]
|
||||
}
|
||||
result
|
||||
139
wwwroot/tag_search/index.dspy
Normal file
139
wwwroot/tag_search/index.dspy
Normal file
@ -0,0 +1,139 @@
|
||||
ns = params_kw.copy()
|
||||
kb_id = ns.get('kb_id', '')
|
||||
tag_ids_str = ns.get('tag_ids', '')
|
||||
match_mode = ns.get('match_mode', 'any')
|
||||
query = ns.get('query', '')
|
||||
tag_ids = [t for t in tag_ids_str.split(',') if t] if tag_ids_str else []
|
||||
|
||||
db = DBPools()
|
||||
dbname = get_module_dbname('rag')
|
||||
base_url = entire_url('/tag_search/index.dspy')
|
||||
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
# Get knowledge bases
|
||||
kbs = await sor.R('knowledge_bases', {})
|
||||
kb_options = [{"value": k.id, "text": k.name} for k in kbs]
|
||||
|
||||
# Get all tags for selected kb
|
||||
tags = []
|
||||
if kb_id:
|
||||
tag_recs = await sor.R('tags', {'kb_id': kb_id})
|
||||
tags = [{"id": t.id, "name": t.name, "color": t.color} for t in tag_recs]
|
||||
|
||||
# --- Build tag pills ---
|
||||
tag_pills = []
|
||||
for t in tags:
|
||||
is_selected = t['id'] in tag_ids
|
||||
pill_bg = t['color'] if is_selected else '#e5e7eb'
|
||||
pill_color = '#fff' if is_selected else '#666'
|
||||
new_ids = [x for x in tag_ids if x != t['id']]
|
||||
if not is_selected:
|
||||
new_ids.append(t['id'])
|
||||
new_tag_str = ','.join(new_ids)
|
||||
url = base_url + '?kb_id=' + kb_id + '&tag_ids=' + new_tag_str + '&match_mode=' + match_mode + '&query=' + query
|
||||
tag_pills.append({
|
||||
"widgettype": "Button",
|
||||
"options": {
|
||||
"label": ('✓ ' if is_selected else '') + t['name'],
|
||||
"bgcolor": pill_bg,
|
||||
"color": pill_color,
|
||||
"cfontsize": 12,
|
||||
"padding": "4px 12px",
|
||||
"marginRight": "6px",
|
||||
"marginBottom": "6px"
|
||||
},
|
||||
"binds": [{
|
||||
"wid": "self", "event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.rag_main_content",
|
||||
"mode": "replace",
|
||||
"options": {"url": url}
|
||||
}]
|
||||
})
|
||||
|
||||
# --- Build results ---
|
||||
results_widgets = []
|
||||
if tag_ids and not query:
|
||||
# Tag-only search via our API
|
||||
from urllib.request import Request, urlopen
|
||||
try:
|
||||
api_url = 'http://localhost:9181/api/tag/search?kb_id=' + kb_id + '&tag_ids=' + tag_ids_str + '&match_mode=' + match_mode
|
||||
req = Request(api_url)
|
||||
data = json.loads(urlopen(req, timeout=5).read())
|
||||
res = data.get('results', [])
|
||||
if isinstance(res, list):
|
||||
for r in res:
|
||||
icon = '📄' if r['type'] == 'document' else ('👤' if r['type'] == 'face' else '🎤')
|
||||
results_widgets.append({
|
||||
"widgettype": "HBox",
|
||||
"options": {"padding": "8px 12px", "bgcolor": "#f9fafb", "marginBottom": "4px", "alignItems": "center"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": icon + ' ' + (r.get('name') or r.get('id','')), "cfontsize": 13, "color": "#333"}},
|
||||
{"widgettype": "Text", "options": {"text": r['type'] + ' · ' + (r.get('file_type','')), "cfontsize": 11, "color": "#888", "css": "filler"}}
|
||||
]
|
||||
})
|
||||
# Show tags for this item
|
||||
try:
|
||||
tag_url = 'http://localhost:9181/api/tag/media_tags?media_type=' + ('document' if r['type'] in ('document','text','image','audio','video') else r['type']) + '&media_id=' + r['id']
|
||||
tag_data = json.loads(urlopen(Request(tag_url), timeout=3).read())
|
||||
item_tags = tag_data.get('tags', [])
|
||||
for it in item_tags:
|
||||
results_widgets[-1]['subwidgets'].append({
|
||||
"widgettype": "Text",
|
||||
"options": {"text": it['name'], "cfontsize": 10, "color": "#fff", "bgcolor": it['color'], "padding": "1px 6px", "marginLeft": "4px"}
|
||||
})
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
for key, items in res.items():
|
||||
if isinstance(items, list):
|
||||
for r in items:
|
||||
results_widgets.append({
|
||||
"widgettype": "Text",
|
||||
"options": {"text": str(r.get('name') or r.get('id','')), "cfontsize": 13, "color": "#333", "padding": "4px 0"}
|
||||
})
|
||||
except Exception as e:
|
||||
results_widgets.append({"widgettype": "Text", "options": {"text": '搜索出错: ' + str(e), "color": "#e53e3e"}})
|
||||
|
||||
if not results_widgets and (tag_ids or query):
|
||||
results_widgets.append({"widgettype": "Text", "options": {"text": '未找到匹配的媒体文件', "color": "#888", "cfontsize": 14, "halign": "center", "padding": "40px 0"}})
|
||||
|
||||
# --- Match mode toggle ---
|
||||
any_url = base_url + '?kb_id=' + kb_id + '&tag_ids=' + tag_ids_str + '&match_mode=any&query=' + query
|
||||
all_url = base_url + '?kb_id=' + kb_id + '&tag_ids=' + tag_ids_str + '&match_mode=all&query=' + query
|
||||
|
||||
result = {
|
||||
"widgettype": "VBox",
|
||||
"options": {"cheight": 40, "width": "100%", "padding": "16px", "spacing": "12px"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": "🔍 标签检索", "cfontsize": 20, "fontWeight": "bold", "color": "#333"}},
|
||||
{"widgettype": "Text", "options": {"text": "组合标签筛选 + 语义搜索", "cfontsize": 13, "color": "#888"}},
|
||||
# KB selector
|
||||
{"widgettype": "Text", "options": {"text": "知识库:", "cfontsize": 13, "color": "#555", "marginTop": "8px"}},
|
||||
{"widgettype": "HBox", "options": {"spacing": "6px", "wrap": True}, "subwidgets": [
|
||||
{"widgettype": "Button", "options": {"label": k['text'], "bgcolor": "#3b82f6" if k['value']==kb_id else "#e5e7eb", "color": "#fff" if k['value']==kb_id else "#666", "cfontsize": 12, "padding": "6px 14px", "marginRight": "4px", "marginBottom": "4px"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.rag_main_content", "mode": "replace",
|
||||
"options": {"url": base_url + '?kb_id=' + k['value'] + '&match_mode=' + match_mode}}]}
|
||||
for k in kb_options]},
|
||||
# Tags
|
||||
{"widgettype": "Text", "options": {"text": '标签 (' + str(len(tag_ids)) + ' 已选):', "cfontsize": 13, "color": "#555", "marginTop": "8px"}},
|
||||
{"widgettype": "HBox", "options": {"spacing": "4px", "wrap": True}, "subwidgets": tag_pills},
|
||||
# Match mode
|
||||
{"widgettype": "HBox", "options": {"spacing": "8px", "alignItems": "center", "marginTop": "4px"}, "subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": "匹配:", "cfontsize": 13, "color": "#555"}},
|
||||
{"widgettype": "Button", "options": {"label": "任一 (OR)", "bgcolor": "#3b82f6" if match_mode=='any' else "#e5e7eb", "color": "#fff" if match_mode=='any' else "#666", "cfontsize": 12, "padding": "4px 12px"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.rag_main_content", "mode": "replace", "options": {"url": any_url}}]},
|
||||
{"widgettype": "Button", "options": {"label": "全部 (AND)", "bgcolor": "#3b82f6" if match_mode=='all' else "#e5e7eb", "color": "#fff" if match_mode=='all' else "#666", "cfontsize": 12, "padding": "4px 12px"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.rag_main_content", "mode": "replace", "options": {"url": all_url}}]}
|
||||
]},
|
||||
# Query input + search button (using form or simple text)
|
||||
{"widgettype": "Text", "options": {"text": "语义查询 (可选):", "cfontsize": 13, "color": "#555", "marginTop": "8px"}},
|
||||
{"widgettype": "HBox", "options": {"spacing": "8px", "alignItems": "center"}, "subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": query if query else '(输入查询关键词)', "cfontsize": 13, "color": "#333" if query else "#aaa", "bgcolor": "#f9fafb", "padding": "8px 12px", "css": "filler", "border": "1px solid #e0e0e0"}}
|
||||
]},
|
||||
# Results
|
||||
{"widgettype": "Text", "options": {"text": '结果 (' + str(len(results_widgets)) + '):', "cfontsize": 14, "fontWeight": "bold", "color": "#333", "marginTop": "16px"}},
|
||||
{"widgettype": "VBox", "options": {"spacing": "4px"}, "subwidgets": results_widgets}
|
||||
]
|
||||
}
|
||||
result
|
||||
19
wwwroot/tags_list/add.dspy
Normal file
19
wwwroot/tags_list/add.dspy
Normal file
@ -0,0 +1,19 @@
|
||||
ns = params_kw.copy()
|
||||
db = DBPools()
|
||||
dbname = get_module_dbname('rag')
|
||||
import uuid
|
||||
|
||||
name = ns.get('name', '').strip()
|
||||
kb_id = ns.get('kb_id', '')
|
||||
color = ns.get('color', '#3b82f6')
|
||||
|
||||
if not name or not kb_id:
|
||||
result = {"widgettype": "Message", "options": {"user_data": {"error": "name and kb_id required"}}}
|
||||
else:
|
||||
async with db.sqlorContext(dbname) 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}$, '', NOW())",
|
||||
{"id": tag_id, "kb_id": kb_id, "name": name, "color": color})
|
||||
result = {"widgettype": "Message", "options": {"user_data": {"id": tag_id, "name": name, "color": color, "kb_id": kb_id}}}
|
||||
result
|
||||
18
wwwroot/tags_list/data.dspy
Normal file
18
wwwroot/tags_list/data.dspy
Normal file
@ -0,0 +1,18 @@
|
||||
ns = params_kw.copy()
|
||||
db = DBPools()
|
||||
dbname = get_module_dbname('rag')
|
||||
userorgid = ns.get('org_id', '')
|
||||
import json
|
||||
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
recs = await sor.R('tags', {})
|
||||
result = []
|
||||
for r in recs:
|
||||
result.append({
|
||||
"id": r.id,
|
||||
"name": r.name,
|
||||
"color": r.color,
|
||||
"kb_id": r.kb_id,
|
||||
"created_at": str(r.created_at) if r.created_at else ''
|
||||
})
|
||||
result
|
||||
14
wwwroot/tags_list/delete.dspy
Normal file
14
wwwroot/tags_list/delete.dspy
Normal file
@ -0,0 +1,14 @@
|
||||
ns = params_kw.copy()
|
||||
db = DBPools()
|
||||
dbname = get_module_dbname('rag')
|
||||
|
||||
tag_id = ns.get('id', '')
|
||||
|
||||
if not tag_id:
|
||||
result = {"widgettype": "Message", "options": {"user_data": {"error": "id required"}}}
|
||||
else:
|
||||
async with db.sqlorContext(dbname) 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})
|
||||
result = {"widgettype": "Message", "options": {"user_data": {"id": tag_id}}}
|
||||
result
|
||||
43
wwwroot/tags_list/index.ui
Normal file
43
wwwroot/tags_list/index.ui
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"cheight": 40,
|
||||
"width": "100%",
|
||||
"padding": "16px",
|
||||
"spacing": "12px"
|
||||
},
|
||||
"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": "Tabular",
|
||||
"options": {
|
||||
"dataurl": "{{entire_url('/tags_list/data.dspy')}}",
|
||||
"title": "标签列表",
|
||||
"height": "70vh",
|
||||
"browserfields": [
|
||||
{"field": "name", "title": "标签名", "width": "25%"},
|
||||
{"field": "color", "title": "颜色", "width": "15%"},
|
||||
{"field": "kb_id", "title": "知识库", "width": "30%"},
|
||||
{"field": "created_at", "title": "创建时间", "width": "30%"}
|
||||
],
|
||||
"editable": {
|
||||
"fields": [
|
||||
{"field": "name", "title": "标签名", "uitype": "str", "label": "标签名", "required": true},
|
||||
{"field": "kb_id", "title": "知识库ID", "uitype": "str", "label": "知识库ID", "required": true},
|
||||
{"field": "color", "title": "颜色", "uitype": "str", "label": "颜色(#hex)", "default": "#3b82f6"}
|
||||
],
|
||||
"add_url": "{{entire_url('/tags_list/add.dspy')}}",
|
||||
"update_url": "{{entire_url('/tags_list/update.dspy')}}",
|
||||
"delete_url": "{{entire_url('/tags_list/delete.dspy')}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
24
wwwroot/tags_list/update.dspy
Normal file
24
wwwroot/tags_list/update.dspy
Normal file
@ -0,0 +1,24 @@
|
||||
ns = params_kw.copy()
|
||||
db = DBPools()
|
||||
dbname = get_module_dbname('rag')
|
||||
|
||||
tag_id = ns.get('id', '')
|
||||
name = ns.get('name', '').strip()
|
||||
color = ns.get('color', '#3b82f6')
|
||||
|
||||
if not tag_id:
|
||||
result = {"widgettype": "Message", "options": {"user_data": {"error": "id required"}}}
|
||||
else:
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
parts = []
|
||||
params = {"id": tag_id}
|
||||
if name:
|
||||
parts.append("name=${name}$")
|
||||
params["name"] = name
|
||||
if color:
|
||||
parts.append("color=${color}$")
|
||||
params["color"] = color
|
||||
if parts:
|
||||
await sor.sqlExe(f"UPDATE tags SET {', '.join(parts)} WHERE id=${{id}}$", params)
|
||||
result = {"widgettype": "Message", "options": {"user_data": {"id": tag_id, "name": name, "color": color}}}
|
||||
result
|
||||
Loading…
x
Reference in New Issue
Block a user