appkit-assistant 0.17.0__tar.gz → 0.17.1__tar.gz
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-0.17.0 → appkit_assistant-0.17.1}/PKG-INFO +1 -1
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/pyproject.toml +1 -1
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/models.py +3 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/processors/gemini_responses_processor.py +3 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/processors/openai_chat_completion_processor.py +25 -8
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/response_accumulator.py +8 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/components/message.py +185 -50
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/state/thread_state.py +136 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/.gitignore +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/README.md +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/docs/assistant.png +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/file_manager.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/mcp_auth_service.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/model_manager.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/processor.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/processors/claude_base.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/processors/claude_responses_processor.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/processors/gemini_base.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/processors/lorem_ipsum_processor.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/processors/openai_base.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/processors/openai_responses_processor.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/processors/perplexity_processor.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/repositories.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/services/thread_service.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/system_prompt_cache.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/components/__init__.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/components/composer.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/components/composer_key_handler.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/components/mcp_oauth.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/components/mcp_server_dialogs.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/components/mcp_server_table.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/components/system_prompt_editor.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/components/thread.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/components/threadlist.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/components/tools_modal.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/configuration.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/pages.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/state/mcp_oauth_state.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/state/mcp_server_state.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/state/system_prompt_state.py +0 -0
- {appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/state/thread_list_state.py +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import uuid
|
|
2
3
|
from datetime import UTC, datetime
|
|
3
4
|
from enum import StrEnum
|
|
4
5
|
from typing import Any
|
|
@@ -82,7 +83,9 @@ class MessageType(StrEnum):
|
|
|
82
83
|
|
|
83
84
|
|
|
84
85
|
class Message(BaseModel):
|
|
86
|
+
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
85
87
|
text: str
|
|
88
|
+
original_text: str | None = None # To store original text if edited
|
|
86
89
|
editable: bool = False
|
|
87
90
|
type: MessageType
|
|
88
91
|
done: bool = False
|
|
@@ -643,6 +643,9 @@ class GeminiResponsesProcessor(BaseGeminiProcessor):
|
|
|
643
643
|
content = candidate.content
|
|
644
644
|
|
|
645
645
|
# List comprehension for text parts
|
|
646
|
+
if not content.parts:
|
|
647
|
+
return None
|
|
648
|
+
|
|
646
649
|
text_parts = [part.text for part in content.parts if part.text]
|
|
647
650
|
|
|
648
651
|
if text_parts:
|
|
@@ -66,23 +66,40 @@ class OpenAIChatCompletionsProcessor(BaseOpenAIProcessor):
|
|
|
66
66
|
if event.choices and event.choices[0].delta:
|
|
67
67
|
content = event.choices[0].delta.content
|
|
68
68
|
if content:
|
|
69
|
-
yield self._create_chunk(
|
|
69
|
+
yield self._create_chunk(
|
|
70
|
+
content,
|
|
71
|
+
model.model,
|
|
72
|
+
stream=True,
|
|
73
|
+
message_id=event.id,
|
|
74
|
+
)
|
|
70
75
|
else:
|
|
71
76
|
content = session.choices[0].message.content
|
|
72
77
|
if content:
|
|
73
|
-
yield self._create_chunk(
|
|
78
|
+
yield self._create_chunk(
|
|
79
|
+
content, model.model, message_id=session.id
|
|
80
|
+
)
|
|
74
81
|
except Exception as e:
|
|
75
82
|
raise e
|
|
76
83
|
|
|
77
|
-
def _create_chunk(
|
|
84
|
+
def _create_chunk(
|
|
85
|
+
self,
|
|
86
|
+
content: str,
|
|
87
|
+
model: str,
|
|
88
|
+
stream: bool = False,
|
|
89
|
+
message_id: str | None = None,
|
|
90
|
+
) -> Chunk:
|
|
91
|
+
metadata = {
|
|
92
|
+
"source": "chat_completions",
|
|
93
|
+
"streaming": str(stream),
|
|
94
|
+
"model": model,
|
|
95
|
+
}
|
|
96
|
+
if message_id:
|
|
97
|
+
metadata["message_id"] = message_id
|
|
98
|
+
|
|
78
99
|
return Chunk(
|
|
79
100
|
type=ChunkType.TEXT,
|
|
80
101
|
text=content,
|
|
81
|
-
chunk_metadata=
|
|
82
|
-
"source": "chat_completions",
|
|
83
|
-
"streaming": str(stream),
|
|
84
|
-
"model": model,
|
|
85
|
-
},
|
|
102
|
+
chunk_metadata=metadata,
|
|
86
103
|
)
|
|
87
104
|
|
|
88
105
|
def _convert_messages_to_openai_format(
|
|
@@ -52,6 +52,14 @@ class ResponseAccumulator:
|
|
|
52
52
|
|
|
53
53
|
def process_chunk(self, chunk: Chunk) -> None:
|
|
54
54
|
"""Process a single chunk and update internal state."""
|
|
55
|
+
# Update message ID if provided in metadata
|
|
56
|
+
if (
|
|
57
|
+
self.messages
|
|
58
|
+
and self.messages[-1].type == MessageType.ASSISTANT
|
|
59
|
+
and "message_id" in chunk.chunk_metadata
|
|
60
|
+
):
|
|
61
|
+
self.messages[-1].id = chunk.chunk_metadata["message_id"]
|
|
62
|
+
|
|
55
63
|
if chunk.type == ChunkType.TEXT:
|
|
56
64
|
if self.messages and self.messages[-1].type == MessageType.ASSISTANT:
|
|
57
65
|
self.messages[-1].text += chunk.text
|
{appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/components/message.py
RENAMED
|
@@ -12,6 +12,7 @@ from appkit_assistant.state.thread_state import (
|
|
|
12
12
|
ThreadState,
|
|
13
13
|
)
|
|
14
14
|
from appkit_ui.components.collabsible import collabsible
|
|
15
|
+
from appkit_ui.components.dialogs import delete_dialog
|
|
15
16
|
|
|
16
17
|
message_styles = {
|
|
17
18
|
"spacing": "4",
|
|
@@ -85,6 +86,70 @@ class AuthCardComponent:
|
|
|
85
86
|
)
|
|
86
87
|
|
|
87
88
|
|
|
89
|
+
class MessageActionsBar:
|
|
90
|
+
"""Component for message action buttons (copy, download, retry)."""
|
|
91
|
+
|
|
92
|
+
@staticmethod
|
|
93
|
+
def render(message: Message) -> rx.Component:
|
|
94
|
+
return rx.hstack(
|
|
95
|
+
rx.tooltip(
|
|
96
|
+
rx.icon_button(
|
|
97
|
+
rx.icon("copy", size=14),
|
|
98
|
+
on_click=ThreadState.copy_message(message.text),
|
|
99
|
+
variant="ghost",
|
|
100
|
+
size="1",
|
|
101
|
+
color_scheme="gray",
|
|
102
|
+
),
|
|
103
|
+
content="Kopieren",
|
|
104
|
+
),
|
|
105
|
+
rx.tooltip(
|
|
106
|
+
rx.icon_button(
|
|
107
|
+
rx.icon("download", size=14),
|
|
108
|
+
on_click=ThreadState.download_message(message.text, message.id),
|
|
109
|
+
variant="ghost",
|
|
110
|
+
size="1",
|
|
111
|
+
color_scheme="gray",
|
|
112
|
+
),
|
|
113
|
+
content="Herunterladen",
|
|
114
|
+
),
|
|
115
|
+
rx.tooltip(
|
|
116
|
+
delete_dialog(
|
|
117
|
+
title="Nachricht löschen",
|
|
118
|
+
content="diese Nachricht",
|
|
119
|
+
on_click=ThreadState.delete_message(message.id),
|
|
120
|
+
icon_button=True,
|
|
121
|
+
variant="ghost",
|
|
122
|
+
size="1",
|
|
123
|
+
color_scheme="gray",
|
|
124
|
+
),
|
|
125
|
+
content="Löschen",
|
|
126
|
+
),
|
|
127
|
+
rx.cond(
|
|
128
|
+
(message.type == MessageType.ASSISTANT)
|
|
129
|
+
| (message.type == MessageType.ERROR),
|
|
130
|
+
rx.tooltip(
|
|
131
|
+
rx.icon_button(
|
|
132
|
+
rx.cond(
|
|
133
|
+
ThreadState.processing,
|
|
134
|
+
rx.spinner(size="1"),
|
|
135
|
+
rx.icon("refresh-cw", size=14),
|
|
136
|
+
),
|
|
137
|
+
on_click=ThreadState.retry_message(message.id),
|
|
138
|
+
variant="ghost",
|
|
139
|
+
size="1",
|
|
140
|
+
color_scheme="gray",
|
|
141
|
+
disabled=ThreadState.processing,
|
|
142
|
+
),
|
|
143
|
+
content="Erneut generieren (folgende Nachrichten werden entfernt)",
|
|
144
|
+
),
|
|
145
|
+
rx.fragment(),
|
|
146
|
+
),
|
|
147
|
+
spacing="3",
|
|
148
|
+
margin_top="-9px",
|
|
149
|
+
margin_left="9px",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
88
153
|
class MessageComponent:
|
|
89
154
|
@staticmethod
|
|
90
155
|
def _file_badge(filename: str) -> rx.Component:
|
|
@@ -116,31 +181,101 @@ class MessageComponent:
|
|
|
116
181
|
|
|
117
182
|
@staticmethod
|
|
118
183
|
def human_message(message: Message) -> rx.Component:
|
|
119
|
-
return rx.
|
|
120
|
-
|
|
184
|
+
return rx.cond(
|
|
185
|
+
ThreadState.editing_message_id == message.id,
|
|
186
|
+
# Edit Mode
|
|
121
187
|
rx.vstack(
|
|
122
|
-
rx.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
188
|
+
rx.text_area(
|
|
189
|
+
value=ThreadState.edited_message_content,
|
|
190
|
+
on_change=ThreadState.set_edited_message_content,
|
|
191
|
+
height="112px",
|
|
192
|
+
width="824px",
|
|
193
|
+
auto_focus=True,
|
|
194
|
+
bg=rx.color("gray", 3),
|
|
195
|
+
variant="soft",
|
|
196
|
+
),
|
|
197
|
+
rx.hstack(
|
|
198
|
+
rx.button(
|
|
199
|
+
"Abbrechen",
|
|
200
|
+
on_click=ThreadState.cancel_edit,
|
|
201
|
+
variant="soft",
|
|
202
|
+
color_scheme="gray",
|
|
128
203
|
),
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
204
|
+
rx.button("Senden", on_click=ThreadState.submit_edited_message),
|
|
205
|
+
justify="end",
|
|
206
|
+
width="100%",
|
|
207
|
+
spacing="2",
|
|
208
|
+
),
|
|
209
|
+
style=message_styles,
|
|
210
|
+
align="end",
|
|
211
|
+
),
|
|
212
|
+
rx.vstack(
|
|
213
|
+
rx.hstack(
|
|
214
|
+
rx.spacer(),
|
|
215
|
+
rx.vstack(
|
|
216
|
+
rx.box(
|
|
217
|
+
rx.text(
|
|
218
|
+
message.text,
|
|
219
|
+
padding="0.5em",
|
|
220
|
+
border_radius="10px",
|
|
221
|
+
white_space="pre-line",
|
|
222
|
+
),
|
|
223
|
+
padding="4px",
|
|
224
|
+
max_width="800px",
|
|
225
|
+
background_color=rx.color_mode_cond(
|
|
226
|
+
light=rx.color("accent", 3),
|
|
227
|
+
dark=rx.color("accent", 3),
|
|
228
|
+
),
|
|
229
|
+
border_radius="9px",
|
|
230
|
+
),
|
|
231
|
+
MessageComponent._attachments_row(message.attachments),
|
|
232
|
+
align="end",
|
|
233
|
+
spacing="1",
|
|
134
234
|
),
|
|
135
|
-
border_radius="9px",
|
|
136
235
|
),
|
|
137
|
-
|
|
236
|
+
rx.hstack(
|
|
237
|
+
rx.spacer(),
|
|
238
|
+
rx.tooltip(
|
|
239
|
+
rx.icon_button(
|
|
240
|
+
rx.icon("pencil", size=14),
|
|
241
|
+
on_click=ThreadState.set_editing_mode(
|
|
242
|
+
message.id, message.text
|
|
243
|
+
),
|
|
244
|
+
variant="ghost",
|
|
245
|
+
size="1",
|
|
246
|
+
color_scheme="gray",
|
|
247
|
+
),
|
|
248
|
+
content="Bearbeiten",
|
|
249
|
+
),
|
|
250
|
+
rx.tooltip(
|
|
251
|
+
delete_dialog(
|
|
252
|
+
title="Nachricht löschen",
|
|
253
|
+
content="diese Nachricht",
|
|
254
|
+
on_click=ThreadState.delete_message(message.id),
|
|
255
|
+
icon_button=True,
|
|
256
|
+
variant="ghost",
|
|
257
|
+
size="1",
|
|
258
|
+
color_scheme="gray",
|
|
259
|
+
),
|
|
260
|
+
content="Löschen",
|
|
261
|
+
),
|
|
262
|
+
rx.tooltip(
|
|
263
|
+
rx.icon_button(
|
|
264
|
+
rx.icon("copy", size=14),
|
|
265
|
+
on_click=ThreadState.copy_message(message.text),
|
|
266
|
+
variant="ghost",
|
|
267
|
+
size="1",
|
|
268
|
+
color_scheme="gray",
|
|
269
|
+
),
|
|
270
|
+
content="Kopieren",
|
|
271
|
+
),
|
|
272
|
+
spacing="3",
|
|
273
|
+
justify="end",
|
|
274
|
+
margin_right="9px",
|
|
275
|
+
),
|
|
138
276
|
align="end",
|
|
139
|
-
|
|
277
|
+
style=message_styles,
|
|
140
278
|
),
|
|
141
|
-
max_width="80%",
|
|
142
|
-
margin_top="24px",
|
|
143
|
-
style=message_styles,
|
|
144
279
|
)
|
|
145
280
|
|
|
146
281
|
@staticmethod
|
|
@@ -190,12 +325,6 @@ class MessageComponent:
|
|
|
190
325
|
color=rx.color("gray", 8),
|
|
191
326
|
margin_right="9px",
|
|
192
327
|
),
|
|
193
|
-
rx.hstack(
|
|
194
|
-
rx.el.span(""),
|
|
195
|
-
rx.el.span(""),
|
|
196
|
-
rx.el.span(""),
|
|
197
|
-
rx.el.span(""),
|
|
198
|
-
),
|
|
199
328
|
class_name="loading",
|
|
200
329
|
height="40px",
|
|
201
330
|
color=rx.color("gray", 8),
|
|
@@ -206,23 +335,24 @@ class MessageComponent:
|
|
|
206
335
|
padding_right="18px",
|
|
207
336
|
),
|
|
208
337
|
# Actual message content
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
338
|
+
rx.box(
|
|
339
|
+
mn.markdown_preview(
|
|
340
|
+
source=message.text,
|
|
341
|
+
enable_mermaid=message.done,
|
|
342
|
+
enable_katex=message.done,
|
|
343
|
+
security_level="standard",
|
|
344
|
+
class_name="markdown",
|
|
345
|
+
),
|
|
214
346
|
padding="0.5em",
|
|
215
|
-
|
|
347
|
+
margin_top="18px",
|
|
216
348
|
max_width="90%",
|
|
217
|
-
class_name="markdown",
|
|
218
349
|
),
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
# ),
|
|
350
|
+
),
|
|
351
|
+
# Actions bar
|
|
352
|
+
rx.cond(
|
|
353
|
+
message.done,
|
|
354
|
+
MessageActionsBar.render(message),
|
|
355
|
+
rx.fragment(),
|
|
226
356
|
),
|
|
227
357
|
spacing="3",
|
|
228
358
|
width="100%",
|
|
@@ -263,7 +393,7 @@ class MessageComponent:
|
|
|
263
393
|
)
|
|
264
394
|
|
|
265
395
|
@staticmethod
|
|
266
|
-
def error_message(message:
|
|
396
|
+
def error_message(message: Message) -> rx.Component:
|
|
267
397
|
return rx.hstack(
|
|
268
398
|
rx.avatar(
|
|
269
399
|
fallback="!",
|
|
@@ -273,15 +403,20 @@ class MessageComponent:
|
|
|
273
403
|
margin_top="16px",
|
|
274
404
|
color_scheme="red",
|
|
275
405
|
),
|
|
276
|
-
rx.
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
406
|
+
rx.vstack(
|
|
407
|
+
rx.callout(
|
|
408
|
+
message.text,
|
|
409
|
+
icon="triangle-alert",
|
|
410
|
+
color_scheme="red",
|
|
411
|
+
max_width="100%",
|
|
412
|
+
size="1",
|
|
413
|
+
padding="0.5em",
|
|
414
|
+
border_radius="9px",
|
|
415
|
+
margin_top="18px",
|
|
416
|
+
),
|
|
417
|
+
MessageActionsBar.render(message),
|
|
418
|
+
width="90%",
|
|
419
|
+
spacing="2",
|
|
285
420
|
),
|
|
286
421
|
style=message_styles,
|
|
287
422
|
)
|
|
@@ -328,7 +463,7 @@ class MessageComponent:
|
|
|
328
463
|
),
|
|
329
464
|
(
|
|
330
465
|
MessageType.ERROR,
|
|
331
|
-
MessageComponent.error_message(message
|
|
466
|
+
MessageComponent.error_message(message),
|
|
332
467
|
),
|
|
333
468
|
(
|
|
334
469
|
MessageType.SYSTEM,
|
{appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/state/thread_state.py
RENAMED
|
@@ -14,6 +14,7 @@ import json
|
|
|
14
14
|
import logging
|
|
15
15
|
import uuid
|
|
16
16
|
from collections.abc import AsyncGenerator
|
|
17
|
+
from datetime import UTC, datetime
|
|
17
18
|
from typing import Any
|
|
18
19
|
|
|
19
20
|
import reflex as rx
|
|
@@ -74,6 +75,10 @@ class ThreadState(rx.State):
|
|
|
74
75
|
# File upload state
|
|
75
76
|
uploaded_files: list[UploadedFile] = []
|
|
76
77
|
|
|
78
|
+
# Editing state
|
|
79
|
+
editing_message_id: str | None = None
|
|
80
|
+
edited_message_content: str = ""
|
|
81
|
+
|
|
77
82
|
# Internal logic helper (not reactive)
|
|
78
83
|
@property
|
|
79
84
|
def _thread_service(self) -> ThreadService:
|
|
@@ -510,6 +515,67 @@ class ThreadState(rx.State):
|
|
|
510
515
|
# Message processing
|
|
511
516
|
# -------------------------------------------------------------------------
|
|
512
517
|
|
|
518
|
+
@rx.event
|
|
519
|
+
def set_editing_mode(self, message_id: str, content: str) -> None:
|
|
520
|
+
"""Enable editing mode for a message."""
|
|
521
|
+
self.editing_message_id = message_id
|
|
522
|
+
self.edited_message_content = content
|
|
523
|
+
|
|
524
|
+
@rx.event
|
|
525
|
+
def set_edited_message_content(self, content: str) -> None:
|
|
526
|
+
"""Set the content of the message currently being edited."""
|
|
527
|
+
self.edited_message_content = content
|
|
528
|
+
|
|
529
|
+
@rx.event
|
|
530
|
+
def cancel_edit(self) -> None:
|
|
531
|
+
"""Cancel editing mode."""
|
|
532
|
+
self.editing_message_id = None
|
|
533
|
+
self.edited_message_content = ""
|
|
534
|
+
|
|
535
|
+
@rx.event(background=True)
|
|
536
|
+
async def submit_edited_message(self) -> AsyncGenerator[Any, Any]:
|
|
537
|
+
"""Submit edited message."""
|
|
538
|
+
async with self:
|
|
539
|
+
content = self.edited_message_content.strip()
|
|
540
|
+
if len(content) < 1:
|
|
541
|
+
yield rx.toast.error(
|
|
542
|
+
"Nachricht darf nicht leer sein", position="top-right"
|
|
543
|
+
)
|
|
544
|
+
return
|
|
545
|
+
|
|
546
|
+
# Find message index
|
|
547
|
+
msg_index = -1
|
|
548
|
+
for i, m in enumerate(self.messages):
|
|
549
|
+
if m.id == self.editing_message_id:
|
|
550
|
+
msg_index = i
|
|
551
|
+
break
|
|
552
|
+
|
|
553
|
+
if msg_index == -1:
|
|
554
|
+
self.cancel_edit()
|
|
555
|
+
return
|
|
556
|
+
|
|
557
|
+
target_message = self.messages[msg_index]
|
|
558
|
+
|
|
559
|
+
# Update message
|
|
560
|
+
target_message.original_text = (
|
|
561
|
+
target_message.original_text or target_message.text
|
|
562
|
+
)
|
|
563
|
+
target_message.text = content
|
|
564
|
+
|
|
565
|
+
# Remove all messages AFTER this one
|
|
566
|
+
self.messages = self.messages[: msg_index + 1]
|
|
567
|
+
|
|
568
|
+
# Set prompt to bypass empty check in _begin_message_processing
|
|
569
|
+
self.prompt = content
|
|
570
|
+
self._skip_user_message = True
|
|
571
|
+
|
|
572
|
+
# Clear edit state
|
|
573
|
+
self.editing_message_id = None
|
|
574
|
+
self.edited_message_content = ""
|
|
575
|
+
|
|
576
|
+
# Trigger processing
|
|
577
|
+
await self._process_message()
|
|
578
|
+
|
|
513
579
|
@rx.event(background=True)
|
|
514
580
|
async def submit_message(self) -> AsyncGenerator[Any, Any]:
|
|
515
581
|
"""Submit a message and process the response."""
|
|
@@ -524,6 +590,76 @@ class ThreadState(rx.State):
|
|
|
524
590
|
}
|
|
525
591
|
""")
|
|
526
592
|
|
|
593
|
+
@rx.event(background=True)
|
|
594
|
+
async def delete_message(self, message_id: str) -> None:
|
|
595
|
+
"""Delete a message from the conversation."""
|
|
596
|
+
async with self:
|
|
597
|
+
self.messages = [m for m in self.messages if m.id != message_id]
|
|
598
|
+
self._thread.messages = self.messages
|
|
599
|
+
|
|
600
|
+
if self._thread.state != ThreadStatus.NEW:
|
|
601
|
+
await self._thread_service.save_thread(
|
|
602
|
+
self._thread, self.current_user_id
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
@rx.event
|
|
606
|
+
def copy_message(self, text: str) -> list[Any]:
|
|
607
|
+
"""Copy message text to clipboard."""
|
|
608
|
+
return [
|
|
609
|
+
rx.set_clipboard(text),
|
|
610
|
+
rx.toast.success("Nachricht kopiert"),
|
|
611
|
+
]
|
|
612
|
+
|
|
613
|
+
@rx.event
|
|
614
|
+
def download_message(self, text: str, message_id: str) -> Any:
|
|
615
|
+
"""Download message as markdown file."""
|
|
616
|
+
timestamp = datetime.now(UTC).strftime("%Y%m%d_%H%M%S")
|
|
617
|
+
filename = (
|
|
618
|
+
f"message_{message_id}_{timestamp}.md"
|
|
619
|
+
if message_id
|
|
620
|
+
else f"message_{timestamp}.md"
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
# Use JavaScript to trigger download
|
|
624
|
+
return rx.call_script(f"""
|
|
625
|
+
const blob = new Blob([{json.dumps(text)}], {{type: 'text/markdown'}});
|
|
626
|
+
const url = URL.createObjectURL(blob);
|
|
627
|
+
const a = document.createElement('a');
|
|
628
|
+
a.href = url;
|
|
629
|
+
a.download = '{filename}';
|
|
630
|
+
document.body.appendChild(a);
|
|
631
|
+
a.click();
|
|
632
|
+
document.body.removeChild(a);
|
|
633
|
+
URL.revokeObjectURL(url);
|
|
634
|
+
""")
|
|
635
|
+
|
|
636
|
+
@rx.event(background=True)
|
|
637
|
+
async def retry_message(self, message_id: str) -> None:
|
|
638
|
+
"""Retry generating a message."""
|
|
639
|
+
async with self:
|
|
640
|
+
# Find message index
|
|
641
|
+
index = -1
|
|
642
|
+
for i, msg in enumerate(self.messages):
|
|
643
|
+
if msg.id == message_id:
|
|
644
|
+
index = i
|
|
645
|
+
break
|
|
646
|
+
|
|
647
|
+
if index == -1:
|
|
648
|
+
return
|
|
649
|
+
|
|
650
|
+
# Keep context up to this message
|
|
651
|
+
# effectively removing this message and everything after
|
|
652
|
+
self.messages = self.messages[:index]
|
|
653
|
+
|
|
654
|
+
# Set prompt to bypass check (content checks)
|
|
655
|
+
self.prompt = "Regenerate"
|
|
656
|
+
|
|
657
|
+
# Flag to skip adding a new user message
|
|
658
|
+
self._skip_user_message = True
|
|
659
|
+
|
|
660
|
+
# Trigger processing directly
|
|
661
|
+
await self._process_message()
|
|
662
|
+
|
|
527
663
|
async def _process_message(self) -> None:
|
|
528
664
|
"""Process the current message and stream the response."""
|
|
529
665
|
logger.debug("Processing message: %s", self.prompt)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/file_manager.py
RENAMED
|
File without changes
|
{appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/mcp_auth_service.py
RENAMED
|
File without changes
|
{appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/model_manager.py
RENAMED
|
File without changes
|
{appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/processor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/backend/repositories.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/components/__init__.py
RENAMED
|
File without changes
|
{appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/components/composer.py
RENAMED
|
File without changes
|
|
File without changes
|
{appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/components/mcp_oauth.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/components/thread.py
RENAMED
|
File without changes
|
{appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/components/threadlist.py
RENAMED
|
File without changes
|
{appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/components/tools_modal.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/state/mcp_oauth_state.py
RENAMED
|
File without changes
|
{appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/state/mcp_server_state.py
RENAMED
|
File without changes
|
|
File without changes
|
{appkit_assistant-0.17.0 → appkit_assistant-0.17.1}/src/appkit_assistant/state/thread_list_state.py
RENAMED
|
File without changes
|