logic_ui.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. import hashlib
  2. import json
  3. import time
  4. import tkinter
  5. import uuid
  6. from datetime import datetime
  7. from tkinter import messagebox
  8. import requests
  9. from common.sql_lite import get_global_db_connection
  10. from config import conf, set_config
  11. from db.batch_task import batch_task_get_list, batch_task_status, batch_task_update_status
  12. from db.sop import sop_task_get_list, sop_task_status, sop_task_type, sop_task_query, sop_task_create, \
  13. sop_task_update_status, sop_stage_create_many, sop_node_create_many
  14. from logic.logic_batch_task_create import open_batch_task_create_win
  15. from logic.logic_batch_task_detail import open_batch_task_detail_win
  16. from plugins.agent import Agent
  17. from plugins.custom_agent import CustomAgent
  18. from plugins.sop import Sop
  19. from service.batch_task import stop_batch_task, start_batch_task
  20. from service.sop_service import start_sop_task, stop_sop_task
  21. from service.robot import get_robot, init_robot
  22. from service.sop_service import get_sop_task_list, get_sop_stage_list, get_sop_node_list
  23. from ui.ui import WinGUI
  24. from common.log import logger
  25. class Win(WinGUI):
  26. def __init__(self):
  27. super().__init__()
  28. self.__event_bind()
  29. self.__style_config()
  30. self.__is_started = False
  31. self.selection_batch_task_id = None
  32. self.selection_task_id = None
  33. self.wx_wxid = None
  34. self.robot = None
  35. def __event_bind(self):
  36. self.tk_tabs_main_tabs.tk_tabs_start.tk_button_save.bind('<Button-1>', self.save_event)
  37. self.tk_tabs_main_tabs.tk_tabs_start.tk_button_start.bind('<Button-1>', self.start_event)
  38. self.tk_tabs_main_tabs.tk_tabs_start.tk_button_pause.bind('<Button-1>', self.stop_event)
  39. self.tk_tabs_main_tabs.tk_tabs_start.tk_button_version.bind('<Button-1>', self.version_event)
  40. self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_button_create.bind('<Button-1>', self.batch_task_create_event)
  41. self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_table_batch_task_list.bind('<Double-1>',
  42. self.batch_task_detail_event)
  43. self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_table_batch_task_list.bind('<Button-1>', self.just_click_event)
  44. self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_table_batch_task_list.bind('<Button-3>',
  45. self.batch_task_action_event)
  46. # SOP管理
  47. self.tk_tabs_main_tabs.tk_tabs_sop.tk_table_sop_list.bind('<Button-3>', self.sop_task_action_event)
  48. self.tk_tabs_main_tabs.tk_tabs_sop.tk_button_sync.bind('<Button-1>', self.sop_task_sync)
  49. pass
  50. def __style_config(self):
  51. pass
  52. def version_event(self, event):
  53. messagebox.showinfo('版本信息', '当前版本:v1.0.0')
  54. def stop_event(self, event):
  55. stop_batch_task()
  56. stop_sop_task()
  57. if self.tk_tabs_main_tabs.tk_tabs_start.tk_button_pause.cget('state').__str__() == tkinter.DISABLED:
  58. return
  59. robot = get_robot()
  60. if robot is not None:
  61. robot.wcf.cleanup()
  62. robot = None
  63. self.tk_tabs_main_tabs.tk_tabs_start.tk_button_start.config(state=tkinter.NORMAL)
  64. self.tk_tabs_main_tabs.tk_tabs_start.tk_button_pause.config(state=tkinter.DISABLED)
  65. self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_button_create.config(state=tkinter.DISABLED)
  66. self.__is_started = False
  67. messagebox.showinfo('提示', '助手已停止运行!')
  68. @staticmethod
  69. def check_token():
  70. # 获取当前网卡mac地址
  71. mac = uuid.getnode()
  72. url = "https://wxadminapi.gkscrm.com/wechat-api/token/check"
  73. token = str(conf().get("token"))
  74. payload = json.dumps({
  75. "token": token,
  76. "mac": str(mac)
  77. })
  78. headers = {
  79. 'Content-Type': 'application/json'
  80. }
  81. response = requests.request("POST", url, headers=headers, data=payload)
  82. if response.status_code == 200:
  83. resp = json.loads(response.text)
  84. if resp['valid'] == True:
  85. sign = resp['sign']
  86. timestamp = resp['timestamp']
  87. # 获取当前unix时间戳
  88. current_timestamp = int(time.time())
  89. if abs(current_timestamp - int(timestamp)) > 180:
  90. messagebox.showerror('错误', '验证失败!请重试')
  91. return False
  92. # 创建一个MD5哈希对象
  93. md5_hash = hashlib.md5()
  94. # 更新哈希对象
  95. md5_hash.update(token.encode('utf-8'))
  96. # 获取十六进制格式的MD5哈希值
  97. md5_token = md5_hash.hexdigest()
  98. md5_hash = hashlib.md5()
  99. md5_hash.update(str(mac).encode('utf-8'))
  100. md5_mac = md5_hash.hexdigest()
  101. md5_hash = hashlib.md5()
  102. md5_hash.update(str(timestamp).encode('utf-8'))
  103. md5_timestamp = md5_hash.hexdigest()
  104. md5_hash = hashlib.md5()
  105. md5_hash.update((md5_token + md5_mac + md5_timestamp).encode('utf-8'))
  106. md5_sign = md5_hash.hexdigest()
  107. if md5_sign != sign:
  108. messagebox.showerror('错误', 'token验证失败!')
  109. return False
  110. agent_info = resp['agent_info']
  111. if agent_info is None:
  112. return False
  113. set_config("agent_id", agent_info.get("id", 0))
  114. set_config("role", agent_info.get("role", ""))
  115. set_config("background", agent_info.get("background", ""))
  116. set_config("examples", agent_info.get("examples", ""))
  117. set_config("dataset_id", agent_info.get("dataset_id", ""))
  118. set_config("collection_id", agent_info.get("collection_id", ""))
  119. set_config("custom_agent_base", resp['custom_agent_base'])
  120. set_config("custom_agent_key", resp['custom_agent_key'])
  121. set_config("openai_base", resp['openai_base'])
  122. set_config("openai_key", resp['openai_key'])
  123. set_config("dataset_base", resp['dataset_base'])
  124. set_config("dataset_key", resp['dataset_key'])
  125. return True
  126. else:
  127. messagebox.showerror('错误',
  128. 'token已失效 或 与当前设备未绑定,每个token只能绑定一台设备!请填入和当前设备绑定的token,或者获取新的token!')
  129. return False
  130. else:
  131. messagebox.showerror('错误', '您的网络状态异常!请稍候重试')
  132. return False
  133. def start_event(self, event):
  134. if self.tk_tabs_main_tabs.tk_tabs_start.tk_button_start.cget('state').__str__() == tkinter.DISABLED:
  135. return
  136. if not self.check_token():
  137. return
  138. # 接收消息
  139. # robot.enableRecvMsg() # 可能会丢消息?
  140. # init_robot()
  141. self.robot = get_robot()
  142. self.robot.enableReceivingMsg() # 加队列
  143. if self.robot.wcf.is_login():
  144. self.__is_started = True
  145. self.tk_tabs_main_tabs.tk_tabs_start.tk_button_start.config(state='disabled')
  146. self.tk_tabs_main_tabs.tk_tabs_start.tk_button_pause.config(state='normal')
  147. self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_button_create.config(state='normal')
  148. messagebox.showinfo('提示', '助手开始运行!')
  149. start_batch_task()
  150. start_sop_task()
  151. # 注册插件
  152. if conf().get("agent_id") != 0 and conf().get("openai_base") and conf().get("openai_key"):
  153. self.robot.register_plugin(Agent())
  154. if conf().get("agent_id") == 0 and conf().get("custom_agent_base") and conf().get("custom_agent_key"):
  155. self.robot.register_plugin(CustomAgent())
  156. # 注册SOP插件
  157. self.robot.register_plugin(Sop())
  158. self.wx_wxid = self.robot.wcf.get_self_wxid()
  159. connection = get_global_db_connection()
  160. def refresh_list():
  161. # 清空列表
  162. self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_table_batch_task_list.delete(
  163. *self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_table_batch_task_list.get_children())
  164. # 查询数据
  165. cursor = connection.cursor()
  166. try:
  167. results = batch_task_get_list(cursor, self.wx_wxid)
  168. finally:
  169. cursor.close()
  170. for result in results:
  171. created_at = datetime.fromtimestamp(result["created_at"]).strftime("%Y-%m-%d %H:%M:%S")
  172. status = batch_task_status[result["status"]]
  173. values = (
  174. created_at, status, f"{result['success'] + result['fail']}/{result['total']}", result['fail'],
  175. result['content'])
  176. self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_table_batch_task_list.insert('', "end",
  177. iid=result['id'],
  178. values=values)
  179. # 维持选中状态
  180. if result['id'] == self.selection_batch_task_id:
  181. self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_table_batch_task_list.selection_set(result['id'])
  182. # 每 3秒调用一次这个函数
  183. self.tk_tabs_main_tabs.after(3000, refresh_list)
  184. self.tk_tabs_main_tabs.after(0, refresh_list)
  185. # sop_list 获取列表内容
  186. self.sop_task_get_list_all()
  187. def save_event(self, event):
  188. conf().update({
  189. # "api_base": self.tk_tabs_main_tabs.tk_tabs_start.tk_input_api_base.get(),
  190. # "api_key": self.tk_tabs_main_tabs.tk_tabs_start.tk_input_api_key.get(),
  191. "token": self.tk_tabs_main_tabs.tk_tabs_start.tk_input_token.get()
  192. })
  193. # 将字典写入 JSON 文件
  194. with open('config.json', 'w') as json_file:
  195. json.dump(conf(), json_file, indent=4)
  196. messagebox.showinfo('提示', '保存文件成功!')
  197. def batch_task_create_event(self, event):
  198. if self.__is_started:
  199. open_batch_task_create_win()
  200. def batch_task_detail_event(self, event):
  201. # 获取双击项的标识符
  202. item_id = self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_table_batch_task_list.identify_row(event.y)
  203. # 获取该项的值
  204. item_values = self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_table_batch_task_list.item(item_id, "values")
  205. # print("Double-clicked item:", item_values)
  206. if len(item_values) > 0:
  207. self.selection_batch_task_id = int(item_id)
  208. open_batch_task_detail_win(int(item_id), item_values)
  209. else:
  210. self.selection_batch_task_id = None
  211. def batch_task_start_event(self, event):
  212. # 获取双击项的标识符
  213. item_id = self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_table_batch_task_list.identify_row(event.y)
  214. # 获取该项的值
  215. item_values = self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_table_batch_task_list.item(item_id, "values")
  216. # print("Double-clicked item:", item_values)
  217. if len(item_values) > 0:
  218. self.selection_batch_task_id = int(item_id)
  219. connection = get_global_db_connection()
  220. cursor = connection.cursor()
  221. try:
  222. batch_task_update_status(cursor, int(item_id), 1)
  223. connection.commit()
  224. except Exception as e:
  225. # 回滚事务
  226. connection.rollback()
  227. print(f"发生错误: {e}")
  228. finally:
  229. # 确保资源被正确释放
  230. cursor.close()
  231. else:
  232. self.selection_batch_task_id = None
  233. def batch_task_stop_event(self, event):
  234. # 获取双击项的标识符
  235. item_id = self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_table_batch_task_list.identify_row(event.y)
  236. # 获取该项的值
  237. item_values = self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_table_batch_task_list.item(item_id, "values")
  238. # print("Double-clicked item:", item_values)
  239. if len(item_values) > 0:
  240. self.selection_batch_task_id = int(item_id)
  241. connection = get_global_db_connection()
  242. cursor = connection.cursor()
  243. try:
  244. batch_task_update_status(cursor, int(item_id), 3)
  245. connection.commit()
  246. except Exception as e:
  247. # 回滚事务
  248. connection.rollback()
  249. print(f"发生错误: {e}")
  250. finally:
  251. # 确保资源被正确释放
  252. cursor.close()
  253. else:
  254. self.selection_batch_task_id = None
  255. def batch_task_action_event(self, event):
  256. # 获取右键项的标识符
  257. item_id = self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_table_batch_task_list.identify_row(event.y)
  258. # print(f"Right-clicked item: {item_id}")
  259. self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_table_batch_task_list.selection_set(item_id)
  260. # 获取该项的值
  261. item_values = self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_table_batch_task_list.item(item_id, "values")
  262. if len(item_values) > 0:
  263. self.selection_batch_task_id = int(item_id)
  264. # print("Right-clicked item:", item_values)
  265. # 创建菜单
  266. menu = tkinter.Menu(self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_table_batch_task_list, tearoff=0)
  267. menu.add_command(label="查看详情", command=lambda: self.batch_task_detail_event(event))
  268. if item_values[1] == "等待中" or item_values[1] == "已开始":
  269. menu.add_command(label="停止", command=lambda: self.batch_task_stop_event(event))
  270. elif item_values[1] == "已停止":
  271. menu.add_command(label="开始", command=lambda: self.batch_task_start_event(event))
  272. menu.post(event.x_root, event.y_root)
  273. else:
  274. self.selection_batch_task_id = None
  275. def just_click_event(self, event):
  276. item_id = self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_table_batch_task_list.identify_row(event.y)
  277. self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_table_batch_task_list.selection_set(item_id)
  278. item_values = self.tk_tabs_main_tabs.tk_tabs_batch_task.tk_table_batch_task_list.item(item_id, "values")
  279. if len(item_values) > 0:
  280. self.selection_batch_task_id = int(item_id)
  281. else:
  282. self.selection_batch_task_id = None
  283. # 点击同步后台SOP到本地DB(每隔3s刷新一次列表内容)
  284. def sop_task_get_list_all(self):
  285. def refresh_sop_task():
  286. # 清空列表
  287. self.tk_tabs_main_tabs.tk_tabs_sop.tk_table_sop_list.delete(
  288. *self.tk_tabs_main_tabs.tk_tabs_sop.tk_table_sop_list.get_children()
  289. )
  290. # 查询数据
  291. connection = get_global_db_connection()
  292. cursor = connection.cursor()
  293. try:
  294. results = sop_task_get_list(cursor, self.wx_wxid)
  295. # logger.info(f"get sop_task list results:{results}")
  296. finally:
  297. cursor.close()
  298. # 把查询到的数据显示在table里
  299. for result in results:
  300. created_at = datetime.fromtimestamp(result["created_at"]).strftime("%Y-%m-%d %H:%M:%S")
  301. status = sop_task_status[result["status"]]
  302. sop_type = sop_task_type[result["type"]]
  303. values = (result['id'], result['name'], result['bot_wxid_list'], status, created_at, sop_type)
  304. self.tk_tabs_main_tabs.tk_tabs_sop.tk_table_sop_list.insert('', "end",
  305. iid=result['id'],
  306. values=values)
  307. # 维持选中状态
  308. if result['id'] == self.selection_task_id:
  309. self.tk_tabs_main_tabs.tk_tabs_sop.tk_table_sop_list.selection_set(result['id'])
  310. # 每 3秒调用一次这个函数
  311. self.tk_tabs_main_tabs.after(3000, refresh_sop_task)
  312. self.tk_tabs_main_tabs.after(0, refresh_sop_task)
  313. # 点击同步后台SOP到本地DB
  314. def sop_task_sync(self, event):
  315. if not self.__is_started:
  316. return
  317. # logger.info(f"[SOP] 点击同步 {event}")
  318. connection = get_global_db_connection()
  319. cursor = connection.cursor()
  320. sop_task_list = get_sop_task_list()
  321. if sop_task_list is not None:
  322. for k in sop_task_list:
  323. try:
  324. sop_task = sop_task_query(cursor, k['id'])
  325. # logger.info(f"sop_task={sop_task}")
  326. if sop_task is None:
  327. _ = sop_task_create(cursor, k, self.wx_wxid)
  328. # 根据 sop_task id 获取 sop_stage 列表
  329. sop_stage_list = get_sop_stage_list([k['id']])
  330. # logger.info(f"sop_stage_list: {sop_stage_list}")
  331. if sop_stage_list is not None:
  332. sop_stage_create_many(cursor, sop_stage_list)
  333. sop_stage_ids = []
  334. for val in sop_stage_list:
  335. sop_stage_ids.append(val['id'])
  336. # logger.info(f"sop_stage_ids={sop_stage_ids}")
  337. # 根据 sop_stage_ids 获取 sop_node 列表
  338. if len(sop_stage_ids) > 0:
  339. sop_node_list = get_sop_node_list(sop_stage_ids)
  340. # logger.info(f"sop_stage_ids={sop_stage_ids}")
  341. # logger.info(f"sop_node_list={sop_node_list}")
  342. if sop_node_list is not None:
  343. sop_node_create_many(cursor, sop_node_list)
  344. connection.commit()
  345. except Exception as e:
  346. # 回滚事务
  347. connection.rollback()
  348. logger.error(f"SQLite DB发生错误: {e}")
  349. finally:
  350. # 确保资源被正确释放
  351. cursor.close()
  352. messagebox.showinfo('SOP管理', '同步SOP成功!')
  353. return
  354. # 同步后台设置的SOP列表到本地,根据租户ID获取
  355. def sop_task_action_event(self, event):
  356. # logger.info(f"event={event}, started={self.__is_started}")
  357. if not self.__is_started:
  358. return
  359. # 获取右键项的标识符
  360. item_id = self.tk_tabs_main_tabs.tk_tabs_sop.tk_table_sop_list.identify_row(event.y)
  361. # logger.info(f"Right-clicked item_id: {item_id}")
  362. self.tk_tabs_main_tabs.tk_tabs_sop.tk_table_sop_list.selection_set(item_id)
  363. # 获取该项的值
  364. item_values = self.tk_tabs_main_tabs.tk_tabs_sop.tk_table_sop_list.item(item_id, "values")
  365. if len(item_values) > 0:
  366. self.selection_task_id = int(item_id)
  367. # logger.info(f"Right-clicked item_values:{item_values}")
  368. # 创建菜单
  369. menu = tkinter.Menu(self.tk_tabs_main_tabs.tk_tabs_sop.tk_table_sop_list, tearoff=0)
  370. if item_values[3] == "已开始":
  371. menu.add_command(label="禁用", command=lambda: self.sop_forbid(event))
  372. elif item_values[3] == "已禁用":
  373. menu.add_command(label="开始", command=lambda: self.sop_resume(event))
  374. menu.post(event.x_root, event.y_root)
  375. else:
  376. self.selection_task_id = None
  377. # logger.info("no item is selected")
  378. # sop_task 禁用
  379. def sop_forbid(self, event):
  380. if not self.__is_started:
  381. return
  382. if self.selection_task_id is None:
  383. return
  384. connection = get_global_db_connection()
  385. cursor = connection.cursor()
  386. try:
  387. sop_task_update_status(cursor, self.selection_task_id, 5)
  388. connection.commit()
  389. except Exception as e:
  390. logger.error(f"set sop_task forbid exception:{e}")
  391. finally:
  392. cursor.close()
  393. # sop_task 重新启用
  394. def sop_resume(self, event):
  395. if not self.__is_started:
  396. return
  397. if self.selection_task_id is None:
  398. return
  399. connection = get_global_db_connection()
  400. cursor = connection.cursor()
  401. try:
  402. sop_task_update_status(cursor, self.selection_task_id, 3)
  403. connection.commit()
  404. except Exception as e:
  405. logger.error(f"set sop_task resume exception:{e}")
  406. finally:
  407. cursor.close()