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.
- appkit_assistant/backend/{models.py → database/models.py} +32 -132
- appkit_assistant/backend/{repositories.py → database/repositories.py} +93 -1
- appkit_assistant/backend/model_manager.py +5 -5
- appkit_assistant/backend/models/__init__.py +28 -0
- appkit_assistant/backend/models/anthropic.py +31 -0
- appkit_assistant/backend/models/google.py +27 -0
- appkit_assistant/backend/models/openai.py +50 -0
- appkit_assistant/backend/models/perplexity.py +56 -0
- appkit_assistant/backend/processors/__init__.py +29 -0
- appkit_assistant/backend/processors/claude_responses_processor.py +205 -387
- appkit_assistant/backend/processors/gemini_responses_processor.py +290 -352
- appkit_assistant/backend/processors/lorem_ipsum_processor.py +6 -4
- appkit_assistant/backend/processors/mcp_mixin.py +297 -0
- appkit_assistant/backend/processors/openai_base.py +11 -125
- appkit_assistant/backend/processors/openai_chat_completion_processor.py +5 -3
- appkit_assistant/backend/processors/openai_responses_processor.py +480 -402
- appkit_assistant/backend/processors/perplexity_processor.py +156 -79
- appkit_assistant/backend/{processor.py → processors/processor_base.py} +7 -2
- appkit_assistant/backend/processors/streaming_base.py +188 -0
- appkit_assistant/backend/schemas.py +138 -0
- appkit_assistant/backend/services/auth_error_detector.py +99 -0
- appkit_assistant/backend/services/chunk_factory.py +273 -0
- appkit_assistant/backend/services/citation_handler.py +292 -0
- appkit_assistant/backend/services/file_cleanup_service.py +316 -0
- appkit_assistant/backend/services/file_upload_service.py +903 -0
- appkit_assistant/backend/services/file_validation.py +138 -0
- appkit_assistant/backend/{mcp_auth_service.py → services/mcp_auth_service.py} +4 -2
- appkit_assistant/backend/services/mcp_token_service.py +61 -0
- appkit_assistant/backend/services/message_converter.py +289 -0
- appkit_assistant/backend/services/openai_client_service.py +120 -0
- appkit_assistant/backend/{response_accumulator.py → services/response_accumulator.py} +163 -1
- appkit_assistant/backend/services/system_prompt_builder.py +89 -0
- appkit_assistant/backend/services/thread_service.py +5 -3
- appkit_assistant/backend/system_prompt_cache.py +3 -3
- appkit_assistant/components/__init__.py +8 -4
- appkit_assistant/components/composer.py +59 -24
- appkit_assistant/components/file_manager.py +623 -0
- appkit_assistant/components/mcp_server_dialogs.py +12 -20
- appkit_assistant/components/mcp_server_table.py +12 -2
- appkit_assistant/components/message.py +119 -2
- appkit_assistant/components/thread.py +1 -1
- appkit_assistant/components/threadlist.py +4 -2
- appkit_assistant/components/tools_modal.py +37 -20
- appkit_assistant/configuration.py +12 -0
- appkit_assistant/state/file_manager_state.py +697 -0
- appkit_assistant/state/mcp_oauth_state.py +3 -3
- appkit_assistant/state/mcp_server_state.py +47 -2
- appkit_assistant/state/system_prompt_state.py +1 -1
- appkit_assistant/state/thread_list_state.py +99 -5
- appkit_assistant/state/thread_state.py +88 -9
- {appkit_assistant-0.17.3.dist-info → appkit_assistant-1.0.1.dist-info}/METADATA +8 -6
- appkit_assistant-1.0.1.dist-info/RECORD +58 -0
- appkit_assistant/backend/processors/claude_base.py +0 -178
- appkit_assistant/backend/processors/gemini_base.py +0 -84
- appkit_assistant-0.17.3.dist-info/RECORD +0 -39
- /appkit_assistant/backend/{file_manager.py → services/file_manager.py} +0 -0
- {appkit_assistant-0.17.3.dist-info → appkit_assistant-1.0.1.dist-info}/WHEEL +0 -0
|
@@ -10,9 +10,9 @@ from collections.abc import AsyncGenerator
|
|
|
10
10
|
import reflex as rx
|
|
11
11
|
from sqlmodel import Session, select
|
|
12
12
|
|
|
13
|
-
from appkit_assistant.backend.
|
|
14
|
-
from appkit_assistant.backend.
|
|
15
|
-
from appkit_assistant.backend.
|
|
13
|
+
from appkit_assistant.backend.database.models import MCPServer
|
|
14
|
+
from appkit_assistant.backend.processors.processor_base import mcp_oauth_redirect_uri
|
|
15
|
+
from appkit_assistant.backend.services.mcp_auth_service import MCPAuthService
|
|
16
16
|
from appkit_commons.database.session import get_session_manager
|
|
17
17
|
from appkit_user.authentication.backend.entities import OAuthStateEntity
|
|
18
18
|
from appkit_user.authentication.states import UserSession
|
|
@@ -8,8 +8,8 @@ from typing import Any
|
|
|
8
8
|
|
|
9
9
|
import reflex as rx
|
|
10
10
|
|
|
11
|
-
from appkit_assistant.backend.models import MCPAuthType, MCPServer
|
|
12
|
-
from appkit_assistant.backend.repositories import (
|
|
11
|
+
from appkit_assistant.backend.database.models import MCPAuthType, MCPServer
|
|
12
|
+
from appkit_assistant.backend.database.repositories import (
|
|
13
13
|
mcp_server_repo,
|
|
14
14
|
)
|
|
15
15
|
from appkit_commons.database.session import get_asyncdb_session
|
|
@@ -245,6 +245,51 @@ class MCPServerState(rx.State):
|
|
|
245
245
|
position="top-right",
|
|
246
246
|
)
|
|
247
247
|
|
|
248
|
+
async def toggle_server_active(
|
|
249
|
+
self, server_id: int, active: bool
|
|
250
|
+
) -> AsyncGenerator[Any, Any]:
|
|
251
|
+
"""Toggle the active status of an MCP server."""
|
|
252
|
+
# Optimistic update: update UI immediately for better UX
|
|
253
|
+
original_servers = list(self.servers)
|
|
254
|
+
for i, s in enumerate(self.servers):
|
|
255
|
+
if s.id == server_id:
|
|
256
|
+
self.servers[i].active = active
|
|
257
|
+
break
|
|
258
|
+
# Yield immediately to flush state update to frontend
|
|
259
|
+
yield
|
|
260
|
+
|
|
261
|
+
try:
|
|
262
|
+
async with get_asyncdb_session() as session:
|
|
263
|
+
server = await mcp_server_repo.find_by_id(session, server_id)
|
|
264
|
+
if not server:
|
|
265
|
+
# Revert optimistic update
|
|
266
|
+
self.servers = original_servers
|
|
267
|
+
yield rx.toast.error(
|
|
268
|
+
"MCP Server nicht gefunden.",
|
|
269
|
+
position="top-right",
|
|
270
|
+
)
|
|
271
|
+
return
|
|
272
|
+
|
|
273
|
+
server.active = active
|
|
274
|
+
await mcp_server_repo.save(session, server)
|
|
275
|
+
server_name = server.name
|
|
276
|
+
|
|
277
|
+
status_text = "aktiviert" if active else "deaktiviert"
|
|
278
|
+
yield rx.toast.info(
|
|
279
|
+
f"MCP Server {server_name} wurde {status_text}.",
|
|
280
|
+
position="top-right",
|
|
281
|
+
)
|
|
282
|
+
logger.debug("Toggled MCP server %s active=%s", server_name, active)
|
|
283
|
+
|
|
284
|
+
except Exception as e:
|
|
285
|
+
# Revert optimistic update on error
|
|
286
|
+
self.servers = original_servers
|
|
287
|
+
logger.error("Failed to toggle MCP server %d: %s", server_id, e)
|
|
288
|
+
yield rx.toast.error(
|
|
289
|
+
"Fehler beim Ändern des MCP Server Status.",
|
|
290
|
+
position="top-right",
|
|
291
|
+
)
|
|
292
|
+
|
|
248
293
|
def _parse_headers_from_form(self, form_data: dict[str, Any]) -> dict[str, str]:
|
|
249
294
|
"""Parse headers from form data."""
|
|
250
295
|
headers_json = form_data.get("headers_json", "").strip()
|
|
@@ -5,7 +5,7 @@ from typing import Any, Final
|
|
|
5
5
|
import reflex as rx
|
|
6
6
|
from reflex.state import State
|
|
7
7
|
|
|
8
|
-
from appkit_assistant.backend.repositories import system_prompt_repo
|
|
8
|
+
from appkit_assistant.backend.database.repositories import system_prompt_repo
|
|
9
9
|
from appkit_assistant.backend.system_prompt_cache import invalidate_prompt_cache
|
|
10
10
|
from appkit_commons.database.session import get_asyncdb_session
|
|
11
11
|
from appkit_user.authentication.states import UserSession
|
|
@@ -15,8 +15,9 @@ from typing import TYPE_CHECKING, Any
|
|
|
15
15
|
|
|
16
16
|
import reflex as rx
|
|
17
17
|
|
|
18
|
-
from appkit_assistant.backend.models import
|
|
19
|
-
from appkit_assistant.backend.repositories import thread_repo
|
|
18
|
+
from appkit_assistant.backend.database.models import ThreadStatus
|
|
19
|
+
from appkit_assistant.backend.database.repositories import thread_repo
|
|
20
|
+
from appkit_assistant.backend.schemas import ThreadModel
|
|
20
21
|
from appkit_commons.database.session import get_asyncdb_session
|
|
21
22
|
from appkit_user.authentication.states import UserSession
|
|
22
23
|
|
|
@@ -196,7 +197,8 @@ class ThreadListState(rx.State):
|
|
|
196
197
|
"""Delete a thread from database and list.
|
|
197
198
|
|
|
198
199
|
If the deleted thread was the active thread, resets ThreadState
|
|
199
|
-
to show an empty thread.
|
|
200
|
+
to show an empty thread. Also triggers background cleanup of
|
|
201
|
+
associated OpenAI files and vector store.
|
|
200
202
|
|
|
201
203
|
Args:
|
|
202
204
|
thread_id: The ID of the thread to delete.
|
|
@@ -214,7 +216,13 @@ class ThreadListState(rx.State):
|
|
|
214
216
|
)
|
|
215
217
|
was_active = thread_id == self.active_thread_id
|
|
216
218
|
|
|
219
|
+
if thread_to_delete:
|
|
220
|
+
self.loading_thread_id = thread_id
|
|
221
|
+
yield
|
|
222
|
+
|
|
217
223
|
if not is_authenticated or not user_id:
|
|
224
|
+
async with self:
|
|
225
|
+
self.loading_thread_id = ""
|
|
218
226
|
return
|
|
219
227
|
|
|
220
228
|
if not thread_to_delete:
|
|
@@ -222,18 +230,42 @@ class ThreadListState(rx.State):
|
|
|
222
230
|
"Chat nicht gefunden.", position="top-right", close_button=True
|
|
223
231
|
)
|
|
224
232
|
logger.warning("Thread %s not found for deletion", thread_id)
|
|
233
|
+
async with self:
|
|
234
|
+
self.loading_thread_id = ""
|
|
225
235
|
return
|
|
226
236
|
|
|
237
|
+
# Capture thread info for cleanup before deletion
|
|
238
|
+
thread_db_id: int | None = None
|
|
239
|
+
vector_store_id: str | None = None
|
|
240
|
+
openai_file_ids: list[str] = []
|
|
241
|
+
|
|
227
242
|
try:
|
|
228
|
-
#
|
|
243
|
+
# Get thread details and file IDs from database BEFORE deletion
|
|
229
244
|
async with get_asyncdb_session() as session:
|
|
245
|
+
db_thread = await thread_repo.find_by_thread_id_and_user(
|
|
246
|
+
session, thread_id, user_id
|
|
247
|
+
)
|
|
248
|
+
if db_thread:
|
|
249
|
+
thread_db_id = db_thread.id
|
|
250
|
+
vector_store_id = db_thread.vector_store_id
|
|
251
|
+
|
|
252
|
+
# Fetch file IDs before deletion (cascade will delete records)
|
|
253
|
+
from appkit_assistant.backend.database.repositories import ( # noqa: PLC0415
|
|
254
|
+
file_upload_repo,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
files = await file_upload_repo.find_by_thread(session, thread_db_id)
|
|
258
|
+
openai_file_ids = [f.openai_file_id for f in files]
|
|
259
|
+
|
|
260
|
+
# Delete thread from database (cascades to file records)
|
|
230
261
|
await thread_repo.delete_by_thread_id_and_user(
|
|
231
262
|
session, thread_id, user_id
|
|
232
263
|
)
|
|
233
264
|
|
|
234
265
|
async with self:
|
|
235
|
-
# Remove from list
|
|
266
|
+
# Remove from list immediately
|
|
236
267
|
self.threads = [t for t in self.threads if t.thread_id != thread_id]
|
|
268
|
+
self.loading_thread_id = ""
|
|
237
269
|
|
|
238
270
|
if was_active:
|
|
239
271
|
self.active_thread_id = ""
|
|
@@ -247,7 +279,18 @@ class ThreadListState(rx.State):
|
|
|
247
279
|
close_button=True,
|
|
248
280
|
)
|
|
249
281
|
|
|
282
|
+
# Trigger background cleanup of OpenAI files (fire-and-forget)
|
|
283
|
+
if openai_file_ids:
|
|
284
|
+
yield ThreadListState.cleanup_thread_openai_files(
|
|
285
|
+
openai_file_ids, vector_store_id
|
|
286
|
+
)
|
|
287
|
+
elif vector_store_id:
|
|
288
|
+
# No files but has vector store - clean it up
|
|
289
|
+
yield ThreadListState.cleanup_thread_openai_files([], vector_store_id)
|
|
290
|
+
|
|
250
291
|
except Exception as e:
|
|
292
|
+
async with self:
|
|
293
|
+
self.loading_thread_id = ""
|
|
251
294
|
logger.error("Error deleting thread %s: %s", thread_id, e)
|
|
252
295
|
yield rx.toast.error(
|
|
253
296
|
"Fehler beim Löschen des Chats.",
|
|
@@ -255,6 +298,57 @@ class ThreadListState(rx.State):
|
|
|
255
298
|
close_button=True,
|
|
256
299
|
)
|
|
257
300
|
|
|
301
|
+
@rx.event(background=True)
|
|
302
|
+
async def cleanup_thread_openai_files(
|
|
303
|
+
self, openai_file_ids: list[str], vector_store_id: str | None
|
|
304
|
+
) -> AsyncGenerator[Any, Any]:
|
|
305
|
+
"""Background task to clean up OpenAI files for a deleted thread.
|
|
306
|
+
|
|
307
|
+
This runs in the background so the user can continue working.
|
|
308
|
+
Failures are logged but not shown to the user.
|
|
309
|
+
|
|
310
|
+
Note: This is called AFTER DB records are cascade-deleted, so it only
|
|
311
|
+
handles OpenAI resource cleanup.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
openai_file_ids: List of OpenAI file IDs to delete.
|
|
315
|
+
vector_store_id: The vector store ID to delete.
|
|
316
|
+
"""
|
|
317
|
+
from appkit_assistant.backend.services.file_upload_service import ( # noqa: PLC0415
|
|
318
|
+
FileUploadService,
|
|
319
|
+
)
|
|
320
|
+
from appkit_assistant.backend.services.openai_client_service import ( # noqa: PLC0415
|
|
321
|
+
get_openai_client_service,
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
openai_service = get_openai_client_service()
|
|
325
|
+
if not openai_service.is_available:
|
|
326
|
+
logger.warning(
|
|
327
|
+
"OpenAI not configured, skipping file cleanup for %d files",
|
|
328
|
+
len(openai_file_ids),
|
|
329
|
+
)
|
|
330
|
+
return
|
|
331
|
+
|
|
332
|
+
client = openai_service.create_client()
|
|
333
|
+
if not client:
|
|
334
|
+
logger.warning(
|
|
335
|
+
"Could not create OpenAI client, skipping file cleanup for %d files",
|
|
336
|
+
len(openai_file_ids),
|
|
337
|
+
)
|
|
338
|
+
return
|
|
339
|
+
|
|
340
|
+
file_service = FileUploadService(client)
|
|
341
|
+
|
|
342
|
+
# Delete vector store (which deletes files FROM store first)
|
|
343
|
+
if vector_store_id:
|
|
344
|
+
await file_service.delete_vector_store(vector_store_id)
|
|
345
|
+
|
|
346
|
+
# Delete files from OpenAI (independent cleanup)
|
|
347
|
+
if openai_file_ids:
|
|
348
|
+
await file_service.delete_files(openai_file_ids)
|
|
349
|
+
|
|
350
|
+
yield # Required for async generator
|
|
351
|
+
|
|
258
352
|
# -------------------------------------------------------------------------
|
|
259
353
|
# Logout handling
|
|
260
354
|
# -------------------------------------------------------------------------
|
|
@@ -20,27 +20,31 @@ from typing import Any
|
|
|
20
20
|
|
|
21
21
|
import reflex as rx
|
|
22
22
|
|
|
23
|
-
from appkit_assistant.backend import
|
|
23
|
+
from appkit_assistant.backend.database.models import (
|
|
24
|
+
MCPServer,
|
|
25
|
+
ThreadStatus,
|
|
26
|
+
)
|
|
27
|
+
from appkit_assistant.backend.database.repositories import mcp_server_repo
|
|
24
28
|
from appkit_assistant.backend.model_manager import ModelManager
|
|
25
|
-
from appkit_assistant.backend.
|
|
29
|
+
from appkit_assistant.backend.schemas import (
|
|
26
30
|
AIModel,
|
|
27
31
|
Chunk,
|
|
28
32
|
ChunkType,
|
|
29
|
-
MCPServer,
|
|
30
33
|
Message,
|
|
31
34
|
MessageType,
|
|
32
35
|
Suggestion,
|
|
33
36
|
Thinking,
|
|
34
37
|
ThinkingType,
|
|
35
38
|
ThreadModel,
|
|
36
|
-
ThreadStatus,
|
|
37
39
|
UploadedFile,
|
|
38
40
|
)
|
|
39
|
-
from appkit_assistant.backend.
|
|
40
|
-
from appkit_assistant.backend.response_accumulator import ResponseAccumulator
|
|
41
|
+
from appkit_assistant.backend.services import file_manager
|
|
42
|
+
from appkit_assistant.backend.services.response_accumulator import ResponseAccumulator
|
|
41
43
|
from appkit_assistant.backend.services.thread_service import ThreadService
|
|
44
|
+
from appkit_assistant.configuration import AssistantConfig
|
|
42
45
|
from appkit_assistant.state.thread_list_state import ThreadListState
|
|
43
46
|
from appkit_commons.database.session import get_asyncdb_session
|
|
47
|
+
from appkit_commons.registry import service_registry
|
|
44
48
|
from appkit_user.authentication.states import UserSession
|
|
45
49
|
|
|
46
50
|
logger = logging.getLogger(__name__)
|
|
@@ -76,6 +80,8 @@ class ThreadState(rx.State):
|
|
|
76
80
|
|
|
77
81
|
# File upload state
|
|
78
82
|
uploaded_files: list[UploadedFile] = []
|
|
83
|
+
max_file_size_mb: int = 50
|
|
84
|
+
max_files_per_thread: int = 10
|
|
79
85
|
|
|
80
86
|
# Editing state
|
|
81
87
|
editing_message_id: str | None = None
|
|
@@ -93,6 +99,9 @@ class ThreadState(rx.State):
|
|
|
93
99
|
temp_selected_mcp_servers: list[int] = []
|
|
94
100
|
server_selection_state: dict[int, bool] = {}
|
|
95
101
|
|
|
102
|
+
# Web Search state
|
|
103
|
+
web_search_enabled: bool = False
|
|
104
|
+
|
|
96
105
|
# MCP OAuth state
|
|
97
106
|
pending_auth_server_id: str = ""
|
|
98
107
|
pending_auth_server_name: str = ""
|
|
@@ -158,6 +167,14 @@ class ThreadState(rx.State):
|
|
|
158
167
|
model = ModelManager().get_model(self.selected_model)
|
|
159
168
|
return model.supports_attachments if model else False
|
|
160
169
|
|
|
170
|
+
@rx.var
|
|
171
|
+
def selected_model_supports_search(self) -> bool:
|
|
172
|
+
"""Check if the currently selected model supports web search."""
|
|
173
|
+
if not self.selected_model:
|
|
174
|
+
return False
|
|
175
|
+
model = ModelManager().get_model(self.selected_model)
|
|
176
|
+
return model.supports_search if model else False
|
|
177
|
+
|
|
161
178
|
@rx.var
|
|
162
179
|
def get_unique_reasoning_sessions(self) -> list[str]:
|
|
163
180
|
"""Get unique reasoning session IDs."""
|
|
@@ -237,6 +254,13 @@ class ThreadState(rx.State):
|
|
|
237
254
|
self.prompt = ""
|
|
238
255
|
self.show_thinking = False
|
|
239
256
|
self._current_user_id = current_user_id
|
|
257
|
+
|
|
258
|
+
# Load config
|
|
259
|
+
config: AssistantConfig | None = service_registry().get(AssistantConfig)
|
|
260
|
+
if config:
|
|
261
|
+
self.max_file_size_mb = config.file_upload.max_file_size_mb
|
|
262
|
+
self.max_files_per_thread = config.file_upload.max_files_per_thread
|
|
263
|
+
|
|
240
264
|
self._initialized = True
|
|
241
265
|
logger.debug("Initialized thread state: %s", self._thread.thread_id)
|
|
242
266
|
|
|
@@ -396,15 +420,20 @@ class ThreadState(rx.State):
|
|
|
396
420
|
"""Toggle the expanded state of the thinking section."""
|
|
397
421
|
self.thinking_expanded = not self.thinking_expanded
|
|
398
422
|
|
|
423
|
+
@rx.event
|
|
424
|
+
def toggle_web_search(self) -> None:
|
|
425
|
+
"""Toggle web search."""
|
|
426
|
+
self.web_search_enabled = not self.web_search_enabled
|
|
427
|
+
|
|
399
428
|
# -------------------------------------------------------------------------
|
|
400
429
|
# MCP Server tool support
|
|
401
430
|
# -------------------------------------------------------------------------
|
|
402
431
|
|
|
403
432
|
@rx.event
|
|
404
433
|
async def load_mcp_servers(self) -> None:
|
|
405
|
-
"""Load available MCP servers from the database."""
|
|
434
|
+
"""Load available active MCP servers from the database."""
|
|
406
435
|
async with get_asyncdb_session() as session:
|
|
407
|
-
servers = await mcp_server_repo.
|
|
436
|
+
servers = await mcp_server_repo.find_all_active_ordered_by_name(session)
|
|
408
437
|
# Create detached copies
|
|
409
438
|
self.available_mcp_servers = [MCPServer(**s.model_dump()) for s in servers]
|
|
410
439
|
|
|
@@ -432,6 +461,12 @@ class ThreadState(rx.State):
|
|
|
432
461
|
]
|
|
433
462
|
self.show_tools_modal = False
|
|
434
463
|
|
|
464
|
+
@rx.event
|
|
465
|
+
def deselect_all_mcp_servers(self) -> None:
|
|
466
|
+
"""Deselect all MCP servers in the modal."""
|
|
467
|
+
self.server_selection_state = {}
|
|
468
|
+
self.temp_selected_mcp_servers = []
|
|
469
|
+
|
|
435
470
|
@rx.event
|
|
436
471
|
def is_mcp_server_selected(self, server_id: int) -> bool:
|
|
437
472
|
"""Check if an MCP server is selected."""
|
|
@@ -467,6 +502,15 @@ class ThreadState(rx.State):
|
|
|
467
502
|
|
|
468
503
|
Moves files to user-specific directory and adds them to state.
|
|
469
504
|
"""
|
|
505
|
+
# Validate file count (using state variables from config)
|
|
506
|
+
if len(files) > self.max_files_per_thread:
|
|
507
|
+
yield rx.toast.error(
|
|
508
|
+
f"Bitte laden Sie maximal {self.max_files_per_thread} Dateien gleichzeitig hoch.",
|
|
509
|
+
position="top-right",
|
|
510
|
+
close_button=True,
|
|
511
|
+
)
|
|
512
|
+
return
|
|
513
|
+
|
|
470
514
|
user_session: UserSession = await self.get_state(UserSession)
|
|
471
515
|
user_id = user_session.user.user_id if user_session.user else "anonymous"
|
|
472
516
|
|
|
@@ -490,7 +534,11 @@ class ThreadState(rx.State):
|
|
|
490
534
|
size=file_size,
|
|
491
535
|
)
|
|
492
536
|
self.uploaded_files = [*self.uploaded_files, uploaded]
|
|
493
|
-
logger.info(
|
|
537
|
+
logger.info(
|
|
538
|
+
"Uploaded file: %s (total files: %d)",
|
|
539
|
+
upload_file.filename,
|
|
540
|
+
len(self.uploaded_files),
|
|
541
|
+
)
|
|
494
542
|
except Exception as e:
|
|
495
543
|
logger.error("Failed to upload file %s: %s", upload_file.filename, e)
|
|
496
544
|
|
|
@@ -692,6 +740,22 @@ class ThreadState(rx.State):
|
|
|
692
740
|
user_session: UserSession = await self.get_state(UserSession)
|
|
693
741
|
user_id = user_session.user.user_id if user_session.user else None
|
|
694
742
|
|
|
743
|
+
logger.debug(
|
|
744
|
+
"Pre-save check: is_new_thread=%s, file_paths=%d, user_id=%s",
|
|
745
|
+
is_new_thread,
|
|
746
|
+
len(file_paths) if file_paths else 0,
|
|
747
|
+
user_id,
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
# Save thread to DB if new and has files to enable file uploads
|
|
751
|
+
if is_new_thread and file_paths and user_id:
|
|
752
|
+
self._thread.state = ThreadStatus.ACTIVE
|
|
753
|
+
await self._thread_service.save_thread(self._thread, user_id)
|
|
754
|
+
logger.debug(
|
|
755
|
+
"Saved new thread %s to DB before file upload",
|
|
756
|
+
self._thread.thread_id,
|
|
757
|
+
)
|
|
758
|
+
|
|
695
759
|
# Initialize ResponseAccumulator logic
|
|
696
760
|
accumulator = ResponseAccumulator()
|
|
697
761
|
accumulator.attach_messages_ref(self.messages)
|
|
@@ -705,11 +769,19 @@ class ThreadState(rx.State):
|
|
|
705
769
|
|
|
706
770
|
first_response_received = False
|
|
707
771
|
try:
|
|
772
|
+
# Build payload with thread_uuid for file upload support
|
|
773
|
+
payload = {"thread_uuid": self._thread.thread_id}
|
|
774
|
+
|
|
775
|
+
# Pass web search state to processor via payload
|
|
776
|
+
if self.web_search_enabled:
|
|
777
|
+
payload["web_search_enabled"] = True
|
|
778
|
+
|
|
708
779
|
async for chunk in processor.process(
|
|
709
780
|
self.messages,
|
|
710
781
|
selected_model,
|
|
711
782
|
files=file_paths or None,
|
|
712
783
|
mcp_servers=mcp_servers,
|
|
784
|
+
payload=payload,
|
|
713
785
|
user_id=user_id,
|
|
714
786
|
cancellation_token=self._cancel_event,
|
|
715
787
|
):
|
|
@@ -758,6 +830,13 @@ class ThreadState(rx.State):
|
|
|
758
830
|
# Capture filenames for message display
|
|
759
831
|
attachment_names = [f.filename for f in self.uploaded_files]
|
|
760
832
|
|
|
833
|
+
logger.debug(
|
|
834
|
+
"Begin processing: is_new_thread=%s, uploaded_files=%d, file_paths=%s",
|
|
835
|
+
is_new_thread,
|
|
836
|
+
len(self.uploaded_files),
|
|
837
|
+
file_paths,
|
|
838
|
+
)
|
|
839
|
+
|
|
761
840
|
# Add user message unless skipped (e.g., OAuth resend)
|
|
762
841
|
if self._skip_user_message:
|
|
763
842
|
self._skip_user_message = False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: appkit-assistant
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.1
|
|
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,14 +16,16 @@ 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.
|
|
19
|
+
Requires-Dist: anthropic>=0.77.0
|
|
20
20
|
Requires-Dist: appkit-commons
|
|
21
21
|
Requires-Dist: appkit-mantine
|
|
22
22
|
Requires-Dist: appkit-ui
|
|
23
|
-
Requires-Dist:
|
|
24
|
-
Requires-Dist:
|
|
25
|
-
Requires-Dist:
|
|
26
|
-
Requires-Dist:
|
|
23
|
+
Requires-Dist: apscheduler>=3.11.2
|
|
24
|
+
Requires-Dist: google-genai>=1.60.0
|
|
25
|
+
Requires-Dist: mcp>=1.26.0
|
|
26
|
+
Requires-Dist: openai>=2.16.0
|
|
27
|
+
Requires-Dist: python-multipart>=0.0.22
|
|
28
|
+
Requires-Dist: reflex>=0.8.26
|
|
27
29
|
Description-Content-Type: text/markdown
|
|
28
30
|
|
|
29
31
|
# appkit-assistant
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
appkit_assistant/configuration.py,sha256=9sz75L2ZJA7LXIkZG-uSSI_OTvIRuW9WQEusaMdYBLw,809
|
|
2
|
+
appkit_assistant/pages.py,sha256=gDvBweUO2WjrhP1RE5AAkjL1_S-givWr3CkkGZKws_E,471
|
|
3
|
+
appkit_assistant/backend/model_manager.py,sha256=ebXAjWsWMJBZ-ecbfbiEMNhJRnMl-N0VARCpmXhWYwA,4415
|
|
4
|
+
appkit_assistant/backend/schemas.py,sha256=jD78oIEs5a3R2dR-q9YBWF_udfnkuPWHuOVC6JOaIbc,3409
|
|
5
|
+
appkit_assistant/backend/system_prompt_cache.py,sha256=uITevz2x304FBlR9bvDNchl0GkCouT-lxWe-SmEbhn8,5514
|
|
6
|
+
appkit_assistant/backend/database/models.py,sha256=gFMJffF47iLOIYfuUR01mZeffjpXAwOZsarYaUe9pko,6460
|
|
7
|
+
appkit_assistant/backend/database/repositories.py,sha256=CRUJDEMSPeqIHj-pGerraNvfcE6W9TNW8K1P3Govkkc,8940
|
|
8
|
+
appkit_assistant/backend/models/__init__.py,sha256=PbY4qgpDxOmw-80YxjQngKZ52k9J_1KT3UfYxW1_Ut0,696
|
|
9
|
+
appkit_assistant/backend/models/anthropic.py,sha256=-GsS_xvlrniPlEXMDZgSz8nvRO8KdTkXiyDD38cb6nk,640
|
|
10
|
+
appkit_assistant/backend/models/google.py,sha256=Tfeg-JIbtgn4p5SSlAy38-S8xfOhyLyHMtCKC8ro1r8,591
|
|
11
|
+
appkit_assistant/backend/models/openai.py,sha256=VZ9bxOZFM4VVALdx1yfV9kIwD9N1fAAkVIP1cJZ18PY,956
|
|
12
|
+
appkit_assistant/backend/models/perplexity.py,sha256=16-pYghTIJdXAghHozVfpv_hHn2T8mVIC-2E3bhC0bQ,1237
|
|
13
|
+
appkit_assistant/backend/processors/__init__.py,sha256=DqN8cgPNUNCkCzwqeuYIlwj_S9Nh-t4K4sm3KkyJx0M,1053
|
|
14
|
+
appkit_assistant/backend/processors/claude_responses_processor.py,sha256=PfA9KRVcMxOpDd8AXYHWU_YZYSByymB1-r-SAYTPEHY,27430
|
|
15
|
+
appkit_assistant/backend/processors/gemini_responses_processor.py,sha256=wuhQNomj8_qnQ7_ZRMk_nAsNCkAx21mkDIziyzKuCuE,25210
|
|
16
|
+
appkit_assistant/backend/processors/lorem_ipsum_processor.py,sha256=iZLVCuYPb_lBG-p3Ug2QvuL28kEhtwhWL2Yy_WiYbrU,5201
|
|
17
|
+
appkit_assistant/backend/processors/mcp_mixin.py,sha256=Uj60p21GXAeNSmcfwMhUOuaWwGfr2ssAKSDjGwFMwls,9824
|
|
18
|
+
appkit_assistant/backend/processors/openai_base.py,sha256=hLg1uIlrcfQjsewQhBKg8_1kjnk1-Pc9Y1KVYUe8_BA,2348
|
|
19
|
+
appkit_assistant/backend/processors/openai_chat_completion_processor.py,sha256=WaYamOWazqjBxpEDhlpSysklAH0WWN86EhTGEOyPQyc,5084
|
|
20
|
+
appkit_assistant/backend/processors/openai_responses_processor.py,sha256=jVOnRxEzoPF9c3VJqecH_8-xEb7g9MFqC_qiQ4vcbY0,34078
|
|
21
|
+
appkit_assistant/backend/processors/perplexity_processor.py,sha256=U6YahaBdYtGOg6y9cOIGCjEer9aoNlDX7hHAXXQUupU,7092
|
|
22
|
+
appkit_assistant/backend/processors/processor_base.py,sha256=82WyLsOMjnnsmXao7lWF9HTtH6QcC7UD5gBkr4NSFHA,2352
|
|
23
|
+
appkit_assistant/backend/processors/streaming_base.py,sha256=fvkkbS2AJO1JEIJ3xKvfHU2dElJryorIVMqtF-EV7e0,5926
|
|
24
|
+
appkit_assistant/backend/services/auth_error_detector.py,sha256=eNdpxemtSEVKHkeD_bM52lEyv9G7etA5NNmhFCV4oZk,2692
|
|
25
|
+
appkit_assistant/backend/services/chunk_factory.py,sha256=CYeurM8v5VGloVV11lKmNksI4Ilu45XoPY-Qyp3Wl7E,8078
|
|
26
|
+
appkit_assistant/backend/services/citation_handler.py,sha256=Qfzn5knLRlW7hi7qoSS_R9y1HyfClT3_opbJRtWapSg,9521
|
|
27
|
+
appkit_assistant/backend/services/file_cleanup_service.py,sha256=sF7qRYZuSE9vXPlayBbPMg2yAEgtmAHi6zQ-kJYu4HI,11025
|
|
28
|
+
appkit_assistant/backend/services/file_manager.py,sha256=54SYphu6FsxbEYuMx8ohQiSAeY2gGDV1q3S6RZuNku0,3153
|
|
29
|
+
appkit_assistant/backend/services/file_upload_service.py,sha256=xQnp70MT2yxsFrq4rw9JSu-_4we2pQAlKIfT69wDnMU,32426
|
|
30
|
+
appkit_assistant/backend/services/file_validation.py,sha256=ULsfSa19jO7RZigKGmxqEqssfbvn-BFIAaDUlqKu8gM,3893
|
|
31
|
+
appkit_assistant/backend/services/mcp_auth_service.py,sha256=4qMLmoQWDalQ9kRsylJD2maeTyhklbYOHh2XTQJdh7A,27826
|
|
32
|
+
appkit_assistant/backend/services/mcp_token_service.py,sha256=sgGU6Zy5bIV4q2J9Yl1ReMWxP8UzAQMiV_4h2VQgj0s,1839
|
|
33
|
+
appkit_assistant/backend/services/message_converter.py,sha256=23W9mdY6g-OOf-p-uk0246GSvSJqqY2rozmHEtRISsw,9195
|
|
34
|
+
appkit_assistant/backend/services/openai_client_service.py,sha256=LxaAsXd18Tec5PRPJPHO5iVyAX84F9ovC-RT7EKJqJ4,3819
|
|
35
|
+
appkit_assistant/backend/services/response_accumulator.py,sha256=qiBVsjg3qaKbMQocv0l4lgSmipG-uiHsLaR2SRPhf-I,16070
|
|
36
|
+
appkit_assistant/backend/services/system_prompt_builder.py,sha256=_xIslLgHoxWZEYOqBGzk7iyu0-tQjifh-HXIi9Mh4ZY,2545
|
|
37
|
+
appkit_assistant/backend/services/thread_service.py,sha256=X7YC-SG18N-f-LhztVAuWJu0Z3z43ATJsLT-N9PAYbM,4730
|
|
38
|
+
appkit_assistant/components/__init__.py,sha256=XRU-I5HHx9Zf4ROlZKkONTEIRkR8JwQzWELS8xfN1g0,1059
|
|
39
|
+
appkit_assistant/components/composer.py,sha256=c3OF3bzeCv9bcOq0Y8wjEXL-0gEC8PLuDdDTXi-UfAc,8780
|
|
40
|
+
appkit_assistant/components/composer_key_handler.py,sha256=KyZYyhxzFR8DH_7F_DrvTFNT6v5kG6JihlGTmCv2wv0,1028
|
|
41
|
+
appkit_assistant/components/file_manager.py,sha256=O_7PTZJIYlF0m-bh2ZpBlrgctdnfAIeLblK5s_OD_2w,24750
|
|
42
|
+
appkit_assistant/components/mcp_oauth.py,sha256=puLwxAhmF25BjnZMdJbKIfC6bFXK2D8LybOX0kD7Ri4,1737
|
|
43
|
+
appkit_assistant/components/mcp_server_dialogs.py,sha256=UHsqZL1uED9dpFwggC4maJGrkSJrJqHAeABXK7DmAXY,23153
|
|
44
|
+
appkit_assistant/components/mcp_server_table.py,sha256=iRuhiZkh-lldd8ik5SBpOSYlL-Yws5l_SKyAbhENvC0,2686
|
|
45
|
+
appkit_assistant/components/message.py,sha256=risxedRZsaOCaH0ldL14D2HaBA9y0_Jk5nFxSc99F38,23686
|
|
46
|
+
appkit_assistant/components/system_prompt_editor.py,sha256=REl33zFmcpYRe9kxvFrBRYg40dV4L4FtVC_3ibLsmrU,2940
|
|
47
|
+
appkit_assistant/components/thread.py,sha256=N9JcPk2wKycAr9LjZCun2ogy5wfZme6JrQ-Dw-dprys,8462
|
|
48
|
+
appkit_assistant/components/threadlist.py,sha256=vw9nHcJ0ICeOvLRj8VewJo2cXVYqIiAMiJRZLSK_V90,4968
|
|
49
|
+
appkit_assistant/components/tools_modal.py,sha256=T3YVR5srunQFLIgrWXEnxcFXbrdsX38WetJGAC9z9kA,4076
|
|
50
|
+
appkit_assistant/state/file_manager_state.py,sha256=G0ZkgzFIqa6H2ctc5uID2aSxt-e78NWwdbyGvby8Q0E,25993
|
|
51
|
+
appkit_assistant/state/mcp_oauth_state.py,sha256=vWiCWolRY-sSUJGPEGHS-rUwlpomGKfpejZck9EImas,7703
|
|
52
|
+
appkit_assistant/state/mcp_server_state.py,sha256=TQOhnXEfuA5bWFh-5f5R5LfTZErXFXNZa4t0_vP1bGM,13174
|
|
53
|
+
appkit_assistant/state/system_prompt_state.py,sha256=E2jbBIGfgifvJRZFmEmeooWv5xihUfPbhFe8MzZAS0E,7714
|
|
54
|
+
appkit_assistant/state/thread_list_state.py,sha256=u26A5FOliTu7sBZVW3vQbO4uC2V0ss1D2DGKqrF8JDg,14066
|
|
55
|
+
appkit_assistant/state/thread_state.py,sha256=udIJWCORMuASIOZMnZidoyC6Te-qyDBkZfi7MOIHmKs,42937
|
|
56
|
+
appkit_assistant-1.0.1.dist-info/METADATA,sha256=ZHN1m1i_NXai64hZxZgTJR6fdDAdatB5gcSvqbE75W8,9574
|
|
57
|
+
appkit_assistant-1.0.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
58
|
+
appkit_assistant-1.0.1.dist-info/RECORD,,
|