From d0dd18342f31a0e385a523a8d1851e205b8131c3 Mon Sep 17 00:00:00 2001 From: zhaojie Date: Wed, 11 Feb 2026 22:53:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E9=A3=9E=E4=B9=A6?= =?UTF-8?q?=E9=9B=86=E6=88=90=E3=80=81=E7=9F=A5=E8=AF=86=E5=BA=93=E3=80=81?= =?UTF-8?q?Agent=E3=80=81=E5=B7=A5=E5=8D=95=E7=AE=A1=E7=90=86=E5=8F=8AAI?= =?UTF-8?q?=E5=BB=BA=E8=AE=AE=E5=8A=9F=E8=83=BD=EF=BC=8C=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E5=89=8D=E7=AB=AF=E5=AF=B9=E8=AF=9D=E5=AD=97=E4=BD=93=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E5=B9=B6=E7=A7=BB=E9=99=A4=E5=B7=A5=E5=8D=95=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E6=96=87=E4=BB=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/tsp_assistant.db | Bin 81920 -> 118784 bytes .../agent_assistant.cpython-310.pyc | Bin 13303 -> 16962 bytes .../__pycache__/agent_core.cpython-310.pyc | Bin 8443 -> 8473 bytes src/agent/agent_core.py | 1 + src/agent_assistant.py | 163 ++++- .../__pycache__/cache_manager.cpython-310.pyc | Bin 7139 -> 7810 bytes .../query_optimizer.cpython-310.pyc | Bin 13487 -> 13544 bytes src/core/cache_manager.py | 30 +- src/core/query_optimizer.py | 4 +- .../ai_suggestion_service.cpython-310.pyc | Bin 15294 -> 15503 bytes src/integrations/ai_suggestion_service.py | 32 +- src/integrations/feishu_longconn_service.py | 28 +- .../knowledge_manager.cpython-310.pyc | Bin 13362 -> 13429 bytes src/knowledge_base/knowledge_manager.py | 24 +- src/utils/__pycache__/helpers.cpython-310.pyc | Bin 6257 -> 6257 bytes .../__pycache__/agent.cpython-310.pyc | Bin 7408 -> 7210 bytes .../__pycache__/feishu_bot.cpython-310.pyc | Bin 5172 -> 5612 bytes .../__pycache__/knowledge.cpython-310.pyc | Bin 4523 -> 6103 bytes .../__pycache__/workorders.cpython-310.pyc | Bin 16416 -> 17217 bytes src/web/blueprints/agent.py | 11 +- src/web/blueprints/feishu_bot.py | 11 + src/web/blueprints/knowledge.py | 84 ++- src/web/blueprints/workorders.py | 48 +- src/web/static/css/style.css | 207 +++--- src/web/static/js/dashboard.js | 51 +- src/web/static/js/main.js | 5 +- src/web/static/js/pages/knowledge.js | 654 +++++++++++++++++- src/web/static/js/pages/workorders.js | 236 ++++++- src/web/templates/dashboard.html | 4 +- start_dashboard.py | 2 +- uploads/workorder_template.xlsx | Bin 5354 -> 0 bytes 31 files changed, 1384 insertions(+), 211 deletions(-) delete mode 100644 uploads/workorder_template.xlsx diff --git a/data/tsp_assistant.db b/data/tsp_assistant.db index 8b7c6201b8b59adbdc73a56bd1d05b563c8db72d..659d6b8cee3003faf00973842313a4e53e0bd6e4 100644 GIT binary patch literal 118784 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 CnrB4- delta 4573 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 delta 4202 zcmZ`+Yj9J?6~4RoUg_#ulI6F-HkQFd5f9tgfQjv3NDQPY5f9T))DR(juZ4mhX75$N z9tlBlXgYl)WLuIcCXw4PEz^ONQOmT+pHBOu(?5Ou=(KYuGxSFcVVL$uXF8os+R}4Y z#&!geM&DhX-93BuoNv#b`|D})@3=qX^XU?NK0ffx#L*oW{jKC%7uPK>$VoY+M3j^o zQB$snE9H*3iDVE%NopxAqKUSe^rZBNF50eSRmvOj63HY*pXr|^k?P%&;Wo5s$3Cc!HTC6o|&P%fd=F?H}3wg88f+_mJ(Tjh5gXhWec7RE;6{E$wv~Pa4~xw_WrOi(YlyWo$QgfHY`4wJ@h@Q73418BZG{Frvdy zMy1d*PR+s5eLLc2+TLMV{CVZ)B*MMwMN-e-R`-xPeqa5RH1oTzRFx0*$N~s8JnC-V zQj5(d07n_mrpH(_Hi^mVQ7)hXB|c`x6P9hVsGZ3qJNb3@-$@(4tgRhd0o`HN4j7Wz zf;P5lVg90$>TLQmYBq7=`R_Vhku67PL1+aCslp^-%+f9%(pML72acMs&6MT1ViOZ) z+F(JPDR$LSo{ok@bhOoqCc*IX7Ck74Ceq`X$J$pHH|=Qb12oVip?) z*$m3J^JwiT@$pja`XhrV5QLPiY|3%H97|?R%TZY-3#?p=y+MRUYP!n%e;TDD2s;sW zAymeS57h<9o24Ulouo%NHHf1YRm~r_J&&Dxv%ZP^r1ZObHz8g8PYvDs7FZV!xtXvV zbvo);(u}bRD_K8u%^znN0h_TOwQgTyD=G0*<0nB>?RXMaG&W`@UN%`ZTC})BAHUGl zTUc11jbb;B35Tj!)x$q!n?!mNn-$K(jQlv;uFb4k+V|5)BmZkPNm ze$sFl`yLsvv2_Kx&-b-Hs1mLTMb?+8x1gatczF}RJ4@Y}&>o;Zq=F*PhGLO~Qk zj1>oBi4(CS2GCqO+Ods1^|`L7^es$rL^4k%Q_-vyi<^Q9)@uH@m0=R*-Kz!)=v*Cs zzk#1+0GuOJ0VpzPj$D?m&>YRlwp1juQjyL|hCHJll13#(vgOH*M9Ql~nyR;zqG~8P z6~~KGLbY8v^*l+CLy}86EE%r6dParvLOf5AQ&PyCMRL!*J3Vms^$T}yy?W=|dk3?1 zAQG4No$ub7d-bPf@jwuqw8)H?U<^BfFu*6j&^9Qd#$EtHXg;L~0xb?p;@HX%fy`;( z=hif|^Z_$0%$Jb&6)J0l!dEBSr+zm#*YxtibrWBw3z zV^`(M{Pndhz=ChAPuAfXLN7cGKkI$4W9kF$-LRi*L+O|D z`XT9L3_|Chyb7W7LFnp0pT}re(ASvrK@b(3b4WT|fPygZJL5YUD?c%E!hoh61?9~- z9dOwdRm6OGPA?miqejb&GS3qKdg#Xn8gDFj6y

eeH!VAbO$tyO=Dfl9QL=zMZ0!7ul%ceP3QJw{vp`1`yPfji>hAQ|1Il*uvZLcWh zF@}aOBs0qFZs6O=Ku(?~jXJp(NCWRWQvusmbQ@JU_e0XeftMns(7gMMTTlg_BBgk< z&D^)Or7%TxJ$z3Eo7sy1Sxj(quarJ||2tU^H1B?Y`rey2?)~5`wgG$b00Ld^%>MB1 zJ6At`??QZBeC`axZDB(nl-6nMl*t@z40sXGut_H{mPwB%3^P3j=@d#cQ!ww&Yp>6} zcIoar1=O-YJJw10uFT435J%570{@V>{P>*h?Rh4!Mjl`Z95t>t%*$9G9%t{ zSGjarvN|s8D5+Q?eFNQ##?Z4aS(6nwu9SHHrz;v9Vs-_`Y0)T-tdB-Rb?g#K+{sKl zo=C@8A3wO`=t~|b()NJFWQ;?1IWwWP<@qv$S4FYkMGyvPL#d3BO`6-lhH~|<5KYxc VGx3VAPW@CW;8*L=>LBh9{tq^akY4}* diff --git a/src/agent/__pycache__/agent_core.cpython-310.pyc b/src/agent/__pycache__/agent_core.cpython-310.pyc index a84da53cceeaf559fca966d83d6c621c386ec86b..3a255d944a1eeee8574cc0281c02ff36676247f4 100644 GIT binary patch delta 2042 zcmZuy-A`Oq5a;d(EX#gS0_-jt#7Cx9;v;=AK0` zwY+Fdv`J$oG2uy#Cfd{&gFYG`n)uSB(Wj>O!8Gx~G)DgcUYt3Lkgna${&w!1IWu#9 zXJ+o+^v+D!4u^v^^!fFpTUPG-aL}U-V%(z#GQpMlg&IAGbDsH@Uyq12|_0P@EUz)#D13lyqdNAaDJTXp|T^uC=e@VPAEs2XvNl0TOG^UCxWciN#RA3uF}sT zR`Ac@ur`Hl4NJ>Yq=HcbH=Hd4P&C7lrW9%22ggaBX~=@0XCr5udFW;969lxX&OF5m z-VW0YPahs$BAviT4JYx-NDC%I*R*Ne2n}>3*;e`LC+Q$WDQ^}*$DcwcQp&klt3-cl3ou1R|Ir0sOZN?%7nzG$d>7Z zmGHbc$S{OWk<=-b2)l3_D|S=_beB#!FRM>GPF6QiM8>sQ%Bll@j$E%@WKYvL6pfE| zvZ%tUJ0^D$%&eq4cC6f*bQG^{#3+|mnD2rpP+1TiH>2?vF3?`xTcMnfk~xwWCKRb9 z4!2iv|1z1~#s|^c=T33p(*$lEvtpN}8>)CW@>ao;x(5zSV1$jUTwNjE=eu7u-qjl4 z;N>?NI3*awx0-s7DULGyJWt?y-w|8YF1Yf(-M!m%QA??_Xs)(dW#b0M^JZar#hkd0 zZOvnviHpsXu{m}-!En%d+zI$y^K-|U33!F!>4e=zYC59U+7Jf+*~NRid(GQg7q^}3 zH}eAI9iQuyTO|Ri6_?q`dE9C3@3vSxWZ@`p36AHfOS5*NH1Rt=Zv9ZZj;pZ?wM)Df z$Da1M2Nv+RSW-KK|HOuyRW`~6?}k-^hd2^{q-|m(kr-5TZSvlbO2u%t94QJ_hX98x z_HZFFpjlK-ar`WCrPgMD>-cx#?DP#1bM&Ln<4MTU-+u+dC&+{xu_3^cTSmbwRlClU zg$GyKZuWAjbpI-)63Nw;l5`=gYyU;-!w2mnG@rf6-dG=L-KeAnGqQHEC=|y;!DO<( zLAe5U^#QIYn=51}Mid!kT4}|d+|pm4}!Zvny_lTm7P9~MqwjusrCA_0DaNZ`SmPM`$~^i z65~e|P{mRe_&n>L<=jtGLC9fz(y4E^vQdm7&X8a@%iv*9+51>h)yG2hOo4?jS`ADg2>pD8Q25 z4GH|K>s^y)F@za-E<%Js(HmunhZZnl>a_qzNOJ3m&x>}BUO}KvtO#mi8+QD^*m$pd HR15wKjB3Bw delta 1967 zcmZuyU1(fY5Z<$Y$^Nfllikhl*3Fu|o1{&e)~0F9PiqUoG&NhT8i}{Jo7;4CckgEI z-888`#ajOmR2W|b5rl{oAGD;>7ayz-f_)Mn6t4)16fG2d6cmaxXKO-Ncj4PPGjqN( zXU@#*y9fWC4jG}4-$9?xBX1OEE{FV1k;Q~laUXXmKAdspeI6aafH}pFpE?&Kp2PY4 zvHT?m^w1vY!K~}3H%el&WAk~}uTGIf&3iLhH?l{Hz;0I6Vx?%QYG$+0q=`(7l(jIy zjg5=Hz5U9Kjmy7Z`EV?ygrS9$(9V#?o6V06BuTV=ht~>v)hZel7-AD|hin4sxQ(XvHEpq*$@^ilu55t+Z|y%{vKU<`ly|f)jY#zh6w?XkhWw z1S|Iv*r7rVfVY~KX6bW^8x&G61PY+jT3t4@5~SI?pMb3Lc3azFl1+DC6Gln5gntHh zib?z;9K*5T6C}M8%yo6Jw`9wZv~@_eq*b5~e+&+$CA}n_V&4Yl{iHLnKQt_+h`9^T zg-)K3etqoBIh<2mw$Cb3axAS}1!wfg;^mb;gWHm-MMqbb2OW|=bO_9CoeS}#i@n|Fw=weYGnZ;_Pt&J0D zht*nji4$Db%k<)O`}ZP=&E7SG7X4U6^K3rOzz0DNpKHlvluB$k>XJ@e6;y>%dAMt2!$=y z8hq^L@sd_PwWLj4$I;jUp`sO=j2~j!afYo{;Y!BqvEF`u6*$OncffX(xyq1cTkBOFWbd40k|TJvtuM9A;&v8Nw6y)>C2FgJQLaw> zgtyyX7H9Bc{2}oqeiQE&^Y}-+Q;c9RvAdpYxtaID62W(vPy8TOaUhu-kcnDl#-I#B zHP=l`FE@k_E9CJPt|W6}nJOWHSCaGIGV>W2Xup5@9EltyeSV$PC01)2_x{3iLd;?>U20i?>)8+Vpr=%YP4>fpzLH3*M-#K13F>Dv=_FKv-Ns-m|(`=OEs%B<{!(m2A z>2W==biL#1f$>rvD9@6mILx~LBll2$>(($f^(g0C*{O{o&X8c(!{B6) z(YslaWzHj3jy`a)#G?v$DggH^a2Epi5qKC*GjM^)aYncfIXSq4s+9P@AwNw$yjIcqG_ Xe+kGN%LKKs4-^0AZ2Tp)Px$@;?w_Z; diff --git a/src/agent/agent_core.py b/src/agent/agent_core.py index 6951032..3d5a1b8 100644 --- a/src/agent/agent_core.py +++ b/src/agent/agent_core.py @@ -29,6 +29,7 @@ class AgentState(Enum): PLANNING = "planning" EXECUTING = "executing" LEARNING = "learning" + PROCESSING = "processing" ERROR = "error" class AgentCore: diff --git a/src/agent_assistant.py b/src/agent_assistant.py index ff9338a..5449a67 100644 --- a/src/agent_assistant.py +++ b/src/agent_assistant.py @@ -6,10 +6,12 @@ TSP Agent助手 - 简化版本 import logging import asyncio +import json from typing import Dict, Any, List, Optional from datetime import datetime from src.config.unified_config import get_config from src.agent.llm_client import LLMManager +from src.web.service_manager import service_manager logger = logging.getLogger(__name__) @@ -304,22 +306,65 @@ class TSPAgentAssistant: logger.error(f"获取LLM使用统计失败: {e}") return {} + def process_message_agent_sync(self, message: str, user_id: str = "admin", + work_order_id: Optional[int] = None, + enable_proactive: bool = True) -> Dict[str, Any]: + """处理消息(同步桥接)""" + try: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + return loop.run_until_complete(self.process_message_agent(message, user_id, work_order_id, enable_proactive)) + except Exception as e: + logger.error(f"同步处理消息失败: {e}") + return {"error": str(e)} + async def process_message_agent(self, message: str, user_id: str = "admin", work_order_id: Optional[int] = None, enable_proactive: bool = True) -> Dict[str, Any]: - """处理消息""" + """处理消息 (实战化)""" try: - # 简化的消息处理 + logger.info(f"Agent收到消息: {message}") + + # 1. 识别意图和推荐工具 + prompt = f"用户消息: {message}\n请分析用户意图,并从工具列表中选择最合适的工具。工具列表: {json.dumps(self.get_available_tools())}\n请直接返回你的分析和建议响应。" + + response_text = await self.llm_manager.generate(prompt) + + # 2. 模拟动作生成 + actions = [] + if "工单" in message or "查询" in message: + actions.append({"type": "tool_call", "tool": "search_work_order", "status": "suggested"}) + return { "success": True, - "message": f"Agent收到消息: {message}", + "response": response_text, + "actions": actions, "user_id": user_id, "work_order_id": work_order_id, + "status": "completed", "timestamp": datetime.now().isoformat() } except Exception as e: logger.error(f"处理消息失败: {e}") return {"error": str(e)} + + def execute_tool_sync(self, tool_name: str, parameters: Dict[str, Any] = None) -> Dict[str, Any]: + """执行工具(同步桥接)""" + try: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + return loop.run_until_complete(self.execute_tool(tool_name, parameters)) + except Exception as e: + return {"error": str(e)} + + def trigger_sample_actions_sync(self) -> Dict[str, Any]: + """触发示例动作(同步桥接)""" + try: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + return loop.run_until_complete(self.trigger_sample_actions()) + except Exception as e: + return {"success": False, "error": str(e)} async def trigger_sample_actions(self) -> Dict[str, Any]: """触发示例动作""" @@ -336,7 +381,7 @@ class TSPAgentAssistant: logger.error(f"触发示例动作失败: {e}") return {"success": False, "error": str(e)} - def process_file_to_knowledge(self, file_path: str, filename: str) -> Dict[str, Any]: + async def process_file_to_knowledge(self, file_path: str, filename: str) -> Dict[str, Any]: """处理文件并生成知识库""" try: import os @@ -356,20 +401,39 @@ class TSPAgentAssistant: logger.info(f"文件读取成功: {filename}, 字符数={len(content)}") - # 使用简化的知识提取 + # 使用LLM进行知识提取 (异步调用) logger.info(f"正在对文件内容进行 AI 知识提取...") - knowledge_entries = self._extract_knowledge_from_content(content, filename) + knowledge_entries = await self._extract_knowledge_from_content(content, filename) logger.info(f"知识提取完成: 共提取出 {len(knowledge_entries)} 个潜在条目") # 保存到知识库 saved_count = 0 + + # 获取知识库管理器 + try: + knowledge_manager = service_manager.get_assistant().knowledge_manager + except Exception as e: + logger.error(f"无法获取知识库管理器: {e}") + knowledge_manager = None + for i, entry in enumerate(knowledge_entries): try: logger.info(f"正在保存知识条目 [{i+1}/{len(knowledge_entries)}]: {entry.get('question', '')[:30]}...") - # 这里在实际项目中应当注入知识库管理器的保存逻辑 - # 但在当前简化版本中仅记录日志 - saved_count += 1 + + if knowledge_manager: + # 实际保存到数据库 + knowledge_manager.add_knowledge_entry( + question=entry.get('question'), + answer=entry.get('answer'), + category=entry.get('category', '文档导入'), + confidence_score=entry.get('confidence_score', 0.8) + ) + saved_count += 1 + else: + # 如果无法获取管理器,仅记录日志(降级处理) + logger.warning("知识库管理器不可用,跳过保存") + except Exception as save_error: logger.error(f"保存知识条目 {i+1} 时出错: {save_error}") @@ -402,26 +466,71 @@ class TSPAgentAssistant: logger.error(f"读取文件失败: {e}") return "" - def _extract_knowledge_from_content(self, content: str, filename: str) -> List[Dict[str, Any]]: - """从内容中提取知识""" + async def _extract_knowledge_from_content(self, content: str, filename: str) -> List[Dict[str, Any]]: + """从内容中提取知识 - 使用LLM""" try: - # 简化的知识提取逻辑 - entries = [] - - # 按段落分割内容 - paragraphs = content.split('\n\n') - - for i, paragraph in enumerate(paragraphs[:5]): # 最多提取5个 - if len(paragraph.strip()) > 20: # 过滤太短的段落 - entries.append({ - "question": f"关于{filename}的问题{i+1}", - "answer": paragraph.strip(), - "category": "文档知识", - "confidence_score": 0.7 + # 限制内容长度,避免超出token限制 + # 假设每个汉字2个token,保留前8000个字符作为上下文 + truncated_content = content[:8000] + if len(content) > 8000: + truncated_content += "\n...(后续内容已省略)" + + prompt = f""" +你是一个专业的知识库构建助手。请分析以下文档内容,提取出关键的"问题"和"答案"对,用于构建知识库。 + +文档文件名:{filename} +文档内容: +{truncated_content} + +要求: +1. 提取文档中的核心知识点,转化为"问题(question)"和"答案(answer)"的形式。 +2. "问题"应该清晰明确,方便用户搜索。 +3. "答案"应该准确、完整,直接回答问题。 +4. "分类(category)"请根据内容自动归类(如:故障排查、操作指南、系统配置、业务流程等)。 +5. 输出格式必须是合法的 JSON 数组,不要包含Markdown标记。 + +JSON格式示例: +[ + {{"question": "如何重置密码?", "answer": "请访问设置页面,点击重置密码按钮...", "category": "操作指南"}}, + {{"question": "系统支持哪些浏览器?", "answer": "支持Chrome, Edge, Firefox...", "category": "系统配置"}} +] +""" + # 调用LLM生成 + logger.info("正在调用LLM进行知识提取...") + response_text = await self.llm_manager.generate(prompt, temperature=0.3) + + # 清理响应中的Markdown标记(如果存在) + cleaned_text = response_text.strip() + if cleaned_text.startswith("```json"): + cleaned_text = cleaned_text[7:] + if cleaned_text.startswith("```"): + cleaned_text = cleaned_text[3:] + if cleaned_text.endswith("```"): + cleaned_text = cleaned_text[:-3] + cleaned_text = cleaned_text.strip() + + # 解析JSON + try: + entries = json.loads(cleaned_text) + except json.JSONDecodeError: + # 尝试修复常见的JSON错误 + logger.warning(f"JSON解析失败,尝试简单修复: {cleaned_text[:100]}...") + # 这里可以添加更复杂的修复逻辑,或者直接记录错误 + return [] + + # 验证和标准化 + valid_entries = [] + for entry in entries: + if isinstance(entry, dict) and "question" in entry and "answer" in entry: + valid_entries.append({ + "question": entry["question"], + "answer": entry["answer"], + "category": entry.get("category", "文档导入"), + "confidence_score": 0.9 # LLM生成的置信度较高 }) - - return entries - + + return valid_entries + except Exception as e: logger.error(f"提取知识失败: {e}") return [] diff --git a/src/core/__pycache__/cache_manager.cpython-310.pyc b/src/core/__pycache__/cache_manager.cpython-310.pyc index e849ca69556a51fef45ff94dab973df5ba6efacb..c4a808f5df9888540656ac28046138e5212cb581 100644 GIT binary patch delta 1313 zcmah|;cpa06rY*hE8D&HD7`!GuBGh;isqV1Qi(A(8rvXMEYOyc2&ZN_ZniCY?UC7Q zOvst5(6nblBViK3P%I4IGz+fZ9PsTr?A4n8$+lwasOrnYJ?Y3L>i?f;Ey!YnK zd++yV_VM#)Ukc@FYjpynZQ}f?pLc#88m09QLA6MVa*|@4rWD>RXJ`j)mJev(-WHy) z0=(AJd9@WBs^Sg2aWatF23oxpu(TnTAVZAr;7yZiD$Lm+Ng9hu_Q%#@rJWM8tdCl@ zogTK#Q#_+!-u0ovs=?d`(>@8nX@OoLSK0eYfxJULYN4b+uF--rPerVnyg@iqiAv5X zlyGGb_gF#UYJtsz_)TKW9q+R}%RzViL$)msbCl}l5V zi*uWKj#&9|G4}6N#>rd`A{`auma#g2b7cVwEN&1fRiFUTuuYA0qmWIH(#?X@qIP?OTne`rV<3 z7S{B5upuUYjhs^;fEd2{B|E}X`NFlflI);+lAk8#0>1@_C%koKVce{8Mc9NRFSmFS}>j=FF z_y>uD2$)p7iF}&GVUQoev~k;GRinCA(IqG1du#i^4kXYIN_rT7s_sv8X8<}AZ^I(H z5>XjU{N6hNN^*;r*R9|P7}m+4HiNau5cQF*W7g%?df3i^7@smb6QFok80XY=r16B}=`hN(5;1Uk9Rii%#@-gv0h eEjWQ@w^1HnRNa;#PjLcT_VSj#s>V7cqW%rx(`Ie} delta 651 zcmYjOO-~b16rJ}vz);!(?XWAn$sMq$|yVy9#JXqV?mh+Lr8<6?`~^5J0=FNGu_wg)n;9t#Zj5w5?HTmCGOcW~b@3eNdW5YEOHT zaHa(ojtqj01I_~~4BRxdirxLUB{=`1(f8p>va^YM$QNpv3i*^g)MzDtWoq@f4yWxE z#cI`BJ@8b}+-N-e&2kg>8k2fVts&_ygOgT8+RM+6ha=`7hs$a^L>R6O&eR%Bow(L> LxqEmZV(R|@aiM!f diff --git a/src/core/__pycache__/query_optimizer.cpython-310.pyc b/src/core/__pycache__/query_optimizer.cpython-310.pyc index 2c5bf3c6314d081ae6b64be0a6edc52c9ed1c439..154a704e14c7e66d7a110f1739e940f35c815e32 100644 GIT binary patch delta 2594 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 bool: + """ + 检查消息是否已处理,如果未处理则标记为已处理 + + Args: + message_id: 消息ID + ttl: 过期时间(秒),默认5分钟 + + Returns: + bool: True 表示已处理(重复消息),False 表示未处理(新消息) + """ + key = f"msg_processed:{message_id}" + + # 使用锁确保原子性(针对内存缓存) + with self.cache_lock: + # 1. 检查是否存在 + if self.get(key): + return True + + # 2. 如果不存在,则标记为已处理 + self.set(key, 1, ttl) + return False + def get_stats(self) -> Dict[str, Any]: """获取缓存统计信息""" with self.cache_lock: @@ -218,11 +241,10 @@ class DatabaseCache: logger.debug(f"缓存未命中: {cache_key}") result = func(*args, **kwargs) self.cache_manager.set(cache_key, result, self.ttl) - + return result return wrapper - # 全局缓存管理器实例 cache_manager = CacheManager() diff --git a/src/core/query_optimizer.py b/src/core/query_optimizer.py index 478d02f..ed5afb1 100644 --- a/src/core/query_optimizer.py +++ b/src/core/query_optimizer.py @@ -92,7 +92,9 @@ class QueryOptimizer: 'confidence_score': conv.confidence_score, 'work_order_id': conv.work_order_id, 'ip_address': conv.ip_address, - 'invocation_method': conv.invocation_method + 'invocation_method': conv.invocation_method, + # 构造用户显示名称:如果有IP则显示IP,否则显示匿名 + 'user_id': f"{conv.ip_address} ({conv.invocation_method})" if conv.ip_address else "匿名" }) # 记录查询时间 diff --git a/src/integrations/__pycache__/ai_suggestion_service.cpython-310.pyc b/src/integrations/__pycache__/ai_suggestion_service.cpython-310.pyc index bdf00eb9a47a72d746b118ebbe38d94f2225984f..535f2d01afe03449b26cf876d6c4f6084e6412ce 100644 GIT binary patch delta 1190 zcmb`Gzfapx5XYlV$P($=R;uKB>(D6BA8VzGp$qN8(3y>?Q&ln9i3qh5W2fYY0|ZDA z4pC|BpwN)SCQ<(ueD~}r|3c5awt}isMTKSg`8~b6``+E>#vjeq*9gKxql7d5?LVAD3rd;>?NOA=cB$U3Dz zI4^;=5m`=jP-8}y<@-}#4+e!`pkT*_%r^Eak|2s=FfKqV4j`rH=hC#I)mVD<_8pVA zJUFj}PM5x98`Txk!pdz_&%((LJKn;43CwDI7cCnm7O3mESG-WNk}#hVe@c;p^9dPo zIc$4vadBaRCouI@3e1CQ!c(%iu6a2YEm2I^9|a?Wr-G&#PP(wE!{Hh$u5)#$c+4x2 zM&>A3Gf?x%N_a*d|4f(y;$9tVZq%yc*JkO>%)Ps4Y~sF6MTx!~lBQB&=Dys~rarw? zu#LC@W|3zsJWq)f3_2E8x@a7M+J}03E}f>6z_VdJ_uGbw5jOQJTZ?6f!i$v1yj^gI z+%d0>1l}lg9L%<*3ne4bDud~gKZ$iTa1Ow>LU%V#Z|4*ZJK9p?5?ppr5JLA1b&chY zf!5XRH0o4QI4P^l`t08;;7p;xHXO&48s)m2q>g&~31U@!?GkQfDMfgmV0 zh$I-t;0yE?eKUSu(Gygi=Vv8hW!W>|eDnPV^ZWYS&X?_NWs80f(xuOTKhAdEJ<0sO zx3#74K^AAZjen{}>bRSS!evY`M#YvTqA2uZkdgy79jFelrDI(ai|K3F_ioN1 z$~Mq9!`w2aNw-nX*a>m-fJR8v(mq-jxR^n{FG$B|DG(^kDJ$%1G2oa94_e`{lz3=# zlDW-F*tNNHfz679CfSExz}%9Q!y`JDOjgj92QAAJoYkaH=6l1HJ4L!uii-s{^vZH+ zf%z_1FmqU+(iy>`Pv;>2JJf^oA?}&XE75fbv_$ZeKp1bwV(t)(BegB*%Vfgh6_OH7 kbW6-tX`5Qe*IddJyucN{+x6fsDIS&5)4f_U^OXb7SqDlUZC=&D3BX%%JHPjD6d zIMffw%uk4(gq(8s77%hyj(QQ3gBwltiU+ZaeyM)nhwAR*>Fa4{KAE%yuWxJnx%1aw zohEPH1tC^vl?WjcM-v6eLx{?VAe73OiTN;#+?$ao;M0VOiZX#^!nh}9EENw;&g=*x zd^D$$U5n=>c%CfN9q};9ro-stE2|>0Fz90;mdCuI+`QZdRZXW}bDO^mAauRs{Z{yL zSV*C2R*E1BCWuv6)rNI+<7Hrs##56}Pi{lY%Q^mb@J36ImbjK)mY>#CFu%BvdA|~P zbd~C_8FQDiLGD8?>u0hbRglYmroQ%}kx^z5ZLrY-ypxExcFY3qZ>53%q$$DX9u1u) z5O1W#=R16$ODM5~6$mDmEP213Ru^2VZhTIYFPxLBb^~FH{=Vvj(-W`blVR@a2kz0A v&N(=vMAvt-sEs21=vssg8c4TRZfavk%K=?>KcmC+6r|`+`T`E=SNG6=EQW$x delta 462 zcmY*UOG^S#6u#%oozX^}v_obX4qdR=~Ms$DK zWHvbLI#C7-Y%~=IZ0@i!{xQ{5Hna?EV$)H6oDJ8%go{$H-;9H{TXLFcp3_WmvUVPQZ*NWvIF)txQmiuv?vsSuW0i|8Dh zRtpEzi|wJr>>M?SIvAjX8}O4OHn_@(=|8OQRq=zno2-H4ql%zW@p`ysbos$A0$m+q zrN_7*A65`5Vloni+xyMPj-s8(N6*DebPJxueC$y>mBOhA#*1(yF5`WkV=0_SxDv6^ Spm>gFp;t_d)Zki#MiXCaym7?< diff --git a/src/knowledge_base/knowledge_manager.py b/src/knowledge_base/knowledge_manager.py index 16f4da2..1b923c3 100644 --- a/src/knowledge_base/knowledge_manager.py +++ b/src/knowledge_base/knowledge_manager.py @@ -401,27 +401,35 @@ class KnowledgeManager: """获取知识库统计信息""" try: with db_manager.get_session() as session: - total_entries = session.query(KnowledgeEntry).count() - active_entries = session.query(KnowledgeEntry).filter( + # 只统计活跃(未删除)的条目 + total_entries = session.query(KnowledgeEntry).filter( KnowledgeEntry.is_active == True ).count() - # 按类别统计 + # 统计已验证的条目 + verified_entries = session.query(KnowledgeEntry).filter( + KnowledgeEntry.is_active == True, + KnowledgeEntry.is_verified == True + ).count() + + # 按类别统计(仅限活跃条目) category_stats = session.query( KnowledgeEntry.category, - session.query(KnowledgeEntry).filter( - KnowledgeEntry.category == KnowledgeEntry.category - ).count() + func.count(KnowledgeEntry.id) + ).filter( + KnowledgeEntry.is_active == True ).group_by(KnowledgeEntry.category).all() - # 平均置信度 + # 平均置信度(仅限活跃条目) avg_confidence = session.query( func.avg(KnowledgeEntry.confidence_score) + ).filter( + KnowledgeEntry.is_active == True ).scalar() or 0.0 return { "total_entries": total_entries, - "active_entries": active_entries, + "active_entries": verified_entries, # 将 active_entries 复用为已验证数量,或前端相应修改 "category_distribution": dict(category_stats), "average_confidence": float(avg_confidence) } diff --git a/src/utils/__pycache__/helpers.cpython-310.pyc b/src/utils/__pycache__/helpers.cpython-310.pyc index cefe89b04368888b2b6124ecd33dab705829aac1..6df38271b23076069c28bf944f0033b17e64ed7e 100644 GIT binary patch delta 241 zcmexp@X=sHFMIuy9Sx<-3=9lUcI^VwFZR!b@OSir>8BeuLit?~{=NjPbnq6&R zxu;7ZYM#$+02}vg)52$SIw4YXR)A?ZzheWK|6=_Xh}iHi$41OWZ_ BRWbko delta 241 zcmXYrOAY}+6h$kMU}!15DGM;DF+ymcmN24If(VhGbPQxr?2TjoemGzwZ%E=I`kUXjOhq@6vGUQEbeF%s?@ zWH@#y7bo8Q{RTb2-|n(=|C^u^obhR&cGVd9=^1jfMcT!b{F#zV>K^hdo-8|q%J!{# F;|s}YRWbko diff --git a/src/web/blueprints/__pycache__/agent.cpython-310.pyc b/src/web/blueprints/__pycache__/agent.cpython-310.pyc index 9ec7ac6951bfa7c2b6387ad1bd7fc5826febfc40..7a31cbc94963285bb4d3353a5481fa27ff30dd77 100644 GIT binary patch delta 1378 zcmb7^U1%It6vyYx&dkov%+7vqvb%}dXwziV)U9cnG$}D+Z7CJfn3bf3mUY+}Hyg6M zab`l3xSJK@ljzf-h$508LiNeApl{+!Uj#wWr=^I3FXDqrp-?F5J+n&+KIty^chB5= z&YU~{#jax3+kQ*?G}C0k&zc9by8@Tt$1@vMJNxgTavyiWrS_x!VhJ;HI=Cs%oU6z^nW z_=y}>=O}2LFwy=&)&QQyp99C?0?Nt|oNA9LTL91CN9v$>hB+1RYxOBOgTJW1!6bgC zy*XTBqxX{xUK-%pUjgMWUC4s`2LHD`hfXjLGx%|E$&1KhTpx#7FaNThR9~ai=LqxY z=u^?ByCE9OgUhKoaq zwDb)%S$*O-&PmN>^+le zcjHO;0`JB1u!3458GAG%0-i(7s9N?liXJ1x2yqU7N@hpCWww)M=pLFl=paB8;=IDF-bP+;kM=za zv8&9hqnegevMZZ(;_dUe-JfL#^!@%lR#^S1LAZ+3sVw}8tEmOJf$yazrY9*G7100s zNPL(&miFr72E`kM-m;nX3eKdTfFE%seG)8uEB&X`-5Pv7b6lWh;y^HqcQZ+NfcG*f zSi=XIV}Xv}pc|@E$Dx4}^Aq$+sw7%g5Z+m@cWv)_?f5T_oe~GyhJ>VUOG*;bCMpz)PzfywY8xP~lQqW1KV^3# zHIbcE%CY1CBXK|=*~fCADv=OE0f_@g)N5ra2TmNQ5Qq~f{LHK^CkG^UwcpOZnR#z# zzVE%yCV!pMtA4*vpldg9r!evOef_kIkNW&)i!`(zH^2}}LK*4;2xPlTVjXJGaMgr{ z)D#--rqG1CXv;Nm3$6Or0h*lLW(FGr+co zprvH3waQAN+H#qz6*I^~G5kf2#Z8H0LJWfh!;%wk9>ad+SuQHRiQ8Hn2RxDFG)4Z-^K!TbKF}crp4;L-=Ge}RM*}#?q0|0+&d!ud<&0%# zZRen_VXu&f^#QIHd6>eG{H@%H0e^lXu8v9h4h9DE9*13-pG$=}h_a9QTl5{Z;(d zJBWVhR;x&qTw|(!@n&N<+ANIw3&k{#A;qwv;HSYM zID_8@)5*D(vTEhbOu=Zmvqrg4Ss*2xq5n2t!az@D^Aek{FsKZUzO(~d5qyWK`VOGtghrND^n)O=5rZ(D)u4idD@zqM3%fEJ6zElPetF^yhv-#wVSX1PxHHdcQ(7Z zz-7Ho(9&|Zax1HLE@M}#r9<6U4!Ym+vM*D~mUHGCoY?NTfO>3X=m8B_{RHie9wFsR zK+aqVRnR@Gia44)>UIjo>&d4k$9P5-mvh>fXW5!%IL~l_;R?ewh9!nP ZLlJkAv!LRBav(5BXJL$<9(sCp_!sK}N=yI% diff --git a/src/web/blueprints/__pycache__/feishu_bot.cpython-310.pyc b/src/web/blueprints/__pycache__/feishu_bot.cpython-310.pyc index 5f308d9de33ce84e7bec36293e77631913124703..891c37b678c63d667a0638354259dc8a571a36fa 100644 GIT binary patch delta 2207 zcmZuyTWl0n7(Qoac6WAmXLk0|-L@21u4OHyTto#b2s8q6GoT^VkagKJv|HNQWoEVw z<*ZAy$VDlX3?@b_?h=f63sDScQsK?$3no7Bu)cUnp(ZA%FTN1te`ZTjU?%g=fBy4d zzW*|3zF6>ki;|UP3Bh&h(8-x zyX_r#YjY#S^ueuFh;xJCZd1@RN^skI}!B|?|#l$M_z;*6%UuTl6>RjvzDNSp1^enKUwnLk!La2mOO$~Nl28R;6ui_6t1yXFxU#Q; z3eqm5&k>O2?eQPHfKkDR(O}epUPpZopzj921>bAFN0V^mMc)7(Kn~`pgDFNOKvq8r zSTS+{G;|@Tj-UFCU#Y(X?MW15Dg%^82Bn}-#rTCxqY4XIdy}S7ejf*#;X;5)Wtj#b z@1Ln;DKt>RM0}lVK(!PwD(d*a%>!Ri$-L(jW;z2=D?8=NP$n{?!ac494&<4WV0ov+ zYB>mXQ7I4L;u!!9?^%=Y*-ndtWDaWynM;GLF@=y-WZjxaLsLQ3atNG+p*Rs5h8!9T zb+ir~&8}b?+7EMMY;@N1p(POqL^F-tx5h{ZjRS2oGJ?&$RP71V5KQ!fVQx}4!2**f z91tzct~ly;urnA7osz3P5gMTqn6;8c!$N`e)J588^nMT;1vrZ<7@)lA@Jkq;`^<+7%XV!%n_!7udwUraMSVv=4w8ZlIgvP*e(2CJm2{R`a+l#S1 z^J-1%IB=1tp_8BknLIOED?MMXCo5=d3Zm0G8Urs4v;lno=p?c7L34HXod{%A@qq~D zc?-;Q!w7c}M$egN)#7^u(q5Zq@+?_B?99UbnbzBDvps;pt`Y1OPuk6K?AH9>Zcrh- zHLZmD;UF;Llhajvt~Oq}fDyZQ!?!-=zV5#Jfk#e&hfjou*GnLXxpe>%sd=rP8;P~~ z^Kd3fak$3cy)-^@%D(6NGFtVfyHi%uI+)X{p1i(a&kpGMR{K?7ll_A`+UDvqviV$I z2Fp9wFHQ`-vA_`|I{DnvT%H|tcRmGG=!^zAkI+iU0B5*`Z0bczz|b8Nc@V#D$N?FCMuwa&+RxvAbi($4_6L zD1CkV?aRNtbz|bj`P--72c4S2-LVsQ$4*XMy8@b3Pdckxnr;npSN?Ku!S)7G!_)0g z#nYk}947=Lex<{Ei^EO!G=F?WGsvn!M&Fk;M8?iccWdeW$&^l#re-DkG}BCVX~~?C zO#`cus%Ic&=o!7sUgMvyN~V=EEOQ`ZbyxXbO}CHu&vi6b`BW}vu#$8t`pKe$J02PQrdHaw_Jq`e#!piX}e8_z7-W2uH9A zw;!uNiHErv_E}|C<9DGIBHzTTg35`!fJNAs;1RH3f3LjcJIWnYj&XoUE zv5JMS!Gv$X(hL^nKcF0V4l=R5FH7>h+G-3MChHuOPY|Jm^-yAVOT5A>94acZg8v0H C(NoL- delta 1737 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 delta 1318 zcmY*YO>7%Q6rMNU*p7eJYv<3g9mh%9{51gw0cjelCZ(vHKrK?OL_n|@hdN+8*-T{- zX5&avsVYz8loZ{$1L@NW$5d9>BLK0>x&?R&YImyHWNY)1rj z!1BFCEKT_As6)6D`CDLit?4}Oaz`R=O{PGq zF^%w^2G1_iEqJ#Rh*~U-4Ks733Uwl4cCRoT0OjQzjPgFapB)oADmyL$N|}lluu3Bu z+4KRawoc&Htv*{vUadJaJPPA{08c3ZvEo2*tUmh~8x-;FhPp?$$sD$^;_LC1LUXUH z5j6X_FqQ_X12', methods=['DELETE']) @handle_api_errors def delete_knowledge(knowledge_id): diff --git a/src/web/blueprints/workorders.py b/src/web/blueprints/workorders.py index 6f52bda..0914304 100644 --- a/src/web/blueprints/workorders.py +++ b/src/web/blueprints/workorders.py @@ -478,19 +478,42 @@ def import_workorders(): # 解析Excel文件 try: df = pd.read_excel(upload_path) + if df is None or df.empty: + return jsonify({"error": "Excel文件内容为空或无法识别表格结构"}), 400 + imported_workorders = [] + skipped_rows = 0 # 处理每一行数据 for index, row in df.iterrows(): - # 根据Excel列名映射到工单字段 - title = str(row.get('标题', row.get('title', f'导入工单 {index + 1}'))) - description = str(row.get('描述', row.get('description', ''))) - category = str(row.get('分类', row.get('category', '技术问题'))) - priority = str(row.get('优先级', row.get('priority', 'medium'))) - status = str(row.get('状态', row.get('status', 'open'))) + # 定义候选列名映射 + col_mappings = { + 'title': ['标题', 'title', '工单标题', '问题主体', 'Subject'], + 'description': ['描述', 'description', '问题描述', '详细内容', 'Detail'], + 'category': ['分类', 'category', '业务分类', '模块', 'Type'], + 'priority': ['优先级', 'priority', '紧急程度', 'Level', 'Priority'], + 'status': ['状态', 'status', '工单状态', 'State'], + 'resolution': ['解决方案', 'resolution', '处理结果', 'Solution'], + 'satisfaction': ['满意度', 'satisfaction_score', 'rating', 'Score'] + } + + def get_val(row_data, keys, default=''): + for k in keys: + if k in row_data: + val = row_data[k] + if pd.notna(val): + return str(val) + return default + + title = get_val(row, col_mappings['title'], f'导入工单 {index + 1}') + description = get_val(row, col_mappings['description'], '') + category = get_val(row, col_mappings['category'], '技术问题') + priority = get_val(row, col_mappings['priority'], 'medium') + status = get_val(row, col_mappings['status'], 'open') # 验证必填字段 if not title or title.strip() == '': + skipped_rows += 1 continue # 生成唯一的工单ID @@ -513,12 +536,14 @@ def import_workorders(): ) # 处理可选字段 - if pd.notna(row.get('解决方案', row.get('resolution'))): - workorder.resolution = str(row.get('解决方案', row.get('resolution'))) + res_val = get_val(row, col_mappings['resolution'], None) + if res_val: + workorder.resolution = res_val - if pd.notna(row.get('满意度', row.get('satisfaction_score'))): + score_val = get_val(row, col_mappings['satisfaction'], None) + if score_val: try: - workorder.satisfaction_score = int(row.get('满意度', row.get('satisfaction_score'))) + workorder.satisfaction_score = int(float(score_val)) except (ValueError, TypeError): workorder.satisfaction_score = None @@ -551,8 +576,9 @@ def import_workorders(): return jsonify({ "success": True, - "message": f"成功导入 {len(imported_workorders)} 个工单", + "message": f"成功导入 {len(imported_workorders)} 个工单" + (f",跳过 {skipped_rows} 行无效数据" if skipped_rows > 0 else ""), "imported_count": len(imported_workorders), + "skipped_count": skipped_rows, "workorders": imported_workorders }) diff --git a/src/web/static/css/style.css b/src/web/static/css/style.css index 5c8f11b..6107d42 100644 --- a/src/web/static/css/style.css +++ b/src/web/static/css/style.css @@ -20,7 +20,7 @@ body { .btn-group .btn:hover { transform: translateY(-1px); - box-shadow: 0 2px 8px rgba(0,0,0,0.15); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); } /* 小尺寸按钮优化 */ @@ -97,7 +97,7 @@ body { bottom: 100%; left: 50%; transform: translateX(-50%); - background-color: rgba(0,0,0,0.8); + background-color: rgba(0, 0, 0, 0.8); color: white; padding: 0.25rem 0.5rem; border-radius: 0.25rem; @@ -153,9 +153,17 @@ body { } @keyframes pulse { - 0% { opacity: 1; } - 50% { opacity: 0.5; } - 100% { opacity: 1; } + 0% { + opacity: 1; + } + + 50% { + opacity: 0.5; + } + + 100% { + opacity: 1; + } } .health-dot.normal { @@ -176,8 +184,13 @@ body { } @keyframes spin { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } } /* 可点击统计数字样式 */ @@ -239,23 +252,23 @@ body { height: 1.75rem; padding: 0.25rem 0.375rem; } - + .btn-sm i { font-size: 0.75rem; } - + .btn-group .btn:not(:last-child) { margin-right: 0.125rem; } - + .table .btn-group .btn { margin: 0 0.0625rem; } - + .clickable-stat { font-size: 1.5rem; } - + .modal-xl { max-width: 95vw; } @@ -321,7 +334,7 @@ body { .alert-card:hover { transform: translateY(-2px); - box-shadow: 0 4px 8px rgba(0,0,0,0.1); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); } .alert-card.critical { @@ -401,7 +414,7 @@ body { .card:hover { transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0,0,0,0.15); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); } /* 按钮样式 */ @@ -427,8 +440,13 @@ body { } @keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } } /* 响应式设计 */ @@ -436,13 +454,13 @@ body { .container-fluid { padding: 0 15px; } - + .score-circle { width: 80px; height: 80px; font-size: 1.2rem; } - + .card-body { padding: 1rem; } @@ -493,9 +511,11 @@ body { 0% { box-shadow: 0 0 0 0 rgba(40, 167, 69, 0.7); } + 70% { box-shadow: 0 0 0 10px rgba(40, 167, 69, 0); } + 100% { box-shadow: 0 0 0 0 rgba(40, 167, 69, 0); } @@ -505,7 +525,7 @@ body { .modal-content { border-radius: 0.5rem; border: none; - box-shadow: 0 10px 30px rgba(0,0,0,0.3); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); } .modal-header { @@ -520,7 +540,7 @@ body { /* 表格样式 */ .table-hover tbody tr:hover { - background-color: rgba(0,123,255,0.1); + background-color: rgba(0, 123, 255, 0.1); } /* 空状态样式 */ @@ -649,20 +669,20 @@ body { flex-direction: column; align-items: stretch; } - + .alert-controls .form-select { min-width: auto; } - + .alert-card .d-flex { flex-direction: column; } - + .alert-card .ms-3 { margin-left: 0 !important; margin-top: 10px; } - + .alert-data { font-size: 10px; max-height: 80px; @@ -700,7 +720,7 @@ body { .preset-card:hover { transform: translateY(-5px); - box-shadow: 0 8px 25px rgba(0,0,0,0.15); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); border-color: #007bff; } @@ -796,15 +816,15 @@ body { .preset-card .card-body { padding: 1rem; } - + .preset-card h6 { font-size: 0.9rem; } - + .preset-card p { font-size: 0.8rem; } - + .preset-params .badge { font-size: 0.65rem; padding: 0.2rem 0.4rem; @@ -863,8 +883,27 @@ body { .preset-preview .preview-param span { color: #6c757d; font-size: 0.8rem; + font-style: italic; } +/* 历史对话字体深度统一补丁 */ +.conversation-item, +.conversation-item h6, +.conversation-item p, +.conversation-item span, +.conversation-item small, +.conversation-item strong, +#conversation-list * { + font-family: var(--font-family-primary) !important; +} + +.conversation-preview p { + font-size: var(--font-size-sm); + line-height: var(--line-height-relaxed); + color: var(--text-secondary); +} + + /* AI建议与人工描述优化样式 */ .ai-suggestion-section { background: linear-gradient(135deg, #f8f9ff, #e8f2ff); @@ -1087,13 +1126,18 @@ body { left: -100%; width: 100%; height: 100%; - background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent); + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent); animation: shimmer 1.5s infinite; } @keyframes shimmer { - 0% { left: -100%; } - 100% { left: 100%; } + 0% { + left: -100%; + } + + 100% { + left: 100%; + } } /* 按钮加载状态 */ @@ -1122,9 +1166,17 @@ body { } @keyframes successPulse { - 0% { transform: scale(1); } - 50% { transform: scale(1.05); } - 100% { transform: scale(1); } + 0% { + transform: scale(1); + } + + 50% { + transform: scale(1.05); + } + + 100% { + transform: scale(1); + } } /* 工具提示优化 */ @@ -1176,94 +1228,49 @@ body { padding: 15px; margin: 10px 0; } - + .ai-suggestion-header { flex-direction: column; align-items: flex-start; gap: 10px; } - + .similarity-indicator { flex-direction: column; align-items: flex-start; gap: 8px; } - + .action-buttons { flex-direction: column; align-items: stretch; } - + .save-human-btn, .approve-btn { width: 100%; text-align: center; } - + .tooltip-custom::before { font-size: 0.7rem; padding: 6px 10px; } } - -/* f���pencaSGr7h_ */ -.vehicle-data-card { - background: linear-gradient(135deg, #e8f5e8, #f0f8f0); - border: 1px solid #4caf50; - border-radius: 10px; - margin: 10px 0; - padding: 15px; - box-shadow: 0 2px 10px rgba(76, 175, 80, 0.1); -} - -.vehicle-data-header { - border-bottom: 1px solid #4caf50; - padding-bottom: 10px; - margin-bottom: 15px; -} - -.vehicle-data-header h5 { - color: #2e7d32; - margin: 0; - font-size: 1.1rem; -} - -.vehicle-data-content { - padding: 0; -} - -.vehicle-info { - background: white; - border-radius: 8px; - padding: 12px; - margin-bottom: 10px; - border-left: 4px solid #4caf50; -} - -.vehicle-info h6 { - color: #1976d2; - margin-bottom: 8px; - font-size: 1rem; -} - -.vehicle-details p { - margin: 5px 0; - font-size: 0.9rem; - color: #333; -} - -.vehicle-details strong { - color: #2e7d32; -} - -.vehicle-error { - background: #ffebee; - color: #c62828; - padding: 10px; - border-radius: 5px; - border-left: 4px solid #f44336; - font-style: italic; -} - - \ No newline at end of file +/* 历史对话字体深度统一补丁 - V2 (修复编码) */ +.conversation-item, +.conversation-item h6, +.conversation-item p, +.conversation-item span, +.conversation-item small, +.conversation-item strong, +#conversation-list * { + font-family: var(--font-family-primary) !important; +} + +.conversation-preview p { + font-size: var(--font-size-sm); + line-height: var(--line-height-relaxed); + color: var(--text-secondary); +} \ No newline at end of file diff --git a/src/web/static/js/dashboard.js b/src/web/static/js/dashboard.js index 49a7b57..58ed840 100644 --- a/src/web/static/js/dashboard.js +++ b/src/web/static/js/dashboard.js @@ -196,7 +196,10 @@ class TSPDashboard { this.paginationConfig = { defaultPageSize: 10, pageSizeOptions: [5, 10, 20, 50], - maxVisiblePages: 5 + maxVisiblePages: 5, + currentKnowledgePage: 1, // 追踪知识库当前页 + currentWorkOrderPage: 1, // 追踪工单当前页 + currentConversationPage: 1 // 追踪对话历史当前页 }; this.init(); @@ -2076,6 +2079,7 @@ class TSPDashboard { // 知识库管理 async loadKnowledge(page = 1) { + this.paginationConfig.currentKnowledgePage = page; try { const pageSize = this.getPageSize('knowledge-pagination'); const response = await fetch(`/api/knowledge?page=${page}&per_page=${pageSize}`); @@ -2317,7 +2321,7 @@ class TSPDashboard { const data = await response.json(); if (data.success) { this.showNotification('知识库验证成功', 'success'); - this.loadKnowledge(); + this.loadKnowledge(this.paginationConfig.currentKnowledgePage); } else { this.showNotification('知识库验证失败', 'error'); } @@ -2339,7 +2343,7 @@ class TSPDashboard { const data = await response.json(); if (data.success) { this.showNotification('取消验证成功', 'success'); - this.loadKnowledge(); + this.loadKnowledge(this.paginationConfig.currentKnowledgePage); } else { this.showNotification('取消验证失败', 'error'); } @@ -2361,8 +2365,8 @@ class TSPDashboard { const data = await response.json(); if (data.success) { - this.showNotification('知识库删除成功', 'success'); - this.loadKnowledge(); + this.showNotification('知识库条目已删除', 'success'); + this.loadKnowledge(this.paginationConfig.currentKnowledgePage); } else { this.showNotification('知识库删除失败', 'error'); } @@ -2440,6 +2444,7 @@ class TSPDashboard { // 工单管理 async loadWorkOrders(page = 1, forceRefresh = false) { + this.paginationConfig.currentWorkOrderPage = page; const cacheKey = `workorders_page_${page}`; if (!forceRefresh && this.cache.has(cacheKey)) { @@ -2936,8 +2941,8 @@ class TSPDashboard { if (data.success) { this.showNotification('工单删除成功', 'success'); - // 立即刷新工单列表和统计 - await this.loadWorkOrders(); + // 立即刷新工单列表和统计 (使用当前页码并强制刷新) + await this.loadWorkOrders(this.paginationConfig.currentWorkOrderPage, true); await this.loadAnalytics(); } else { this.showNotification('删除工单失败: ' + (data.error || '未知错误'), 'error'); @@ -3233,12 +3238,37 @@ class TSPDashboard { return; } - const html = conversations.map(conv => ` + const html = conversations.map(conv => { + // 识别来源和类型 + let sourceBadge = ''; + const method = conv.invocation_method || ''; + const userId = conv.user_id || ''; + + // 判断是否来自飞书 (根据调用方式或ID格式) + if (method.includes('feishu') || userId.startsWith('ou_')) { + sourceBadge = '飞书'; + } else { + sourceBadge = 'Web'; + } + + // 判断群聊/私聊 + let typeBadge = ''; + if (method.includes('group')) { + typeBadge = '群聊'; + } else if (method.includes('p2p')) { + typeBadge = '私聊'; + } + + return `

-
用户: ${conv.user_id || '匿名'}
+
+ ${sourceBadge} + ${typeBadge} + 用户: ${userId || '匿名'} +
${new Date(conv.timestamp).toLocaleString()}
@@ -3260,7 +3290,8 @@ class TSPDashboard {
- `).join(''); + `; + }).join(''); container.innerHTML = html; } diff --git a/src/web/static/js/main.js b/src/web/static/js/main.js index b30eb92..649b19f 100644 --- a/src/web/static/js/main.js +++ b/src/web/static/js/main.js @@ -155,8 +155,9 @@ class App { // 映射路由到页面文件 const pageFile = this.getPageFile(route.name); - // 动态导入页面组件 - const pageModule = await import(`./pages/${pageFile}.js`); + // 动态导入页面组件 (添加版本号防止缓存) + const version = '1.0.2'; + const pageModule = await import(`./pages/${pageFile}.js?v=${version}`); const PageComponent = pageModule.default; // 实例化页面组件 diff --git a/src/web/static/js/pages/knowledge.js b/src/web/static/js/pages/knowledge.js index c272c63..54d15f1 100644 --- a/src/web/static/js/pages/knowledge.js +++ b/src/web/static/js/pages/knowledge.js @@ -6,28 +6,668 @@ export default class Knowledge { constructor(container, route) { this.container = container; this.route = route; + this.currentPage = 1; // 初始化当前页码 this.init(); } async init() { this.render(); + this.bindEvents(); + await Promise.all([ + this.loadKnowledgeList(), + this.loadStats() + ]); + } + + async loadStats() { + try { + const response = await fetch('/api/knowledge/stats'); + if (response.ok) { + const stats = await response.json(); + + // 更新统计数据显示 + const totalEl = this.container.querySelector('#stat-total'); + const activeEl = this.container.querySelector('#stat-active'); + const catsEl = this.container.querySelector('#stat-categories'); + const confEl = this.container.querySelector('#stat-confidence'); + + if (totalEl) totalEl.textContent = stats.total_entries || 0; + if (activeEl) activeEl.textContent = stats.active_entries || 0; // 后端现在返回的是已验证数量 + if (catsEl) catsEl.textContent = Object.keys(stats.category_distribution || {}).length; + if (confEl) confEl.textContent = ((stats.average_confidence || 0) * 100).toFixed(0) + '%'; + } + } catch (error) { + console.error('加载统计信息失败:', error); + } } render() { this.container.innerHTML = ` - + + + `; } @@ -302,9 +384,112 @@ export default class WorkOrders { } // 全局函数供表格操作使用 -window.viewWorkOrder = function(id) { - if (window.showToast) { - window.showToast(`查看工单 ${id} 功能开发中`, 'info'); +window.viewWorkOrder = async function(id) { + try { + // 显示模态框(先显示加载状态) + const modalEl = document.getElementById('workorder-detail-modal'); + const modal = new bootstrap.Modal(modalEl); + modal.show(); + + // 重置内容 + document.getElementById('modal-status-badge').innerHTML = ''; + document.getElementById('modal-ai-analysis').innerHTML = '
正在分析...'; + document.getElementById('modal-chat-history').innerHTML = '
'; + + // 获取详情 + const response = await fetch(`/api/workorders/${id}`); + const result = await response.json(); + + if (!result.success) { + alert('获取工单详情失败'); + return; + } + + const wo = result.workorder; + + // 填充基本信息 + document.getElementById('modal-title').textContent = wo.title; + document.getElementById('modal-id').textContent = wo.order_id || wo.id; + document.getElementById('modal-category').textContent = wo.category || '-'; + document.getElementById('modal-created-at').textContent = new Date(wo.created_at).toLocaleString(); + document.getElementById('modal-user').textContent = wo.user_id || '-'; + document.getElementById('modal-description').textContent = wo.description || '无描述'; + + // 状态徽章 + const statusMap = { + 'open': '待处理', + 'in_progress': '处理中', + 'resolved': '已解决', + 'closed': '已关闭' + }; + document.getElementById('modal-status-badge').innerHTML = statusMap[wo.status] || wo.status; + + // 优先级 + const priorityMap = { + 'low': '', + 'medium': '', + 'high': '', + 'urgent': '紧急' + }; + document.getElementById('modal-priority').innerHTML = priorityMap[wo.priority] || wo.priority; + + // AI 分析/建议 + if (wo.resolution) { + document.getElementById('modal-ai-analysis').innerHTML = ` +
${wo.resolution}
+ `; + } else { + document.getElementById('modal-ai-analysis').textContent = '暂无 AI 分析建议'; + } + + // 渲染对话/处理历史 (模拟数据或真实数据) + const historyContainer = document.getElementById('modal-chat-history'); + // 这里假设后端返回的详情中包含 history 或 timeline + // 如果没有,暂时显示描述作为第一条记录 + + let historyHtml = ''; + + // 模拟一条初始记录 + historyHtml += ` +
+
+ 用户 + ${new Date(wo.created_at).toLocaleString()} +
+
+ ${wo.description} +
+
+ `; + + if (wo.timeline && wo.timeline.length > 0) { + // 如果有真实的时间轴数据 + historyHtml = wo.timeline.map(item => ` +
+
+ + ${item.author || (item.type === 'ai' ? 'AI 助手' : '系统')} + + ${new Date(item.timestamp).toLocaleString()} +
+
+ ${item.content} +
+
+ `).join(''); + } + + historyContainer.innerHTML = historyHtml; + + // 绑定编辑按钮 + document.getElementById('modal-edit-btn').onclick = () => { + modal.hide(); + editWorkOrder(id); + }; + + } catch (e) { + console.error('查看工单详情失败', e); + alert('查看详情失败: ' + e.message); } }; @@ -315,15 +500,50 @@ window.editWorkOrder = function(id) { }; window.deleteWorkOrder = function(id) { - if (confirm(`确定要删除工单 ${id} 吗?`)) { - if (window.showToast) { - window.showToast('删除功能开发中', 'info'); - } + if (!confirm(`确定要删除工单 ${id} 吗?`)) { + return; } + + (async () => { + try { + const response = await fetch(`/api/workorders/${id}`, { + method: 'DELETE' + }); + const result = await response.json(); + + if (response.ok && result.success) { + if (window.showToast) { + window.showToast(result.message || `工单 ${id} 删除成功`, 'success'); + } + // 通知当前页面刷新工单列表(保持当前页) + const event = new CustomEvent('workorder-deleted', { detail: { id } }); + document.dispatchEvent(event); + } else { + if (window.showToast) { + window.showToast(result.message || '删除工单失败', 'error'); + } else { + alert(result.message || '删除工单失败'); + } + } + } catch (error) { + console.error('删除工单失败:', error); + if (window.showToast) { + window.showToast('删除失败,请检查网络或查看控制台日志', 'error'); + } else { + alert('删除失败,请检查网络或查看控制台日志'); + } + } + })(); }; window.changePage = function(page) { // 重新加载当前页面实例 const event = new CustomEvent('changePage', { detail: { page } }); document.dispatchEvent(event); -}; \ No newline at end of file +}; + +// 监听删除事件,触发当前 WorkOrders 列表刷新(保持当前页) +document.addEventListener('workorder-deleted', () => { + const event = new CustomEvent('reloadWorkOrders'); + document.dispatchEvent(event); +}); \ No newline at end of file diff --git a/src/web/templates/dashboard.html b/src/web/templates/dashboard.html index da4b558..9aa9963 100644 --- a/src/web/templates/dashboard.html +++ b/src/web/templates/dashboard.html @@ -10,8 +10,8 @@ - - + +