appkit-assistant 0.17.3__py3-none-any.whl → 1.0.1__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 (57) hide show
  1. appkit_assistant/backend/{models.py → database/models.py} +32 -132
  2. appkit_assistant/backend/{repositories.py → database/repositories.py} +93 -1
  3. appkit_assistant/backend/model_manager.py +5 -5
  4. appkit_assistant/backend/models/__init__.py +28 -0
  5. appkit_assistant/backend/models/anthropic.py +31 -0
  6. appkit_assistant/backend/models/google.py +27 -0
  7. appkit_assistant/backend/models/openai.py +50 -0
  8. appkit_assistant/backend/models/perplexity.py +56 -0
  9. appkit_assistant/backend/processors/__init__.py +29 -0
  10. appkit_assistant/backend/processors/claude_responses_processor.py +205 -387
  11. appkit_assistant/backend/processors/gemini_responses_processor.py +290 -352
  12. appkit_assistant/backend/processors/lorem_ipsum_processor.py +6 -4
  13. appkit_assistant/backend/processors/mcp_mixin.py +297 -0
  14. appkit_assistant/backend/processors/openai_base.py +11 -125
  15. appkit_assistant/backend/processors/openai_chat_completion_processor.py +5 -3
  16. appkit_assistant/backend/processors/openai_responses_processor.py +480 -402
  17. appkit_assistant/backend/processors/perplexity_processor.py +156 -79
  18. appkit_assistant/backend/{processor.py → processors/processor_base.py} +7 -2
  19. appkit_assistant/backend/processors/streaming_base.py +188 -0
  20. appkit_assistant/backend/schemas.py +138 -0
  21. appkit_assistant/backend/services/auth_error_detector.py +99 -0
  22. appkit_assistant/backend/services/chunk_factory.py +273 -0
  23. appkit_assistant/backend/services/citation_handler.py +292 -0
  24. appkit_assistant/backend/services/file_cleanup_service.py +316 -0
  25. appkit_assistant/backend/services/file_upload_service.py +903 -0
  26. appkit_assistant/backend/services/file_validation.py +138 -0
  27. appkit_assistant/backend/{mcp_auth_service.py → services/mcp_auth_service.py} +4 -2
  28. appkit_assistant/backend/services/mcp_token_service.py +61 -0
  29. appkit_assistant/backend/services/message_converter.py +289 -0
  30. appkit_assistant/backend/services/openai_client_service.py +120 -0
  31. appkit_assistant/backend/{response_accumulator.py → services/response_accumulator.py} +163 -1
  32. appkit_assistant/backend/services/system_prompt_builder.py +89 -0
  33. appkit_assistant/backend/services/thread_service.py +5 -3
  34. appkit_assistant/backend/system_prompt_cache.py +3 -3
  35. appkit_assistant/components/__init__.py +8 -4
  36. appkit_assistant/components/composer.py +59 -24
  37. appkit_assistant/components/file_manager.py +623 -0
  38. appkit_assistant/components/mcp_server_dialogs.py +12 -20
  39. appkit_assistant/components/mcp_server_table.py +12 -2
  40. appkit_assistant/components/message.py +119 -2
  41. appkit_assistant/components/thread.py +1 -1
  42. appkit_assistant/components/threadlist.py +4 -2
  43. appkit_assistant/components/tools_modal.py +37 -20
  44. appkit_assistant/configuration.py +12 -0
  45. appkit_assistant/state/file_manager_state.py +697 -0
  46. appkit_assistant/state/mcp_oauth_state.py +3 -3
  47. appkit_assistant/state/mcp_server_state.py +47 -2
  48. appkit_assistant/state/system_prompt_state.py +1 -1
  49. appkit_assistant/state/thread_list_state.py +99 -5
  50. appkit_assistant/state/thread_state.py +88 -9
  51. {appkit_assistant-0.17.3.dist-info → appkit_assistant-1.0.1.dist-info}/METADATA +8 -6
  52. appkit_assistant-1.0.1.dist-info/RECORD +58 -0
  53. appkit_assistant/backend/processors/claude_base.py +0 -178
  54. appkit_assistant/backend/processors/gemini_base.py +0 -84
  55. appkit_assistant-0.17.3.dist-info/RECORD +0 -39
  56. /appkit_assistant/backend/{file_manager.py → services/file_manager.py} +0 -0
  57. {appkit_assistant-0.17.3.dist-info → appkit_assistant-1.0.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,623 @@
1
+ """File manager component for assistant administration."""
2
+
3
+ import reflex as rx
4
+ from reflex.components.radix.themes.components.table import TableRow
5
+
6
+ import appkit_mantine as mn
7
+ from appkit_assistant.state.file_manager_state import (
8
+ CleanupStats,
9
+ FileInfo,
10
+ FileManagerState,
11
+ OpenAIFileInfo,
12
+ VectorStoreInfo,
13
+ )
14
+ from appkit_ui.components.dialogs import delete_dialog
15
+
16
+
17
+ def vector_store_item(store_info: VectorStoreInfo) -> rx.Component:
18
+ """Render a single vector store item in the list."""
19
+ is_selected = FileManagerState.selected_vector_store_id == store_info.store_id
20
+ is_deleting = FileManagerState.deleting_vector_store_id == store_info.store_id
21
+
22
+ return rx.box(
23
+ rx.hstack(
24
+ rx.icon("database", size=16, color=rx.color("gray", 11)),
25
+ rx.vstack(
26
+ rx.text(
27
+ store_info.name,
28
+ size="2",
29
+ weight=rx.cond(is_selected, "bold", "regular"),
30
+ title=store_info.name,
31
+ style={
32
+ "overflow": "hidden",
33
+ "text_overflow": "ellipsis",
34
+ "white_space": "nowrap",
35
+ "max_width": "100%",
36
+ },
37
+ ),
38
+ rx.text(
39
+ store_info.store_id,
40
+ title=store_info.store_id,
41
+ size="1",
42
+ color="gray",
43
+ style={
44
+ "overflow": "hidden",
45
+ "text_overflow": "ellipsis",
46
+ "white_space": "nowrap",
47
+ "max_width": "100%",
48
+ },
49
+ ),
50
+ spacing="0",
51
+ align="start",
52
+ width="100%",
53
+ min_width="0",
54
+ flex="1",
55
+ ),
56
+ rx.tooltip(
57
+ rx.button(
58
+ rx.icon("trash", size=13, stroke_width=1.5),
59
+ variant="ghost",
60
+ size="1",
61
+ color_scheme="gray",
62
+ loading=is_deleting,
63
+ on_click=FileManagerState.delete_vector_store(
64
+ store_info.store_id
65
+ ).stop_propagation,
66
+ ),
67
+ content="Vector Store löschen",
68
+ ),
69
+ spacing="2",
70
+ align="center",
71
+ width="100%",
72
+ ),
73
+ padding="10px 12px",
74
+ cursor="pointer",
75
+ background=rx.cond(
76
+ is_selected,
77
+ rx.color("blue", 3),
78
+ "transparent",
79
+ ),
80
+ border_radius="6px",
81
+ _hover={
82
+ "background": rx.cond(
83
+ is_selected,
84
+ rx.color("blue", 4),
85
+ rx.color("gray", 3),
86
+ ),
87
+ },
88
+ on_click=lambda: FileManagerState.select_vector_store(
89
+ store_info.store_id, store_info.name
90
+ ),
91
+ width="100%",
92
+ )
93
+
94
+
95
+ def file_table_row(file_info: FileInfo) -> TableRow:
96
+ """Render a single file row in the table."""
97
+ return rx.table.row(
98
+ rx.table.cell(
99
+ rx.hstack(
100
+ rx.icon("file-text", size=16, color=rx.color("gray", 11)),
101
+ rx.text(
102
+ file_info.filename,
103
+ title=file_info.filename,
104
+ style={
105
+ "overflow": "hidden",
106
+ "text_overflow": "ellipsis",
107
+ "white_space": "nowrap",
108
+ },
109
+ ),
110
+ spacing="2",
111
+ align="center",
112
+ ),
113
+ style={
114
+ "max_width": "0",
115
+ "width": "100%",
116
+ },
117
+ ),
118
+ rx.table.cell(
119
+ rx.text(file_info.created_at, size="2"),
120
+ white_space="nowrap",
121
+ ),
122
+ rx.table.cell(
123
+ rx.text(file_info.user_name, size="2"),
124
+ white_space="nowrap",
125
+ ),
126
+ rx.table.cell(
127
+ rx.hstack(
128
+ mn.number_formatter(
129
+ value=file_info.formatted_size,
130
+ decimal_scale=1,
131
+ suffix=file_info.size_suffix,
132
+ ),
133
+ spacing="1",
134
+ ),
135
+ white_space="nowrap",
136
+ ),
137
+ rx.table.cell(
138
+ rx.cond(
139
+ FileManagerState.deleting_file_id == file_info.id,
140
+ rx.spinner(size="1"),
141
+ delete_dialog(
142
+ title="Datei löschen",
143
+ content=file_info.filename,
144
+ on_click=lambda: FileManagerState.delete_file(file_info.id),
145
+ icon_button=True,
146
+ size="1",
147
+ variant="ghost",
148
+ color_scheme="red",
149
+ ),
150
+ ),
151
+ white_space="nowrap",
152
+ ),
153
+ vertical_align="middle",
154
+ style={"_hover": {"bg": rx.color("gray", 2)}},
155
+ )
156
+
157
+
158
+ def empty_state(message: str) -> rx.Component:
159
+ """Render an empty state message."""
160
+ return rx.center(
161
+ rx.vstack(
162
+ rx.icon("inbox", size=48, color=rx.color("gray", 8)),
163
+ rx.text(message, size="3", color="gray"),
164
+ spacing="3",
165
+ align="center",
166
+ ),
167
+ height="200px",
168
+ width="100%",
169
+ )
170
+
171
+
172
+ def cleanup_stat_row(label: str, value: rx.Var[int]) -> rx.Component:
173
+ """Render a single cleanup statistic row."""
174
+ return rx.hstack(
175
+ rx.text(label, size="2", color="gray"),
176
+ rx.text(value.to_string(), size="2", weight="bold"),
177
+ justify="between",
178
+ width="100%",
179
+ )
180
+
181
+
182
+ def cleanup_progress_modal() -> rx.Component:
183
+ """Render the cleanup progress modal with live statistics."""
184
+ stats: CleanupStats = FileManagerState.cleanup_stats
185
+ is_running = FileManagerState.cleanup_running
186
+ is_completed = stats.status == "completed"
187
+ is_error = stats.status == "error"
188
+
189
+ # Status message based on current state
190
+ status_message = rx.match(
191
+ stats.status,
192
+ ("idle", "Bereit zur Bereinigung"),
193
+ ("starting", "Starte Bereinigung..."),
194
+ ("checking", "Prüfe Vector Stores..."),
195
+ ("deleting", "Lösche abgelaufene Stores..."),
196
+ ("completed", "Bereinigung abgeschlossen"),
197
+ ("error", "Fehler bei der Bereinigung"),
198
+ "Unbekannter Status",
199
+ )
200
+
201
+ return rx.dialog.root(
202
+ rx.dialog.content(
203
+ rx.dialog.title(
204
+ rx.hstack(
205
+ rx.icon(
206
+ rx.cond(is_error, "alert-circle", "trash-2"),
207
+ size=20,
208
+ color=rx.cond(
209
+ is_error,
210
+ rx.color("red", 11),
211
+ rx.cond(
212
+ is_completed,
213
+ rx.color("green", 11),
214
+ rx.color("blue", 11),
215
+ ),
216
+ ),
217
+ ),
218
+ rx.text("Bereinigung"),
219
+ spacing="2",
220
+ align="center",
221
+ ),
222
+ ),
223
+ rx.dialog.description(
224
+ rx.vstack(
225
+ # Status message
226
+ rx.hstack(
227
+ rx.cond(
228
+ is_running,
229
+ rx.spinner(size="1"),
230
+ rx.fragment(),
231
+ ),
232
+ rx.text(status_message, size="2"),
233
+ spacing="2",
234
+ align="center",
235
+ ),
236
+ # Error message
237
+ rx.cond(
238
+ is_error,
239
+ rx.callout(
240
+ stats.error,
241
+ icon="triangle-alert",
242
+ color="red",
243
+ size="1",
244
+ ),
245
+ rx.fragment(),
246
+ ),
247
+ # Progress indicator
248
+ rx.cond(
249
+ stats.total_vector_stores > 0,
250
+ rx.vstack(
251
+ rx.progress(
252
+ value=rx.cond(
253
+ stats.total_vector_stores > 0,
254
+ (stats.vector_stores_checked * 100)
255
+ / stats.total_vector_stores,
256
+ 0,
257
+ ),
258
+ width="100%",
259
+ ),
260
+ rx.text(
261
+ f"Geprüft: {stats.vector_stores_checked} / "
262
+ f"{stats.total_vector_stores}",
263
+ size="1",
264
+ color="gray",
265
+ ),
266
+ spacing="1",
267
+ width="100%",
268
+ ),
269
+ rx.fragment(),
270
+ ),
271
+ # Current processing
272
+ rx.cond(
273
+ stats.current_vector_store.is_not_none(),
274
+ rx.text(
275
+ f"Aktuell: {stats.current_vector_store}",
276
+ size="1",
277
+ color="gray",
278
+ style={
279
+ "overflow": "hidden",
280
+ "text_overflow": "ellipsis",
281
+ "white_space": "nowrap",
282
+ "max_width": "100%",
283
+ },
284
+ ),
285
+ rx.fragment(),
286
+ ),
287
+ # Statistics
288
+ rx.divider(),
289
+ rx.vstack(
290
+ cleanup_stat_row(
291
+ "Abgelaufene Stores:", stats.vector_stores_expired
292
+ ),
293
+ cleanup_stat_row(
294
+ "Gelöschte Stores:", stats.vector_stores_deleted
295
+ ),
296
+ cleanup_stat_row(
297
+ "Aktualisierte Threads:", stats.threads_updated
298
+ ),
299
+ spacing="1",
300
+ width="100%",
301
+ ),
302
+ spacing="3",
303
+ width="100%",
304
+ padding_y="2",
305
+ ),
306
+ ),
307
+ rx.flex(
308
+ rx.button(
309
+ "Schließen",
310
+ variant="soft",
311
+ disabled=is_running,
312
+ on_click=FileManagerState.close_cleanup_modal,
313
+ ),
314
+ justify="end",
315
+ spacing="2",
316
+ margin_top="16px",
317
+ ),
318
+ max_width="400px",
319
+ ),
320
+ open=FileManagerState.cleanup_modal_open,
321
+ on_open_change=FileManagerState.set_cleanup_modal_open,
322
+ )
323
+
324
+
325
+ def cleanup_button() -> rx.Component:
326
+ """Render the cleanup button."""
327
+ return rx.button(
328
+ rx.icon("trash-2", size=14),
329
+ rx.text("Vector Stores aufräumen"),
330
+ variant="soft",
331
+ color_scheme="red",
332
+ size="2",
333
+ disabled=FileManagerState.cleanup_running,
334
+ loading=FileManagerState.cleanup_running,
335
+ on_click=[
336
+ FileManagerState.open_cleanup_modal,
337
+ FileManagerState.start_cleanup,
338
+ ],
339
+ )
340
+
341
+
342
+ def openai_file_table_row(file_info: OpenAIFileInfo) -> TableRow:
343
+ """Render a single OpenAI file row in the table."""
344
+ return rx.table.row(
345
+ rx.table.cell(
346
+ rx.hstack(
347
+ rx.icon("file-text", size=16, color=rx.color("gray", 11)),
348
+ rx.text(
349
+ file_info.filename,
350
+ title=file_info.filename,
351
+ style={
352
+ "overflow": "hidden",
353
+ "text_overflow": "ellipsis",
354
+ "white_space": "nowrap",
355
+ },
356
+ ),
357
+ spacing="2",
358
+ align="center",
359
+ ),
360
+ style={
361
+ "max_width": "0",
362
+ "width": "100%",
363
+ },
364
+ ),
365
+ rx.table.cell(
366
+ rx.text(file_info.purpose, size="2"),
367
+ white_space="nowrap",
368
+ ),
369
+ rx.table.cell(
370
+ rx.text(file_info.created_at, size="2"),
371
+ white_space="nowrap",
372
+ ),
373
+ rx.table.cell(
374
+ rx.text(file_info.expires_at, size="2"),
375
+ white_space="nowrap",
376
+ ),
377
+ rx.table.cell(
378
+ rx.hstack(
379
+ mn.number_formatter(
380
+ value=file_info.formatted_size,
381
+ decimal_scale=1,
382
+ suffix=file_info.size_suffix,
383
+ ),
384
+ spacing="1",
385
+ ),
386
+ white_space="nowrap",
387
+ ),
388
+ rx.table.cell(
389
+ rx.cond(
390
+ FileManagerState.deleting_openai_file_id == file_info.openai_id,
391
+ rx.spinner(size="1"),
392
+ delete_dialog(
393
+ title="Datei löschen",
394
+ content=file_info.filename,
395
+ on_click=lambda: FileManagerState.delete_openai_file(
396
+ file_info.openai_id
397
+ ),
398
+ icon_button=True,
399
+ size="1",
400
+ variant="ghost",
401
+ color_scheme="red",
402
+ ),
403
+ ),
404
+ white_space="nowrap",
405
+ ),
406
+ vertical_align="middle",
407
+ style={"_hover": {"bg": rx.color("gray", 2)}},
408
+ )
409
+
410
+
411
+ def file_manager() -> rx.Component:
412
+ """File manager component with tabs for vector stores and OpenAI files."""
413
+ return rx.fragment(
414
+ cleanup_progress_modal(),
415
+ rx.tabs.root(
416
+ rx.hstack(
417
+ rx.tabs.list(
418
+ rx.tabs.trigger("Vector Store Dateien", value="vector_stores"),
419
+ rx.tabs.trigger("OpenAI Dateien", value="openai_files"),
420
+ width="100%",
421
+ ),
422
+ rx.spacer(),
423
+ cleanup_button(),
424
+ width="100%",
425
+ align="center",
426
+ padding_right="16px",
427
+ ),
428
+ rx.tabs.content(
429
+ rx.hstack(
430
+ # Left column: Vector stores list
431
+ rx.box(
432
+ rx.vstack(
433
+ rx.cond(
434
+ FileManagerState.vector_stores.length() > 0,
435
+ mn.scroll_area(
436
+ rx.vstack(
437
+ rx.foreach(
438
+ FileManagerState.vector_stores,
439
+ vector_store_item,
440
+ ),
441
+ spacing="1",
442
+ width="100%",
443
+ ),
444
+ height="calc(100vh - 350px)",
445
+ width="100%",
446
+ scrollbars="y",
447
+ type="auto",
448
+ ),
449
+ empty_state("Keine Vector Stores vorhanden."),
450
+ ),
451
+ spacing="2",
452
+ width="100%",
453
+ align="start",
454
+ ),
455
+ width="280px",
456
+ min_width="280px",
457
+ padding="16px",
458
+ border_right=f"1px solid {rx.color('gray', 5)}",
459
+ height="calc(100vh - 280px)",
460
+ ),
461
+ # Right column: Files table
462
+ rx.box(
463
+ rx.vstack(
464
+ rx.cond(
465
+ FileManagerState.selected_vector_store_id == "",
466
+ empty_state("Wähle einen Vector Store aus."),
467
+ rx.cond(
468
+ FileManagerState.loading,
469
+ rx.center(
470
+ rx.vstack(
471
+ rx.spinner(size="3"),
472
+ rx.text(
473
+ "Dateien werden geladen...",
474
+ size="2",
475
+ color="gray",
476
+ ),
477
+ spacing="3",
478
+ align="center",
479
+ ),
480
+ height="200px",
481
+ width="100%",
482
+ ),
483
+ rx.cond(
484
+ FileManagerState.files.length() > 0,
485
+ mn.scroll_area(
486
+ rx.table.root(
487
+ rx.table.header(
488
+ rx.table.row(
489
+ rx.table.column_header_cell(
490
+ "Dateiname", width="auto"
491
+ ),
492
+ rx.table.column_header_cell(
493
+ "Erstellt am", width="140px"
494
+ ),
495
+ rx.table.column_header_cell(
496
+ "Benutzer", width="150px"
497
+ ),
498
+ rx.table.column_header_cell(
499
+ "Größe", width="100px"
500
+ ),
501
+ rx.table.column_header_cell(
502
+ "", width="50px"
503
+ ),
504
+ ),
505
+ ),
506
+ rx.table.body(
507
+ rx.foreach(
508
+ FileManagerState.files,
509
+ file_table_row,
510
+ )
511
+ ),
512
+ size="2",
513
+ width="100%",
514
+ table_layout="fixed",
515
+ ),
516
+ height="calc(100vh - 350px)",
517
+ width="100%",
518
+ scrollbars="y",
519
+ type="auto",
520
+ ),
521
+ empty_state("Keine Dateien vorhanden."),
522
+ ),
523
+ ),
524
+ ),
525
+ spacing="2",
526
+ width="100%",
527
+ align="start",
528
+ ),
529
+ flex="1",
530
+ padding="16px",
531
+ height="calc(100vh - 280px)",
532
+ ),
533
+ spacing="0",
534
+ width="100%",
535
+ align="start",
536
+ ),
537
+ value="vector_stores",
538
+ ),
539
+ rx.tabs.content(
540
+ rx.box(
541
+ rx.vstack(
542
+ rx.cond(
543
+ FileManagerState.loading,
544
+ rx.center(
545
+ rx.vstack(
546
+ rx.spinner(size="3"),
547
+ rx.text(
548
+ "Dateien werden geladen...",
549
+ size="2",
550
+ color="gray",
551
+ ),
552
+ spacing="3",
553
+ align="center",
554
+ ),
555
+ height="200px",
556
+ width="100%",
557
+ ),
558
+ rx.cond(
559
+ FileManagerState.openai_files.length() > 0,
560
+ mn.scroll_area(
561
+ rx.table.root(
562
+ rx.table.header(
563
+ rx.table.row(
564
+ rx.table.column_header_cell(
565
+ "Dateiname", width="auto"
566
+ ),
567
+ rx.table.column_header_cell(
568
+ "Zweck",
569
+ width="120px",
570
+ white_space="nowrap",
571
+ ),
572
+ rx.table.column_header_cell(
573
+ "Erstellt am",
574
+ width="140px",
575
+ white_space="nowrap",
576
+ ),
577
+ rx.table.column_header_cell(
578
+ "Läuft ab",
579
+ width="140px",
580
+ white_space="nowrap",
581
+ ),
582
+ rx.table.column_header_cell(
583
+ "Größe", width="100px"
584
+ ),
585
+ rx.table.column_header_cell(
586
+ "", width="50px"
587
+ ),
588
+ ),
589
+ ),
590
+ rx.table.body(
591
+ rx.foreach(
592
+ FileManagerState.openai_files,
593
+ openai_file_table_row,
594
+ )
595
+ ),
596
+ size="2",
597
+ width="100%",
598
+ table_layout="fixed",
599
+ ),
600
+ height="calc(100vh - 350px)",
601
+ width="100%",
602
+ scrollbars="y",
603
+ type="auto",
604
+ ),
605
+ empty_state("Keine OpenAI-Dateien vorhanden."),
606
+ ),
607
+ ),
608
+ spacing="2",
609
+ width="100%",
610
+ align="start",
611
+ ),
612
+ flex="1",
613
+ padding="16px",
614
+ height="calc(100vh - 280px)",
615
+ ),
616
+ value="openai_files",
617
+ ),
618
+ default_value="vector_stores",
619
+ width="100%",
620
+ on_change=FileManagerState.on_tab_change,
621
+ on_mount=FileManagerState.load_vector_stores,
622
+ ),
623
+ )