realtimex-deeptutor 0.5.0.post1__py3-none-any.whl → 0.5.0.post3__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.
- {realtimex_deeptutor-0.5.0.post1.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/METADATA +24 -17
- {realtimex_deeptutor-0.5.0.post1.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/RECORD +143 -123
- {realtimex_deeptutor-0.5.0.post1.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/WHEEL +1 -1
- realtimex_deeptutor-0.5.0.post3.dist-info/entry_points.txt +4 -0
- {realtimex_deeptutor-0.5.0.post1.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/top_level.txt +1 -0
- scripts/__init__.py +1 -0
- scripts/audit_prompts.py +179 -0
- scripts/check_install.py +460 -0
- scripts/generate_roster.py +327 -0
- scripts/install_all.py +653 -0
- scripts/migrate_kb.py +655 -0
- scripts/start.py +807 -0
- scripts/start_web.py +632 -0
- scripts/sync_prompts_from_en.py +147 -0
- src/__init__.py +2 -2
- src/agents/ideagen/material_organizer_agent.py +2 -0
- src/agents/solve/__init__.py +6 -0
- src/agents/solve/main_solver.py +9 -0
- src/agents/solve/prompts/zh/analysis_loop/investigate_agent.yaml +9 -7
- src/agents/solve/session_manager.py +345 -0
- src/api/main.py +14 -0
- src/api/routers/chat.py +3 -3
- src/api/routers/co_writer.py +12 -7
- src/api/routers/config.py +1 -0
- src/api/routers/guide.py +3 -1
- src/api/routers/ideagen.py +7 -0
- src/api/routers/knowledge.py +64 -12
- src/api/routers/question.py +2 -0
- src/api/routers/realtimex.py +137 -0
- src/api/routers/research.py +9 -0
- src/api/routers/solve.py +120 -2
- src/cli/__init__.py +13 -0
- src/cli/start.py +209 -0
- src/config/constants.py +11 -9
- src/knowledge/add_documents.py +453 -213
- src/knowledge/extract_numbered_items.py +9 -10
- src/knowledge/initializer.py +102 -101
- src/knowledge/manager.py +251 -74
- src/knowledge/progress_tracker.py +43 -2
- src/knowledge/start_kb.py +11 -2
- src/logging/__init__.py +5 -0
- src/logging/adapters/__init__.py +1 -0
- src/logging/adapters/lightrag.py +25 -18
- src/logging/adapters/llamaindex.py +1 -0
- src/logging/config.py +30 -27
- src/logging/handlers/__init__.py +1 -0
- src/logging/handlers/console.py +7 -50
- src/logging/handlers/file.py +5 -20
- src/logging/handlers/websocket.py +23 -19
- src/logging/logger.py +161 -126
- src/logging/stats/__init__.py +1 -0
- src/logging/stats/llm_stats.py +37 -17
- src/services/__init__.py +17 -1
- src/services/config/__init__.py +1 -0
- src/services/config/knowledge_base_config.py +1 -0
- src/services/config/loader.py +1 -1
- src/services/config/unified_config.py +211 -4
- src/services/embedding/__init__.py +1 -0
- src/services/embedding/adapters/__init__.py +3 -0
- src/services/embedding/adapters/base.py +1 -0
- src/services/embedding/adapters/cohere.py +1 -0
- src/services/embedding/adapters/jina.py +1 -0
- src/services/embedding/adapters/ollama.py +1 -0
- src/services/embedding/adapters/openai_compatible.py +1 -0
- src/services/embedding/adapters/realtimex.py +125 -0
- src/services/embedding/client.py +27 -0
- src/services/embedding/config.py +3 -0
- src/services/embedding/provider.py +1 -0
- src/services/llm/__init__.py +17 -3
- src/services/llm/capabilities.py +47 -0
- src/services/llm/client.py +32 -0
- src/services/llm/cloud_provider.py +21 -4
- src/services/llm/config.py +36 -2
- src/services/llm/error_mapping.py +1 -0
- src/services/llm/exceptions.py +30 -0
- src/services/llm/factory.py +55 -16
- src/services/llm/local_provider.py +1 -0
- src/services/llm/providers/anthropic.py +1 -0
- src/services/llm/providers/base_provider.py +1 -0
- src/services/llm/providers/open_ai.py +1 -0
- src/services/llm/realtimex_provider.py +240 -0
- src/services/llm/registry.py +1 -0
- src/services/llm/telemetry.py +1 -0
- src/services/llm/types.py +1 -0
- src/services/llm/utils.py +1 -0
- src/services/prompt/__init__.py +1 -0
- src/services/prompt/manager.py +3 -2
- src/services/rag/__init__.py +27 -5
- src/services/rag/components/__init__.py +1 -0
- src/services/rag/components/base.py +1 -0
- src/services/rag/components/chunkers/__init__.py +1 -0
- src/services/rag/components/chunkers/base.py +1 -0
- src/services/rag/components/chunkers/fixed.py +1 -0
- src/services/rag/components/chunkers/numbered_item.py +1 -0
- src/services/rag/components/chunkers/semantic.py +1 -0
- src/services/rag/components/embedders/__init__.py +1 -0
- src/services/rag/components/embedders/base.py +1 -0
- src/services/rag/components/embedders/openai.py +1 -0
- src/services/rag/components/indexers/__init__.py +1 -0
- src/services/rag/components/indexers/base.py +1 -0
- src/services/rag/components/indexers/graph.py +5 -44
- src/services/rag/components/indexers/lightrag.py +5 -44
- src/services/rag/components/indexers/vector.py +1 -0
- src/services/rag/components/parsers/__init__.py +1 -0
- src/services/rag/components/parsers/base.py +1 -0
- src/services/rag/components/parsers/markdown.py +1 -0
- src/services/rag/components/parsers/pdf.py +1 -0
- src/services/rag/components/parsers/text.py +1 -0
- src/services/rag/components/retrievers/__init__.py +1 -0
- src/services/rag/components/retrievers/base.py +1 -0
- src/services/rag/components/retrievers/dense.py +1 -0
- src/services/rag/components/retrievers/hybrid.py +5 -44
- src/services/rag/components/retrievers/lightrag.py +5 -44
- src/services/rag/components/routing.py +48 -0
- src/services/rag/factory.py +112 -46
- src/services/rag/pipeline.py +1 -0
- src/services/rag/pipelines/__init__.py +27 -18
- src/services/rag/pipelines/lightrag.py +1 -0
- src/services/rag/pipelines/llamaindex.py +99 -0
- src/services/rag/pipelines/raganything.py +67 -100
- src/services/rag/pipelines/raganything_docling.py +368 -0
- src/services/rag/service.py +5 -12
- src/services/rag/types.py +1 -0
- src/services/rag/utils/__init__.py +17 -0
- src/services/rag/utils/image_migration.py +279 -0
- src/services/search/__init__.py +1 -0
- src/services/search/base.py +1 -0
- src/services/search/consolidation.py +1 -0
- src/services/search/providers/__init__.py +1 -0
- src/services/search/providers/baidu.py +1 -0
- src/services/search/providers/exa.py +1 -0
- src/services/search/providers/jina.py +1 -0
- src/services/search/providers/perplexity.py +1 -0
- src/services/search/providers/serper.py +1 -0
- src/services/search/providers/tavily.py +1 -0
- src/services/search/types.py +1 -0
- src/services/settings/__init__.py +1 -0
- src/services/settings/interface_settings.py +78 -0
- src/services/setup/__init__.py +1 -0
- src/services/tts/__init__.py +1 -0
- src/services/tts/config.py +1 -0
- src/utils/realtimex.py +284 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/entry_points.txt +0 -2
- src/services/rag/pipelines/academic.py +0 -44
- {realtimex_deeptutor-0.5.0.post1.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/licenses/LICENSE +0 -0
src/api/routers/co_writer.py
CHANGED
|
@@ -23,6 +23,7 @@ from src.agents.co_writer.edit_agent import (
|
|
|
23
23
|
from src.agents.co_writer.narrator_agent import NarratorAgent
|
|
24
24
|
from src.logging import get_logger
|
|
25
25
|
from src.services.config import load_config_with_main
|
|
26
|
+
from src.services.settings.interface_settings import get_ui_language
|
|
26
27
|
from src.services.tts import get_tts_config
|
|
27
28
|
|
|
28
29
|
router = APIRouter()
|
|
@@ -33,15 +34,17 @@ config = load_config_with_main("solve_config.yaml", project_root) # Use any con
|
|
|
33
34
|
log_dir = config.get("paths", {}).get("user_log_dir") or config.get("logging", {}).get("log_dir")
|
|
34
35
|
logger = get_logger("CoWriter", level="INFO", log_dir=log_dir)
|
|
35
36
|
|
|
36
|
-
# Get system language for agent
|
|
37
|
-
_system_language = config.get("system", {}).get("language", "en")
|
|
38
|
-
|
|
39
37
|
# Singleton agent instances - use refresh_config() before each request
|
|
40
38
|
# to pick up any configuration changes from Settings
|
|
41
39
|
_edit_agent: EditAgent | None = None
|
|
42
40
|
_narrator_agent: NarratorAgent | None = None
|
|
43
41
|
|
|
44
42
|
|
|
43
|
+
def _current_language() -> str:
|
|
44
|
+
# Prefer UI settings, fall back to main.yaml system.language
|
|
45
|
+
return get_ui_language(default=config.get("system", {}).get("language", "en"))
|
|
46
|
+
|
|
47
|
+
|
|
45
48
|
def get_edit_agent() -> EditAgent:
|
|
46
49
|
"""
|
|
47
50
|
Get the singleton EditAgent instance with refreshed configuration.
|
|
@@ -51,8 +54,9 @@ def get_edit_agent() -> EditAgent:
|
|
|
51
54
|
2. Latest LLM configuration from Settings is always used
|
|
52
55
|
"""
|
|
53
56
|
global _edit_agent
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
lang = _current_language()
|
|
58
|
+
if _edit_agent is None or getattr(_edit_agent, "language", None) != lang:
|
|
59
|
+
_edit_agent = EditAgent(language=lang)
|
|
56
60
|
# Refresh config to pick up any changes from Settings
|
|
57
61
|
_edit_agent.refresh_config()
|
|
58
62
|
return _edit_agent
|
|
@@ -67,8 +71,9 @@ def get_narrator_agent() -> NarratorAgent:
|
|
|
67
71
|
2. Latest LLM configuration from Settings is always used
|
|
68
72
|
"""
|
|
69
73
|
global _narrator_agent
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
lang = _current_language()
|
|
75
|
+
if _narrator_agent is None or getattr(_narrator_agent, "language", None) != lang:
|
|
76
|
+
_narrator_agent = NarratorAgent(language=lang)
|
|
72
77
|
# Refresh config to pick up any changes from Settings
|
|
73
78
|
_narrator_agent.refresh_config()
|
|
74
79
|
return _narrator_agent
|
src/api/routers/config.py
CHANGED
|
@@ -168,6 +168,7 @@ async def get_config_status():
|
|
|
168
168
|
"provider": active.get("provider") if active else None,
|
|
169
169
|
"env_configured": env_status,
|
|
170
170
|
"total_configs": len(configs),
|
|
171
|
+
"source": active.get("source") if active else None, # "realtimex" when using RTX
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
return ConfigStatusResponse(
|
src/api/routers/guide.py
CHANGED
|
@@ -22,6 +22,7 @@ from src.api.utils.task_id_manager import TaskIDManager
|
|
|
22
22
|
from src.logging import get_logger
|
|
23
23
|
from src.services.config import load_config_with_main
|
|
24
24
|
from src.services.llm import get_llm_config
|
|
25
|
+
from src.services.settings.interface_settings import get_ui_language
|
|
25
26
|
|
|
26
27
|
router = APIRouter()
|
|
27
28
|
|
|
@@ -76,11 +77,12 @@ def get_guide_manager():
|
|
|
76
77
|
except Exception as e:
|
|
77
78
|
raise HTTPException(status_code=500, detail=f"LLM config error: {e!s}")
|
|
78
79
|
|
|
80
|
+
ui_language = get_ui_language(default=config.get("system", {}).get("language", "en"))
|
|
79
81
|
return GuideManager(
|
|
80
82
|
api_key=api_key,
|
|
81
83
|
base_url=base_url,
|
|
82
84
|
api_version=api_version,
|
|
83
|
-
language=
|
|
85
|
+
language=ui_language,
|
|
84
86
|
binding=binding,
|
|
85
87
|
) # Read from config file
|
|
86
88
|
|
src/api/routers/ideagen.py
CHANGED
|
@@ -23,6 +23,7 @@ from src.api.utils.task_id_manager import TaskIDManager
|
|
|
23
23
|
from src.logging import get_logger
|
|
24
24
|
from src.services.config import load_config_with_main
|
|
25
25
|
from src.services.llm import get_llm_config
|
|
26
|
+
from src.services.settings.interface_settings import get_ui_language
|
|
26
27
|
|
|
27
28
|
router = APIRouter()
|
|
28
29
|
|
|
@@ -148,6 +149,7 @@ async def websocket_ideagen(websocket: WebSocket):
|
|
|
148
149
|
|
|
149
150
|
# Get LLM configuration
|
|
150
151
|
llm_config = get_llm_config()
|
|
152
|
+
ui_language = get_ui_language(default=config.get("system", {}).get("language", "en"))
|
|
151
153
|
|
|
152
154
|
# Get records
|
|
153
155
|
records = []
|
|
@@ -198,6 +200,7 @@ async def websocket_ideagen(websocket: WebSocket):
|
|
|
198
200
|
base_url=llm_config.base_url,
|
|
199
201
|
api_version=getattr(llm_config, "api_version", None),
|
|
200
202
|
model=llm_config.model,
|
|
203
|
+
language=ui_language,
|
|
201
204
|
)
|
|
202
205
|
|
|
203
206
|
knowledge_points = await organizer.process(
|
|
@@ -258,6 +261,7 @@ async def websocket_ideagen(websocket: WebSocket):
|
|
|
258
261
|
api_version=getattr(llm_config, "api_version", None),
|
|
259
262
|
model=llm_config.model,
|
|
260
263
|
progress_callback=None, # We manually manage status here
|
|
264
|
+
language=ui_language,
|
|
261
265
|
)
|
|
262
266
|
|
|
263
267
|
filtered_points = await workflow.loose_filter(knowledge_points)
|
|
@@ -412,6 +416,9 @@ async def websocket_ideagen(websocket: WebSocket):
|
|
|
412
416
|
task_manager.update_task_status(task_id, "error", error=str(e))
|
|
413
417
|
|
|
414
418
|
try:
|
|
419
|
+
# Send unified error message via send_status
|
|
420
|
+
# Note: send_status sends {"type": "status", "stage": "error", ...}
|
|
421
|
+
# which is the standard format for this WebSocket protocol
|
|
415
422
|
await send_status(
|
|
416
423
|
websocket,
|
|
417
424
|
IdeaGenStage.ERROR,
|
src/api/routers/knowledge.py
CHANGED
|
@@ -142,8 +142,19 @@ async def run_upload_processing_task(
|
|
|
142
142
|
base_url: str,
|
|
143
143
|
uploaded_file_paths: list[str],
|
|
144
144
|
rag_provider: str = None,
|
|
145
|
+
folder_id: str = None,
|
|
145
146
|
):
|
|
146
|
-
"""Background task for processing uploaded files
|
|
147
|
+
"""Background task for processing uploaded files.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
kb_name: Knowledge base name
|
|
151
|
+
base_dir: Base directory for knowledge bases
|
|
152
|
+
api_key: LLM API key
|
|
153
|
+
base_url: LLM API base URL
|
|
154
|
+
uploaded_file_paths: List of file paths to process
|
|
155
|
+
rag_provider: RAG provider (ignored - we use the one from KB metadata)
|
|
156
|
+
folder_id: Optional folder ID for sync state update
|
|
157
|
+
"""
|
|
147
158
|
task_manager = TaskIDManager.get_instance()
|
|
148
159
|
task_key = f"{kb_name}_upload_{len(uploaded_file_paths)}"
|
|
149
160
|
task_id = task_manager.generate_task_id("kb_upload", task_key)
|
|
@@ -169,8 +180,22 @@ async def run_upload_processing_task(
|
|
|
169
180
|
rag_provider=rag_provider,
|
|
170
181
|
)
|
|
171
182
|
|
|
172
|
-
|
|
173
|
-
|
|
183
|
+
# Stage files and check for duplicates
|
|
184
|
+
staged_files = adder.add_documents(uploaded_file_paths, allow_duplicates=False)
|
|
185
|
+
|
|
186
|
+
if not staged_files:
|
|
187
|
+
logger.info(f"[{task_id}] No new files to process (all duplicates or invalid)")
|
|
188
|
+
progress_tracker.update(
|
|
189
|
+
ProgressStage.COMPLETED,
|
|
190
|
+
"No new files to process (all duplicates or invalid)",
|
|
191
|
+
current=0,
|
|
192
|
+
total=0,
|
|
193
|
+
)
|
|
194
|
+
task_manager.update_task_status(task_id, "completed")
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
# Process staged files
|
|
198
|
+
processed_files = await adder.process_new_documents(staged_files)
|
|
174
199
|
|
|
175
200
|
if processed_files:
|
|
176
201
|
progress_tracker.update(
|
|
@@ -181,16 +206,28 @@ async def run_upload_processing_task(
|
|
|
181
206
|
)
|
|
182
207
|
adder.extract_numbered_items_for_new_docs(processed_files, batch_size=20)
|
|
183
208
|
|
|
184
|
-
adder.update_metadata(len(
|
|
209
|
+
adder.update_metadata(len(processed_files) if processed_files else 0)
|
|
210
|
+
|
|
211
|
+
# Update folder sync state if this was a folder sync
|
|
212
|
+
if folder_id and processed_files:
|
|
213
|
+
try:
|
|
214
|
+
manager = get_kb_manager()
|
|
215
|
+
manager.update_folder_sync_state(
|
|
216
|
+
kb_name, folder_id, [str(f) for f in processed_files]
|
|
217
|
+
)
|
|
218
|
+
logger.info(f"[{task_id}] Updated folder sync state for folder '{folder_id}'")
|
|
219
|
+
except Exception as sync_err:
|
|
220
|
+
logger.warning(f"[{task_id}] Failed to update folder sync state: {sync_err}")
|
|
185
221
|
|
|
222
|
+
num_processed = len(processed_files) if processed_files else 0
|
|
186
223
|
progress_tracker.update(
|
|
187
224
|
ProgressStage.COMPLETED,
|
|
188
|
-
f"Successfully processed {
|
|
189
|
-
current=
|
|
190
|
-
total=
|
|
225
|
+
f"Successfully processed {num_processed} files!",
|
|
226
|
+
current=num_processed,
|
|
227
|
+
total=num_processed,
|
|
191
228
|
)
|
|
192
229
|
|
|
193
|
-
logger.success(f"[{task_id}] Processed {
|
|
230
|
+
logger.success(f"[{task_id}] Processed {num_processed} files to KB '{kb_name}'")
|
|
194
231
|
task_manager.update_task_status(task_id, "completed")
|
|
195
232
|
except Exception as e:
|
|
196
233
|
error_msg = f"Upload processing failed (KB '{kb_name}'): {e}"
|
|
@@ -531,13 +568,28 @@ async def create_knowledge_base(
|
|
|
531
568
|
except ValueError as e:
|
|
532
569
|
raise HTTPException(status_code=500, detail=f"LLM config error: {e!s}")
|
|
533
570
|
|
|
534
|
-
progress_tracker = ProgressTracker(name, _kb_base_dir)
|
|
535
|
-
|
|
536
571
|
logger.info(f"Creating KB: {name}")
|
|
537
572
|
|
|
538
|
-
|
|
539
|
-
|
|
573
|
+
# Register KB to kb_config.json immediately with "initializing" status
|
|
574
|
+
# This ensures the KB appears in the list right away
|
|
575
|
+
manager.update_kb_status(
|
|
576
|
+
name=name,
|
|
577
|
+
status="initializing",
|
|
578
|
+
progress={
|
|
579
|
+
"stage": "initializing",
|
|
580
|
+
"message": "Initializing knowledge base...",
|
|
581
|
+
"percent": 0,
|
|
582
|
+
"current": 0,
|
|
583
|
+
"total": len(files),
|
|
584
|
+
},
|
|
540
585
|
)
|
|
586
|
+
# Also store rag_provider in config (reload and update)
|
|
587
|
+
manager.config = manager._load_config()
|
|
588
|
+
if name in manager.config.get("knowledge_bases", {}):
|
|
589
|
+
manager.config["knowledge_bases"][name]["rag_provider"] = rag_provider
|
|
590
|
+
manager._save_config()
|
|
591
|
+
|
|
592
|
+
progress_tracker = ProgressTracker(name, _kb_base_dir)
|
|
541
593
|
|
|
542
594
|
initializer = KnowledgeBaseInitializer(
|
|
543
595
|
kb_name=name,
|
src/api/routers/question.py
CHANGED
|
@@ -23,6 +23,7 @@ sys.path.insert(0, str(project_root))
|
|
|
23
23
|
from src.logging import get_logger
|
|
24
24
|
from src.services.config import load_config_with_main
|
|
25
25
|
from src.services.llm.config import get_llm_config
|
|
26
|
+
from src.services.settings.interface_settings import get_ui_language
|
|
26
27
|
|
|
27
28
|
# Setup module logger with unified logging system (from config)
|
|
28
29
|
project_root = Path(__file__).parent.parent.parent.parent
|
|
@@ -382,6 +383,7 @@ async def websocket_question_generate(websocket: WebSocket):
|
|
|
382
383
|
base_url=base_url,
|
|
383
384
|
api_version=api_version,
|
|
384
385
|
kb_name=kb_name,
|
|
386
|
+
language=get_ui_language(default=config.get("system", {}).get("language", "en")),
|
|
385
387
|
max_rounds=10,
|
|
386
388
|
output_dir=str(output_base),
|
|
387
389
|
)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""
|
|
2
|
+
RealTimeX SDK Status Router
|
|
3
|
+
============================
|
|
4
|
+
|
|
5
|
+
Provides status information about RealTimeX SDK connection and environment.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from fastapi import APIRouter
|
|
9
|
+
|
|
10
|
+
from src.utils.realtimex import get_realtimex_sdk, should_use_realtimex_sdk
|
|
11
|
+
|
|
12
|
+
router = APIRouter()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@router.get("/realtimex/status")
|
|
16
|
+
async def get_realtimex_status():
|
|
17
|
+
"""
|
|
18
|
+
Get RealTimeX SDK connection status.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
{
|
|
22
|
+
"connected": bool,
|
|
23
|
+
"mode": str | null, # "development" or "production"
|
|
24
|
+
"appId": str | null,
|
|
25
|
+
"timestamp": str | null,
|
|
26
|
+
"error": str | null
|
|
27
|
+
}
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
# Check if RealTimeX is detected
|
|
31
|
+
is_detected = should_use_realtimex_sdk()
|
|
32
|
+
|
|
33
|
+
if not is_detected:
|
|
34
|
+
return {
|
|
35
|
+
"connected": False,
|
|
36
|
+
"mode": None,
|
|
37
|
+
"appId": None,
|
|
38
|
+
"timestamp": None,
|
|
39
|
+
"error": None,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Get SDK instance and ping (async)
|
|
43
|
+
try:
|
|
44
|
+
sdk = get_realtimex_sdk()
|
|
45
|
+
# Use async ping() instead of ping_sync() to avoid event loop conflict
|
|
46
|
+
ping_result = await sdk.ping()
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
"connected": ping_result.get("success", False),
|
|
50
|
+
"mode": ping_result.get("mode"),
|
|
51
|
+
"appId": ping_result.get("appId"),
|
|
52
|
+
"timestamp": ping_result.get("timestamp"),
|
|
53
|
+
"error": None,
|
|
54
|
+
}
|
|
55
|
+
except Exception as e:
|
|
56
|
+
return {
|
|
57
|
+
"connected": False,
|
|
58
|
+
"mode": None,
|
|
59
|
+
"appId": None,
|
|
60
|
+
"timestamp": None,
|
|
61
|
+
"error": str(e),
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
except Exception as e:
|
|
65
|
+
return {
|
|
66
|
+
"connected": False,
|
|
67
|
+
"mode": None,
|
|
68
|
+
"appId": None,
|
|
69
|
+
"timestamp": None,
|
|
70
|
+
"error": f"Detection failed: {str(e)}",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@router.get("/realtimex/providers")
|
|
75
|
+
async def get_providers():
|
|
76
|
+
"""
|
|
77
|
+
Get available providers from RealTimeX SDK.
|
|
78
|
+
Returns empty lists if SDK not enabled, allowing frontend to use defaults.
|
|
79
|
+
"""
|
|
80
|
+
from src.utils.realtimex import get_cached_providers
|
|
81
|
+
|
|
82
|
+
return await get_cached_providers()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
from pydantic import BaseModel
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class RTXConfigApplyRequest(BaseModel):
|
|
89
|
+
config_type: str # "llm" or "embedding"
|
|
90
|
+
provider: str
|
|
91
|
+
model: str
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@router.post("/realtimex/config/apply")
|
|
95
|
+
async def apply_rtx_config(request: RTXConfigApplyRequest):
|
|
96
|
+
"""
|
|
97
|
+
Apply RTX provider/model selection.
|
|
98
|
+
|
|
99
|
+
Saves the selection to rtx_active.json and sets the active config
|
|
100
|
+
to 'rtx' in the unified config manager.
|
|
101
|
+
"""
|
|
102
|
+
from fastapi import HTTPException
|
|
103
|
+
|
|
104
|
+
from src.services.config.unified_config import ConfigType, get_config_manager
|
|
105
|
+
from src.utils.realtimex import set_rtx_active_config, should_use_realtimex_sdk
|
|
106
|
+
|
|
107
|
+
if not should_use_realtimex_sdk():
|
|
108
|
+
raise HTTPException(400, "RealTimeX SDK not available")
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
# Validate config type
|
|
112
|
+
if request.config_type == "llm":
|
|
113
|
+
config_type_enum = ConfigType.LLM
|
|
114
|
+
elif request.config_type == "embedding":
|
|
115
|
+
config_type_enum = ConfigType.EMBEDDING
|
|
116
|
+
else:
|
|
117
|
+
raise HTTPException(400, f"Invalid config type: {request.config_type}")
|
|
118
|
+
|
|
119
|
+
# Save RTX selection to rtx_active.json
|
|
120
|
+
if not set_rtx_active_config(request.config_type, request.provider, request.model):
|
|
121
|
+
raise HTTPException(500, "Failed to save RTX configuration")
|
|
122
|
+
|
|
123
|
+
# Set 'rtx' as the active config in unified config manager
|
|
124
|
+
manager = get_config_manager()
|
|
125
|
+
manager.set_active_config(config_type_enum, "rtx")
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
"success": True,
|
|
129
|
+
"config_type": request.config_type,
|
|
130
|
+
"provider": request.provider,
|
|
131
|
+
"model": request.model,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
except HTTPException:
|
|
135
|
+
raise
|
|
136
|
+
except Exception as e:
|
|
137
|
+
raise HTTPException(500, f"Failed to apply configuration: {str(e)}")
|
src/api/routers/research.py
CHANGED
|
@@ -15,6 +15,7 @@ from src.api.utils.task_id_manager import TaskIDManager
|
|
|
15
15
|
from src.logging import get_logger
|
|
16
16
|
from src.services.config import load_config_with_main
|
|
17
17
|
from src.services.llm import get_llm_config
|
|
18
|
+
from src.services.settings.interface_settings import get_ui_language
|
|
18
19
|
|
|
19
20
|
# Force stdout to use utf-8 to prevent encoding errors with emojis on Windows
|
|
20
21
|
if sys.platform == "win32":
|
|
@@ -46,6 +47,10 @@ class OptimizeRequest(BaseModel):
|
|
|
46
47
|
async def optimize_topic(request: OptimizeRequest):
|
|
47
48
|
try:
|
|
48
49
|
config = load_config()
|
|
50
|
+
config.setdefault("system", {})
|
|
51
|
+
config["system"]["language"] = get_ui_language(
|
|
52
|
+
default=config.get("system", {}).get("language", "en")
|
|
53
|
+
)
|
|
49
54
|
|
|
50
55
|
# Inject API keys
|
|
51
56
|
try:
|
|
@@ -111,6 +116,10 @@ async def websocket_research_run(websocket: WebSocket):
|
|
|
111
116
|
|
|
112
117
|
# Use unified logger
|
|
113
118
|
config = load_config()
|
|
119
|
+
config.setdefault("system", {})
|
|
120
|
+
config["system"]["language"] = get_ui_language(
|
|
121
|
+
default=config.get("system", {}).get("language", "en")
|
|
122
|
+
)
|
|
114
123
|
try:
|
|
115
124
|
# Get log_dir from config
|
|
116
125
|
log_dir = config.get("paths", {}).get("user_log_dir") or config.get("logging", {}).get(
|
src/api/routers/solve.py
CHANGED
|
@@ -11,9 +11,9 @@ import re
|
|
|
11
11
|
import sys
|
|
12
12
|
from typing import Any
|
|
13
13
|
|
|
14
|
-
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
|
14
|
+
from fastapi import APIRouter, HTTPException, WebSocket, WebSocketDisconnect
|
|
15
15
|
|
|
16
|
-
from src.agents.solve import MainSolver
|
|
16
|
+
from src.agents.solve import MainSolver, SolverSessionManager
|
|
17
17
|
from src.api.utils.history import ActivityType, history_manager
|
|
18
18
|
from src.api.utils.log_interceptor import LogInterceptor
|
|
19
19
|
from src.api.utils.task_id_manager import TaskIDManager
|
|
@@ -23,6 +23,7 @@ sys.path.insert(0, str(_project_root))
|
|
|
23
23
|
from src.logging import get_logger
|
|
24
24
|
from src.services.config import load_config_with_main
|
|
25
25
|
from src.services.llm import get_llm_config
|
|
26
|
+
from src.services.settings.interface_settings import get_ui_language
|
|
26
27
|
|
|
27
28
|
# Initialize logger with config
|
|
28
29
|
project_root = Path(__file__).parent.parent.parent.parent
|
|
@@ -32,6 +33,66 @@ logger = get_logger("SolveAPI", level="INFO", log_dir=log_dir)
|
|
|
32
33
|
|
|
33
34
|
router = APIRouter()
|
|
34
35
|
|
|
36
|
+
# Initialize session manager
|
|
37
|
+
solver_session_manager = SolverSessionManager()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# =============================================================================
|
|
41
|
+
# REST Endpoints for Session Management
|
|
42
|
+
# =============================================================================
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@router.get("/solve/sessions")
|
|
46
|
+
async def list_solver_sessions(limit: int = 20):
|
|
47
|
+
"""
|
|
48
|
+
List recent solver sessions.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
limit: Maximum number of sessions to return
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
List of session summaries
|
|
55
|
+
"""
|
|
56
|
+
return solver_session_manager.list_sessions(limit=limit, include_messages=False)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@router.get("/solve/sessions/{session_id}")
|
|
60
|
+
async def get_solver_session(session_id: str):
|
|
61
|
+
"""
|
|
62
|
+
Get a specific solver session with full message history.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
session_id: Session identifier
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Complete session data including messages
|
|
69
|
+
"""
|
|
70
|
+
session = solver_session_manager.get_session(session_id)
|
|
71
|
+
if not session:
|
|
72
|
+
raise HTTPException(status_code=404, detail="Session not found")
|
|
73
|
+
return session
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@router.delete("/solve/sessions/{session_id}")
|
|
77
|
+
async def delete_solver_session(session_id: str):
|
|
78
|
+
"""
|
|
79
|
+
Delete a solver session.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
session_id: Session identifier
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Success message
|
|
86
|
+
"""
|
|
87
|
+
if solver_session_manager.delete_session(session_id):
|
|
88
|
+
return {"status": "deleted", "session_id": session_id}
|
|
89
|
+
raise HTTPException(status_code=404, detail="Session not found")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# =============================================================================
|
|
93
|
+
# WebSocket Endpoint for Solving
|
|
94
|
+
# =============================================================================
|
|
95
|
+
|
|
35
96
|
|
|
36
97
|
@router.websocket("/solve")
|
|
37
98
|
async def websocket_solve(websocket: WebSocket):
|
|
@@ -81,16 +142,47 @@ async def websocket_solve(websocket: WebSocket):
|
|
|
81
142
|
logger.debug(f"Error in log_pusher: {e}")
|
|
82
143
|
break
|
|
83
144
|
|
|
145
|
+
session_id = None # Track session for this connection
|
|
146
|
+
|
|
84
147
|
try:
|
|
85
148
|
# 1. Wait for the initial message with the question and config
|
|
86
149
|
data = await websocket.receive_json()
|
|
87
150
|
question = data.get("question")
|
|
88
151
|
kb_name = data.get("kb_name", "ai_textbook")
|
|
152
|
+
session_id = data.get("session_id") # Optional session ID
|
|
89
153
|
|
|
90
154
|
if not question:
|
|
91
155
|
await websocket.send_json({"type": "error", "content": "Question is required"})
|
|
92
156
|
return
|
|
93
157
|
|
|
158
|
+
# Get or create session
|
|
159
|
+
if session_id:
|
|
160
|
+
session = solver_session_manager.get_session(session_id)
|
|
161
|
+
if not session:
|
|
162
|
+
# Session not found, create new one
|
|
163
|
+
session = solver_session_manager.create_session(
|
|
164
|
+
title=question[:50] + ("..." if len(question) > 50 else ""),
|
|
165
|
+
kb_name=kb_name,
|
|
166
|
+
)
|
|
167
|
+
session_id = session["session_id"]
|
|
168
|
+
else:
|
|
169
|
+
# Create new session
|
|
170
|
+
session = solver_session_manager.create_session(
|
|
171
|
+
title=question[:50] + ("..." if len(question) > 50 else ""),
|
|
172
|
+
kb_name=kb_name,
|
|
173
|
+
)
|
|
174
|
+
session_id = session["session_id"]
|
|
175
|
+
|
|
176
|
+
# Send session ID to frontend
|
|
177
|
+
await websocket.send_json({"type": "session", "session_id": session_id})
|
|
178
|
+
|
|
179
|
+
# Add user message to session
|
|
180
|
+
solver_session_manager.add_message(
|
|
181
|
+
session_id=session_id,
|
|
182
|
+
role="user",
|
|
183
|
+
content=question,
|
|
184
|
+
)
|
|
185
|
+
|
|
94
186
|
task_key = f"solve_{kb_name}_{hash(str(question))}"
|
|
95
187
|
task_id = task_manager.generate_task_id("solve", task_key)
|
|
96
188
|
|
|
@@ -110,12 +202,14 @@ async def websocket_solve(websocket: WebSocket):
|
|
|
110
202
|
await websocket.send_json({"type": "error", "content": f"LLM configuration error: {e}"})
|
|
111
203
|
return
|
|
112
204
|
|
|
205
|
+
ui_language = get_ui_language(default=config.get("system", {}).get("language", "en"))
|
|
113
206
|
solver = MainSolver(
|
|
114
207
|
kb_name=kb_name,
|
|
115
208
|
output_base_dir=str(output_base),
|
|
116
209
|
api_key=api_key,
|
|
117
210
|
base_url=base_url,
|
|
118
211
|
api_version=api_version,
|
|
212
|
+
language=ui_language,
|
|
119
213
|
)
|
|
120
214
|
|
|
121
215
|
# Complete async initialization
|
|
@@ -247,14 +341,37 @@ async def websocket_solve(websocket: WebSocket):
|
|
|
247
341
|
)
|
|
248
342
|
|
|
249
343
|
# Send final result
|
|
344
|
+
# Extract relative path from output_dir for frontend use
|
|
345
|
+
dir_name = ""
|
|
346
|
+
if output_dir_str:
|
|
347
|
+
parts = output_dir_str.replace("\\", "/").split("/")
|
|
348
|
+
dir_name = parts[-1] if parts else ""
|
|
349
|
+
|
|
250
350
|
final_res = {
|
|
251
351
|
"type": "result",
|
|
352
|
+
"session_id": session_id,
|
|
252
353
|
"final_answer": final_answer,
|
|
253
354
|
"output_dir": output_dir_str,
|
|
355
|
+
"output_dir_name": dir_name,
|
|
254
356
|
"metadata": result.get("metadata"),
|
|
255
357
|
}
|
|
256
358
|
await safe_send_json(final_res)
|
|
257
359
|
|
|
360
|
+
# Save assistant message to session
|
|
361
|
+
if session_id:
|
|
362
|
+
solver_session_manager.add_message(
|
|
363
|
+
session_id=session_id,
|
|
364
|
+
role="assistant",
|
|
365
|
+
content=final_answer,
|
|
366
|
+
output_dir=dir_name,
|
|
367
|
+
)
|
|
368
|
+
# Update token stats in session
|
|
369
|
+
if display_manager:
|
|
370
|
+
solver_session_manager.update_token_stats(
|
|
371
|
+
session_id=session_id,
|
|
372
|
+
token_stats=display_manager.stats.copy(),
|
|
373
|
+
)
|
|
374
|
+
|
|
258
375
|
# Save to history
|
|
259
376
|
history_manager.add_entry(
|
|
260
377
|
activity_type=ActivityType.SOLVE,
|
|
@@ -263,6 +380,7 @@ async def websocket_solve(websocket: WebSocket):
|
|
|
263
380
|
"question": question,
|
|
264
381
|
"answer": result.get("final_answer"),
|
|
265
382
|
"kb_name": kb_name,
|
|
383
|
+
"session_id": session_id,
|
|
266
384
|
},
|
|
267
385
|
summary=(
|
|
268
386
|
result.get("final_answer")[:100] + "..." if result.get("final_answer") else ""
|
src/cli/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line interface for DeepTutor.
|
|
3
|
+
|
|
4
|
+
Provides entry points for:
|
|
5
|
+
- Full-stack startup (backend + frontend)
|
|
6
|
+
- Backend-only mode (future)
|
|
7
|
+
- Frontend-only mode (future)
|
|
8
|
+
- Config management (future)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .start import main as start_main
|
|
12
|
+
|
|
13
|
+
__all__ = ["start_main"]
|