knowledge_intro.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 知识库介绍模块:支持自动播放、中断回答问题、恢复续播未完成的文案
  5. 核心逻辑:记录播放进度 → 中断时暂停 →回答后恢复续播
  6. """
  7. from logger import logger
  8. from knowledge_base import query_knowledge_a
  9. class KnowledgeIntro:
  10. """
  11. 知识库介绍核心类
  12. 特性:
  13. 1. 仅支持完整版/简短版二选一
  14. 2.记录播放进度,支持中断/恢复
  15. 3. 中断后回答用户问题,回答完成可续播剩余文案
  16. """
  17. def __init__(self):
  18. # 从知识库A获取介绍内容
  19. self.intro_content = {
  20. "full": query_knowledge_a("full"),
  21. "short": query_knowledge_a("short")
  22. }
  23. # 播放状态管理
  24. self.play_state = {
  25. "current_type": None, # 当前播放类型:"full"(完整版)/"short"(简短版)
  26. "played_index": 0, #已播放的文案索引(下一次从该索引开始)
  27. "is_paused": False, # 是否因用户提问中断(暂停)
  28. "total_count": 0 # 当前类型的文案总数
  29. }
  30. def start_play(self, play_type: str = "full") -> dict:
  31. """
  32. 启动自动播放(页面打开时调用)
  33. :param play_type:类型:"full"(完整版)/"short"(简短版),默认完整版
  34. :return: 第一条播报的参数
  35. """
  36. # 校验播放类型
  37. if play_type not in ["full", "short"]:
  38. logger.error(f"无效的播放类型:{play_type},默认使用完整版")
  39. play_type = "full"
  40. # 从知识库A重新获取最新内容
  41. self.intro_content = {
  42. "full": query_knowledge_a("full"),
  43. "short": query_knowledge_a("short")
  44. }
  45. # 初始化播放状态
  46. self.play_state = {
  47. "current_type": play_type,
  48. "played_index": 0,
  49. "is_paused": False,
  50. "total_count": len(self.intro_content[play_type])
  51. }
  52. logger.info(f"启动自动播放 - 类型:{play_type},总条数:{self.play_state['total_count']}")
  53. # 返回第一条文案的播报参数
  54. return self._get_next_content()
  55. def _get_next_content(self) -> dict:
  56. """
  57. 获取下一条要播放的文案(内部方法)
  58. :return:播参数(无剩余文案时返回空字典)
  59. """
  60. # 检查播放状态:暂停/无播放类型 → 返回空
  61. if self.play_state["is_paused"] or not self.play_state["current_type"]:
  62. return {}
  63. current_type = self.play_state["current_type"]
  64. current_index = self.play_state["played_index"]
  65. total_count = self.play_state["total_count"]
  66. # 已播完所有文案 → 返回空字典,表示播放完成
  67. if current_index >= total_count:
  68. logger.info(f"播放完成 - 类型:{current_type},已播完所有{total_count}条文案")
  69. return {}
  70. # 获取当前文案,索引+1(记录下一次播放位置)
  71. content = self.intro_content[current_type][current_index]
  72. self.play_state["played_index"] += 1
  73. logger.info(
  74. f"播放文案 - 类型:{current_type} |序号:{current_index+1}/{total_count} |内容:{content}"
  75. )
  76. # 返回播报参数(interrupt=True确保能正常播放)
  77. return {
  78. "text": content,
  79. "type": "echo",
  80. "interrupt": True,
  81. "during_intro": True,
  82. "sessionid": -1, # 页面初始化播放,会话ID可临时设为-1,实际使用时替换
  83. "play_index": current_index + 1, # 便于前端显示播放进度
  84. "total_count": total_count,
  85. "mode": "intro", # 标记为介绍模式
  86. "is_last": current_index + 1 == total_count # 标记是否为最后一条
  87. }
  88. def pause_play(self):
  89. """
  90. 暂停播放(用户提问时调用)
  91. """
  92. self.play_state["is_paused"] = True
  93. logger.info("暂停播放:用户发起提问,优先回答问题")
  94. def resume_play(self) -> dict:
  95. """
  96. 恢复播放(回答完用户问题后调用)
  97. :return: 下一条未播放的文案参数(无剩余则返回空)
  98. """
  99. self.play_state["is_paused"] = False
  100. logger.info("恢复播放:用户问题已回答,继续播放剩余文案")
  101. return self._get_next_content()
  102. def answer_user_question(self, session_id: int, question: str, answer: str) -> dict:
  103. """
  104. 回答用户问题(中断播放时调用)
  105. :param session_id: 会话ID
  106. :param question: 用户提问内容
  107. :param answer:回答内容
  108. :return:回答的播报参数
  109. """
  110. #先暂停播放
  111. self.pause_play()
  112. logger.info(f"回答用户问题 - 会话ID:{session_id} | 问题:{question} | 回答:{answer}")
  113. # 返回回答的播报参数(interrupt=True强打断当前播放,优先回答)
  114. return {
  115. "text": answer,
  116. "type": "answer", #标记为回答类型,区分介绍文案
  117. "interrupt": True,
  118. "during_intro": False,
  119. "sessionid": session_id,
  120. "question": question,
  121. "mode": "qa" # 标记为问答模式
  122. }
  123. # --------------------------全局实例 & 便捷函数 --------------------------
  124. knowledge_intro = KnowledgeIntro()
  125. def start_intro_play(play_type: str = "full"):
  126. """便捷函数:启动介绍文案播放(页面打开时调用)"""
  127. return knowledge_intro.start_play(play_type)
  128. def pause_intro_play():
  129. """便捷函数:暂停播放(用户提问时调用)"""
  130. knowledge_intro.pause_play()
  131. def resume_intro_play():
  132. """便捷函数:恢复播放(回答完问题后调用)"""
  133. return knowledge_intro.resume_play()
  134. def answer_question(session_id: int, question: str, answer: str):
  135. """便捷函数:回答用户问题(自动暂停播放)"""
  136. return knowledge_intro.answer_user_question(session_id, question, answer)
  137. # --------------------------测试代码(模拟完整流程) --------------------------
  138. if __name__ == "__main__":
  139. print("===步骤1:页面打开,启动完整版介绍播放 ===")
  140. #启动播放(完整版)
  141. first_play = start_intro_play("full")
  142. print(f"播放第{first_play['play_index']}条:{first_play['text']}\n")
  143. print("===步骤2:播放第2条介绍文案 ===")
  144. second_play = resume_intro_play()
  145. print(f"播放第{second_play['play_index']}条:{second_play['text']}\n")
  146. print("=== 步骤3:用户提问,中断播放并回答 ===")
  147. # 用户提问:"如何使用系统"
  148. answer_res = answer_question(
  149. session_id=1001,
  150. question="如何使用系统",
  151. answer="您可以通过文本输入框输入问题,或使用语音功能与系统交流。系统会自动识别您的问题并提供相应的回答。"
  152. )
  153. print(f"回答用户:{answer_res['text']}\n")
  154. print("=== 步骤4:回答完成,恢复播放剩余文案 ===")
  155. resume_res = resume_intro_play()
  156. print(f"继续播放第{resume_res['play_index']}条:{resume_res['text']}")