appkit-assistant 0.16.2__py3-none-any.whl → 0.17.0__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.
- appkit_assistant/backend/file_manager.py +117 -0
- appkit_assistant/backend/models.py +9 -0
- appkit_assistant/backend/processors/claude_base.py +178 -0
- appkit_assistant/backend/processors/claude_responses_processor.py +923 -0
- appkit_assistant/backend/processors/gemini_base.py +84 -0
- appkit_assistant/backend/processors/gemini_responses_processor.py +723 -0
- appkit_assistant/backend/processors/lorem_ipsum_processor.py +2 -0
- appkit_assistant/backend/processors/openai_base.py +10 -10
- appkit_assistant/backend/processors/openai_responses_processor.py +22 -15
- appkit_assistant/{logic → backend}/response_accumulator.py +50 -11
- appkit_assistant/components/__init__.py +2 -0
- appkit_assistant/components/composer.py +99 -12
- appkit_assistant/components/message.py +50 -17
- appkit_assistant/components/thread.py +2 -1
- appkit_assistant/configuration.py +2 -0
- appkit_assistant/state/thread_state.py +103 -5
- {appkit_assistant-0.16.2.dist-info → appkit_assistant-0.17.0.dist-info}/METADATA +4 -1
- {appkit_assistant-0.16.2.dist-info → appkit_assistant-0.17.0.dist-info}/RECORD +19 -14
- {appkit_assistant-0.16.2.dist-info → appkit_assistant-0.17.0.dist-info}/WHEEL +0 -0
|
@@ -18,6 +18,7 @@ from typing import Any
|
|
|
18
18
|
|
|
19
19
|
import reflex as rx
|
|
20
20
|
|
|
21
|
+
from appkit_assistant.backend import file_manager
|
|
21
22
|
from appkit_assistant.backend.model_manager import ModelManager
|
|
22
23
|
from appkit_assistant.backend.models import (
|
|
23
24
|
AIModel,
|
|
@@ -31,10 +32,11 @@ from appkit_assistant.backend.models import (
|
|
|
31
32
|
ThinkingType,
|
|
32
33
|
ThreadModel,
|
|
33
34
|
ThreadStatus,
|
|
35
|
+
UploadedFile,
|
|
34
36
|
)
|
|
35
37
|
from appkit_assistant.backend.repositories import mcp_server_repo
|
|
38
|
+
from appkit_assistant.backend.response_accumulator import ResponseAccumulator
|
|
36
39
|
from appkit_assistant.backend.services.thread_service import ThreadService
|
|
37
|
-
from appkit_assistant.logic.response_accumulator import ResponseAccumulator
|
|
38
40
|
from appkit_assistant.state.thread_list_state import ThreadListState
|
|
39
41
|
from appkit_commons.database.session import get_asyncdb_session
|
|
40
42
|
from appkit_user.authentication.states import UserSession
|
|
@@ -69,6 +71,9 @@ class ThreadState(rx.State):
|
|
|
69
71
|
thinking_expanded: bool = False
|
|
70
72
|
current_activity: str = ""
|
|
71
73
|
|
|
74
|
+
# File upload state
|
|
75
|
+
uploaded_files: list[UploadedFile] = []
|
|
76
|
+
|
|
72
77
|
# Internal logic helper (not reactive)
|
|
73
78
|
@property
|
|
74
79
|
def _thread_service(self) -> ThreadService:
|
|
@@ -97,6 +102,7 @@ class ThreadState(rx.State):
|
|
|
97
102
|
_initialized: bool = False
|
|
98
103
|
_current_user_id: str = ""
|
|
99
104
|
_skip_user_message: bool = False # Skip adding user message (for OAuth resend)
|
|
105
|
+
_pending_file_cleanup: list[str] = [] # Files to delete after processing
|
|
100
106
|
|
|
101
107
|
# -------------------------------------------------------------------------
|
|
102
108
|
# Computed properties
|
|
@@ -135,6 +141,14 @@ class ThreadState(rx.State):
|
|
|
135
141
|
model = ModelManager().get_model(self.selected_model)
|
|
136
142
|
return model.supports_tools if model else False
|
|
137
143
|
|
|
144
|
+
@rx.var
|
|
145
|
+
def selected_model_supports_attachments(self) -> bool:
|
|
146
|
+
"""Check if the currently selected model supports attachments."""
|
|
147
|
+
if not self.selected_model:
|
|
148
|
+
return False
|
|
149
|
+
model = ModelManager().get_model(self.selected_model)
|
|
150
|
+
return model.supports_attachments if model else False
|
|
151
|
+
|
|
138
152
|
@rx.var
|
|
139
153
|
def get_unique_reasoning_sessions(self) -> list[str]:
|
|
140
154
|
"""Get unique reasoning session IDs."""
|
|
@@ -432,6 +446,65 @@ class ThreadState(rx.State):
|
|
|
432
446
|
self.thinking_items = []
|
|
433
447
|
self.image_chunks = []
|
|
434
448
|
self.show_thinking = False
|
|
449
|
+
self._clear_uploaded_files()
|
|
450
|
+
|
|
451
|
+
# -------------------------------------------------------------------------
|
|
452
|
+
# File upload handling
|
|
453
|
+
# -------------------------------------------------------------------------
|
|
454
|
+
|
|
455
|
+
@rx.event
|
|
456
|
+
async def handle_upload(self, files: list[rx.UploadFile]) -> None:
|
|
457
|
+
"""Handle uploaded files from the browser.
|
|
458
|
+
|
|
459
|
+
Moves files to user-specific directory and adds them to state.
|
|
460
|
+
"""
|
|
461
|
+
user_session: UserSession = await self.get_state(UserSession)
|
|
462
|
+
user_id = user_session.user.user_id if user_session.user else "anonymous"
|
|
463
|
+
|
|
464
|
+
for upload_file in files:
|
|
465
|
+
try:
|
|
466
|
+
# Save uploaded file to disk first
|
|
467
|
+
upload_data = await upload_file.read()
|
|
468
|
+
temp_path = rx.get_upload_dir() / upload_file.filename
|
|
469
|
+
temp_path.parent.mkdir(parents=True, exist_ok=True)
|
|
470
|
+
temp_path.write_bytes(upload_data)
|
|
471
|
+
|
|
472
|
+
# Move to user directory
|
|
473
|
+
final_path = file_manager.move_to_user_directory(
|
|
474
|
+
str(temp_path), str(user_id)
|
|
475
|
+
)
|
|
476
|
+
file_size = file_manager.get_file_size(final_path)
|
|
477
|
+
|
|
478
|
+
uploaded = UploadedFile(
|
|
479
|
+
filename=upload_file.filename,
|
|
480
|
+
file_path=final_path,
|
|
481
|
+
size=file_size,
|
|
482
|
+
)
|
|
483
|
+
self.uploaded_files = [*self.uploaded_files, uploaded]
|
|
484
|
+
logger.info("Uploaded file: %s", upload_file.filename)
|
|
485
|
+
except Exception as e:
|
|
486
|
+
logger.error("Failed to upload file %s: %s", upload_file.filename, e)
|
|
487
|
+
|
|
488
|
+
@rx.event
|
|
489
|
+
def remove_file_from_prompt(self, file_path: str) -> None:
|
|
490
|
+
"""Remove an uploaded file from the prompt."""
|
|
491
|
+
# Delete the file from disk
|
|
492
|
+
file_manager.cleanup_uploaded_files([file_path])
|
|
493
|
+
# Remove from state
|
|
494
|
+
self.uploaded_files = [
|
|
495
|
+
f for f in self.uploaded_files if f.file_path != file_path
|
|
496
|
+
]
|
|
497
|
+
logger.debug("Removed uploaded file: %s", file_path)
|
|
498
|
+
|
|
499
|
+
def _clear_uploaded_files(self) -> None:
|
|
500
|
+
"""Clear all uploaded files from state and disk."""
|
|
501
|
+
if not self.uploaded_files:
|
|
502
|
+
return
|
|
503
|
+
count = len(self.uploaded_files)
|
|
504
|
+
file_paths = [f.file_path for f in self.uploaded_files]
|
|
505
|
+
file_manager.cleanup_uploaded_files(file_paths)
|
|
506
|
+
self.uploaded_files = []
|
|
507
|
+
logger.debug("Cleared %d uploaded files", count)
|
|
435
508
|
|
|
436
509
|
# -------------------------------------------------------------------------
|
|
437
510
|
# Message processing
|
|
@@ -458,7 +531,7 @@ class ThreadState(rx.State):
|
|
|
458
531
|
start = await self._begin_message_processing()
|
|
459
532
|
if not start:
|
|
460
533
|
return
|
|
461
|
-
current_prompt, selected_model, mcp_servers, is_new_thread = start
|
|
534
|
+
current_prompt, selected_model, mcp_servers, file_paths, is_new_thread = start
|
|
462
535
|
|
|
463
536
|
processor = ModelManager().get_processor_for_model(selected_model)
|
|
464
537
|
if not processor:
|
|
@@ -475,11 +548,16 @@ class ThreadState(rx.State):
|
|
|
475
548
|
accumulator = ResponseAccumulator()
|
|
476
549
|
accumulator.attach_messages_ref(self.messages)
|
|
477
550
|
|
|
551
|
+
# Clear uploaded files from UI and mark for cleanup after processing
|
|
552
|
+
self.uploaded_files = []
|
|
553
|
+
self._pending_file_cleanup = file_paths
|
|
554
|
+
|
|
478
555
|
first_response_received = False
|
|
479
556
|
try:
|
|
480
557
|
async for chunk in processor.process(
|
|
481
558
|
self.messages,
|
|
482
559
|
selected_model,
|
|
560
|
+
files=file_paths or None,
|
|
483
561
|
mcp_servers=mcp_servers,
|
|
484
562
|
user_id=user_id,
|
|
485
563
|
):
|
|
@@ -506,7 +584,7 @@ class ThreadState(rx.State):
|
|
|
506
584
|
|
|
507
585
|
async def _begin_message_processing(
|
|
508
586
|
self,
|
|
509
|
-
) -> tuple[str, str, list[MCPServer], bool] | None:
|
|
587
|
+
) -> tuple[str, str, list[MCPServer], list[str], bool] | None:
|
|
510
588
|
"""Prepare state for sending a message. Returns None if no-op."""
|
|
511
589
|
async with self:
|
|
512
590
|
current_prompt = self.prompt.strip()
|
|
@@ -523,12 +601,21 @@ class ThreadState(rx.State):
|
|
|
523
601
|
|
|
524
602
|
is_new_thread = self._thread.state == ThreadStatus.NEW
|
|
525
603
|
|
|
604
|
+
# Capture file paths before clearing
|
|
605
|
+
file_paths = [f.file_path for f in self.uploaded_files]
|
|
606
|
+
# Capture filenames for message display
|
|
607
|
+
attachment_names = [f.filename for f in self.uploaded_files]
|
|
608
|
+
|
|
526
609
|
# Add user message unless skipped (e.g., OAuth resend)
|
|
527
610
|
if self._skip_user_message:
|
|
528
611
|
self._skip_user_message = False
|
|
529
612
|
else:
|
|
530
613
|
self.messages.append(
|
|
531
|
-
Message(
|
|
614
|
+
Message(
|
|
615
|
+
text=current_prompt,
|
|
616
|
+
type=MessageType.HUMAN,
|
|
617
|
+
attachments=attachment_names,
|
|
618
|
+
)
|
|
532
619
|
)
|
|
533
620
|
# Always add assistant placeholder
|
|
534
621
|
self.messages.append(Message(text="", type=MessageType.ASSISTANT))
|
|
@@ -540,7 +627,13 @@ class ThreadState(rx.State):
|
|
|
540
627
|
return None
|
|
541
628
|
|
|
542
629
|
mcp_servers = self.selected_mcp_servers
|
|
543
|
-
return
|
|
630
|
+
return (
|
|
631
|
+
current_prompt,
|
|
632
|
+
selected_model,
|
|
633
|
+
mcp_servers,
|
|
634
|
+
file_paths,
|
|
635
|
+
is_new_thread,
|
|
636
|
+
)
|
|
544
637
|
|
|
545
638
|
async def _stop_processing_with_error(self, error_msg: str) -> None:
|
|
546
639
|
"""Stop processing and show an error message."""
|
|
@@ -650,6 +743,11 @@ class ThreadState(rx.State):
|
|
|
650
743
|
self.processing = False
|
|
651
744
|
self.current_activity = ""
|
|
652
745
|
|
|
746
|
+
# Clean up uploaded files from disk
|
|
747
|
+
if self._pending_file_cleanup:
|
|
748
|
+
file_manager.cleanup_uploaded_files(self._pending_file_cleanup)
|
|
749
|
+
self._pending_file_cleanup = []
|
|
750
|
+
|
|
653
751
|
def _handle_auth_required_from_accumulator(
|
|
654
752
|
self, accumulator: ResponseAccumulator
|
|
655
753
|
) -> None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: appkit-assistant
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.0
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Project-URL: Homepage, https://github.com/jenreh/appkit
|
|
6
6
|
Project-URL: Documentation, https://github.com/jenreh/appkit/tree/main/docs
|
|
@@ -16,9 +16,12 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
16
16
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
17
|
Classifier: Topic :: Software Development :: User Interfaces
|
|
18
18
|
Requires-Python: >=3.13
|
|
19
|
+
Requires-Dist: anthropic>=0.40.0
|
|
19
20
|
Requires-Dist: appkit-commons
|
|
20
21
|
Requires-Dist: appkit-mantine
|
|
21
22
|
Requires-Dist: appkit-ui
|
|
23
|
+
Requires-Dist: google-genai>=1.52.0
|
|
24
|
+
Requires-Dist: mcp>=1.0.0
|
|
22
25
|
Requires-Dist: openai>=2.3.0
|
|
23
26
|
Requires-Dist: reflex>=0.8.22
|
|
24
27
|
Description-Content-Type: text/markdown
|
|
@@ -1,34 +1,39 @@
|
|
|
1
|
-
appkit_assistant/configuration.py,sha256=
|
|
1
|
+
appkit_assistant/configuration.py,sha256=Wpo3EuGWQrV0WIQnAhkj19PzgGkJoAde5ky-MA7kJwg,429
|
|
2
2
|
appkit_assistant/pages.py,sha256=gDvBweUO2WjrhP1RE5AAkjL1_S-givWr3CkkGZKws_E,471
|
|
3
|
+
appkit_assistant/backend/file_manager.py,sha256=54SYphu6FsxbEYuMx8ohQiSAeY2gGDV1q3S6RZuNku0,3153
|
|
3
4
|
appkit_assistant/backend/mcp_auth_service.py,sha256=lYQEe4yOZ48ear6dvcuOXsaOc6RClIBMsOOkV7SG5Aw,27768
|
|
4
5
|
appkit_assistant/backend/model_manager.py,sha256=fmv3yP63LxDnne4vjT7IzETTI2aSxViC2FSUfHQajlk,4382
|
|
5
|
-
appkit_assistant/backend/models.py,sha256=
|
|
6
|
+
appkit_assistant/backend/models.py,sha256=vymgJ7npF-L-4WGXWwVUdHqLo3MAypPPaaD_SyDcAIw,8198
|
|
6
7
|
appkit_assistant/backend/processor.py,sha256=LtkkC6v0idNtbuMm3Sw5J9zIjYqtNhtxYxjYefPSS9A,2135
|
|
7
8
|
appkit_assistant/backend/repositories.py,sha256=R-7kYdxg4RWQrTEOU4tbcOEhJA_FlesWrt65UpItRSU,5547
|
|
9
|
+
appkit_assistant/backend/response_accumulator.py,sha256=dFJZiXcDn4V2POSk7Us6gQLsrUIRBMLdoOP61LwutdI,9766
|
|
8
10
|
appkit_assistant/backend/system_prompt_cache.py,sha256=83OIyixeTb3HKOy3XIzPyTAE-G2JyqrfcG8xVeTS2Ls,5514
|
|
9
|
-
appkit_assistant/backend/processors/
|
|
10
|
-
appkit_assistant/backend/processors/
|
|
11
|
+
appkit_assistant/backend/processors/claude_base.py,sha256=j0DhBn8EVAjW_bfCghXaEHyORO1raUNdQeemVWCKJlA,5376
|
|
12
|
+
appkit_assistant/backend/processors/claude_responses_processor.py,sha256=_quuN5FRsPSpQjaNIceN-Z66Fb3RnhXtcsS4gE0NrPA,32980
|
|
13
|
+
appkit_assistant/backend/processors/gemini_base.py,sha256=ijCa8-_xdddD6ms_pkUjCj5kHZOoQ1wfw-7AhhXu6vo,2286
|
|
14
|
+
appkit_assistant/backend/processors/gemini_responses_processor.py,sha256=pzbczuw7MsoABdkuJGNxlebV8POA2D72XMzLoMhj9k4,26429
|
|
15
|
+
appkit_assistant/backend/processors/lorem_ipsum_processor.py,sha256=WR4s1RyOi5xgWVpre04xoxoMLIAINkpdvRCkSfNuoMc,4699
|
|
16
|
+
appkit_assistant/backend/processors/openai_base.py,sha256=TBQCZW7RxaPMag_DX_tpLyMpZ_PZ_KiO8_sFN49OCpk,4424
|
|
11
17
|
appkit_assistant/backend/processors/openai_chat_completion_processor.py,sha256=nTxouoXDU6VcQr8UhA2KiMNt60KvIwM8cH9Z8lo4dXY,4218
|
|
12
|
-
appkit_assistant/backend/processors/openai_responses_processor.py,sha256=
|
|
18
|
+
appkit_assistant/backend/processors/openai_responses_processor.py,sha256=hysK7mlsG2t8SfltQpCvKA6MxpeIomRMGX-tSMaUshc,29492
|
|
13
19
|
appkit_assistant/backend/processors/perplexity_processor.py,sha256=weHukv78MSCF_uSCKGSMpNYHsET9OB8IhpvUiMfPQ8A,3355
|
|
14
20
|
appkit_assistant/backend/services/thread_service.py,sha256=LpM8ZZHt1o4MYEKzH_XPURSi3qS6p3pAQA53tOE53MU,4663
|
|
15
|
-
appkit_assistant/components/__init__.py,sha256=
|
|
16
|
-
appkit_assistant/components/composer.py,sha256=
|
|
21
|
+
appkit_assistant/components/__init__.py,sha256=ptv0wShA4CHjgNpehlHqgoinCl-yyofjkV6rTTNIRHE,954
|
|
22
|
+
appkit_assistant/components/composer.py,sha256=jQNZd8Y0eQgbfC3d1AV1osRNnwfztEpXtrFp7aagXaY,7190
|
|
17
23
|
appkit_assistant/components/composer_key_handler.py,sha256=KyZYyhxzFR8DH_7F_DrvTFNT6v5kG6JihlGTmCv2wv0,1028
|
|
18
24
|
appkit_assistant/components/mcp_oauth.py,sha256=puLwxAhmF25BjnZMdJbKIfC6bFXK2D8LybOX0kD7Ri4,1737
|
|
19
25
|
appkit_assistant/components/mcp_server_dialogs.py,sha256=afIImmhfrNyLmxDZBpCxHxvD8HKpDanIloLEC8dJgro,23444
|
|
20
26
|
appkit_assistant/components/mcp_server_table.py,sha256=1dziN7hDDvE8Y3XcdIs0wUPv1H64kP9gRAEjgH9Yvzo,2323
|
|
21
|
-
appkit_assistant/components/message.py,sha256=
|
|
27
|
+
appkit_assistant/components/message.py,sha256=xFMcrSMErHbbgZGl0rujNwhL7nRGwIp-S-a47cYwaJ4,14605
|
|
22
28
|
appkit_assistant/components/system_prompt_editor.py,sha256=REl33zFmcpYRe9kxvFrBRYg40dV4L4FtVC_3ibLsmrU,2940
|
|
23
|
-
appkit_assistant/components/thread.py,sha256
|
|
29
|
+
appkit_assistant/components/thread.py,sha256=-KK1vcEmITR_oDGLbQhIa2WxRCVxEqel12m4haS7y9w,8461
|
|
24
30
|
appkit_assistant/components/threadlist.py,sha256=1xVakSTQYi5-wgED3fTJVggeIjL_fkthehce0wKUYtM,4896
|
|
25
31
|
appkit_assistant/components/tools_modal.py,sha256=12iiAVahy3j4JwjGfRlegVEa4ePhGsEu7Bq92JLn1ZI,3353
|
|
26
|
-
appkit_assistant/logic/response_accumulator.py,sha256=85NKtIxpWVsI4rDkNAy_U34uavY8NoUEp9n2kO0ief4,8060
|
|
27
32
|
appkit_assistant/state/mcp_oauth_state.py,sha256=6MofExrbOOEl_YUcUOqcSTN3h7KAaERI5IdVfXdVUVs,7669
|
|
28
33
|
appkit_assistant/state/mcp_server_state.py,sha256=3AFvy53xx_eLTxw-LfJklPTgq4Ohqu4xs1QlLs-kU4U,11387
|
|
29
34
|
appkit_assistant/state/system_prompt_state.py,sha256=zdnYrTnl7EszALRiodu6pcuQUd2tmtPG1eJ10j_OotI,7705
|
|
30
35
|
appkit_assistant/state/thread_list_state.py,sha256=DEOR5Nklj1qfYaxSRMXCZdZRv2iq2Jb37JSg739_wL4,10250
|
|
31
|
-
appkit_assistant/state/thread_state.py,sha256=
|
|
32
|
-
appkit_assistant-0.
|
|
33
|
-
appkit_assistant-0.
|
|
34
|
-
appkit_assistant-0.
|
|
36
|
+
appkit_assistant/state/thread_state.py,sha256=xLydHA5YDFj3J1fGBQoAZsWg3zlcKOh2-9SzvlGvyU0,34742
|
|
37
|
+
appkit_assistant-0.17.0.dist-info/METADATA,sha256=jzEwclPQN3VXn5APgK9EQMLiGKnug-wI-JRxPu44Kao,9498
|
|
38
|
+
appkit_assistant-0.17.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
39
|
+
appkit_assistant-0.17.0.dist-info/RECORD,,
|
|
File without changes
|