realtimex-deeptutor 0.5.0.post1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. realtimex_deeptutor/__init__.py +67 -0
  2. realtimex_deeptutor-0.5.0.post1.dist-info/METADATA +1612 -0
  3. realtimex_deeptutor-0.5.0.post1.dist-info/RECORD +276 -0
  4. realtimex_deeptutor-0.5.0.post1.dist-info/WHEEL +5 -0
  5. realtimex_deeptutor-0.5.0.post1.dist-info/entry_points.txt +2 -0
  6. realtimex_deeptutor-0.5.0.post1.dist-info/licenses/LICENSE +661 -0
  7. realtimex_deeptutor-0.5.0.post1.dist-info/top_level.txt +2 -0
  8. src/__init__.py +40 -0
  9. src/agents/__init__.py +24 -0
  10. src/agents/base_agent.py +657 -0
  11. src/agents/chat/__init__.py +24 -0
  12. src/agents/chat/chat_agent.py +435 -0
  13. src/agents/chat/prompts/en/chat_agent.yaml +35 -0
  14. src/agents/chat/prompts/zh/chat_agent.yaml +35 -0
  15. src/agents/chat/session_manager.py +311 -0
  16. src/agents/co_writer/__init__.py +0 -0
  17. src/agents/co_writer/edit_agent.py +260 -0
  18. src/agents/co_writer/narrator_agent.py +423 -0
  19. src/agents/co_writer/prompts/en/edit_agent.yaml +113 -0
  20. src/agents/co_writer/prompts/en/narrator_agent.yaml +88 -0
  21. src/agents/co_writer/prompts/zh/edit_agent.yaml +113 -0
  22. src/agents/co_writer/prompts/zh/narrator_agent.yaml +88 -0
  23. src/agents/guide/__init__.py +16 -0
  24. src/agents/guide/agents/__init__.py +11 -0
  25. src/agents/guide/agents/chat_agent.py +104 -0
  26. src/agents/guide/agents/interactive_agent.py +223 -0
  27. src/agents/guide/agents/locate_agent.py +149 -0
  28. src/agents/guide/agents/summary_agent.py +150 -0
  29. src/agents/guide/guide_manager.py +500 -0
  30. src/agents/guide/prompts/en/chat_agent.yaml +41 -0
  31. src/agents/guide/prompts/en/interactive_agent.yaml +202 -0
  32. src/agents/guide/prompts/en/locate_agent.yaml +68 -0
  33. src/agents/guide/prompts/en/summary_agent.yaml +157 -0
  34. src/agents/guide/prompts/zh/chat_agent.yaml +41 -0
  35. src/agents/guide/prompts/zh/interactive_agent.yaml +626 -0
  36. src/agents/guide/prompts/zh/locate_agent.yaml +68 -0
  37. src/agents/guide/prompts/zh/summary_agent.yaml +157 -0
  38. src/agents/ideagen/__init__.py +12 -0
  39. src/agents/ideagen/idea_generation_workflow.py +426 -0
  40. src/agents/ideagen/material_organizer_agent.py +173 -0
  41. src/agents/ideagen/prompts/en/idea_generation.yaml +187 -0
  42. src/agents/ideagen/prompts/en/material_organizer.yaml +69 -0
  43. src/agents/ideagen/prompts/zh/idea_generation.yaml +187 -0
  44. src/agents/ideagen/prompts/zh/material_organizer.yaml +69 -0
  45. src/agents/question/__init__.py +24 -0
  46. src/agents/question/agents/__init__.py +18 -0
  47. src/agents/question/agents/generate_agent.py +381 -0
  48. src/agents/question/agents/relevance_analyzer.py +207 -0
  49. src/agents/question/agents/retrieve_agent.py +239 -0
  50. src/agents/question/coordinator.py +718 -0
  51. src/agents/question/example.py +109 -0
  52. src/agents/question/prompts/en/coordinator.yaml +75 -0
  53. src/agents/question/prompts/en/generate_agent.yaml +77 -0
  54. src/agents/question/prompts/en/relevance_analyzer.yaml +41 -0
  55. src/agents/question/prompts/en/retrieve_agent.yaml +32 -0
  56. src/agents/question/prompts/zh/coordinator.yaml +75 -0
  57. src/agents/question/prompts/zh/generate_agent.yaml +77 -0
  58. src/agents/question/prompts/zh/relevance_analyzer.yaml +39 -0
  59. src/agents/question/prompts/zh/retrieve_agent.yaml +30 -0
  60. src/agents/research/agents/__init__.py +23 -0
  61. src/agents/research/agents/decompose_agent.py +507 -0
  62. src/agents/research/agents/manager_agent.py +228 -0
  63. src/agents/research/agents/note_agent.py +180 -0
  64. src/agents/research/agents/rephrase_agent.py +263 -0
  65. src/agents/research/agents/reporting_agent.py +1333 -0
  66. src/agents/research/agents/research_agent.py +714 -0
  67. src/agents/research/data_structures.py +451 -0
  68. src/agents/research/main.py +188 -0
  69. src/agents/research/prompts/en/decompose_agent.yaml +89 -0
  70. src/agents/research/prompts/en/manager_agent.yaml +24 -0
  71. src/agents/research/prompts/en/note_agent.yaml +121 -0
  72. src/agents/research/prompts/en/rephrase_agent.yaml +58 -0
  73. src/agents/research/prompts/en/reporting_agent.yaml +380 -0
  74. src/agents/research/prompts/en/research_agent.yaml +173 -0
  75. src/agents/research/prompts/zh/decompose_agent.yaml +89 -0
  76. src/agents/research/prompts/zh/manager_agent.yaml +24 -0
  77. src/agents/research/prompts/zh/note_agent.yaml +121 -0
  78. src/agents/research/prompts/zh/rephrase_agent.yaml +58 -0
  79. src/agents/research/prompts/zh/reporting_agent.yaml +380 -0
  80. src/agents/research/prompts/zh/research_agent.yaml +173 -0
  81. src/agents/research/research_pipeline.py +1309 -0
  82. src/agents/research/utils/__init__.py +60 -0
  83. src/agents/research/utils/citation_manager.py +799 -0
  84. src/agents/research/utils/json_utils.py +98 -0
  85. src/agents/research/utils/token_tracker.py +297 -0
  86. src/agents/solve/__init__.py +80 -0
  87. src/agents/solve/analysis_loop/__init__.py +14 -0
  88. src/agents/solve/analysis_loop/investigate_agent.py +414 -0
  89. src/agents/solve/analysis_loop/note_agent.py +190 -0
  90. src/agents/solve/main_solver.py +862 -0
  91. src/agents/solve/memory/__init__.py +34 -0
  92. src/agents/solve/memory/citation_memory.py +353 -0
  93. src/agents/solve/memory/investigate_memory.py +226 -0
  94. src/agents/solve/memory/solve_memory.py +340 -0
  95. src/agents/solve/prompts/en/analysis_loop/investigate_agent.yaml +55 -0
  96. src/agents/solve/prompts/en/analysis_loop/note_agent.yaml +54 -0
  97. src/agents/solve/prompts/en/solve_loop/manager_agent.yaml +67 -0
  98. src/agents/solve/prompts/en/solve_loop/precision_answer_agent.yaml +62 -0
  99. src/agents/solve/prompts/en/solve_loop/response_agent.yaml +90 -0
  100. src/agents/solve/prompts/en/solve_loop/solve_agent.yaml +75 -0
  101. src/agents/solve/prompts/en/solve_loop/tool_agent.yaml +38 -0
  102. src/agents/solve/prompts/zh/analysis_loop/investigate_agent.yaml +53 -0
  103. src/agents/solve/prompts/zh/analysis_loop/note_agent.yaml +54 -0
  104. src/agents/solve/prompts/zh/solve_loop/manager_agent.yaml +66 -0
  105. src/agents/solve/prompts/zh/solve_loop/precision_answer_agent.yaml +62 -0
  106. src/agents/solve/prompts/zh/solve_loop/response_agent.yaml +90 -0
  107. src/agents/solve/prompts/zh/solve_loop/solve_agent.yaml +76 -0
  108. src/agents/solve/prompts/zh/solve_loop/tool_agent.yaml +41 -0
  109. src/agents/solve/solve_loop/__init__.py +22 -0
  110. src/agents/solve/solve_loop/citation_manager.py +74 -0
  111. src/agents/solve/solve_loop/manager_agent.py +274 -0
  112. src/agents/solve/solve_loop/precision_answer_agent.py +96 -0
  113. src/agents/solve/solve_loop/response_agent.py +301 -0
  114. src/agents/solve/solve_loop/solve_agent.py +325 -0
  115. src/agents/solve/solve_loop/tool_agent.py +470 -0
  116. src/agents/solve/utils/__init__.py +64 -0
  117. src/agents/solve/utils/config_validator.py +313 -0
  118. src/agents/solve/utils/display_manager.py +223 -0
  119. src/agents/solve/utils/error_handler.py +363 -0
  120. src/agents/solve/utils/json_utils.py +98 -0
  121. src/agents/solve/utils/performance_monitor.py +407 -0
  122. src/agents/solve/utils/token_tracker.py +541 -0
  123. src/api/__init__.py +0 -0
  124. src/api/main.py +240 -0
  125. src/api/routers/__init__.py +1 -0
  126. src/api/routers/agent_config.py +69 -0
  127. src/api/routers/chat.py +296 -0
  128. src/api/routers/co_writer.py +337 -0
  129. src/api/routers/config.py +627 -0
  130. src/api/routers/dashboard.py +18 -0
  131. src/api/routers/guide.py +337 -0
  132. src/api/routers/ideagen.py +436 -0
  133. src/api/routers/knowledge.py +821 -0
  134. src/api/routers/notebook.py +247 -0
  135. src/api/routers/question.py +537 -0
  136. src/api/routers/research.py +394 -0
  137. src/api/routers/settings.py +164 -0
  138. src/api/routers/solve.py +305 -0
  139. src/api/routers/system.py +252 -0
  140. src/api/run_server.py +61 -0
  141. src/api/utils/history.py +172 -0
  142. src/api/utils/log_interceptor.py +21 -0
  143. src/api/utils/notebook_manager.py +415 -0
  144. src/api/utils/progress_broadcaster.py +72 -0
  145. src/api/utils/task_id_manager.py +100 -0
  146. src/config/__init__.py +0 -0
  147. src/config/accessors.py +18 -0
  148. src/config/constants.py +34 -0
  149. src/config/defaults.py +18 -0
  150. src/config/schema.py +38 -0
  151. src/config/settings.py +50 -0
  152. src/core/errors.py +62 -0
  153. src/knowledge/__init__.py +23 -0
  154. src/knowledge/add_documents.py +606 -0
  155. src/knowledge/config.py +65 -0
  156. src/knowledge/example_add_documents.py +236 -0
  157. src/knowledge/extract_numbered_items.py +1039 -0
  158. src/knowledge/initializer.py +621 -0
  159. src/knowledge/kb.py +22 -0
  160. src/knowledge/manager.py +782 -0
  161. src/knowledge/progress_tracker.py +182 -0
  162. src/knowledge/start_kb.py +535 -0
  163. src/logging/__init__.py +103 -0
  164. src/logging/adapters/__init__.py +17 -0
  165. src/logging/adapters/lightrag.py +184 -0
  166. src/logging/adapters/llamaindex.py +141 -0
  167. src/logging/config.py +80 -0
  168. src/logging/handlers/__init__.py +20 -0
  169. src/logging/handlers/console.py +75 -0
  170. src/logging/handlers/file.py +201 -0
  171. src/logging/handlers/websocket.py +127 -0
  172. src/logging/logger.py +709 -0
  173. src/logging/stats/__init__.py +16 -0
  174. src/logging/stats/llm_stats.py +179 -0
  175. src/services/__init__.py +56 -0
  176. src/services/config/__init__.py +61 -0
  177. src/services/config/knowledge_base_config.py +210 -0
  178. src/services/config/loader.py +260 -0
  179. src/services/config/unified_config.py +603 -0
  180. src/services/embedding/__init__.py +45 -0
  181. src/services/embedding/adapters/__init__.py +22 -0
  182. src/services/embedding/adapters/base.py +106 -0
  183. src/services/embedding/adapters/cohere.py +127 -0
  184. src/services/embedding/adapters/jina.py +99 -0
  185. src/services/embedding/adapters/ollama.py +116 -0
  186. src/services/embedding/adapters/openai_compatible.py +96 -0
  187. src/services/embedding/client.py +159 -0
  188. src/services/embedding/config.py +156 -0
  189. src/services/embedding/provider.py +119 -0
  190. src/services/llm/__init__.py +152 -0
  191. src/services/llm/capabilities.py +313 -0
  192. src/services/llm/client.py +302 -0
  193. src/services/llm/cloud_provider.py +530 -0
  194. src/services/llm/config.py +200 -0
  195. src/services/llm/error_mapping.py +103 -0
  196. src/services/llm/exceptions.py +152 -0
  197. src/services/llm/factory.py +450 -0
  198. src/services/llm/local_provider.py +347 -0
  199. src/services/llm/providers/anthropic.py +95 -0
  200. src/services/llm/providers/base_provider.py +93 -0
  201. src/services/llm/providers/open_ai.py +83 -0
  202. src/services/llm/registry.py +71 -0
  203. src/services/llm/telemetry.py +40 -0
  204. src/services/llm/types.py +27 -0
  205. src/services/llm/utils.py +333 -0
  206. src/services/prompt/__init__.py +25 -0
  207. src/services/prompt/manager.py +206 -0
  208. src/services/rag/__init__.py +64 -0
  209. src/services/rag/components/__init__.py +29 -0
  210. src/services/rag/components/base.py +59 -0
  211. src/services/rag/components/chunkers/__init__.py +18 -0
  212. src/services/rag/components/chunkers/base.py +34 -0
  213. src/services/rag/components/chunkers/fixed.py +71 -0
  214. src/services/rag/components/chunkers/numbered_item.py +94 -0
  215. src/services/rag/components/chunkers/semantic.py +97 -0
  216. src/services/rag/components/embedders/__init__.py +14 -0
  217. src/services/rag/components/embedders/base.py +32 -0
  218. src/services/rag/components/embedders/openai.py +63 -0
  219. src/services/rag/components/indexers/__init__.py +18 -0
  220. src/services/rag/components/indexers/base.py +35 -0
  221. src/services/rag/components/indexers/graph.py +172 -0
  222. src/services/rag/components/indexers/lightrag.py +156 -0
  223. src/services/rag/components/indexers/vector.py +146 -0
  224. src/services/rag/components/parsers/__init__.py +18 -0
  225. src/services/rag/components/parsers/base.py +35 -0
  226. src/services/rag/components/parsers/markdown.py +52 -0
  227. src/services/rag/components/parsers/pdf.py +115 -0
  228. src/services/rag/components/parsers/text.py +86 -0
  229. src/services/rag/components/retrievers/__init__.py +18 -0
  230. src/services/rag/components/retrievers/base.py +34 -0
  231. src/services/rag/components/retrievers/dense.py +200 -0
  232. src/services/rag/components/retrievers/hybrid.py +164 -0
  233. src/services/rag/components/retrievers/lightrag.py +169 -0
  234. src/services/rag/components/routing.py +286 -0
  235. src/services/rag/factory.py +234 -0
  236. src/services/rag/pipeline.py +215 -0
  237. src/services/rag/pipelines/__init__.py +32 -0
  238. src/services/rag/pipelines/academic.py +44 -0
  239. src/services/rag/pipelines/lightrag.py +43 -0
  240. src/services/rag/pipelines/llamaindex.py +313 -0
  241. src/services/rag/pipelines/raganything.py +384 -0
  242. src/services/rag/service.py +244 -0
  243. src/services/rag/types.py +73 -0
  244. src/services/search/__init__.py +284 -0
  245. src/services/search/base.py +87 -0
  246. src/services/search/consolidation.py +398 -0
  247. src/services/search/providers/__init__.py +128 -0
  248. src/services/search/providers/baidu.py +188 -0
  249. src/services/search/providers/exa.py +194 -0
  250. src/services/search/providers/jina.py +161 -0
  251. src/services/search/providers/perplexity.py +153 -0
  252. src/services/search/providers/serper.py +209 -0
  253. src/services/search/providers/tavily.py +161 -0
  254. src/services/search/types.py +114 -0
  255. src/services/setup/__init__.py +34 -0
  256. src/services/setup/init.py +285 -0
  257. src/services/tts/__init__.py +16 -0
  258. src/services/tts/config.py +99 -0
  259. src/tools/__init__.py +91 -0
  260. src/tools/code_executor.py +536 -0
  261. src/tools/paper_search_tool.py +171 -0
  262. src/tools/query_item_tool.py +310 -0
  263. src/tools/question/__init__.py +15 -0
  264. src/tools/question/exam_mimic.py +616 -0
  265. src/tools/question/pdf_parser.py +211 -0
  266. src/tools/question/question_extractor.py +397 -0
  267. src/tools/rag_tool.py +173 -0
  268. src/tools/tex_chunker.py +339 -0
  269. src/tools/tex_downloader.py +253 -0
  270. src/tools/web_search.py +71 -0
  271. src/utils/config_manager.py +206 -0
  272. src/utils/document_validator.py +168 -0
  273. src/utils/error_rate_tracker.py +111 -0
  274. src/utils/error_utils.py +82 -0
  275. src/utils/json_parser.py +110 -0
  276. src/utils/network/circuit_breaker.py +79 -0
@@ -0,0 +1,337 @@
1
+ """
2
+ Guided Learning API Router
3
+ ==========================
4
+
5
+ Provides session creation, learning progress management, and chat interaction.
6
+ """
7
+
8
+ from pathlib import Path
9
+ import sys
10
+
11
+ from fastapi import APIRouter, HTTPException, WebSocket, WebSocketDisconnect
12
+ from pydantic import BaseModel
13
+
14
+ project_root = Path(__file__).parent.parent.parent.parent
15
+ if str(project_root) not in sys.path:
16
+ sys.path.insert(0, str(project_root))
17
+
18
+ from src.agents.base_agent import BaseAgent
19
+ from src.agents.guide.guide_manager import GuideManager
20
+ from src.api.utils.notebook_manager import notebook_manager
21
+ from src.api.utils.task_id_manager import TaskIDManager
22
+ from src.logging import get_logger
23
+ from src.services.config import load_config_with_main
24
+ from src.services.llm import get_llm_config
25
+
26
+ router = APIRouter()
27
+
28
+ # Initialize logger with config
29
+ project_root = Path(__file__).parent.parent.parent.parent
30
+ config = load_config_with_main("guide_config.yaml", project_root)
31
+ log_dir = config.get("paths", {}).get("user_log_dir") or config.get("logging", {}).get("log_dir")
32
+ logger = get_logger("Guide", level="INFO", log_dir=log_dir)
33
+
34
+
35
+ # === Request/Response Models ===
36
+
37
+
38
+ class CreateSessionRequest(BaseModel):
39
+ """Create session request"""
40
+
41
+ notebook_id: str | None = None # Optional, single notebook mode
42
+ records: list[dict] | None = None # Optional, cross-notebook mode with direct records
43
+
44
+
45
+ class ChatRequest(BaseModel):
46
+ """Chat request"""
47
+
48
+ session_id: str
49
+ message: str
50
+
51
+
52
+ class FixHtmlRequest(BaseModel):
53
+ """Fix HTML request"""
54
+
55
+ session_id: str
56
+ bug_description: str
57
+
58
+
59
+ class NextKnowledgeRequest(BaseModel):
60
+ """Next knowledge point request"""
61
+
62
+ session_id: str
63
+
64
+
65
+ # === Helper Functions ===
66
+
67
+
68
+ def get_guide_manager():
69
+ """Get GuideManager instance"""
70
+ try:
71
+ llm_config = get_llm_config()
72
+ api_key = llm_config.api_key
73
+ base_url = llm_config.base_url
74
+ api_version = getattr(llm_config, "api_version", None)
75
+ binding = llm_config.binding
76
+ except Exception as e:
77
+ raise HTTPException(status_code=500, detail=f"LLM config error: {e!s}")
78
+
79
+ return GuideManager(
80
+ api_key=api_key,
81
+ base_url=base_url,
82
+ api_version=api_version,
83
+ language=None,
84
+ binding=binding,
85
+ ) # Read from config file
86
+
87
+
88
+ # === REST API Endpoints ===
89
+
90
+
91
+ @router.post("/create_session")
92
+ async def create_session(request: CreateSessionRequest):
93
+ """
94
+ Create a new guided learning session.
95
+
96
+ Returns:
97
+ Session creation result with knowledge point list.
98
+ """
99
+ task_manager = TaskIDManager.get_instance()
100
+
101
+ try:
102
+ records = []
103
+ notebook_name = "Unknown"
104
+
105
+ # Mode 1: Cross-notebook mode - use provided records directly
106
+ if request.records and isinstance(request.records, list):
107
+ records = request.records
108
+ notebook_name = f"Cross-notebook ({len(records)} records)"
109
+ # Mode 2: Single notebook mode - get records from notebook
110
+ elif request.notebook_id:
111
+ notebook = notebook_manager.get_notebook(request.notebook_id)
112
+ if not notebook:
113
+ raise HTTPException(status_code=404, detail="Notebook not found")
114
+
115
+ records = notebook.get("records", [])
116
+ notebook_name = notebook.get("name", "Unknown")
117
+ else:
118
+ raise HTTPException(status_code=400, detail="Must provide notebook_id or records")
119
+
120
+ if not records:
121
+ raise HTTPException(status_code=400, detail="No available records")
122
+
123
+ # Reset LLM stats for new session
124
+ BaseAgent.reset_stats("guide")
125
+
126
+ manager = get_guide_manager()
127
+ result = await manager.create_session(
128
+ notebook_id=request.notebook_id or "cross_notebook",
129
+ notebook_name=notebook_name,
130
+ records=records,
131
+ )
132
+
133
+ if result and "session_id" in result:
134
+ session_id = result["session_id"]
135
+ task_id = task_manager.generate_task_id("guide", session_id)
136
+ logger.info(f"[{task_id}] Session created: {session_id}")
137
+
138
+ return result
139
+
140
+ except HTTPException:
141
+ raise
142
+ except Exception as e:
143
+ logger.error(f"Create session failed: {e}")
144
+ raise HTTPException(status_code=500, detail=str(e))
145
+
146
+
147
+ @router.post("/start")
148
+ async def start_learning(request: NextKnowledgeRequest):
149
+ """
150
+ Start learning (get the first knowledge point).
151
+ """
152
+ try:
153
+ manager = get_guide_manager()
154
+ result = await manager.start_learning(request.session_id)
155
+ return result
156
+ except Exception as e:
157
+ logger.error(f"Start learning failed: {e}")
158
+ raise HTTPException(status_code=500, detail=str(e))
159
+
160
+
161
+ @router.post("/next")
162
+ async def next_knowledge(request: NextKnowledgeRequest):
163
+ """
164
+ Move to the next knowledge point.
165
+ """
166
+ try:
167
+ manager = get_guide_manager()
168
+ result = await manager.next_knowledge(request.session_id)
169
+
170
+ # Print stats if learning completed
171
+ if result.get("learning_complete", False):
172
+ BaseAgent.print_stats("guide")
173
+
174
+ return result
175
+ except Exception as e:
176
+ logger.error(f"Next knowledge failed: {e}")
177
+ raise HTTPException(status_code=500, detail=str(e))
178
+
179
+
180
+ @router.post("/chat")
181
+ async def chat(request: ChatRequest):
182
+ """
183
+ Send a chat message.
184
+ """
185
+ try:
186
+ manager = get_guide_manager()
187
+ result = await manager.chat(request.session_id, request.message)
188
+ return result
189
+ except Exception as e:
190
+ logger.error(f"Chat failed: {e}")
191
+ raise HTTPException(status_code=500, detail=str(e))
192
+
193
+
194
+ @router.post("/fix_html")
195
+ async def fix_html(request: FixHtmlRequest):
196
+ """
197
+ Fix HTML page bugs.
198
+ """
199
+ try:
200
+ manager = get_guide_manager()
201
+ result = await manager.fix_html(request.session_id, request.bug_description)
202
+ return result
203
+ except Exception as e:
204
+ logger.error(f"Fix HTML failed: {e}")
205
+ raise HTTPException(status_code=500, detail=str(e))
206
+
207
+
208
+ @router.get("/session/{session_id}")
209
+ async def get_session(session_id: str):
210
+ """
211
+ Get session information.
212
+ """
213
+ try:
214
+ manager = get_guide_manager()
215
+ session = manager.get_session(session_id)
216
+ if not session:
217
+ raise HTTPException(status_code=404, detail="Session not found")
218
+ return session
219
+ except HTTPException:
220
+ raise
221
+ except Exception as e:
222
+ logger.error(f"Get session failed: {e}")
223
+ raise HTTPException(status_code=500, detail=str(e))
224
+
225
+
226
+ @router.get("/session/{session_id}/html")
227
+ async def get_current_html(session_id: str):
228
+ """
229
+ Get the current HTML page.
230
+ """
231
+ try:
232
+ manager = get_guide_manager()
233
+ html = manager.get_current_html(session_id)
234
+ if html is None:
235
+ raise HTTPException(status_code=404, detail="Session not found or no HTML content")
236
+ return {"html": html}
237
+ except HTTPException:
238
+ raise
239
+ except Exception as e:
240
+ logger.error(f"Get HTML failed: {e}")
241
+ raise HTTPException(status_code=500, detail=str(e))
242
+
243
+
244
+ # === WebSocket Endpoint ===
245
+
246
+
247
+ @router.websocket("/ws/{session_id}")
248
+ async def websocket_guide(websocket: WebSocket, session_id: str):
249
+ """
250
+ WebSocket endpoint for real-time interaction.
251
+
252
+ Message types:
253
+ - start: Start learning
254
+ - next: Next knowledge point
255
+ - chat: Send chat message
256
+ - fix_html: Fix HTML
257
+ - get_session: Get session state
258
+ """
259
+ await websocket.accept()
260
+
261
+ task_manager = TaskIDManager.get_instance()
262
+ task_id = task_manager.generate_task_id("guide", session_id)
263
+
264
+ try:
265
+ await websocket.send_json({"type": "task_id", "task_id": task_id})
266
+ except (RuntimeError, WebSocketDisconnect, ConnectionError) as e:
267
+ logger.debug(f"Failed to send task_id: {e}")
268
+
269
+ try:
270
+ manager = get_guide_manager()
271
+
272
+ session = manager.get_session(session_id)
273
+ if not session:
274
+ await websocket.send_json({"type": "error", "content": "Session not found"})
275
+ await websocket.close()
276
+ return
277
+
278
+ logger.info(f"[{task_id}] Guide session started: {session_id}")
279
+
280
+ await websocket.send_json({"type": "session_info", "data": session})
281
+
282
+ while True:
283
+ try:
284
+ data = await websocket.receive_json()
285
+ msg_type = data.get("type", "")
286
+
287
+ if msg_type == "start":
288
+ logger.debug(f"[{task_id}] Start learning")
289
+ result = await manager.start_learning(session_id)
290
+ await websocket.send_json({"type": "start_result", "data": result})
291
+
292
+ elif msg_type == "next":
293
+ logger.debug(f"[{task_id}] Next knowledge point")
294
+ result = await manager.next_knowledge(session_id)
295
+ await websocket.send_json({"type": "next_result", "data": result})
296
+
297
+ elif msg_type == "chat":
298
+ message = data.get("message", "")
299
+ if message:
300
+ logger.debug(f"[{task_id}] User message: {message[:50]}...")
301
+ result = await manager.chat(session_id, message)
302
+ await websocket.send_json({"type": "chat_result", "data": result})
303
+
304
+ elif msg_type == "fix_html":
305
+ bug_desc = data.get("bug_description", "")
306
+ logger.debug(f"[{task_id}] Fix HTML: {bug_desc[:50]}...")
307
+ result = await manager.fix_html(session_id, bug_desc)
308
+ await websocket.send_json({"type": "fix_result", "data": result})
309
+
310
+ elif msg_type == "get_session":
311
+ session = manager.get_session(session_id)
312
+ await websocket.send_json({"type": "session_info", "data": session})
313
+
314
+ else:
315
+ await websocket.send_json(
316
+ {"type": "error", "content": f"Unknown message type: {msg_type}"}
317
+ )
318
+
319
+ except WebSocketDisconnect:
320
+ logger.debug(f"WebSocket disconnected: {session_id}")
321
+ break
322
+ except Exception as e:
323
+ logger.error(f"WebSocket error: {e}")
324
+ await websocket.send_json({"type": "error", "content": str(e)})
325
+
326
+ except Exception as e:
327
+ logger.error(f"WebSocket connection error: {e}")
328
+ try:
329
+ await websocket.close()
330
+ except (RuntimeError, WebSocketDisconnect, ConnectionError):
331
+ pass # Connection already closed
332
+
333
+
334
+ @router.get("/health")
335
+ async def health_check():
336
+ """Health check"""
337
+ return {"status": "healthy", "service": "guide"}