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,285 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ System Setup and Initialization
5
+ Combines user directory initialization and port configuration management.
6
+ """
7
+
8
+ import json
9
+ import os
10
+ from pathlib import Path
11
+
12
+ from src.logging import get_logger
13
+ from src.services.config import load_config_with_main
14
+
15
+ # Initialize logger for setup operations
16
+ _setup_logger = None
17
+
18
+
19
+ def _get_setup_logger():
20
+ """Get logger for setup operations"""
21
+ global _setup_logger
22
+ if _setup_logger is None:
23
+ _setup_logger = get_logger("Setup")
24
+ return _setup_logger
25
+
26
+
27
+ # ============================================================================
28
+ # User Directory Initialization (from user_dir_init.py)
29
+ # ============================================================================
30
+
31
+
32
+ def init_user_directories(project_root: Path | None = None) -> None:
33
+ """
34
+ Initialize user data directories if they don't exist.
35
+
36
+ Creates the following directory structure:
37
+ data/user/
38
+ ├── solve/ # Problem solving outputs
39
+ ├── question/ # Question generation outputs
40
+ ├── research/ # Research outputs
41
+ │ ├── cache/ # Research cache
42
+ │ └── reports/ # Research reports
43
+ ├── guide/ # Guided learning outputs
44
+ ├── notebook/ # Notebook data
45
+ ├── co-writer/ # Co-writer outputs
46
+ │ ├── audio/ # TTS audio files
47
+ │ └── tool_calls/ # Tool call history
48
+ ├── logs/ # User logs
49
+ ├── run_code_workspace/ # Code execution workspace
50
+ └── user_history.json # User history file
51
+
52
+ Args:
53
+ project_root: Project root directory (if None, will try to detect)
54
+ """
55
+ if project_root is None:
56
+ # Path(__file__) = src/services/setup/init.py
57
+ # .parent = src/services/setup/
58
+ # .parent.parent = src/services/
59
+ # .parent.parent.parent = src/
60
+ # .parent.parent.parent.parent = DeepTutor/ (project root)
61
+ project_root = Path(__file__).parent.parent.parent.parent
62
+
63
+ # Get user data directory from config
64
+ try:
65
+ config = load_config_with_main("solve_config.yaml", project_root)
66
+ user_data_dir = config.get("paths", {}).get("user_data_dir", "./data/user")
67
+
68
+ # Convert relative path to absolute
69
+ if not Path(user_data_dir).is_absolute():
70
+ user_data_dir = project_root / user_data_dir
71
+ else:
72
+ user_data_dir = Path(user_data_dir)
73
+ except Exception:
74
+ # Fallback to default
75
+ user_data_dir = project_root / "data" / "user"
76
+
77
+ # Required subdirectories (based on actual usage in the codebase)
78
+ required_dirs = [
79
+ "solve", # Problem solving outputs
80
+ "question", # Question generation outputs
81
+ "research", # Research outputs (will have cache/ and reports/ subdirs)
82
+ "guide", # Guided learning outputs
83
+ "notebook", # Notebook data
84
+ "co-writer", # Co-writer outputs
85
+ "logs", # User logs
86
+ "run_code_workspace", # Code execution workspace
87
+ ]
88
+
89
+ # Additional subdirectories for specific modules
90
+ co_writer_subdirs = [
91
+ "audio", # TTS audio files
92
+ "tool_calls", # Tool call history
93
+ ]
94
+
95
+ research_subdirs = [
96
+ "cache", # Research cache
97
+ "reports", # Research reports
98
+ ]
99
+
100
+ # Check if user directory exists and is empty
101
+ user_dir_exists = user_data_dir.exists()
102
+ user_dir_empty = False
103
+ if user_dir_exists:
104
+ try:
105
+ # Check if directory is empty (no files or subdirectories)
106
+ user_dir_empty = not any(user_data_dir.iterdir())
107
+ except (OSError, PermissionError) as e:
108
+ # If we can't check directory contents, assume it's not empty
109
+ logger = _get_setup_logger()
110
+ logger.warning(f"Cannot check if user directory is empty: {e}")
111
+ user_dir_empty = False
112
+
113
+ if not user_dir_exists or user_dir_empty:
114
+ logger = _get_setup_logger()
115
+ logger.info("\n" + "=" * 80)
116
+ logger.info("INITIALIZING USER DATA DIRECTORY")
117
+ logger.info("=" * 80)
118
+
119
+ if not user_dir_exists:
120
+ logger.info(f"Creating user data directory: {user_data_dir}")
121
+ else:
122
+ logger.info(f"User data directory is empty, initializing: {user_data_dir}")
123
+
124
+ # Create main user directory
125
+ user_data_dir.mkdir(parents=True, exist_ok=True)
126
+
127
+ # Create all required subdirectories
128
+ for dir_name in required_dirs:
129
+ dir_path = user_data_dir / dir_name
130
+ dir_path.mkdir(parents=True, exist_ok=True)
131
+ logger.success(f"Created: {dir_name}/")
132
+
133
+ # Create co-writer subdirectories
134
+ co_writer_dir = user_data_dir / "co-writer"
135
+ for subdir_name in co_writer_subdirs:
136
+ subdir_path = co_writer_dir / subdir_name
137
+ subdir_path.mkdir(parents=True, exist_ok=True)
138
+ logger.success(f"Created: co-writer/{subdir_name}/")
139
+
140
+ # Create research subdirectories
141
+ research_dir = user_data_dir / "research"
142
+ for subdir_name in research_subdirs:
143
+ subdir_path = research_dir / subdir_name
144
+ subdir_path.mkdir(parents=True, exist_ok=True)
145
+ logger.success(f"Created: research/{subdir_name}/")
146
+
147
+ # Create user_history.json if it doesn't exist
148
+ user_history_file = user_data_dir / "user_history.json"
149
+ if not user_history_file.exists():
150
+ initial_history = {"version": "1.0", "created_at": None, "sessions": []}
151
+ try:
152
+ with open(user_history_file, "w", encoding="utf-8") as f:
153
+ json.dump(initial_history, f, indent=2, ensure_ascii=False)
154
+ logger.success("Created: user_history.json")
155
+ except Exception as e:
156
+ logger.warning(f"Failed to create user_history.json: {e}")
157
+
158
+ # Create interface.json in settings folder if it doesn't exist
159
+ settings_dir = user_data_dir / "settings"
160
+ settings_dir.mkdir(parents=True, exist_ok=True)
161
+ interface_file = settings_dir / "interface.json"
162
+ if not interface_file.exists():
163
+ initial_settings = {"theme": "light", "language": "en", "output_language": "en"}
164
+ try:
165
+ with open(interface_file, "w", encoding="utf-8") as f:
166
+ json.dump(initial_settings, f, indent=2, ensure_ascii=False)
167
+ logger.success("Created: settings/interface.json")
168
+ except Exception as e:
169
+ logger.warning(f"Failed to create settings/interface.json: {e}")
170
+
171
+ logger.info("=" * 80)
172
+ logger.success("User data directory initialization complete!")
173
+ logger.info("=" * 80 + "\n")
174
+ else:
175
+ # Directory exists and is not empty, just ensure all subdirectories exist
176
+ for dir_name in required_dirs:
177
+ dir_path = user_data_dir / dir_name
178
+ dir_path.mkdir(parents=True, exist_ok=True)
179
+
180
+ # Ensure co-writer subdirectories exist
181
+ co_writer_dir = user_data_dir / "co-writer"
182
+ for subdir_name in co_writer_subdirs:
183
+ subdir_path = co_writer_dir / subdir_name
184
+ subdir_path.mkdir(parents=True, exist_ok=True)
185
+
186
+ # Ensure research subdirectories exist
187
+ research_dir = user_data_dir / "research"
188
+ for subdir_name in research_subdirs:
189
+ subdir_path = research_dir / subdir_name
190
+ subdir_path.mkdir(parents=True, exist_ok=True)
191
+
192
+ # Ensure user_history.json exists
193
+ user_history_file = user_data_dir / "user_history.json"
194
+ if not user_history_file.exists():
195
+ initial_history = {"version": "1.0", "created_at": None, "sessions": []}
196
+ try:
197
+ with open(user_history_file, "w", encoding="utf-8") as f:
198
+ json.dump(initial_history, f, indent=2, ensure_ascii=False)
199
+ except Exception:
200
+ pass # Silent fail if file creation fails but directory exists
201
+
202
+ # Ensure interface.json exists in settings folder
203
+ settings_dir = user_data_dir / "settings"
204
+ settings_dir.mkdir(parents=True, exist_ok=True)
205
+ interface_file = settings_dir / "interface.json"
206
+ if not interface_file.exists():
207
+ initial_settings = {"theme": "light", "language": "en", "output_language": "en"}
208
+ try:
209
+ with open(interface_file, "w", encoding="utf-8") as f:
210
+ json.dump(initial_settings, f, indent=2, ensure_ascii=False)
211
+ except Exception:
212
+ pass # Silent fail if file creation fails but directory exists
213
+
214
+
215
+ # ============================================================================
216
+ # Port Configuration Management
217
+ # ============================================================================
218
+ # Ports are configured via environment variables in .env file:
219
+ # BACKEND_PORT=8001 (default: 8001)
220
+ # FRONTEND_PORT=3782 (default: 3782)
221
+ # ============================================================================
222
+
223
+
224
+ def get_backend_port(project_root: Path | None = None) -> int:
225
+ """
226
+ Get backend port from environment variable.
227
+
228
+ Configure in .env file: BACKEND_PORT=8001
229
+
230
+ Returns:
231
+ Backend port number (default: 8001)
232
+ """
233
+ env_port = os.environ.get("BACKEND_PORT", "8001")
234
+ try:
235
+ return int(env_port)
236
+ except ValueError:
237
+ logger = _get_setup_logger()
238
+ logger.warning(f"Invalid BACKEND_PORT: {env_port}, using default 8001")
239
+ return 8001
240
+
241
+
242
+ def get_frontend_port(project_root: Path | None = None) -> int:
243
+ """
244
+ Get frontend port from environment variable.
245
+
246
+ Configure in .env file: FRONTEND_PORT=3782
247
+
248
+ Returns:
249
+ Frontend port number (default: 3782)
250
+ """
251
+ env_port = os.environ.get("FRONTEND_PORT", "3782")
252
+ try:
253
+ return int(env_port)
254
+ except ValueError:
255
+ logger = _get_setup_logger()
256
+ logger.warning(f"Invalid FRONTEND_PORT: {env_port}, using default 3782")
257
+ return 3782
258
+
259
+
260
+ def get_ports(project_root: Path | None = None) -> tuple[int, int]:
261
+ """
262
+ Get both backend and frontend ports from configuration.
263
+
264
+ Args:
265
+ project_root: Project root directory (if None, will try to detect)
266
+
267
+ Returns:
268
+ Tuple of (backend_port, frontend_port)
269
+
270
+ Raises:
271
+ SystemExit: If ports are not configured
272
+ """
273
+ backend_port = get_backend_port(project_root)
274
+ frontend_port = get_frontend_port(project_root)
275
+ return (backend_port, frontend_port)
276
+
277
+
278
+ __all__ = [
279
+ # User directory initialization
280
+ "init_user_directories",
281
+ # Port configuration (from .env)
282
+ "get_backend_port",
283
+ "get_frontend_port",
284
+ "get_ports",
285
+ ]
@@ -0,0 +1,16 @@
1
+ """
2
+ TTS Service
3
+ ===========
4
+
5
+ Text-to-Speech configuration for DeepTutor.
6
+
7
+ Usage:
8
+ from src.services.tts import get_tts_config
9
+
10
+ config = get_tts_config()
11
+ # config = {"model": "tts-1", "api_key": "...", "base_url": "...", "voice": "alloy"}
12
+ """
13
+
14
+ from .config import get_tts_config
15
+
16
+ __all__ = ["get_tts_config"]
@@ -0,0 +1,99 @@
1
+ """
2
+ TTS Configuration
3
+ =================
4
+
5
+ Configuration management for Text-to-Speech services.
6
+ Simplified version - loads from unified config service or falls back to .env.
7
+ """
8
+
9
+ import logging
10
+ import os
11
+ from pathlib import Path
12
+ from typing import Optional
13
+
14
+ from dotenv import load_dotenv
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ # Load environment variables
19
+ PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent.parent
20
+ load_dotenv(PROJECT_ROOT / "DeepTutor.env", override=False)
21
+ load_dotenv(PROJECT_ROOT / ".env", override=False)
22
+
23
+
24
+ def _strip_value(value: Optional[str]) -> Optional[str]:
25
+ """Remove leading/trailing whitespace and quotes from string."""
26
+ if value is None:
27
+ return None
28
+ return value.strip().strip("\"'")
29
+
30
+
31
+ def get_tts_config() -> dict:
32
+ """
33
+ Return complete configuration for TTS (Text-to-Speech).
34
+
35
+ Priority:
36
+ 1. Active configuration from unified config service
37
+ 2. Environment variables (.env)
38
+
39
+ Returns:
40
+ dict: Dictionary containing the following keys:
41
+ - model: TTS model name
42
+ - api_key: TTS API key
43
+ - base_url: TTS API endpoint URL
44
+ - api_version: TTS API version (for Azure OpenAI)
45
+ - voice: Default voice character
46
+
47
+ Raises:
48
+ ValueError: If required configuration is missing
49
+ """
50
+ # 1. Try to get active config from unified config service
51
+ try:
52
+ from src.services.config import get_active_tts_config
53
+
54
+ config = get_active_tts_config()
55
+ if config and config.get("model"):
56
+ return {
57
+ "model": config["model"],
58
+ "api_key": config.get("api_key", ""),
59
+ "base_url": config.get("base_url", ""),
60
+ "api_version": config.get("api_version"),
61
+ "voice": config.get("voice", "alloy"),
62
+ }
63
+ except ImportError:
64
+ # Unified config service not yet available, fall back to env
65
+ pass
66
+ except Exception as e:
67
+ logger.warning(f"Failed to load from unified config: {e}")
68
+
69
+ # 2. Fallback to environment variables
70
+ model = _strip_value(os.getenv("TTS_MODEL"))
71
+ api_key = _strip_value(os.getenv("TTS_API_KEY"))
72
+ base_url = _strip_value(os.getenv("TTS_URL"))
73
+ api_version = _strip_value(os.getenv("TTS_BINDING_API_VERSION"))
74
+ voice = _strip_value(os.getenv("TTS_VOICE", "alloy"))
75
+
76
+ # Validate required configuration
77
+ if not model:
78
+ raise ValueError(
79
+ "TTS_MODEL not set. Please configure it in .env file or add a configuration in Settings"
80
+ )
81
+ if not api_key:
82
+ raise ValueError(
83
+ "TTS_API_KEY not set. Please configure it in .env file or add a configuration in Settings"
84
+ )
85
+ if not base_url:
86
+ raise ValueError(
87
+ "TTS_URL not set. Please configure it in .env file or add a configuration in Settings"
88
+ )
89
+
90
+ return {
91
+ "model": model,
92
+ "api_key": api_key,
93
+ "base_url": base_url,
94
+ "api_version": api_version,
95
+ "voice": voice,
96
+ }
97
+
98
+
99
+ __all__ = ["get_tts_config"]
src/tools/__init__.py ADDED
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Tools Package - Unified tool collection
5
+
6
+ Includes:
7
+ - rag_tool: RAG retrieval tool
8
+ - web_search: Web search tool
9
+ - query_item_tool: Query item tool
10
+ - paper_search_tool: Paper search tool
11
+ - tex_downloader: LaTeX source download tool
12
+ - tex_chunker: LaTeX text chunking tool
13
+ - question: Question generation tools (pdf_parser, question_extractor, exam_mimic)
14
+ """
15
+
16
+ # Patch lightrag.utils BEFORE any imports that use lightrag
17
+ import importlib.util
18
+ import sys
19
+
20
+ try:
21
+ # Directly load lightrag.utils module without triggering lightrag/__init__.py
22
+ _spec = importlib.util.find_spec("lightrag.utils")
23
+ if _spec and _spec.origin:
24
+ _utils = importlib.util.module_from_spec(_spec)
25
+ sys.modules["lightrag.utils"] = _utils
26
+ _spec.loader.exec_module(_utils)
27
+
28
+ # Apply patches
29
+ for _k, _v in {
30
+ "verbose_debug": lambda *args, **kwargs: None,
31
+ "VERBOSE_DEBUG": False,
32
+ "get_env_value": lambda key, default=None: default,
33
+ "safe_unicode_decode": lambda t: (
34
+ t.decode("utf-8", errors="ignore") if isinstance(t, bytes) else t
35
+ ),
36
+ }.items():
37
+ if not hasattr(_utils, _k):
38
+ setattr(_utils, _k, _v)
39
+
40
+ if not hasattr(_utils, "wrap_embedding_func_with_attrs"):
41
+
42
+ def _wrap(**attrs):
43
+ def dec(f):
44
+ for k, v in attrs.items():
45
+ setattr(f, k, v)
46
+ return f
47
+
48
+ return dec
49
+
50
+ _utils.wrap_embedding_func_with_attrs = _wrap
51
+ except Exception as e:
52
+ import traceback
53
+
54
+ print(f"Warning: Failed to patch lightrag.utils: {e}")
55
+ traceback.print_exc()
56
+
57
+ from .code_executor import run_code, run_code_sync
58
+ from .query_item_tool import query_numbered_item
59
+ from .rag_tool import rag_search
60
+ from .web_search import web_search
61
+
62
+ # Paper research related tools
63
+ try:
64
+ from .paper_search_tool import PaperSearchTool
65
+ from .tex_chunker import TexChunker
66
+ from .tex_downloader import TexDownloader, read_tex_file
67
+
68
+ __all__ = [
69
+ "PaperSearchTool",
70
+ "TexChunker",
71
+ "TexDownloader",
72
+ "query_numbered_item",
73
+ "rag_search",
74
+ "read_tex_file",
75
+ "run_code",
76
+ "run_code_sync",
77
+ "web_search",
78
+ ]
79
+ except ImportError as e:
80
+ # If import fails (e.g., missing tiktoken), only export basic tools
81
+ print(f"⚠️ Some paper tools import failed: {e}")
82
+ __all__ = [
83
+ "query_numbered_item",
84
+ "rag_search",
85
+ "run_code",
86
+ "run_code_sync",
87
+ "web_search",
88
+ ]
89
+
90
+ # Question generation tools (lazy import to avoid circular dependencies)
91
+ # Access via: from src.tools.question import parse_pdf_with_mineru, etc.