cite-agent 1.0.4__py3-none-any.whl → 1.2.3__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.
Potentially problematic release.
This version of cite-agent might be problematic. Click here for more details.
- cite_agent/__init__.py +1 -1
- cite_agent/account_client.py +19 -46
- cite_agent/agent_backend_only.py +30 -4
- cite_agent/cli.py +397 -64
- cite_agent/cli_conversational.py +294 -0
- cite_agent/cli_workflow.py +276 -0
- cite_agent/enhanced_ai_agent.py +3222 -117
- cite_agent/session_manager.py +215 -0
- cite_agent/setup_config.py +5 -21
- cite_agent/streaming_ui.py +252 -0
- cite_agent/updater.py +50 -17
- cite_agent/workflow.py +427 -0
- cite_agent/workflow_integration.py +275 -0
- cite_agent-1.2.3.dist-info/METADATA +442 -0
- cite_agent-1.2.3.dist-info/RECORD +54 -0
- {cite_agent-1.0.4.dist-info → cite_agent-1.2.3.dist-info}/top_level.txt +1 -0
- src/__init__.py +1 -0
- src/services/__init__.py +132 -0
- src/services/auth_service/__init__.py +3 -0
- src/services/auth_service/auth_manager.py +33 -0
- src/services/graph/__init__.py +1 -0
- src/services/graph/knowledge_graph.py +194 -0
- src/services/llm_service/__init__.py +5 -0
- src/services/llm_service/llm_manager.py +495 -0
- src/services/paper_service/__init__.py +5 -0
- src/services/paper_service/openalex.py +231 -0
- src/services/performance_service/__init__.py +1 -0
- src/services/performance_service/rust_performance.py +395 -0
- src/services/research_service/__init__.py +23 -0
- src/services/research_service/chatbot.py +2056 -0
- src/services/research_service/citation_manager.py +436 -0
- src/services/research_service/context_manager.py +1441 -0
- src/services/research_service/conversation_manager.py +597 -0
- src/services/research_service/critical_paper_detector.py +577 -0
- src/services/research_service/enhanced_research.py +121 -0
- src/services/research_service/enhanced_synthesizer.py +375 -0
- src/services/research_service/query_generator.py +777 -0
- src/services/research_service/synthesizer.py +1273 -0
- src/services/search_service/__init__.py +5 -0
- src/services/search_service/indexer.py +186 -0
- src/services/search_service/search_engine.py +342 -0
- src/services/simple_enhanced_main.py +287 -0
- cite_agent/__distribution__.py +0 -7
- cite_agent-1.0.4.dist-info/METADATA +0 -234
- cite_agent-1.0.4.dist-info/RECORD +0 -23
- {cite_agent-1.0.4.dist-info → cite_agent-1.2.3.dist-info}/WHEEL +0 -0
- {cite_agent-1.0.4.dist-info → cite_agent-1.2.3.dist-info}/entry_points.txt +0 -0
- {cite_agent-1.0.4.dist-info → cite_agent-1.2.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Nocturnal Archive - Conversational Data Analysis Assistant
|
|
4
|
+
Main CLI with streaming UI, Jarvis personality, and full capabilities
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
# Add nocturnal_archive to path
|
|
14
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
15
|
+
|
|
16
|
+
from streaming_ui import StreamingChatUI, simulate_streaming
|
|
17
|
+
from web_search import WebSearchIntegration
|
|
18
|
+
from enhanced_ai_agent import EnhancedNocturnalAgent
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class NocturnalArchiveCLI:
|
|
22
|
+
"""
|
|
23
|
+
Main CLI orchestrator bringing together:
|
|
24
|
+
- Streaming chat UI (Cursor/Claude style)
|
|
25
|
+
- Enhanced AI agent (with APIs)
|
|
26
|
+
- Web search capability
|
|
27
|
+
- Jarvis-like professional personality
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self):
|
|
31
|
+
self.ui = None
|
|
32
|
+
self.agent = None
|
|
33
|
+
self.web_search = None
|
|
34
|
+
self.working_dir = os.getcwd()
|
|
35
|
+
self.conversation_active = True
|
|
36
|
+
|
|
37
|
+
async def initialize(self):
|
|
38
|
+
"""Initialize all components"""
|
|
39
|
+
# Initialize UI
|
|
40
|
+
self.ui = StreamingChatUI(
|
|
41
|
+
app_name="Nocturnal Archive",
|
|
42
|
+
working_dir=self.working_dir
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Initialize agent
|
|
46
|
+
self.agent = EnhancedNocturnalAgent()
|
|
47
|
+
await self.agent.initialize()
|
|
48
|
+
|
|
49
|
+
# Initialize web search
|
|
50
|
+
self.web_search = WebSearchIntegration()
|
|
51
|
+
|
|
52
|
+
# Update agent personality to Jarvis-like professional tone
|
|
53
|
+
await self._configure_jarvis_personality()
|
|
54
|
+
|
|
55
|
+
async def _configure_jarvis_personality(self):
|
|
56
|
+
"""Configure the agent with Jarvis-like professional personality"""
|
|
57
|
+
# This will update the system prompts in the agent
|
|
58
|
+
# The agent already has conversation memory, we just adjust tone
|
|
59
|
+
|
|
60
|
+
jarvis_system_prompt = """You are a professional research and data analysis assistant with a refined, helpful demeanor similar to Jarvis from Iron Man.
|
|
61
|
+
|
|
62
|
+
Your communication style:
|
|
63
|
+
- Professional and intelligent, never casual or overly friendly
|
|
64
|
+
- Verbose when explaining complex topics, but always clear
|
|
65
|
+
- Use complete sentences and proper grammar
|
|
66
|
+
- Greet users formally: "Good evening" / "Good morning" (based on time)
|
|
67
|
+
- Ask clarifying questions when needed
|
|
68
|
+
- Acknowledge tasks before executing: "Understood. Let me..." or "Certainly, I'll..."
|
|
69
|
+
- Show intermediate progress: "I can see..." / "The data indicates..."
|
|
70
|
+
- Offer next steps conversationally: "Would you like me to..."
|
|
71
|
+
|
|
72
|
+
Your capabilities:
|
|
73
|
+
- Analyze local data files (CSV, Excel, Stata, etc.)
|
|
74
|
+
- Run statistical tests and data transformations
|
|
75
|
+
- Search academic papers (Archive API)
|
|
76
|
+
- Fetch financial data (FinSight API)
|
|
77
|
+
- Browse the web for current information
|
|
78
|
+
- Execute shell commands safely
|
|
79
|
+
- Generate visualizations
|
|
80
|
+
|
|
81
|
+
Primary role: You're a DATA ANALYSIS assistant first, research assistant second.
|
|
82
|
+
- When users mention data files, offer to analyze them
|
|
83
|
+
- Proactively suggest relevant statistical tests
|
|
84
|
+
- Explain results in context, not just numbers
|
|
85
|
+
- Synthesize information naturally, avoid bullet-point lists unless specifically listing items
|
|
86
|
+
|
|
87
|
+
Remember:
|
|
88
|
+
- Never invent data or citations
|
|
89
|
+
- Be honest about limitations
|
|
90
|
+
- Offer alternatives when rate-limited
|
|
91
|
+
- Maintain conversation flow naturally
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
# Store this for when we make requests
|
|
95
|
+
self.jarvis_prompt = jarvis_system_prompt
|
|
96
|
+
|
|
97
|
+
async def run(self):
|
|
98
|
+
"""Main conversation loop"""
|
|
99
|
+
try:
|
|
100
|
+
# Show welcome
|
|
101
|
+
self.ui.show_header()
|
|
102
|
+
|
|
103
|
+
# Jarvis-style greeting based on time of day
|
|
104
|
+
from datetime import datetime
|
|
105
|
+
hour = datetime.now().hour
|
|
106
|
+
if hour < 12:
|
|
107
|
+
greeting = "Good morning."
|
|
108
|
+
elif hour < 18:
|
|
109
|
+
greeting = "Good afternoon."
|
|
110
|
+
else:
|
|
111
|
+
greeting = "Good evening."
|
|
112
|
+
|
|
113
|
+
welcome_message = (
|
|
114
|
+
f"{greeting} I'm ready to assist with your analysis. "
|
|
115
|
+
"What would you like to work on today?"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Stream the welcome message
|
|
119
|
+
async def welcome_gen():
|
|
120
|
+
async for chunk in simulate_streaming(welcome_message, chunk_size=3):
|
|
121
|
+
yield chunk
|
|
122
|
+
|
|
123
|
+
await self.ui.stream_agent_response(welcome_gen())
|
|
124
|
+
|
|
125
|
+
# Main conversation loop
|
|
126
|
+
while self.conversation_active:
|
|
127
|
+
try:
|
|
128
|
+
# Get user input
|
|
129
|
+
user_input = self.ui.get_user_input()
|
|
130
|
+
|
|
131
|
+
if not user_input:
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
# Check for exit commands
|
|
135
|
+
if user_input.lower() in ['exit', 'quit', 'bye', 'goodbye']:
|
|
136
|
+
farewell = "Goodbye. Feel free to return whenever you need assistance."
|
|
137
|
+
async def farewell_gen():
|
|
138
|
+
async for chunk in simulate_streaming(farewell, chunk_size=3):
|
|
139
|
+
yield chunk
|
|
140
|
+
await self.ui.stream_agent_response(farewell_gen())
|
|
141
|
+
break
|
|
142
|
+
|
|
143
|
+
# Process the request
|
|
144
|
+
await self._process_user_request(user_input)
|
|
145
|
+
|
|
146
|
+
except KeyboardInterrupt:
|
|
147
|
+
self.ui.show_info("\nInterrupted. Goodbye.")
|
|
148
|
+
break
|
|
149
|
+
except Exception as e:
|
|
150
|
+
self.ui.show_error(f"An error occurred: {str(e)}")
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
finally:
|
|
154
|
+
await self.cleanup()
|
|
155
|
+
|
|
156
|
+
async def _process_user_request(self, user_input: str):
|
|
157
|
+
"""
|
|
158
|
+
Process user request with full capabilities:
|
|
159
|
+
- Detect intent (data analysis, web search, research, etc.)
|
|
160
|
+
- Use appropriate tools
|
|
161
|
+
- Stream response naturally
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
# Determine if this is a web search request
|
|
165
|
+
is_web_search = any(keyword in user_input.lower() for keyword in [
|
|
166
|
+
'google', 'search for', 'browse', 'look up', 'find on the web',
|
|
167
|
+
'what does', 'who is', 'recent news'
|
|
168
|
+
])
|
|
169
|
+
|
|
170
|
+
# Determine if this is a data analysis request
|
|
171
|
+
is_data_analysis = any(keyword in user_input.lower() for keyword in [
|
|
172
|
+
'analyze', 'data', 'csv', 'plot', 'graph', 'test', 'regression',
|
|
173
|
+
'correlation', 'statistics', 'mean', 'median', 'distribution'
|
|
174
|
+
])
|
|
175
|
+
|
|
176
|
+
# For now, let's handle web search directly as an example
|
|
177
|
+
if is_web_search and 'google' in user_input.lower():
|
|
178
|
+
# Extract search query
|
|
179
|
+
query = user_input.lower().replace('google', '').replace('search for', '').strip()
|
|
180
|
+
|
|
181
|
+
# Show indicator
|
|
182
|
+
indicator = self.ui.show_action_indicator(f"browsing web for '{query}'")
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
# Perform web search
|
|
186
|
+
result = await self.web_search.search_web(query, num_results=5)
|
|
187
|
+
indicator.stop()
|
|
188
|
+
|
|
189
|
+
# Stream the response
|
|
190
|
+
response_text = result.get('formatted_response', 'No results found.')
|
|
191
|
+
async def response_gen():
|
|
192
|
+
async for chunk in simulate_streaming(response_text, chunk_size=4):
|
|
193
|
+
yield chunk
|
|
194
|
+
|
|
195
|
+
await self.ui.stream_agent_response(response_gen())
|
|
196
|
+
|
|
197
|
+
except Exception as e:
|
|
198
|
+
indicator.stop()
|
|
199
|
+
self.ui.show_error(f"Web search failed: {str(e)}")
|
|
200
|
+
|
|
201
|
+
else:
|
|
202
|
+
# For other requests, use the enhanced agent
|
|
203
|
+
indicator = self.ui.show_action_indicator("processing your request")
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
# Call the agent with REAL Groq streaming
|
|
207
|
+
from enhanced_ai_agent import ChatRequest
|
|
208
|
+
from streaming_ui import groq_stream_to_generator
|
|
209
|
+
|
|
210
|
+
request = ChatRequest(
|
|
211
|
+
question=user_input,
|
|
212
|
+
user_id="cli_user",
|
|
213
|
+
conversation_id="main_session"
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# Get streaming response from agent
|
|
217
|
+
try:
|
|
218
|
+
# Check if agent has streaming support
|
|
219
|
+
if hasattr(self.agent, 'process_request_streaming'):
|
|
220
|
+
# Use real streaming
|
|
221
|
+
stream = await self.agent.process_request_streaming(request)
|
|
222
|
+
indicator.stop()
|
|
223
|
+
await self.ui.stream_agent_response(groq_stream_to_generator(stream))
|
|
224
|
+
else:
|
|
225
|
+
# Fallback to non-streaming
|
|
226
|
+
response = await self.agent.process_request(request)
|
|
227
|
+
indicator.stop()
|
|
228
|
+
|
|
229
|
+
# Check for rate limiting
|
|
230
|
+
if response.error_message and 'limit' in response.error_message.lower():
|
|
231
|
+
self.ui.show_rate_limit_message(
|
|
232
|
+
limit_type="API queries",
|
|
233
|
+
remaining_capabilities=[
|
|
234
|
+
"Local data analysis (unlimited)",
|
|
235
|
+
"Web searches (unlimited)",
|
|
236
|
+
"General conversation",
|
|
237
|
+
"File operations"
|
|
238
|
+
]
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Stream the response (simulated for now)
|
|
242
|
+
response_text = response.response
|
|
243
|
+
async def response_gen():
|
|
244
|
+
for char in response_text:
|
|
245
|
+
yield char
|
|
246
|
+
|
|
247
|
+
await self.ui.stream_agent_response(response_gen())
|
|
248
|
+
|
|
249
|
+
except Exception as e:
|
|
250
|
+
indicator.stop()
|
|
251
|
+
self.ui.show_error(f"Error: {str(e)}")
|
|
252
|
+
|
|
253
|
+
except Exception as e:
|
|
254
|
+
indicator.stop()
|
|
255
|
+
self.ui.show_error(f"Request processing failed: {str(e)}")
|
|
256
|
+
|
|
257
|
+
async def cleanup(self):
|
|
258
|
+
"""Clean up resources"""
|
|
259
|
+
if self.agent:
|
|
260
|
+
await self.agent.close()
|
|
261
|
+
if self.web_search:
|
|
262
|
+
await self.web_search.close()
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
async def main():
|
|
266
|
+
"""Main entry point"""
|
|
267
|
+
cli = NocturnalArchiveCLI()
|
|
268
|
+
|
|
269
|
+
try:
|
|
270
|
+
await cli.initialize()
|
|
271
|
+
await cli.run()
|
|
272
|
+
except KeyboardInterrupt:
|
|
273
|
+
print("\n\nGoodbye!")
|
|
274
|
+
except Exception as e:
|
|
275
|
+
print(f"\n❌ Fatal error: {e}")
|
|
276
|
+
import traceback
|
|
277
|
+
traceback.print_exc()
|
|
278
|
+
finally:
|
|
279
|
+
await cli.cleanup()
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
if __name__ == "__main__":
|
|
283
|
+
# Check dependencies
|
|
284
|
+
try:
|
|
285
|
+
import rich
|
|
286
|
+
import groq
|
|
287
|
+
import aiohttp
|
|
288
|
+
except ImportError as e:
|
|
289
|
+
print(f"❌ Missing dependency: {e}")
|
|
290
|
+
print("Install requirements: pip install -r requirements.txt")
|
|
291
|
+
sys.exit(1)
|
|
292
|
+
|
|
293
|
+
# Run the CLI
|
|
294
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enhanced CLI with workflow integration features
|
|
3
|
+
Reduces context switching for scholars
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, List, Any, Optional
|
|
11
|
+
try:
|
|
12
|
+
import structlog
|
|
13
|
+
except ImportError:
|
|
14
|
+
import logging
|
|
15
|
+
structlog = logging
|
|
16
|
+
|
|
17
|
+
from .enhanced_ai_agent import EnhancedNocturnalAgent, ChatRequest
|
|
18
|
+
from .workflow_integration import WorkflowIntegration
|
|
19
|
+
|
|
20
|
+
logger = structlog.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
class WorkflowCLI:
|
|
23
|
+
"""Enhanced CLI with workflow integration"""
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self.agent = None
|
|
27
|
+
self.workflow = WorkflowIntegration()
|
|
28
|
+
self.session_id = f"workflow_{os.getpid()}"
|
|
29
|
+
|
|
30
|
+
async def initialize(self):
|
|
31
|
+
"""Initialize the agent and workflow"""
|
|
32
|
+
self.agent = EnhancedNocturnalAgent()
|
|
33
|
+
await self.agent.initialize()
|
|
34
|
+
|
|
35
|
+
async def close(self):
|
|
36
|
+
"""Clean up resources"""
|
|
37
|
+
if self.agent:
|
|
38
|
+
await self.agent.close()
|
|
39
|
+
|
|
40
|
+
async def search_and_save(self, query: str, user_id: str = "default") -> Dict[str, Any]:
|
|
41
|
+
"""Search papers and save to library"""
|
|
42
|
+
try:
|
|
43
|
+
# Search for papers
|
|
44
|
+
request = ChatRequest(
|
|
45
|
+
question=f"Find academic papers about: {query}",
|
|
46
|
+
user_id=user_id,
|
|
47
|
+
conversation_id=self.session_id
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
response = await self.agent.process_request(request)
|
|
51
|
+
|
|
52
|
+
# Extract papers from response
|
|
53
|
+
papers = self._extract_papers_from_response(response)
|
|
54
|
+
|
|
55
|
+
# Save papers to library
|
|
56
|
+
saved_papers = []
|
|
57
|
+
for paper in papers:
|
|
58
|
+
paper_id = self.workflow.save_paper_to_library(paper, user_id)
|
|
59
|
+
saved_papers.append({
|
|
60
|
+
"id": paper_id,
|
|
61
|
+
"title": paper.get("title", "Unknown Title")
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
# Save session
|
|
65
|
+
session_id = self.workflow.save_session_history(
|
|
66
|
+
user_id,
|
|
67
|
+
query,
|
|
68
|
+
{
|
|
69
|
+
"response": response.response,
|
|
70
|
+
"papers": papers,
|
|
71
|
+
"tools_used": response.tools_used
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
"success": True,
|
|
77
|
+
"papers_found": len(papers),
|
|
78
|
+
"papers_saved": len(saved_papers),
|
|
79
|
+
"session_id": session_id,
|
|
80
|
+
"saved_papers": saved_papers
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logger.error("Error in search_and_save", error=str(e))
|
|
85
|
+
return {"success": False, "error": str(e)}
|
|
86
|
+
|
|
87
|
+
def _extract_papers_from_response(self, response) -> List[Dict[str, Any]]:
|
|
88
|
+
"""Extract paper data from agent response"""
|
|
89
|
+
papers = []
|
|
90
|
+
|
|
91
|
+
# Try to extract from execution_results
|
|
92
|
+
if hasattr(response, 'execution_results') and response.execution_results:
|
|
93
|
+
for result in response.execution_results.values():
|
|
94
|
+
if isinstance(result, dict) and 'papers' in result:
|
|
95
|
+
papers.extend(result['papers'])
|
|
96
|
+
|
|
97
|
+
# Try to extract from api_results
|
|
98
|
+
if hasattr(response, 'api_results') and response.api_results:
|
|
99
|
+
for result in response.api_results.values():
|
|
100
|
+
if isinstance(result, dict) and 'papers' in result:
|
|
101
|
+
papers.extend(result['papers'])
|
|
102
|
+
|
|
103
|
+
return papers
|
|
104
|
+
|
|
105
|
+
async def export_library(self, user_id: str, format: str = "bibtex") -> str:
|
|
106
|
+
"""Export user's library in specified format"""
|
|
107
|
+
try:
|
|
108
|
+
library = self.workflow.get_user_library(user_id)
|
|
109
|
+
papers = [paper_data.get('paper', {}) for paper_data in library]
|
|
110
|
+
|
|
111
|
+
if format == "bibtex":
|
|
112
|
+
filename = f"library_{user_id}.bib"
|
|
113
|
+
file_path = self.workflow.export_to_bibtex(papers, filename)
|
|
114
|
+
elif format == "markdown":
|
|
115
|
+
filename = f"library_{user_id}.md"
|
|
116
|
+
file_path = self.workflow.export_to_markdown(papers, filename)
|
|
117
|
+
else:
|
|
118
|
+
raise ValueError(f"Unsupported format: {format}")
|
|
119
|
+
|
|
120
|
+
return file_path
|
|
121
|
+
|
|
122
|
+
except Exception as e:
|
|
123
|
+
logger.error("Error exporting library", error=str(e))
|
|
124
|
+
raise
|
|
125
|
+
|
|
126
|
+
def search_library(self, user_id: str, query: str) -> List[Dict[str, Any]]:
|
|
127
|
+
"""Search user's saved library"""
|
|
128
|
+
return self.workflow.search_library(user_id, query)
|
|
129
|
+
|
|
130
|
+
def get_library_stats(self, user_id: str) -> Dict[str, Any]:
|
|
131
|
+
"""Get library statistics"""
|
|
132
|
+
library = self.workflow.get_user_library(user_id)
|
|
133
|
+
sessions = self.workflow.get_session_history(user_id, 100)
|
|
134
|
+
|
|
135
|
+
# Calculate stats
|
|
136
|
+
total_papers = len(library)
|
|
137
|
+
|
|
138
|
+
# Papers by year
|
|
139
|
+
papers_by_year = {}
|
|
140
|
+
for paper_data in library:
|
|
141
|
+
paper = paper_data.get('paper', {})
|
|
142
|
+
year = paper.get('year', 'Unknown')
|
|
143
|
+
papers_by_year[year] = papers_by_year.get(year, 0) + 1
|
|
144
|
+
|
|
145
|
+
# Most used tools
|
|
146
|
+
tools_used = {}
|
|
147
|
+
for session in sessions:
|
|
148
|
+
for tool in session.get('tools_used', []):
|
|
149
|
+
tools_used[tool] = tools_used.get(tool, 0) + 1
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
"total_papers": total_papers,
|
|
153
|
+
"total_sessions": len(sessions),
|
|
154
|
+
"papers_by_year": papers_by_year,
|
|
155
|
+
"most_used_tools": dict(sorted(tools_used.items(), key=lambda x: x[1], reverse=True)[:5])
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async def get_citation_suggestions(self, paper: Dict[str, Any]) -> List[str]:
|
|
159
|
+
"""Get citation suggestions for a paper"""
|
|
160
|
+
return self.workflow.generate_citation_suggestions(paper)
|
|
161
|
+
|
|
162
|
+
def get_session_history(self, user_id: str, limit: int = 10) -> List[Dict[str, Any]]:
|
|
163
|
+
"""Get recent session history"""
|
|
164
|
+
return self.workflow.get_session_history(user_id, limit)
|
|
165
|
+
|
|
166
|
+
async def interactive_workflow(self, user_id: str = "default"):
|
|
167
|
+
"""Interactive workflow mode"""
|
|
168
|
+
print("🔬 Cite-Agent Workflow Mode")
|
|
169
|
+
print("=" * 50)
|
|
170
|
+
print("Commands:")
|
|
171
|
+
print(" search <query> - Search and save papers")
|
|
172
|
+
print(" library - Show saved papers")
|
|
173
|
+
print(" export <format> - Export library (bibtex/markdown)")
|
|
174
|
+
print(" stats - Show library statistics")
|
|
175
|
+
print(" history - Show recent searches")
|
|
176
|
+
print(" suggest <title> - Get citation suggestions")
|
|
177
|
+
print(" quit - Exit workflow mode")
|
|
178
|
+
print()
|
|
179
|
+
|
|
180
|
+
while True:
|
|
181
|
+
try:
|
|
182
|
+
command = input("workflow> ").strip()
|
|
183
|
+
|
|
184
|
+
if command.lower() in ['quit', 'exit', 'q']:
|
|
185
|
+
break
|
|
186
|
+
elif command.startswith('search '):
|
|
187
|
+
query = command[7:].strip()
|
|
188
|
+
if query:
|
|
189
|
+
result = await self.search_and_save(query, user_id)
|
|
190
|
+
if result['success']:
|
|
191
|
+
print(f"✅ Found {result['papers_found']} papers, saved {result['papers_saved']}")
|
|
192
|
+
else:
|
|
193
|
+
print(f"❌ Error: {result.get('error', 'Unknown error')}")
|
|
194
|
+
else:
|
|
195
|
+
print("❌ Please provide a search query")
|
|
196
|
+
|
|
197
|
+
elif command == 'library':
|
|
198
|
+
library = self.workflow.get_user_library(user_id)
|
|
199
|
+
if library:
|
|
200
|
+
print(f"📚 Library ({len(library)} papers):")
|
|
201
|
+
for i, paper_data in enumerate(library[:10], 1):
|
|
202
|
+
paper = paper_data.get('paper', {})
|
|
203
|
+
title = paper.get('title', 'Unknown Title')[:60]
|
|
204
|
+
year = paper.get('year', 'Unknown')
|
|
205
|
+
print(f" {i}. {title}... ({year})")
|
|
206
|
+
if len(library) > 10:
|
|
207
|
+
print(f" ... and {len(library) - 10} more")
|
|
208
|
+
else:
|
|
209
|
+
print("📚 Library is empty")
|
|
210
|
+
|
|
211
|
+
elif command.startswith('export '):
|
|
212
|
+
format = command[7:].strip().lower()
|
|
213
|
+
if format in ['bibtex', 'markdown']:
|
|
214
|
+
try:
|
|
215
|
+
file_path = await self.export_library(user_id, format)
|
|
216
|
+
print(f"✅ Exported to: {file_path}")
|
|
217
|
+
except Exception as e:
|
|
218
|
+
print(f"❌ Export error: {e}")
|
|
219
|
+
else:
|
|
220
|
+
print("❌ Supported formats: bibtex, markdown")
|
|
221
|
+
|
|
222
|
+
elif command == 'stats':
|
|
223
|
+
stats = self.get_library_stats(user_id)
|
|
224
|
+
print("📊 Library Statistics:")
|
|
225
|
+
print(f" Total papers: {stats['total_papers']}")
|
|
226
|
+
print(f" Total sessions: {stats['total_sessions']}")
|
|
227
|
+
print(" Papers by year:")
|
|
228
|
+
for year, count in sorted(stats['papers_by_year'].items()):
|
|
229
|
+
print(f" {year}: {count}")
|
|
230
|
+
print(" Most used tools:")
|
|
231
|
+
for tool, count in stats['most_used_tools'].items():
|
|
232
|
+
print(f" {tool}: {count}")
|
|
233
|
+
|
|
234
|
+
elif command == 'history':
|
|
235
|
+
history = self.get_session_history(user_id, 5)
|
|
236
|
+
if history:
|
|
237
|
+
print("🕒 Recent Searches:")
|
|
238
|
+
for i, session in enumerate(history, 1):
|
|
239
|
+
query = session.get('query', 'Unknown')[:50]
|
|
240
|
+
timestamp = session.get('timestamp', 'Unknown')[:19]
|
|
241
|
+
papers = session.get('papers_found', 0)
|
|
242
|
+
print(f" {i}. {query}... ({papers} papers) - {timestamp}")
|
|
243
|
+
else:
|
|
244
|
+
print("🕒 No search history")
|
|
245
|
+
|
|
246
|
+
elif command.startswith('suggest '):
|
|
247
|
+
title = command[8:].strip()
|
|
248
|
+
if title:
|
|
249
|
+
# Find paper in library
|
|
250
|
+
library = self.workflow.get_user_library(user_id)
|
|
251
|
+
paper = None
|
|
252
|
+
for paper_data in library:
|
|
253
|
+
if title.lower() in paper_data.get('paper', {}).get('title', '').lower():
|
|
254
|
+
paper = paper_data.get('paper', {})
|
|
255
|
+
break
|
|
256
|
+
|
|
257
|
+
if paper:
|
|
258
|
+
suggestions = await self.get_citation_suggestions(paper)
|
|
259
|
+
print(f"💡 Suggestions for '{paper.get('title', 'Unknown')}':")
|
|
260
|
+
for suggestion in suggestions:
|
|
261
|
+
print(f" • {suggestion}")
|
|
262
|
+
else:
|
|
263
|
+
print(f"❌ Paper not found in library: {title}")
|
|
264
|
+
else:
|
|
265
|
+
print("❌ Please provide a paper title")
|
|
266
|
+
|
|
267
|
+
else:
|
|
268
|
+
print("❌ Unknown command. Type 'quit' to exit.")
|
|
269
|
+
|
|
270
|
+
except KeyboardInterrupt:
|
|
271
|
+
print("\n👋 Goodbye!")
|
|
272
|
+
break
|
|
273
|
+
except Exception as e:
|
|
274
|
+
print(f"❌ Error: {e}")
|
|
275
|
+
|
|
276
|
+
print("👋 Workflow mode ended")
|