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
+ from pathlib import Path
2
+ import sys
3
+ import traceback
4
+ from typing import Literal
5
+
6
+ from fastapi import APIRouter, HTTPException
7
+ from fastapi.responses import Response
8
+ from pydantic import BaseModel
9
+
10
+ # Ensure co_writer module can be imported
11
+ project_root = Path(__file__).parent.parent.parent.parent
12
+ if str(project_root) not in sys.path:
13
+ sys.path.insert(0, str(project_root))
14
+
15
+ import json
16
+
17
+ from src.agents.co_writer.edit_agent import (
18
+ TOOL_CALLS_DIR,
19
+ EditAgent,
20
+ load_history,
21
+ print_stats,
22
+ )
23
+ from src.agents.co_writer.narrator_agent import NarratorAgent
24
+ from src.logging import get_logger
25
+ from src.services.config import load_config_with_main
26
+ from src.services.tts import get_tts_config
27
+
28
+ router = APIRouter()
29
+
30
+ # Initialize logger with config
31
+ project_root = Path(__file__).parent.parent.parent.parent
32
+ config = load_config_with_main("solve_config.yaml", project_root) # Use any config to get main.yaml
33
+ log_dir = config.get("paths", {}).get("user_log_dir") or config.get("logging", {}).get("log_dir")
34
+ logger = get_logger("CoWriter", level="INFO", log_dir=log_dir)
35
+
36
+ # Get system language for agent
37
+ _system_language = config.get("system", {}).get("language", "en")
38
+
39
+ # Singleton agent instances - use refresh_config() before each request
40
+ # to pick up any configuration changes from Settings
41
+ _edit_agent: EditAgent | None = None
42
+ _narrator_agent: NarratorAgent | None = None
43
+
44
+
45
+ def get_edit_agent() -> EditAgent:
46
+ """
47
+ Get the singleton EditAgent instance with refreshed configuration.
48
+
49
+ Uses a singleton pattern with refresh_config() to ensure:
50
+ 1. Efficient reuse of the agent instance
51
+ 2. Latest LLM configuration from Settings is always used
52
+ """
53
+ global _edit_agent
54
+ if _edit_agent is None:
55
+ _edit_agent = EditAgent(language=_system_language)
56
+ # Refresh config to pick up any changes from Settings
57
+ _edit_agent.refresh_config()
58
+ return _edit_agent
59
+
60
+
61
+ def get_narrator_agent() -> NarratorAgent:
62
+ """
63
+ Get the singleton NarratorAgent instance with refreshed configuration.
64
+
65
+ Uses a singleton pattern with refresh_config() to ensure:
66
+ 1. Efficient reuse of the agent instance
67
+ 2. Latest LLM configuration from Settings is always used
68
+ """
69
+ global _narrator_agent
70
+ if _narrator_agent is None:
71
+ _narrator_agent = NarratorAgent(language=_system_language)
72
+ # Refresh config to pick up any changes from Settings
73
+ _narrator_agent.refresh_config()
74
+ return _narrator_agent
75
+
76
+
77
+ class EditRequest(BaseModel):
78
+ text: str
79
+ instruction: str
80
+ action: Literal["rewrite", "shorten", "expand"] = "rewrite"
81
+ source: Literal["rag", "web"] | None = None
82
+ kb_name: str | None = None
83
+
84
+
85
+ class EditResponse(BaseModel):
86
+ edited_text: str
87
+ operation_id: str
88
+
89
+
90
+ class AutoMarkRequest(BaseModel):
91
+ text: str
92
+
93
+
94
+ class AutoMarkResponse(BaseModel):
95
+ marked_text: str
96
+ operation_id: str
97
+
98
+
99
+ @router.post("/edit", response_model=EditResponse)
100
+ async def edit_text(request: EditRequest):
101
+ try:
102
+ # Get agent with refreshed LLM configuration from Settings
103
+ agent = get_edit_agent()
104
+
105
+ result = await agent.process(
106
+ text=request.text,
107
+ instruction=request.instruction,
108
+ action=request.action,
109
+ source=request.source,
110
+ kb_name=request.kb_name,
111
+ )
112
+
113
+ # Print token stats
114
+ print_stats()
115
+
116
+ return result
117
+
118
+ except Exception as e:
119
+ traceback.print_exc()
120
+ raise HTTPException(status_code=500, detail=str(e))
121
+
122
+
123
+ @router.post("/automark", response_model=AutoMarkResponse)
124
+ async def auto_mark_text(request: AutoMarkRequest):
125
+ """AI auto-mark text"""
126
+ try:
127
+ # Get agent with refreshed LLM configuration from Settings
128
+ agent = get_edit_agent()
129
+
130
+ result = await agent.auto_mark(text=request.text)
131
+
132
+ # Print token stats
133
+ print_stats()
134
+
135
+ return result
136
+ except Exception as e:
137
+ traceback.print_exc()
138
+ raise HTTPException(status_code=500, detail=str(e))
139
+
140
+
141
+ @router.get("/history")
142
+ async def get_history():
143
+ """Get all operation history"""
144
+ try:
145
+ history = load_history()
146
+ return {"history": history, "total": len(history)}
147
+ except Exception as e:
148
+ raise HTTPException(status_code=500, detail=str(e))
149
+
150
+
151
+ @router.get("/history/{operation_id}")
152
+ async def get_operation(operation_id: str):
153
+ """Get single operation details"""
154
+ try:
155
+ history = load_history()
156
+ for op in history:
157
+ if op.get("id") == operation_id:
158
+ return op
159
+ raise HTTPException(status_code=404, detail="Operation not found")
160
+ except HTTPException:
161
+ raise
162
+ except Exception as e:
163
+ raise HTTPException(status_code=500, detail=str(e))
164
+
165
+
166
+ @router.get("/tool_calls/{operation_id}")
167
+ async def get_tool_call(operation_id: str):
168
+ """Get tool call details"""
169
+ try:
170
+ # Find matching file
171
+ for filepath in TOOL_CALLS_DIR.glob(f"{operation_id}_*.json"):
172
+ with open(filepath, encoding="utf-8") as f:
173
+ return json.load(f)
174
+ raise HTTPException(status_code=404, detail="Tool call not found")
175
+ except HTTPException:
176
+ raise
177
+ except Exception as e:
178
+ raise HTTPException(status_code=500, detail=str(e))
179
+
180
+
181
+ @router.post("/export/markdown")
182
+ async def export_markdown(content: dict):
183
+ """Export as Markdown file"""
184
+ try:
185
+ markdown_content = content.get("content", "")
186
+ filename = content.get("filename", "document.md")
187
+
188
+ return Response(
189
+ content=markdown_content,
190
+ media_type="text/markdown",
191
+ headers={"Content-Disposition": f"attachment; filename={filename}"},
192
+ )
193
+ except Exception as e:
194
+ raise HTTPException(status_code=500, detail=str(e))
195
+
196
+
197
+ # ================= TTS Narration Feature =================
198
+
199
+
200
+ class NarrateRequest(BaseModel):
201
+ """Narration request"""
202
+
203
+ content: str
204
+ style: Literal["friendly", "academic", "concise"] = "friendly"
205
+ voice: str | None = None # If None, will use default value from config
206
+ skip_audio: bool = False
207
+
208
+
209
+ class NarrateResponse(BaseModel):
210
+ """Narration response"""
211
+
212
+ script: str
213
+ key_points: list[str]
214
+ style: str
215
+ original_length: int
216
+ script_length: int
217
+ has_audio: bool
218
+ audio_url: str | None = None
219
+ audio_id: str | None = None
220
+ voice: str | None = None
221
+ audio_error: str | None = None
222
+
223
+
224
+ class ScriptOnlyRequest(BaseModel):
225
+ """Script-only generation request"""
226
+
227
+ content: str
228
+ style: Literal["friendly", "academic", "concise"] = "friendly"
229
+
230
+
231
+ @router.post("/narrate", response_model=NarrateResponse)
232
+ async def narrate_content(request: NarrateRequest):
233
+ """
234
+ Generate note narration script and optionally generate TTS audio
235
+
236
+ - style: Narration style
237
+ - friendly: Friendly and approachable tutor style
238
+ - academic: Rigorous academic lecture style
239
+ - concise: Efficient and concise knowledge delivery style
240
+ - voice: TTS voice role (alloy, echo, fable, onyx, nova, shimmer)
241
+ - skip_audio: Whether to skip audio generation (set to true to return only script)
242
+ """
243
+ try:
244
+ narrator = get_narrator_agent()
245
+ result = await narrator.narrate(
246
+ content=request.content,
247
+ style=request.style,
248
+ voice=request.voice,
249
+ skip_audio=request.skip_audio,
250
+ )
251
+ return result
252
+ except ValueError as e:
253
+ # TTS configuration related error
254
+ raise HTTPException(status_code=400, detail=str(e))
255
+ except Exception as e:
256
+ traceback.print_exc()
257
+ raise HTTPException(status_code=500, detail=str(e))
258
+
259
+
260
+ @router.post("/narrate/script")
261
+ async def generate_script_only(request: ScriptOnlyRequest):
262
+ """
263
+ Generate script only (no audio generation)
264
+
265
+ Fast endpoint, suitable for previewing script effect
266
+ """
267
+ try:
268
+ narrator = get_narrator_agent()
269
+ result = await narrator.generate_script(content=request.content, style=request.style)
270
+ return result
271
+ except Exception as e:
272
+ traceback.print_exc()
273
+ raise HTTPException(status_code=500, detail=str(e))
274
+
275
+
276
+ @router.get("/tts/status")
277
+ async def get_tts_status():
278
+ """
279
+ Check TTS service status
280
+
281
+ Returns whether TTS configuration is available
282
+ """
283
+ try:
284
+ tts_config = get_tts_config()
285
+ return {
286
+ "available": True,
287
+ "model": tts_config.get("model"),
288
+ "default_voice": tts_config.get("voice", "alloy"),
289
+ }
290
+ except ValueError as e:
291
+ return {
292
+ "available": False,
293
+ "error": str(e),
294
+ "hint": "Please configure TTS_MODEL, TTS_API_KEY, TTS_URL in .env file",
295
+ }
296
+ except Exception as e:
297
+ return {"available": False, "error": str(e)}
298
+
299
+
300
+ @router.get("/tts/voices")
301
+ async def get_available_voices():
302
+ """
303
+ Get available TTS voice role list (OpenAI TTS voices)
304
+ """
305
+ voices = [
306
+ {
307
+ "id": "alloy",
308
+ "name": "Alloy",
309
+ "description": "Neutral and balanced voice",
310
+ },
311
+ {
312
+ "id": "echo",
313
+ "name": "Echo",
314
+ "description": "Warm and conversational voice",
315
+ },
316
+ {
317
+ "id": "fable",
318
+ "name": "Fable",
319
+ "description": "Expressive and dramatic voice",
320
+ },
321
+ {
322
+ "id": "onyx",
323
+ "name": "Onyx",
324
+ "description": "Deep and authoritative voice",
325
+ },
326
+ {
327
+ "id": "nova",
328
+ "name": "Nova",
329
+ "description": "Friendly and upbeat voice",
330
+ },
331
+ {
332
+ "id": "shimmer",
333
+ "name": "Shimmer",
334
+ "description": "Clear and pleasant voice",
335
+ },
336
+ ]
337
+ return {"voices": voices}