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
@@ -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.mcp_auth_service import MCPAuthService
14
- from appkit_assistant.backend.models import MCPServer
15
- from appkit_assistant.backend.processor import mcp_oauth_redirect_uri
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 ThreadModel, ThreadStatus
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
- # Delete from database
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 file_manager
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.models import (
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.repositories import mcp_server_repo
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.find_all_ordered_by_name(session)
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("Uploaded file: %s", upload_file.filename)
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.17.3
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.40.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: google-genai>=1.52.0
24
- Requires-Dist: mcp>=1.0.0
25
- Requires-Dist: openai>=2.3.0
26
- Requires-Dist: reflex>=0.8.22
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,,