diff --git a/wwwroot/tag_assign/index.dspy b/wwwroot/tag_assign/index.dspy deleted file mode 100644 index c5f2d74..0000000 --- a/wwwroot/tag_assign/index.dspy +++ /dev/null @@ -1,94 +0,0 @@ -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: - 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 - - kbs = await sor.R('knowledge_bases', {}) - 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] - - 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] - - 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} - - tag_buttons = [] - for t in all_tags: - is_assigned = t['id'] in current_tag_ids - act = 'unassign' if is_assigned else 'assign' - lbl = ('✓ ' if is_assigned else '+ ') + t['name'] - bg = t['color'] if is_assigned else '#e5e7eb' - clr = '#fff' if is_assigned else '#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}}]}) - - 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 - icon = '📄' if media_type == 'document' else ('👤' if media_type == 'face' else '🎤') - 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}}]}) - - 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 = [] - 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 = json.dumps({"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"}}, - {"widgettype": "Text", "options": {"text": "知识库:", "cfontsize": 13, "color": "#555", "marginTop": "8px"}}, - {"widgettype": "HBox", "options": {"spacing": "4px", "wrap": True}, "subwidgets": kb_buttons}, - {"widgettype": "Text", "options": {"text": "媒体类型:", "cfontsize": 13, "color": "#555", "marginTop": "4px"}}, - {"widgettype": "HBox", "options": {"spacing": "4px"}, "subwidgets": type_tabs}, - {"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": "Text", "options": {"text": "当前标签:", "cfontsize": 13, "color": "#555", "marginTop": "8px"}}, - {"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": "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"}}] - ]}) - -return json.loads(result) diff --git a/wwwroot/tag_search/index.dspy b/wwwroot/tag_search/index.dspy deleted file mode 100644 index 1ee474e..0000000 --- a/wwwroot/tag_search/index.dspy +++ /dev/null @@ -1,102 +0,0 @@ -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 [] - -import json -db = DBPools() -dbname = get_module_dbname('rag') -base_url = entire_url('/tag_search/index.dspy') - -async with db.sqlorContext(dbname) as sor: - kbs = await sor.R('knowledge_bases', {}) - kb_options = [{"value": k.id, "text": k.name} for k in kbs] - - 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] - - 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} - }] - }) - - results_widgets = [] - if tag_ids: - 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 '🎤') - entry = { - "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"}} - ] - } - results_widgets.append(entry) - except Exception as e: - results_widgets.append({"widgettype": "Text", "options": {"text": '搜索出错: ' + str(e), "color": "#e53e3e"}}) - - if not results_widgets and tag_ids: - results_widgets.append({"widgettype": "Text", "options": {"text": '未找到匹配的媒体文件', "color": "#888", "cfontsize": 14, "halign": "center", "padding": "40px 0"}}) - - 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"}}, - {"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]}, - {"widgettype": "Text", "options": {"text": '标签 (' + str(len(tag_ids)) + ' 已选):', "cfontsize": 13, "color": "#555", "marginTop": "8px"}}, - {"widgettype": "HBox", "options": {"spacing": "4px", "wrap": True}, "subwidgets": tag_pills}, - {"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}}]} - ]}, - {"widgettype": "Text", "options": {"text": "结果 (" + str(len(results_widgets)) + "):", "cfontsize": 14, "fontWeight": "bold", "color": "#333", "marginTop": "16px"}}, - {"widgettype": "VBox", "options": {"spacing": "4px"}, "subwidgets": results_widgets} - ] - } - return result diff --git a/wwwroot/tags_list/add.dspy b/wwwroot/tags_list/add.dspy deleted file mode 100644 index 96bc232..0000000 --- a/wwwroot/tags_list/add.dspy +++ /dev/null @@ -1,17 +0,0 @@ -ns = params_kw.copy() -import uuid -db = DBPools() -dbname = get_module_dbname('rag') -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 diff --git a/wwwroot/tags_list/data.dspy b/wwwroot/tags_list/data.dspy deleted file mode 100644 index 679971c..0000000 --- a/wwwroot/tags_list/data.dspy +++ /dev/null @@ -1,9 +0,0 @@ -ns = params_kw.copy() -db = DBPools() -dbname = get_module_dbname('rag') -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 diff --git a/wwwroot/tags_list/delete.dspy b/wwwroot/tags_list/delete.dspy deleted file mode 100644 index 5402873..0000000 --- a/wwwroot/tags_list/delete.dspy +++ /dev/null @@ -1,12 +0,0 @@ -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 diff --git a/wwwroot/tags_list/index.ui b/wwwroot/tags_list/index.ui deleted file mode 100644 index a2b6eb8..0000000 --- a/wwwroot/tags_list/index.ui +++ /dev/null @@ -1,32 +0,0 @@ -{ - "widgettype": "VBox", - "options": {"cheight": 40, "width": "100%", "padding": "16px", "spacing": "12px"}, - "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')}}" - } - } - } - ] -} diff --git a/wwwroot/tags_list/update.dspy b/wwwroot/tags_list/update.dspy deleted file mode 100644 index 5559876..0000000 --- a/wwwroot/tags_list/update.dspy +++ /dev/null @@ -1,22 +0,0 @@ -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