appkit-assistant 0.14.1__py3-none-any.whl → 0.15.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/mcp_auth_service.py +796 -0
- appkit_assistant/backend/model_manager.py +2 -1
- appkit_assistant/backend/models.py +43 -0
- appkit_assistant/backend/processors/openai_responses_processor.py +265 -36
- appkit_assistant/backend/repositories.py +1 -1
- appkit_assistant/backend/system_prompt_cache.py +5 -5
- appkit_assistant/components/mcp_server_dialogs.py +327 -21
- appkit_assistant/components/message.py +62 -0
- appkit_assistant/components/thread.py +99 -1
- appkit_assistant/state/mcp_server_state.py +42 -1
- appkit_assistant/state/system_prompt_state.py +4 -4
- appkit_assistant/state/thread_list_state.py +5 -5
- appkit_assistant/state/thread_state.py +190 -28
- {appkit_assistant-0.14.1.dist-info → appkit_assistant-0.15.1.dist-info}/METADATA +1 -1
- appkit_assistant-0.15.1.dist-info/RECORD +29 -0
- appkit_assistant-0.14.1.dist-info/RECORD +0 -28
- {appkit_assistant-0.14.1.dist-info → appkit_assistant-0.15.1.dist-info}/WHEEL +0 -0
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
5
5
|
from collections.abc import AsyncGenerator
|
|
6
|
+
from datetime import UTC, datetime
|
|
6
7
|
from typing import Any
|
|
7
8
|
|
|
8
9
|
import reflex as rx
|
|
9
10
|
|
|
10
|
-
from appkit_assistant.backend.models import MCPServer
|
|
11
|
+
from appkit_assistant.backend.models import MCPAuthType, MCPServer
|
|
11
12
|
from appkit_assistant.backend.repositories import (
|
|
12
13
|
mcp_server_repo,
|
|
13
14
|
)
|
|
@@ -73,12 +74,32 @@ class MCPServerState(rx.State):
|
|
|
73
74
|
"""Add a new MCP server."""
|
|
74
75
|
try:
|
|
75
76
|
headers = self._parse_headers_from_form(form_data)
|
|
77
|
+
auth_type = form_data.get("auth_type", MCPAuthType.API_KEY)
|
|
78
|
+
|
|
76
79
|
server_entity = MCPServer(
|
|
77
80
|
name=form_data["name"],
|
|
78
81
|
url=form_data["url"],
|
|
79
82
|
headers=headers,
|
|
80
83
|
description=form_data.get("description") or None,
|
|
81
84
|
prompt=form_data.get("prompt") or None,
|
|
85
|
+
auth_type=auth_type,
|
|
86
|
+
oauth_client_id=(
|
|
87
|
+
form_data.get("oauth_client_id")
|
|
88
|
+
if auth_type == MCPAuthType.OAUTH_DISCOVERY
|
|
89
|
+
else None
|
|
90
|
+
),
|
|
91
|
+
oauth_client_secret=(
|
|
92
|
+
form_data.get("oauth_client_secret")
|
|
93
|
+
if auth_type == MCPAuthType.OAUTH_DISCOVERY
|
|
94
|
+
else None
|
|
95
|
+
),
|
|
96
|
+
oauth_issuer=form_data.get("oauth_issuer"),
|
|
97
|
+
oauth_authorize_url=form_data.get("oauth_authorize_url"),
|
|
98
|
+
oauth_token_url=form_data.get("oauth_token_url"),
|
|
99
|
+
oauth_scopes=form_data.get("oauth_scopes"),
|
|
100
|
+
oauth_discovered_at=(
|
|
101
|
+
datetime.now(UTC) if form_data.get("oauth_issuer") else None
|
|
102
|
+
),
|
|
82
103
|
)
|
|
83
104
|
|
|
84
105
|
async with get_asyncdb_session() as session:
|
|
@@ -119,6 +140,7 @@ class MCPServerState(rx.State):
|
|
|
119
140
|
|
|
120
141
|
try:
|
|
121
142
|
headers = self._parse_headers_from_form(form_data)
|
|
143
|
+
auth_type = form_data.get("auth_type", MCPAuthType.API_KEY)
|
|
122
144
|
updated_name = ""
|
|
123
145
|
|
|
124
146
|
async with get_asyncdb_session() as session:
|
|
@@ -134,6 +156,25 @@ class MCPServerState(rx.State):
|
|
|
134
156
|
existing_server.headers = headers
|
|
135
157
|
existing_server.description = form_data.get("description") or None
|
|
136
158
|
existing_server.prompt = form_data.get("prompt") or None
|
|
159
|
+
existing_server.auth_type = auth_type
|
|
160
|
+
existing_server.oauth_client_id = (
|
|
161
|
+
form_data.get("oauth_client_id")
|
|
162
|
+
if auth_type == MCPAuthType.OAUTH_DISCOVERY
|
|
163
|
+
else None
|
|
164
|
+
)
|
|
165
|
+
existing_server.oauth_client_secret = (
|
|
166
|
+
form_data.get("oauth_client_secret")
|
|
167
|
+
if auth_type == MCPAuthType.OAUTH_DISCOVERY
|
|
168
|
+
else None
|
|
169
|
+
)
|
|
170
|
+
existing_server.oauth_issuer = form_data.get("oauth_issuer")
|
|
171
|
+
existing_server.oauth_authorize_url = form_data.get(
|
|
172
|
+
"oauth_authorize_url"
|
|
173
|
+
)
|
|
174
|
+
existing_server.oauth_token_url = form_data.get("oauth_token_url")
|
|
175
|
+
existing_server.oauth_scopes = form_data.get("oauth_scopes")
|
|
176
|
+
if form_data.get("oauth_issuer"):
|
|
177
|
+
existing_server.oauth_discovered_at = datetime.now(UTC)
|
|
137
178
|
|
|
138
179
|
updated_server = await mcp_server_repo.save(
|
|
139
180
|
session, existing_server
|
|
@@ -76,7 +76,7 @@ class SystemPromptState(State):
|
|
|
76
76
|
# Force textarea to re-render with loaded content
|
|
77
77
|
self.textarea_key += 1
|
|
78
78
|
|
|
79
|
-
logger.
|
|
79
|
+
logger.debug("Loaded %s system prompt versions", len(self.versions))
|
|
80
80
|
except Exception as exc:
|
|
81
81
|
self.error_message = f"Fehler beim Laden: {exc!s}"
|
|
82
82
|
logger.exception("Failed to load system prompt versions")
|
|
@@ -115,12 +115,12 @@ class SystemPromptState(State):
|
|
|
115
115
|
|
|
116
116
|
# Invalidate cache to force reload of new prompt version
|
|
117
117
|
await invalidate_prompt_cache()
|
|
118
|
-
logger.
|
|
118
|
+
logger.debug("System prompt cache invalidated after save")
|
|
119
119
|
|
|
120
120
|
await self.load_versions()
|
|
121
121
|
|
|
122
122
|
yield rx.toast.success("Neue Version erfolgreich gespeichert.")
|
|
123
|
-
logger.
|
|
123
|
+
logger.debug("Saved new system prompt version by user %s", user_id)
|
|
124
124
|
except Exception as exc:
|
|
125
125
|
self.error_message = f"Fehler beim Speichern: {exc!s}"
|
|
126
126
|
logger.exception("Failed to save system prompt")
|
|
@@ -154,7 +154,7 @@ class SystemPromptState(State):
|
|
|
154
154
|
|
|
155
155
|
# Invalidate cache since latest version might have changed
|
|
156
156
|
await invalidate_prompt_cache()
|
|
157
|
-
logger.
|
|
157
|
+
logger.debug("System prompt cache invalidated after deletion")
|
|
158
158
|
|
|
159
159
|
await self.load_versions()
|
|
160
160
|
yield rx.toast.success("Version erfolgreich gelöscht.")
|
|
@@ -88,7 +88,7 @@ class ThreadListState(rx.State):
|
|
|
88
88
|
|
|
89
89
|
# Handle user change
|
|
90
90
|
if self._current_user_id != current_user_id:
|
|
91
|
-
logger.
|
|
91
|
+
logger.debug(
|
|
92
92
|
"User changed from '%s' to '%s' - resetting state",
|
|
93
93
|
self._current_user_id or "(none)",
|
|
94
94
|
current_user_id or "(none)",
|
|
@@ -100,7 +100,7 @@ class ThreadListState(rx.State):
|
|
|
100
100
|
|
|
101
101
|
# Reset ThreadState
|
|
102
102
|
thread_state: ThreadState = await self.get_state(ThreadState)
|
|
103
|
-
thread_state.new_thread()
|
|
103
|
+
await thread_state.new_thread()
|
|
104
104
|
|
|
105
105
|
if self._initialized:
|
|
106
106
|
self.loading = False
|
|
@@ -239,7 +239,7 @@ class ThreadListState(rx.State):
|
|
|
239
239
|
self.active_thread_id = ""
|
|
240
240
|
# Reset ThreadState to empty thread
|
|
241
241
|
thread_state: ThreadState = await self.get_state(ThreadState)
|
|
242
|
-
thread_state.new_thread()
|
|
242
|
+
await thread_state.new_thread()
|
|
243
243
|
|
|
244
244
|
yield rx.toast.info(
|
|
245
245
|
f"Chat '{thread_to_delete.title}' gelöscht.",
|
|
@@ -265,7 +265,7 @@ class ThreadListState(rx.State):
|
|
|
265
265
|
# Late import to avoid circular dependency
|
|
266
266
|
from appkit_assistant.state.thread_state import ThreadState # noqa: PLC0415
|
|
267
267
|
|
|
268
|
-
logger.
|
|
268
|
+
logger.debug(
|
|
269
269
|
"Resetting ThreadListState on logout for user: %s",
|
|
270
270
|
self._current_user_id,
|
|
271
271
|
)
|
|
@@ -277,7 +277,7 @@ class ThreadListState(rx.State):
|
|
|
277
277
|
|
|
278
278
|
# Reset ThreadState
|
|
279
279
|
thread_state: ThreadState = await self.get_state(ThreadState)
|
|
280
|
-
thread_state.new_thread()
|
|
280
|
+
await thread_state.new_thread()
|
|
281
281
|
|
|
282
282
|
logger.debug("ThreadListState reset complete")
|
|
283
283
|
|
|
@@ -10,6 +10,7 @@ This module contains ThreadState which manages the current active thread:
|
|
|
10
10
|
See thread_list_state.py for ThreadListState which manages the thread list sidebar.
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
+
import json
|
|
13
14
|
import logging
|
|
14
15
|
import uuid
|
|
15
16
|
from collections.abc import AsyncGenerator
|
|
@@ -98,17 +99,30 @@ class ThreadState(rx.State):
|
|
|
98
99
|
temp_selected_mcp_servers: list[int] = []
|
|
99
100
|
server_selection_state: dict[int, bool] = {}
|
|
100
101
|
|
|
102
|
+
# MCP OAuth state
|
|
103
|
+
pending_auth_server_id: str = ""
|
|
104
|
+
pending_auth_server_name: str = ""
|
|
105
|
+
pending_auth_url: str = ""
|
|
106
|
+
show_auth_card: bool = False
|
|
107
|
+
pending_oauth_message: str = "" # Message that triggered OAuth, resent on success
|
|
108
|
+
|
|
101
109
|
# Thread list integration
|
|
102
110
|
with_thread_list: bool = False
|
|
103
111
|
|
|
104
112
|
# Internal state
|
|
105
113
|
_initialized: bool = False
|
|
106
114
|
_current_user_id: str = ""
|
|
115
|
+
_skip_user_message: bool = False # Skip adding user message (for OAuth resend)
|
|
107
116
|
|
|
108
117
|
# -------------------------------------------------------------------------
|
|
109
118
|
# Computed properties
|
|
110
119
|
# -------------------------------------------------------------------------
|
|
111
120
|
|
|
121
|
+
@rx.var
|
|
122
|
+
def current_user_id(self) -> str:
|
|
123
|
+
"""Get the current user ID for OAuth validation."""
|
|
124
|
+
return self._current_user_id
|
|
125
|
+
|
|
112
126
|
@rx.var
|
|
113
127
|
def get_selected_model(self) -> str:
|
|
114
128
|
"""Get the currently selected model ID."""
|
|
@@ -168,20 +182,44 @@ class ThreadState(rx.State):
|
|
|
168
182
|
# -------------------------------------------------------------------------
|
|
169
183
|
|
|
170
184
|
@rx.event
|
|
171
|
-
def initialize(self) -> None:
|
|
185
|
+
async def initialize(self) -> None:
|
|
172
186
|
"""Initialize the state with models and a new empty thread.
|
|
173
187
|
|
|
174
188
|
Only initializes once per user session. Resets when user changes.
|
|
175
189
|
"""
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
190
|
+
user_session: UserSession = await self.get_state(UserSession)
|
|
191
|
+
user = await user_session.authenticated_user
|
|
192
|
+
current_user_id = str(user.user_id) if user else ""
|
|
193
|
+
|
|
194
|
+
# If already initialized and user hasn't changed, skip
|
|
195
|
+
if self._initialized and self._current_user_id == current_user_id:
|
|
196
|
+
logger.debug(
|
|
197
|
+
"Thread state already initialized for user %s", current_user_id
|
|
198
|
+
)
|
|
179
199
|
return
|
|
180
200
|
|
|
181
201
|
model_manager = ModelManager()
|
|
182
|
-
|
|
202
|
+
all_models = model_manager.get_all_models()
|
|
183
203
|
self.selected_model = model_manager.get_default_model()
|
|
184
204
|
|
|
205
|
+
# Filter models based on user roles
|
|
206
|
+
user_roles = user.roles if user else []
|
|
207
|
+
|
|
208
|
+
self.ai_models = [
|
|
209
|
+
m
|
|
210
|
+
for m in all_models
|
|
211
|
+
if not m.requires_role or m.requires_role in user_roles
|
|
212
|
+
]
|
|
213
|
+
|
|
214
|
+
# If selected model is not in available models, pick the first one
|
|
215
|
+
available_model_ids = [m.id for m in self.ai_models]
|
|
216
|
+
if self.selected_model not in available_model_ids:
|
|
217
|
+
if available_model_ids:
|
|
218
|
+
self.selected_model = available_model_ids[0]
|
|
219
|
+
else:
|
|
220
|
+
logger.warning("No models available for user")
|
|
221
|
+
self.selected_model = ""
|
|
222
|
+
|
|
185
223
|
self._thread = ThreadModel(
|
|
186
224
|
thread_id=str(uuid.uuid4()),
|
|
187
225
|
title="Neuer Chat",
|
|
@@ -196,11 +234,12 @@ class ThreadState(rx.State):
|
|
|
196
234
|
self.image_chunks = []
|
|
197
235
|
self.prompt = ""
|
|
198
236
|
self.show_thinking = False
|
|
237
|
+
self._current_user_id = current_user_id
|
|
199
238
|
self._initialized = True
|
|
200
239
|
logger.debug("Initialized thread state: %s", self._thread.thread_id)
|
|
201
240
|
|
|
202
241
|
@rx.event
|
|
203
|
-
def new_thread(self) -> None:
|
|
242
|
+
async def new_thread(self) -> None:
|
|
204
243
|
"""Create a new empty thread (not persisted, not in list yet).
|
|
205
244
|
|
|
206
245
|
Called when user clicks "New Chat" or when active thread is deleted.
|
|
@@ -208,7 +247,7 @@ class ThreadState(rx.State):
|
|
|
208
247
|
"""
|
|
209
248
|
# Ensure state is initialized first
|
|
210
249
|
if not self._initialized:
|
|
211
|
-
self.initialize()
|
|
250
|
+
await self.initialize()
|
|
212
251
|
|
|
213
252
|
# Don't create new if current thread is already empty
|
|
214
253
|
if self._thread.state == ThreadStatus.NEW and not self.messages:
|
|
@@ -277,8 +316,6 @@ class ThreadState(rx.State):
|
|
|
277
316
|
|
|
278
317
|
if not thread_entity:
|
|
279
318
|
logger.warning("Thread %s not found in database", thread_id)
|
|
280
|
-
# We can't access self in here easily to clear loading state unless we break out
|
|
281
|
-
# but we can check thread_entity after context
|
|
282
319
|
|
|
283
320
|
# Convert to ThreadModel if found
|
|
284
321
|
full_thread = None
|
|
@@ -292,14 +329,13 @@ class ThreadState(rx.State):
|
|
|
292
329
|
messages=[Message(**m) for m in thread_entity.messages],
|
|
293
330
|
)
|
|
294
331
|
|
|
295
|
-
if not full_thread:
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
return
|
|
332
|
+
if not full_thread and not thread_entity: # it was not found
|
|
333
|
+
async with self:
|
|
334
|
+
threadlist_state: ThreadListState = await self.get_state(
|
|
335
|
+
ThreadListState
|
|
336
|
+
)
|
|
337
|
+
threadlist_state.loading_thread_id = ""
|
|
338
|
+
return
|
|
303
339
|
|
|
304
340
|
# Mark all messages as done (loaded from DB)
|
|
305
341
|
if full_thread:
|
|
@@ -347,9 +383,15 @@ class ThreadState(rx.State):
|
|
|
347
383
|
self.prompt = prompt
|
|
348
384
|
|
|
349
385
|
@rx.event
|
|
350
|
-
def set_suggestions(self, suggestions: list[Suggestion]) -> None:
|
|
351
|
-
"""Set custom suggestions for the thread.
|
|
352
|
-
|
|
386
|
+
def set_suggestions(self, suggestions: list[Suggestion] | list[dict]) -> None:
|
|
387
|
+
"""Set custom suggestions for the thread.
|
|
388
|
+
|
|
389
|
+
Accepts either Suggestion objects or dicts (for Reflex serialization).
|
|
390
|
+
"""
|
|
391
|
+
if suggestions and isinstance(suggestions[0], dict):
|
|
392
|
+
self.suggestions = [Suggestion(**s) for s in suggestions]
|
|
393
|
+
else:
|
|
394
|
+
self.suggestions = suggestions # type: ignore[assignment]
|
|
353
395
|
|
|
354
396
|
@rx.event
|
|
355
397
|
def set_selected_model(self, model_id: str) -> None:
|
|
@@ -465,12 +507,17 @@ class ThreadState(rx.State):
|
|
|
465
507
|
)
|
|
466
508
|
return
|
|
467
509
|
|
|
510
|
+
async with self:
|
|
511
|
+
user_session: UserSession = await self.get_state(UserSession)
|
|
512
|
+
user_id = user_session.user.user_id if user_session.user else None
|
|
513
|
+
|
|
468
514
|
first_response_received = False
|
|
469
515
|
try:
|
|
470
516
|
async for chunk in processor.process(
|
|
471
517
|
self.messages,
|
|
472
518
|
selected_model,
|
|
473
519
|
mcp_servers=mcp_servers,
|
|
520
|
+
user_id=user_id,
|
|
474
521
|
):
|
|
475
522
|
first_response_received = await self._handle_stream_chunk(
|
|
476
523
|
chunk=chunk,
|
|
@@ -508,12 +555,16 @@ class ThreadState(rx.State):
|
|
|
508
555
|
self.prompt = ""
|
|
509
556
|
|
|
510
557
|
is_new_thread = self._thread.state == ThreadStatus.NEW
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
558
|
+
|
|
559
|
+
# Add user message unless skipped (e.g., OAuth resend)
|
|
560
|
+
if self._skip_user_message:
|
|
561
|
+
self._skip_user_message = False
|
|
562
|
+
else:
|
|
563
|
+
self.messages.append(
|
|
564
|
+
Message(text=current_prompt, type=MessageType.HUMAN)
|
|
565
|
+
)
|
|
566
|
+
# Always add assistant placeholder
|
|
567
|
+
self.messages.append(Message(text="", type=MessageType.ASSISTANT))
|
|
517
568
|
|
|
518
569
|
selected_model = self.get_selected_model
|
|
519
570
|
if not selected_model:
|
|
@@ -561,7 +612,9 @@ class ThreadState(rx.State):
|
|
|
561
612
|
"""Finalize state after a successful full response."""
|
|
562
613
|
async with self:
|
|
563
614
|
self.show_thinking = False
|
|
564
|
-
|
|
615
|
+
# Convert Reflex proxy list to standard list to avoid Pydantic
|
|
616
|
+
# serializer warnings
|
|
617
|
+
self._thread.messages = list(self.messages) # noqa: E501
|
|
565
618
|
self._thread.ai_model = self.selected_model
|
|
566
619
|
|
|
567
620
|
if self.with_thread_list:
|
|
@@ -588,7 +641,9 @@ class ThreadState(rx.State):
|
|
|
588
641
|
self._thread.title = current_prompt[:100]
|
|
589
642
|
await self._notify_thread_created()
|
|
590
643
|
|
|
591
|
-
|
|
644
|
+
# Convert Reflex proxy list to standard list to avoid Pydantic serializer
|
|
645
|
+
# warnings
|
|
646
|
+
self._thread.messages = list(self.messages) # noqa: E501
|
|
592
647
|
if self.with_thread_list:
|
|
593
648
|
await self._save_thread_to_db()
|
|
594
649
|
|
|
@@ -716,6 +771,8 @@ class ThreadState(rx.State):
|
|
|
716
771
|
elif chunk.type == ChunkType.COMPLETION:
|
|
717
772
|
self.show_thinking = False
|
|
718
773
|
logger.debug("Response generation completed")
|
|
774
|
+
elif chunk.type == ChunkType.AUTH_REQUIRED:
|
|
775
|
+
self._handle_auth_required_chunk(chunk)
|
|
719
776
|
elif chunk.type == ChunkType.ERROR:
|
|
720
777
|
self.messages.append(Message(text=chunk.text, type=MessageType.ERROR))
|
|
721
778
|
logger.error("Chunk error: %s", chunk.text)
|
|
@@ -844,6 +901,111 @@ class ThreadState(rx.State):
|
|
|
844
901
|
|
|
845
902
|
self.thinking_items = self.thinking_items.copy()
|
|
846
903
|
|
|
904
|
+
def _handle_auth_required_chunk(self, chunk: Chunk) -> None:
|
|
905
|
+
"""Handle AUTH_REQUIRED chunks by showing the auth card."""
|
|
906
|
+
self.pending_auth_server_id = chunk.chunk_metadata.get("server_id", "")
|
|
907
|
+
self.pending_auth_server_name = chunk.chunk_metadata.get("server_name", "")
|
|
908
|
+
self.pending_auth_url = chunk.chunk_metadata.get("auth_url", "")
|
|
909
|
+
self.show_auth_card = True
|
|
910
|
+
# Store the last user message to resend after successful OAuth
|
|
911
|
+
for msg in reversed(self.messages):
|
|
912
|
+
if msg.type == MessageType.HUMAN:
|
|
913
|
+
self.pending_oauth_message = msg.text
|
|
914
|
+
break
|
|
915
|
+
logger.debug(
|
|
916
|
+
"Auth required for server %s, showing auth card, pending message: %s",
|
|
917
|
+
self.pending_auth_server_name,
|
|
918
|
+
self.pending_oauth_message[:50] if self.pending_oauth_message else "None",
|
|
919
|
+
)
|
|
920
|
+
|
|
921
|
+
@rx.event
|
|
922
|
+
def start_mcp_oauth(self) -> rx.event.EventSpec:
|
|
923
|
+
"""Start the OAuth flow by opening the auth URL in a popup window."""
|
|
924
|
+
if not self.pending_auth_url:
|
|
925
|
+
return rx.toast.error("Keine Authentifizierungs-URL verfügbar")
|
|
926
|
+
|
|
927
|
+
# NOTE: We do not append server_id here anymore to avoid errors with strict
|
|
928
|
+
# OAuth providers. server_id must be recovered from the state parameter in the
|
|
929
|
+
# callback.
|
|
930
|
+
auth_url = self.pending_auth_url
|
|
931
|
+
|
|
932
|
+
return rx.call_script(
|
|
933
|
+
f"window.open('{auth_url}', 'mcp_oauth', 'width=600,height=700')"
|
|
934
|
+
)
|
|
935
|
+
|
|
936
|
+
@rx.event
|
|
937
|
+
async def handle_mcp_oauth_success(
|
|
938
|
+
self, server_id: str, server_name: str
|
|
939
|
+
) -> AsyncGenerator[Any, Any]:
|
|
940
|
+
"""Handle successful OAuth completion from popup window."""
|
|
941
|
+
logger.debug("OAuth success for server %s (%s)", server_name, server_id)
|
|
942
|
+
self.show_auth_card = False
|
|
943
|
+
self.pending_auth_server_id = ""
|
|
944
|
+
self.pending_auth_server_name = ""
|
|
945
|
+
self.pending_auth_url = ""
|
|
946
|
+
|
|
947
|
+
# Check if we have a pending message to resend
|
|
948
|
+
pending_message = self.pending_oauth_message
|
|
949
|
+
self.pending_oauth_message = ""
|
|
950
|
+
|
|
951
|
+
if pending_message:
|
|
952
|
+
# Remove the incomplete assistant message from the failed attempt
|
|
953
|
+
if self.messages and self.messages[-1].type == MessageType.ASSISTANT:
|
|
954
|
+
self.messages = self.messages[:-1]
|
|
955
|
+
# Add success message
|
|
956
|
+
self.messages.append(
|
|
957
|
+
Message(
|
|
958
|
+
text=f"Erfolgreich mit {server_name} verbunden. "
|
|
959
|
+
"Anfrage wird erneut gesendet...",
|
|
960
|
+
type=MessageType.INFO,
|
|
961
|
+
)
|
|
962
|
+
)
|
|
963
|
+
# Resend the original message by setting prompt and yielding the event
|
|
964
|
+
self.prompt = pending_message
|
|
965
|
+
self._skip_user_message = True # User message already in list
|
|
966
|
+
yield ThreadState.submit_message
|
|
967
|
+
else:
|
|
968
|
+
# No pending message - just show success
|
|
969
|
+
self.messages.append(
|
|
970
|
+
Message(
|
|
971
|
+
text=f"Erfolgreich mit {server_name} verbunden.",
|
|
972
|
+
type=MessageType.INFO,
|
|
973
|
+
)
|
|
974
|
+
)
|
|
975
|
+
|
|
976
|
+
@rx.event
|
|
977
|
+
def handle_mcp_oauth_success_from_js(self) -> rx.event.EventSpec:
|
|
978
|
+
"""Handle OAuth success triggered from JS - retrieves data from window."""
|
|
979
|
+
return rx.call_script(
|
|
980
|
+
"window._mcpOAuthData ? JSON.stringify(window._mcpOAuthData) : '{}'",
|
|
981
|
+
callback=ThreadState.process_oauth_success_data,
|
|
982
|
+
)
|
|
983
|
+
|
|
984
|
+
@rx.event
|
|
985
|
+
async def process_oauth_success_data(
|
|
986
|
+
self, data_str: str
|
|
987
|
+
) -> AsyncGenerator[Any, Any]:
|
|
988
|
+
"""Process OAuth success data retrieved from window."""
|
|
989
|
+
try:
|
|
990
|
+
data = json.loads(data_str) if data_str else {}
|
|
991
|
+
server_id = data.get("serverId", "")
|
|
992
|
+
server_name = data.get("serverName", "Unknown")
|
|
993
|
+
logger.info(
|
|
994
|
+
"Processing OAuth success from JS: server_id=%s, server_name=%s",
|
|
995
|
+
server_id,
|
|
996
|
+
server_name,
|
|
997
|
+
)
|
|
998
|
+
# Yield events from handle_mcp_oauth_success
|
|
999
|
+
async for event in self.handle_mcp_oauth_success(server_id, server_name):
|
|
1000
|
+
yield event
|
|
1001
|
+
except json.JSONDecodeError:
|
|
1002
|
+
logger.warning("Failed to parse OAuth data from JS: %s", data_str)
|
|
1003
|
+
|
|
1004
|
+
@rx.event
|
|
1005
|
+
def dismiss_auth_card(self) -> None:
|
|
1006
|
+
"""Dismiss the auth card without authenticating."""
|
|
1007
|
+
self.show_auth_card = False
|
|
1008
|
+
|
|
847
1009
|
def _add_error_message(self, error_msg: str) -> None:
|
|
848
1010
|
"""Add an error message to the conversation."""
|
|
849
1011
|
logger.error(error_msg)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
appkit_assistant/configuration.py,sha256=3nBL-dEGYsvSnRDNpxtikZn4QMMkMlNbb4VqGOPolJI,346
|
|
2
|
+
appkit_assistant/backend/mcp_auth_service.py,sha256=lYQEe4yOZ48ear6dvcuOXsaOc6RClIBMsOOkV7SG5Aw,27768
|
|
3
|
+
appkit_assistant/backend/model_manager.py,sha256=fmv3yP63LxDnne4vjT7IzETTI2aSxViC2FSUfHQajlk,4382
|
|
4
|
+
appkit_assistant/backend/models.py,sha256=y9pjoHMtkaGTO1CVrjcc3T4FoOn3ROnaust9c3OIaVQ,7488
|
|
5
|
+
appkit_assistant/backend/processor.py,sha256=dhBg3pYXdmpj9JtAJc-d83SeUA1NsICj1C_YI0M2QYE,1289
|
|
6
|
+
appkit_assistant/backend/repositories.py,sha256=R-7kYdxg4RWQrTEOU4tbcOEhJA_FlesWrt65UpItRSU,5547
|
|
7
|
+
appkit_assistant/backend/system_prompt_cache.py,sha256=83OIyixeTb3HKOy3XIzPyTAE-G2JyqrfcG8xVeTS2Ls,5514
|
|
8
|
+
appkit_assistant/backend/processors/lorem_ipsum_processor.py,sha256=j-MZhzibrtabzbGB2Pf4Xcdlr1TlTYWNRdE22LsDp9Q,4635
|
|
9
|
+
appkit_assistant/backend/processors/openai_base.py,sha256=IQS4m375BOD_K0PBFOk4i7wL1z5MEiPFxbSmC-HBNgU,4414
|
|
10
|
+
appkit_assistant/backend/processors/openai_chat_completion_processor.py,sha256=nTxouoXDU6VcQr8UhA2KiMNt60KvIwM8cH9Z8lo4dXY,4218
|
|
11
|
+
appkit_assistant/backend/processors/openai_responses_processor.py,sha256=W_AHBrX8-gir0kh6oyy72YwlHDQBIDlntNdxgO4NbP8,28180
|
|
12
|
+
appkit_assistant/backend/processors/perplexity_processor.py,sha256=weHukv78MSCF_uSCKGSMpNYHsET9OB8IhpvUiMfPQ8A,3355
|
|
13
|
+
appkit_assistant/components/__init__.py,sha256=5tzK5VjX9FGKK-qTUHLjr8-ohT4ykb4a-zC-I3yeRLY,916
|
|
14
|
+
appkit_assistant/components/composer.py,sha256=F4VPxWp4P6fvTW4rQ7S-YWn0eje5c3jGsWrpC1aewss,3885
|
|
15
|
+
appkit_assistant/components/composer_key_handler.py,sha256=KyZYyhxzFR8DH_7F_DrvTFNT6v5kG6JihlGTmCv2wv0,1028
|
|
16
|
+
appkit_assistant/components/mcp_server_dialogs.py,sha256=afIImmhfrNyLmxDZBpCxHxvD8HKpDanIloLEC8dJgro,23444
|
|
17
|
+
appkit_assistant/components/mcp_server_table.py,sha256=1dziN7hDDvE8Y3XcdIs0wUPv1H64kP9gRAEjgH9Yvzo,2323
|
|
18
|
+
appkit_assistant/components/message.py,sha256=gbHp3VJmOTS6aF4QOhNhCrvh5ra38UhJxVTLtY8_o6A,11927
|
|
19
|
+
appkit_assistant/components/system_prompt_editor.py,sha256=REl33zFmcpYRe9kxvFrBRYg40dV4L4FtVC_3ibLsmrU,2940
|
|
20
|
+
appkit_assistant/components/thread.py,sha256=Hyb09R_zsRUzFXN4a2ndkbH-EvHy9Pnl-Q3y6-djjP0,12660
|
|
21
|
+
appkit_assistant/components/threadlist.py,sha256=1xVakSTQYi5-wgED3fTJVggeIjL_fkthehce0wKUYtM,4896
|
|
22
|
+
appkit_assistant/components/tools_modal.py,sha256=12iiAVahy3j4JwjGfRlegVEa4ePhGsEu7Bq92JLn1ZI,3353
|
|
23
|
+
appkit_assistant/state/mcp_server_state.py,sha256=3AFvy53xx_eLTxw-LfJklPTgq4Ohqu4xs1QlLs-kU4U,11387
|
|
24
|
+
appkit_assistant/state/system_prompt_state.py,sha256=zdnYrTnl7EszALRiodu6pcuQUd2tmtPG1eJ10j_OotI,7705
|
|
25
|
+
appkit_assistant/state/thread_list_state.py,sha256=DEOR5Nklj1qfYaxSRMXCZdZRv2iq2Jb37JSg739_wL4,10250
|
|
26
|
+
appkit_assistant/state/thread_state.py,sha256=3LNnEmdp6oU8npt1MEUBYA31cdF-QAP_-Nfh7sVCEFQ,38735
|
|
27
|
+
appkit_assistant-0.15.1.dist-info/METADATA,sha256=GC_7LbC349aBKoylne3lYED40EY-Ynz2ZVjlT1ZVnGY,9403
|
|
28
|
+
appkit_assistant-0.15.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
29
|
+
appkit_assistant-0.15.1.dist-info/RECORD,,
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
appkit_assistant/configuration.py,sha256=3nBL-dEGYsvSnRDNpxtikZn4QMMkMlNbb4VqGOPolJI,346
|
|
2
|
-
appkit_assistant/backend/model_manager.py,sha256=7f65UbZ51qOYM6F73eKbZ8hMnCzxdanFZKgdKF8bbCk,4366
|
|
3
|
-
appkit_assistant/backend/models.py,sha256=-sr10xChq_lMlLpt7Jbmq4VGweuW4_UUwYsRtIY7HFY,6015
|
|
4
|
-
appkit_assistant/backend/processor.py,sha256=dhBg3pYXdmpj9JtAJc-d83SeUA1NsICj1C_YI0M2QYE,1289
|
|
5
|
-
appkit_assistant/backend/repositories.py,sha256=arYagqYDgn65uFvnFTXP6zaXOn1c3zoAYuPO69-qrSk,5546
|
|
6
|
-
appkit_assistant/backend/system_prompt_cache.py,sha256=4a0sX3qzz3XTF7MX4YJuH29eaekmEx6jciY1zDKcuQ8,5509
|
|
7
|
-
appkit_assistant/backend/processors/lorem_ipsum_processor.py,sha256=j-MZhzibrtabzbGB2Pf4Xcdlr1TlTYWNRdE22LsDp9Q,4635
|
|
8
|
-
appkit_assistant/backend/processors/openai_base.py,sha256=IQS4m375BOD_K0PBFOk4i7wL1z5MEiPFxbSmC-HBNgU,4414
|
|
9
|
-
appkit_assistant/backend/processors/openai_chat_completion_processor.py,sha256=nTxouoXDU6VcQr8UhA2KiMNt60KvIwM8cH9Z8lo4dXY,4218
|
|
10
|
-
appkit_assistant/backend/processors/openai_responses_processor.py,sha256=Ns8owrvimtZofyyzhoTgi2t_P0feEgLzooJVfCxC3kw,18800
|
|
11
|
-
appkit_assistant/backend/processors/perplexity_processor.py,sha256=weHukv78MSCF_uSCKGSMpNYHsET9OB8IhpvUiMfPQ8A,3355
|
|
12
|
-
appkit_assistant/components/__init__.py,sha256=5tzK5VjX9FGKK-qTUHLjr8-ohT4ykb4a-zC-I3yeRLY,916
|
|
13
|
-
appkit_assistant/components/composer.py,sha256=F4VPxWp4P6fvTW4rQ7S-YWn0eje5c3jGsWrpC1aewss,3885
|
|
14
|
-
appkit_assistant/components/composer_key_handler.py,sha256=KyZYyhxzFR8DH_7F_DrvTFNT6v5kG6JihlGTmCv2wv0,1028
|
|
15
|
-
appkit_assistant/components/mcp_server_dialogs.py,sha256=00BL6xdHeWcW1MMGJrmUFjiZ-Ksa22L-lydqPihZJwk,11451
|
|
16
|
-
appkit_assistant/components/mcp_server_table.py,sha256=1dziN7hDDvE8Y3XcdIs0wUPv1H64kP9gRAEjgH9Yvzo,2323
|
|
17
|
-
appkit_assistant/components/message.py,sha256=Tr19CsRqKiMP_fhSByVlUtigeXF13duR6Rp2mQ08IeQ,9636
|
|
18
|
-
appkit_assistant/components/system_prompt_editor.py,sha256=REl33zFmcpYRe9kxvFrBRYg40dV4L4FtVC_3ibLsmrU,2940
|
|
19
|
-
appkit_assistant/components/thread.py,sha256=rYM4LA6PVdEvZ5oz5ZtheVfQfFuvTXTIskTt15kI1kg,7886
|
|
20
|
-
appkit_assistant/components/threadlist.py,sha256=1xVakSTQYi5-wgED3fTJVggeIjL_fkthehce0wKUYtM,4896
|
|
21
|
-
appkit_assistant/components/tools_modal.py,sha256=12iiAVahy3j4JwjGfRlegVEa4ePhGsEu7Bq92JLn1ZI,3353
|
|
22
|
-
appkit_assistant/state/mcp_server_state.py,sha256=DJJ9LVIMk-RXio-BbeZv6J9eShqg_dNANDdINkZECpc,9287
|
|
23
|
-
appkit_assistant/state/system_prompt_state.py,sha256=iwjz3ABr1GGFau7sTP3c-5vfc0VYer_1mPbBNhOZsys,7701
|
|
24
|
-
appkit_assistant/state/thread_list_state.py,sha256=uDWPJ82ZqSSj8jos1P2NmbRSm-BZz5xwenl9aMzrpHo,10230
|
|
25
|
-
appkit_assistant/state/thread_state.py,sha256=8nAALYOa55GVoonVcf57rusUcsVEatn-zcofeLdBtfk,31882
|
|
26
|
-
appkit_assistant-0.14.1.dist-info/METADATA,sha256=-1mHhd3lEzev9gqZR_c-6sM93yEhx9pAaSfTRUYq22k,9403
|
|
27
|
-
appkit_assistant-0.14.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
28
|
-
appkit_assistant-0.14.1.dist-info/RECORD,,
|
|
File without changes
|