arionxiv 1.0.32__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 (69) hide show
  1. arionxiv/__init__.py +40 -0
  2. arionxiv/__main__.py +10 -0
  3. arionxiv/arxiv_operations/__init__.py +0 -0
  4. arionxiv/arxiv_operations/client.py +225 -0
  5. arionxiv/arxiv_operations/fetcher.py +173 -0
  6. arionxiv/arxiv_operations/searcher.py +122 -0
  7. arionxiv/arxiv_operations/utils.py +293 -0
  8. arionxiv/cli/__init__.py +4 -0
  9. arionxiv/cli/commands/__init__.py +1 -0
  10. arionxiv/cli/commands/analyze.py +587 -0
  11. arionxiv/cli/commands/auth.py +365 -0
  12. arionxiv/cli/commands/chat.py +714 -0
  13. arionxiv/cli/commands/daily.py +482 -0
  14. arionxiv/cli/commands/fetch.py +217 -0
  15. arionxiv/cli/commands/library.py +295 -0
  16. arionxiv/cli/commands/preferences.py +426 -0
  17. arionxiv/cli/commands/search.py +254 -0
  18. arionxiv/cli/commands/settings_unified.py +1407 -0
  19. arionxiv/cli/commands/trending.py +41 -0
  20. arionxiv/cli/commands/welcome.py +168 -0
  21. arionxiv/cli/main.py +407 -0
  22. arionxiv/cli/ui/__init__.py +1 -0
  23. arionxiv/cli/ui/global_theme_manager.py +173 -0
  24. arionxiv/cli/ui/logo.py +127 -0
  25. arionxiv/cli/ui/splash.py +89 -0
  26. arionxiv/cli/ui/theme.py +32 -0
  27. arionxiv/cli/ui/theme_system.py +391 -0
  28. arionxiv/cli/utils/__init__.py +54 -0
  29. arionxiv/cli/utils/animations.py +522 -0
  30. arionxiv/cli/utils/api_client.py +583 -0
  31. arionxiv/cli/utils/api_config.py +505 -0
  32. arionxiv/cli/utils/command_suggestions.py +147 -0
  33. arionxiv/cli/utils/db_config_manager.py +254 -0
  34. arionxiv/github_actions_runner.py +206 -0
  35. arionxiv/main.py +23 -0
  36. arionxiv/prompts/__init__.py +9 -0
  37. arionxiv/prompts/prompts.py +247 -0
  38. arionxiv/rag_techniques/__init__.py +8 -0
  39. arionxiv/rag_techniques/basic_rag.py +1531 -0
  40. arionxiv/scheduler_daemon.py +139 -0
  41. arionxiv/server.py +1000 -0
  42. arionxiv/server_main.py +24 -0
  43. arionxiv/services/__init__.py +73 -0
  44. arionxiv/services/llm_client.py +30 -0
  45. arionxiv/services/llm_inference/__init__.py +58 -0
  46. arionxiv/services/llm_inference/groq_client.py +469 -0
  47. arionxiv/services/llm_inference/llm_utils.py +250 -0
  48. arionxiv/services/llm_inference/openrouter_client.py +564 -0
  49. arionxiv/services/unified_analysis_service.py +872 -0
  50. arionxiv/services/unified_auth_service.py +457 -0
  51. arionxiv/services/unified_config_service.py +456 -0
  52. arionxiv/services/unified_daily_dose_service.py +823 -0
  53. arionxiv/services/unified_database_service.py +1633 -0
  54. arionxiv/services/unified_llm_service.py +366 -0
  55. arionxiv/services/unified_paper_service.py +604 -0
  56. arionxiv/services/unified_pdf_service.py +522 -0
  57. arionxiv/services/unified_prompt_service.py +344 -0
  58. arionxiv/services/unified_scheduler_service.py +589 -0
  59. arionxiv/services/unified_user_service.py +954 -0
  60. arionxiv/utils/__init__.py +51 -0
  61. arionxiv/utils/api_helpers.py +200 -0
  62. arionxiv/utils/file_cleanup.py +150 -0
  63. arionxiv/utils/ip_helper.py +96 -0
  64. arionxiv-1.0.32.dist-info/METADATA +336 -0
  65. arionxiv-1.0.32.dist-info/RECORD +69 -0
  66. arionxiv-1.0.32.dist-info/WHEEL +5 -0
  67. arionxiv-1.0.32.dist-info/entry_points.txt +4 -0
  68. arionxiv-1.0.32.dist-info/licenses/LICENSE +21 -0
  69. arionxiv-1.0.32.dist-info/top_level.txt +1 -0
@@ -0,0 +1,522 @@
1
+ """
2
+ Animation utilities for ArionXiv CLI
3
+ Provides shake, reveal, slam, and other visual effects for terminal UI
4
+ """
5
+
6
+ import time
7
+ from io import StringIO
8
+ from rich.console import Console
9
+ from rich.panel import Panel
10
+ from rich.columns import Columns
11
+ from rich.text import Text
12
+ from rich.live import Live
13
+
14
+
15
+ def slam_content(console_instance: Console, content: str, style: str = "", duration: float = 0.6):
16
+ """
17
+ Slam content onto screen with dramatic zoom-in and impact effect
18
+ Content appears to fly in from distance and slam with a shake on impact
19
+
20
+ Args:
21
+ console_instance: Rich console instance
22
+ content: Content to slam (can be multi-line)
23
+ style: Rich style to apply
24
+ duration: Duration of effect in seconds
25
+ """
26
+ lines = content.split('\n')
27
+ num_lines = len(lines)
28
+
29
+ # Phase 1: Zoom in (content grows from nothing) - 60% of duration
30
+ # Phase 2: Impact shake - 40% of duration
31
+ zoom_duration = duration * 0.5
32
+ shake_duration = duration * 0.5
33
+
34
+ zoom_frames = int(zoom_duration * 40)
35
+ shake_frames = int(shake_duration * 30)
36
+
37
+ with Live(console=console_instance, refresh_per_second=40, transient=True) as live:
38
+ # Phase 1: Zoom in - show progressively more lines from center
39
+ for i in range(zoom_frames):
40
+ progress = (i + 1) / zoom_frames
41
+ # Ease-out curve for dramatic deceleration
42
+ eased = 1 - (1 - progress) ** 3
43
+
44
+ # Calculate how many lines to show (from center outward)
45
+ lines_to_show = max(1, int(num_lines * eased))
46
+
47
+ # Center the visible lines
48
+ start_idx = (num_lines - lines_to_show) // 2
49
+ end_idx = start_idx + lines_to_show
50
+
51
+ visible_lines = lines[start_idx:end_idx]
52
+
53
+ # Add padding from top to simulate coming from distance
54
+ top_padding = int((1 - eased) * 5)
55
+ padded_content = '\n' * top_padding + '\n'.join(visible_lines)
56
+
57
+ if style:
58
+ live.update(Text(padded_content, style=style))
59
+ else:
60
+ live.update(Text(padded_content))
61
+
62
+ time.sleep(zoom_duration / zoom_frames)
63
+
64
+ # Phase 2: Impact shake - violent shake that settles
65
+ for i in range(shake_frames):
66
+ progress = i / shake_frames
67
+ # Start intense, decay quickly
68
+ intensity = int(10 * (1 - progress) ** 2)
69
+
70
+ # Rapid oscillation
71
+ if i % 2 == 0:
72
+ offset = intensity
73
+ else:
74
+ offset = -intensity // 2
75
+
76
+ shifted_lines = []
77
+ for line in lines:
78
+ if offset > 0:
79
+ shifted_lines.append(" " * offset + line)
80
+ else:
81
+ shifted_lines.append(line)
82
+
83
+ shifted_content = '\n'.join(shifted_lines)
84
+
85
+ if style:
86
+ live.update(Text(shifted_content, style=style))
87
+ else:
88
+ live.update(Text(shifted_content))
89
+
90
+ time.sleep(shake_duration / shake_frames)
91
+
92
+ # Print final stable content
93
+ if style:
94
+ console_instance.print(Text(content, style=style))
95
+ else:
96
+ console_instance.print(content)
97
+
98
+ def slam_columns(console_instance: Console, columns: Columns, duration: float = 0.6):
99
+ """
100
+ Slam Rich Columns onto screen with dramatic zoom-in and impact effect
101
+
102
+ Args:
103
+ console_instance: Rich console instance
104
+ columns: Rich Columns object to slam
105
+ duration: Duration of effect in seconds
106
+ """
107
+ # Capture columns as string
108
+ temp_console = Console(file=StringIO(), force_terminal=True, width=console_instance.width)
109
+ temp_console.print(columns)
110
+ columns_str = temp_console.file.getvalue()
111
+
112
+ lines = columns_str.split('\n')
113
+ num_lines = len(lines)
114
+
115
+ zoom_duration = duration * 0.5
116
+ shake_duration = duration * 0.5
117
+
118
+ zoom_frames = int(zoom_duration * 40)
119
+ shake_frames = int(shake_duration * 30)
120
+
121
+ with Live(console=console_instance, refresh_per_second=40, transient=True) as live:
122
+ # Phase 1: Zoom in from center
123
+ for i in range(zoom_frames):
124
+ progress = (i + 1) / zoom_frames
125
+ eased = 1 - (1 - progress) ** 3
126
+
127
+ lines_to_show = max(1, int(num_lines * eased))
128
+ start_idx = (num_lines - lines_to_show) // 2
129
+ end_idx = start_idx + lines_to_show
130
+
131
+ visible_lines = lines[start_idx:end_idx]
132
+
133
+ # Horizontal squeeze effect - indent from sides
134
+ h_squeeze = int((1 - eased) * 15)
135
+ squeezed_lines = [" " * h_squeeze + line for line in visible_lines]
136
+
137
+ live.update(Text.from_ansi('\n'.join(squeezed_lines)))
138
+ time.sleep(zoom_duration / zoom_frames)
139
+
140
+ # Phase 2: Impact shake
141
+ for i in range(shake_frames):
142
+ progress = i / shake_frames
143
+ intensity = int(5 * (1 - progress) ** 2)
144
+
145
+ if i % 2 == 0:
146
+ offset = intensity
147
+ else:
148
+ offset = -intensity // 2
149
+
150
+ shifted_lines = []
151
+ for line in lines:
152
+ if offset > 0:
153
+ shifted_lines.append(" " * offset + line)
154
+ else:
155
+ shifted_lines.append(line)
156
+
157
+ live.update(Text.from_ansi('\n'.join(shifted_lines)))
158
+ time.sleep(shake_duration / shake_frames)
159
+
160
+ console_instance.print(columns)
161
+
162
+ def shake_content(console_instance: Console, content: str, style: str = "", duration: float = 1.0, intensity: int = 4):
163
+ """
164
+ Shake content with earthquake effect for specified duration
165
+
166
+ Args:
167
+ console_instance: Rich console instance
168
+ content: Content to shake (can be multi-line)
169
+ style: Rich style to apply
170
+ duration: Duration of shake in seconds
171
+ intensity: Max spaces to shift left/right
172
+ """
173
+ frames = int(duration * 30) # 30 fps
174
+
175
+ with Live(console=console_instance, refresh_per_second=30, transient=True) as live:
176
+ for i in range(frames):
177
+ progress = i / frames
178
+ current_intensity = int(intensity * (1 - progress * 0.5))
179
+ offset = int(current_intensity * (1 if (i % 4) < 2 else -1) * (1 if (i % 2) == 0 else 0.5))
180
+
181
+ lines = content.split('\n')
182
+ shifted_lines = []
183
+ for line in lines:
184
+ if offset > 0:
185
+ shifted_lines.append(" " * offset + line)
186
+ else:
187
+ shifted_lines.append(line)
188
+
189
+ shifted_content = '\n'.join(shifted_lines)
190
+
191
+ if style:
192
+ live.update(Text(shifted_content, style=style))
193
+ else:
194
+ live.update(Text(shifted_content))
195
+
196
+ time.sleep(duration / frames)
197
+
198
+ if style:
199
+ console_instance.print(Text(content, style=style))
200
+ else:
201
+ console_instance.print(content)
202
+
203
+ def shake_text(console_instance: Console, message: str, style: str = "", duration: float = 0.5, intensity: int = 3):
204
+ """
205
+ Shake a single line of text with earthquake effect
206
+
207
+ Args:
208
+ console_instance: Rich console instance
209
+ message: The message to display with shake effect
210
+ style: Rich style to apply
211
+ duration: Duration of shake in seconds
212
+ intensity: Max spaces to shift left/right
213
+ """
214
+ shake_content(console_instance, message, style=style, duration=duration, intensity=intensity)
215
+
216
+ def shake_columns(console_instance: Console, columns: Columns, duration: float = 1.0, intensity: int = 3):
217
+ """
218
+ Shake Rich Columns with earthquake effect
219
+
220
+ Args:
221
+ console_instance: Rich console instance
222
+ columns: Rich Columns object to shake
223
+ duration: Duration of shake in seconds
224
+ intensity: Max spaces to shift
225
+ """
226
+ frames = int(duration * 25)
227
+
228
+ temp_console = Console(file=StringIO(), force_terminal=True, width=console_instance.width)
229
+ temp_console.print(columns)
230
+ columns_str = temp_console.file.getvalue()
231
+
232
+ with Live(console=console_instance, refresh_per_second=25, transient=True) as live:
233
+ for i in range(frames):
234
+ progress = i / frames
235
+ current_intensity = int(intensity * (1 - progress * 0.3))
236
+ offset = int(current_intensity * (1 if (i % 4) < 2 else -1) * (1 if (i % 2) == 0 else 0.6))
237
+
238
+ lines = columns_str.split('\n')
239
+ shifted_lines = []
240
+ for line in lines:
241
+ if offset > 0:
242
+ shifted_lines.append(" " * offset + line)
243
+ else:
244
+ shifted_lines.append(line)
245
+
246
+ live.update(Text.from_ansi('\n'.join(shifted_lines)))
247
+ time.sleep(duration / frames)
248
+
249
+ console_instance.print(columns)
250
+
251
+ def shake_panel(console_instance: Console, panel: Panel, duration: float = 1.0, intensity: int = 3):
252
+ """
253
+ Shake a Rich Panel with earthquake effect
254
+
255
+ Args:
256
+ console_instance: Rich console instance
257
+ panel: Rich Panel to shake
258
+ duration: Duration of shake in seconds
259
+ intensity: Max spaces to shift
260
+ """
261
+ frames = int(duration * 25)
262
+
263
+ temp_console = Console(file=StringIO(), force_terminal=True, width=console_instance.width)
264
+ temp_console.print(panel)
265
+ panel_str = temp_console.file.getvalue()
266
+
267
+ with Live(console=console_instance, refresh_per_second=25, transient=True) as live:
268
+ for i in range(frames):
269
+ progress = i / frames
270
+ current_intensity = int(intensity * (1 - progress * 0.3))
271
+ offset = int(current_intensity * (1 if (i % 4) < 2 else -1) * (1 if (i % 2) == 0 else 0.6))
272
+
273
+ lines = panel_str.split('\n')
274
+ shifted_lines = []
275
+ for line in lines:
276
+ if offset > 0:
277
+ shifted_lines.append(" " * offset + line)
278
+ else:
279
+ shifted_lines.append(line)
280
+
281
+ live.update(Text.from_ansi('\n'.join(shifted_lines)))
282
+ time.sleep(duration / frames)
283
+
284
+ console_instance.print(panel)
285
+
286
+ def left_to_right_reveal(console_instance: Console, text: str, style: str = "", duration: float = 0.8):
287
+ """
288
+ Reveal text from left to right character by character
289
+
290
+ Args:
291
+ console_instance: Rich console instance
292
+ text: Text to reveal
293
+ style: Rich style to apply
294
+ duration: Total duration of reveal
295
+ """
296
+ delay = duration / len(text) if text else 0.01
297
+ revealed = ""
298
+
299
+ with Live(console=console_instance, refresh_per_second=60, transient=True) as live:
300
+ for char in text:
301
+ revealed += char
302
+ if style:
303
+ live.update(Text(revealed, style=style))
304
+ else:
305
+ live.update(Text(revealed))
306
+ time.sleep(delay)
307
+
308
+ if style:
309
+ console_instance.print(Text(text, style=style))
310
+ else:
311
+ console_instance.print(text)
312
+
313
+ def top_to_bottom_reveal(console_instance: Console, content: str, style: str = "", duration: float = 0.5):
314
+ """
315
+ Reveal content line by line from top to bottom
316
+
317
+ Args:
318
+ console_instance: Rich console instance
319
+ content: Multi-line content to reveal
320
+ style: Rich style string
321
+ duration: Total duration of reveal
322
+ """
323
+ lines = content.split('\n')
324
+ line_delay = duration / len(lines) if lines else 0.03
325
+ revealed = []
326
+
327
+ with Live(console=console_instance, refresh_per_second=60, transient=True) as live:
328
+ for line in lines:
329
+ revealed.append(line)
330
+ current_display = '\n'.join(revealed)
331
+ if style:
332
+ live.update(Text(current_display, style=style))
333
+ else:
334
+ live.update(Text.from_markup(current_display))
335
+ time.sleep(line_delay)
336
+
337
+ if style:
338
+ console_instance.print(Text(content, style=style))
339
+ else:
340
+ console_instance.print(content)
341
+
342
+ def typewriter_reveal(console_instance: Console, text: str, style: str = "", delay: float = 0.02):
343
+ """
344
+ Reveal text with typewriter effect (character by character)
345
+
346
+ Args:
347
+ console_instance: Rich console instance
348
+ text: Text to reveal
349
+ style: Rich style to apply
350
+ delay: Delay between each character
351
+ """
352
+ revealed = ""
353
+
354
+ with Live(console=console_instance, refresh_per_second=60, transient=True) as live:
355
+ for char in text:
356
+ revealed += char
357
+ if style:
358
+ live.update(Text(revealed, style=style))
359
+ else:
360
+ live.update(Text(revealed))
361
+ time.sleep(delay)
362
+
363
+ if style:
364
+ console_instance.print(Text(text, style=style))
365
+ else:
366
+ console_instance.print(text)
367
+
368
+ async def row_by_row_table_reveal(console_instance: Console, table_creator, num_rows: int, duration: float = 1.0):
369
+ """
370
+ Reveal a table row by row with animation
371
+
372
+ Args:
373
+ console_instance: Rich console instance
374
+ table_creator: Callable that takes num_rows and returns a Table
375
+ num_rows: Total number of rows to animate
376
+ duration: Total duration for all rows to appear (default 1 second)
377
+ """
378
+ import asyncio
379
+
380
+ if num_rows == 0:
381
+ return
382
+
383
+ row_delay = duration / num_rows
384
+
385
+ with Live(console=console_instance, refresh_per_second=30, transient=True) as live:
386
+ for i in range(1, num_rows + 1):
387
+ live.update(table_creator(i))
388
+ await asyncio.sleep(row_delay)
389
+
390
+ # Print final table
391
+ console_instance.print(table_creator(num_rows))
392
+
393
+ def stream_text_response(console_instance: Console, text: str, style: str = "", duration: float = 3.0):
394
+ """
395
+ Stream a long text response with smooth left-to-right flow
396
+ Ideal for AI responses - streams word by word for natural reading
397
+
398
+ Args:
399
+ console_instance: Rich console instance
400
+ text: Text to stream
401
+ style: Rich style to apply
402
+ duration: Total duration of streaming (default 3 seconds)
403
+ """
404
+ words = text.split()
405
+ if not words:
406
+ return
407
+
408
+ delay = duration / len(words)
409
+ revealed = ""
410
+
411
+ with Live(console=console_instance, refresh_per_second=60, transient=True) as live:
412
+ for word in words:
413
+ revealed += word + " "
414
+ if style:
415
+ live.update(Text(revealed.strip(), style=style))
416
+ else:
417
+ live.update(Text(revealed.strip()))
418
+ time.sleep(delay)
419
+
420
+ if style:
421
+ console_instance.print(Text(text, style=style))
422
+ else:
423
+ console_instance.print(text)
424
+
425
+ def stream_markdown_response(console_instance: Console, text: str, panel_title: str = "", border_style: str = None, duration: float = 3.0):
426
+ """
427
+ Stream a markdown response inside a panel with smooth word-by-word flow
428
+ Perfect for AI assistant responses
429
+
430
+ Args:
431
+ console_instance: Rich console instance
432
+ text: Markdown text to stream
433
+ panel_title: Title for the panel
434
+ border_style: Border style for the panel (defaults to theme primary color)
435
+ duration: Total duration of streaming (default 3 seconds)
436
+ """
437
+ from rich.markdown import Markdown
438
+ from ..ui.theme import get_theme_colors
439
+
440
+ if border_style is None:
441
+ colors = get_theme_colors()
442
+ border_style = colors['primary']
443
+
444
+ # For complex markdown (tables, code blocks, long responses), skip streaming animation
445
+ # to avoid visual glitches with partial rendering
446
+ has_complex_markdown = (
447
+ '|' in text and '-' in text and # Tables
448
+ len([line for line in text.split('\n') if '|' in line]) > 2
449
+ ) or len(text) > 2000 # Long responses
450
+
451
+ if has_complex_markdown:
452
+ # Just show a brief thinking indicator then display the final result
453
+ with Live(console=console_instance, refresh_per_second=10, transient=True) as live:
454
+ live.update(Panel("Formatting response...", title=panel_title, border_style=border_style))
455
+ time.sleep(0.3)
456
+
457
+ # Print final panel directly
458
+ final_panel = Panel(
459
+ Markdown(text),
460
+ title=panel_title,
461
+ border_style=border_style
462
+ )
463
+ console_instance.print(final_panel)
464
+ return
465
+
466
+ words = text.split()
467
+ if not words:
468
+ return
469
+
470
+ # Cap the streaming duration for very long responses
471
+ delay = min(duration / len(words), 0.05) # Max 50ms per word
472
+ revealed = ""
473
+
474
+ with Live(console=console_instance, refresh_per_second=30, transient=True) as live:
475
+ for word in words:
476
+ revealed += word + " "
477
+ panel = Panel(
478
+ Markdown(revealed.strip()),
479
+ title=panel_title,
480
+ border_style=border_style
481
+ )
482
+ live.update(panel)
483
+ time.sleep(delay)
484
+
485
+ # Print final panel
486
+ final_panel = Panel(
487
+ Markdown(text),
488
+ title=panel_title,
489
+ border_style=border_style
490
+ )
491
+ console_instance.print(final_panel)
492
+
493
+
494
+ def animated_help_line(console_instance: Console, cmd_text: str, desc_text: str, primary_color: str, padding: str, duration: float = 0.5):
495
+ """
496
+ Animate a command/option line for help pages: reveal command name character by character,
497
+ then show the full line with description.
498
+
499
+ Args:
500
+ console_instance: Rich console instance
501
+ cmd_text: Command or option name to animate
502
+ desc_text: Description text to show after command
503
+ primary_color: Primary theme color for styling
504
+ padding: Padding string between command and description
505
+ duration: Duration of the animation in seconds
506
+ """
507
+ if not cmd_text:
508
+ return
509
+
510
+ delay = duration / len(cmd_text) if cmd_text else 0.01
511
+ revealed = ""
512
+
513
+ # Animate the command name character by character
514
+ with Live(console=console_instance, refresh_per_second=60, transient=True) as live:
515
+ for char in cmd_text:
516
+ revealed += char
517
+ live.update(Text(f" {revealed}", style=f"bold {primary_color}"))
518
+ time.sleep(delay)
519
+
520
+ # Print the final line with both command and description
521
+ console_instance.print(f" [{primary_color} bold]{cmd_text}[/{primary_color} bold]{padding}{desc_text}")
522
+