fix: rewrite tag_assign dspy with explicit return

This commit is contained in:
yumoqing 2026-07-24 11:37:04 +08:00
parent b322c4533e
commit 5d0ec6f3c4
2 changed files with 21 additions and 84 deletions

View File

@ -11,7 +11,6 @@ import json
base_url = entire_url('/tag_assign/index.dspy') base_url = entire_url('/tag_assign/index.dspy')
async with db.sqlorContext(dbname) as sor: async with db.sqlorContext(dbname) as sor:
# Handle assign/unassign actions
if action and media_id and tag_id: if action and media_id and tag_id:
import uuid import uuid
try: try:
@ -27,37 +26,24 @@ async with db.sqlorContext(dbname) as sor:
except Exception: except Exception:
pass pass
# Get knowledge bases
kbs = await sor.R('knowledge_bases', {}) kbs = await sor.R('knowledge_bases', {})
# Get media items
media_items = [] media_items = []
if kb_id: if kb_id:
if media_type == 'document': if media_type == 'document':
recs = await sor.sqlExe( 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})
"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] media_items = [{"id": r.id, "name": r.file_name, "type": r.file_type} for r in recs]
elif media_type == 'face': elif media_type == 'face':
recs = await sor.sqlExe( 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})
"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] media_items = [{"id": r.id, "name": r.name or '人脸#'+r.id[:8], "type": "face"} for r in recs]
elif media_type == 'voice': elif media_type == 'voice':
recs = await sor.sqlExe( 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})
"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] 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 = [] current_tags = []
if media_id: if media_id:
recs = await sor.sqlExe( 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})
"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] current_tags = [{"id": r.id, "name": r.name, "color": r.color} for r in recs]
# Get all available tags
all_tags = [] all_tags = []
if kb_id: if kb_id:
tag_recs = await sor.R('tags', {'kb_id': kb_id}) tag_recs = await sor.R('tags', {'kb_id': kb_id})
@ -65,93 +51,44 @@ async with db.sqlorContext(dbname) as sor:
current_tag_ids = {t['id'] for t in current_tags} current_tag_ids = {t['id'] for t in current_tags}
# Build available tag buttons
tag_buttons = [] tag_buttons = []
for t in all_tags: for t in all_tags:
is_assigned = t['id'] in current_tag_ids is_assigned = t['id'] in current_tag_ids
if is_assigned: act = 'unassign' if is_assigned else 'assign'
act = 'unassign' lbl = ('✓ ' if is_assigned else '+ ') + t['name']
lbl = '✓ ' + t['name'] bg = t['color'] if is_assigned else '#e5e7eb'
bg = t['color'] clr = '#fff' if is_assigned else '#666'
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_url = base_url + '?kb_id=' + kb_id + '&media_type=' + media_type + '&media_id=' + media_id + '&action=' + act + '&tag_id=' + t['id']
tag_buttons.append({ 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}}]})
"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 = [] media_buttons = []
for m in media_items: for m in media_items:
murl = base_url + '?kb_id=' + kb_id + '&media_type=' + media_type + '&media_id=' + m['id'] murl = base_url + '?kb_id=' + kb_id + '&media_type=' + media_type + '&media_id=' + m['id']
is_current = m['id'] == media_id is_current = m['id'] == media_id
media_buttons.append({ icon = '📄' if media_type == 'document' else ('👤' if media_type == 'face' else '🎤')
"widgettype": "Button", media_buttons.append({"widgettype": "Button", "options": {"label": icon + ' ' + 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}}]})
"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 = [] kb_buttons = []
for k in kbs: for k in kbs:
kb_buttons.append({ 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}}]})
"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 = [] type_tabs = []
for mt, mt_label in [('document', '📄 文档'), ('face', '👤 人脸'), ('voice', '🎤 声纹')]: for mt, mt_label in [('document', '📄 文档'), ('face', '👤 人脸'), ('voice', '🎤 声纹')]:
type_tabs.append({ 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}}]})
"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 = { result = json.dumps({"widgettype": "VBox", "options": {"cheight": 40, "width": "100%", "padding": "16px", "spacing": "12px"}, "subwidgets": [
"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": 20, "fontWeight": "bold", "color": "#333"}},
{"widgettype": "Text", "options": {"text": "为文档、人脸、声纹设置标签", "cfontsize": 13, "color": "#888"}}, {"widgettype": "Text", "options": {"text": "为文档、人脸、声纹设置标签", "cfontsize": 13, "color": "#888"}},
# KB
{"widgettype": "Text", "options": {"text": "知识库:", "cfontsize": 13, "color": "#555", "marginTop": "8px"}}, {"widgettype": "Text", "options": {"text": "知识库:", "cfontsize": 13, "color": "#555", "marginTop": "8px"}},
{"widgettype": "HBox", "options": {"spacing": "4px", "wrap": True}, "subwidgets": kb_buttons}, {"widgettype": "HBox", "options": {"spacing": "4px", "wrap": True}, "subwidgets": kb_buttons},
# Media type
{"widgettype": "Text", "options": {"text": "媒体类型:", "cfontsize": 13, "color": "#555", "marginTop": "4px"}}, {"widgettype": "Text", "options": {"text": "媒体类型:", "cfontsize": 13, "color": "#555", "marginTop": "4px"}},
{"widgettype": "HBox", "options": {"spacing": "4px"}, "subwidgets": type_tabs}, {"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": "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}, {"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": "Text", "options": {"text": "当前标签:", "cfontsize": 13, "color": "#555", "marginTop": "8px"}},
{"widgettype": "HBox", "options": {"spacing": "4px", "wrap": True}, "subwidgets": [ {"widgettype": "HBox", "options": {"spacing": "4px", "wrap": True}, "subwidgets": current_tags and [{"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] or [{"widgettype": "Text", "options": {"text": "(无)", "color": "#aaa", "cfontsize": 12}}]},
{"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": "Text", "options": {"text": "可用标签 (点击切换):", "cfontsize": 13, "color": "#555", "marginTop": "4px"}},
{"widgettype": "HBox", "options": {"spacing": "4px", "wrap": True}, "subwidgets": tag_buttons} if media_id else [ {"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"}}]
{"widgettype": "Text", "options": {"text": "请先选择媒体文件", "color": "#aaa", "cfontsize": 12, "padding": "12px 0"}} ]})
]
] return json.loads(result)
}
result

View File

@ -99,4 +99,4 @@ async with db.sqlorContext(dbname) as sor:
{"widgettype": "VBox", "options": {"spacing": "4px"}, "subwidgets": results_widgets} {"widgettype": "VBox", "options": {"spacing": "4px"}, "subwidgets": results_widgets}
] ]
} }
result return result