appkit-assistant 0.7.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/model_manager.py +133 -0
- appkit_assistant/backend/models.py +103 -0
- appkit_assistant/backend/processor.py +46 -0
- appkit_assistant/backend/processors/ai_models.py +109 -0
- appkit_assistant/backend/processors/knowledgeai_processor.py +275 -0
- appkit_assistant/backend/processors/lorem_ipsum_processor.py +123 -0
- appkit_assistant/backend/processors/openai_base.py +73 -0
- appkit_assistant/backend/processors/openai_chat_completion_processor.py +117 -0
- appkit_assistant/backend/processors/openai_responses_processor.py +508 -0
- appkit_assistant/backend/processors/perplexity_processor.py +118 -0
- appkit_assistant/backend/repositories.py +96 -0
- appkit_assistant/backend/system_prompt.py +56 -0
- appkit_assistant/components/__init__.py +38 -0
- appkit_assistant/components/composer.py +154 -0
- appkit_assistant/components/composer_key_handler.py +38 -0
- appkit_assistant/components/mcp_server_dialogs.py +344 -0
- appkit_assistant/components/mcp_server_table.py +76 -0
- appkit_assistant/components/message.py +299 -0
- appkit_assistant/components/thread.py +252 -0
- appkit_assistant/components/threadlist.py +134 -0
- appkit_assistant/components/tools_modal.py +97 -0
- appkit_assistant/configuration.py +10 -0
- appkit_assistant/state/mcp_server_state.py +222 -0
- appkit_assistant/state/thread_state.py +874 -0
- appkit_assistant-0.7.1.dist-info/METADATA +8 -0
- appkit_assistant-0.7.1.dist-info/RECORD +27 -0
- appkit_assistant-0.7.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import reflex as rx
|
|
2
|
+
|
|
3
|
+
import appkit_mantine as mn
|
|
4
|
+
from appkit_assistant.backend.models import (
|
|
5
|
+
Message,
|
|
6
|
+
MessageType,
|
|
7
|
+
)
|
|
8
|
+
from appkit_assistant.state.thread_state import (
|
|
9
|
+
Thinking,
|
|
10
|
+
ThinkingStatus,
|
|
11
|
+
ThinkingType,
|
|
12
|
+
ThreadState,
|
|
13
|
+
)
|
|
14
|
+
from appkit_ui.components.collabsible import collabsible
|
|
15
|
+
|
|
16
|
+
message_styles = {
|
|
17
|
+
"spacing": "4",
|
|
18
|
+
"width": "100%",
|
|
19
|
+
"max_width": "880px",
|
|
20
|
+
"margin_top": "24px",
|
|
21
|
+
"margin_left": "auto",
|
|
22
|
+
"margin_right": "auto",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class MessageComponent:
|
|
27
|
+
@staticmethod
|
|
28
|
+
def human_message(message: str) -> rx.Component:
|
|
29
|
+
return rx.hstack(
|
|
30
|
+
rx.spacer(),
|
|
31
|
+
rx.box(
|
|
32
|
+
rx.text(
|
|
33
|
+
message,
|
|
34
|
+
padding="0.5em",
|
|
35
|
+
border_radius="10px",
|
|
36
|
+
white_space="pre-line",
|
|
37
|
+
),
|
|
38
|
+
padding="4px",
|
|
39
|
+
max_width="80%",
|
|
40
|
+
margin_top="24px",
|
|
41
|
+
# margin_right="14px",
|
|
42
|
+
background_color=rx.color_mode_cond(
|
|
43
|
+
light=rx.color("accent", 3),
|
|
44
|
+
dark=rx.color("accent", 3),
|
|
45
|
+
),
|
|
46
|
+
border_radius="9px",
|
|
47
|
+
),
|
|
48
|
+
style=message_styles,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def assistant_message(message: Message) -> rx.Component:
|
|
53
|
+
"""Display an assistant message with thinking content when items exist."""
|
|
54
|
+
|
|
55
|
+
# Show thinking content only for the last assistant message
|
|
56
|
+
should_show_thinking = (
|
|
57
|
+
message.text == ThreadState.last_assistant_message_text
|
|
58
|
+
) & ThreadState.has_thinking_content
|
|
59
|
+
|
|
60
|
+
# Main content area with all components
|
|
61
|
+
content_area = rx.vstack(
|
|
62
|
+
# Always rendered with conditional styling for smooth animations
|
|
63
|
+
collabsible(
|
|
64
|
+
rx.scroll_area(
|
|
65
|
+
rx.foreach(
|
|
66
|
+
ThreadState.thinking_items,
|
|
67
|
+
lambda item: ToolCallComponent.render(item),
|
|
68
|
+
),
|
|
69
|
+
spacing="3",
|
|
70
|
+
max_height="180px",
|
|
71
|
+
padding="9px 12px",
|
|
72
|
+
width="100%",
|
|
73
|
+
scrollbars="vertical",
|
|
74
|
+
),
|
|
75
|
+
title="Denkprozess & Werkzeuge",
|
|
76
|
+
info_text=(
|
|
77
|
+
f"{ThreadState.unique_reasoning_sessions.length()} "
|
|
78
|
+
f"Nachdenken, "
|
|
79
|
+
f"{ThreadState.unique_tool_calls.length()} Werkzeuge"
|
|
80
|
+
),
|
|
81
|
+
show_condition=should_show_thinking,
|
|
82
|
+
expanded=ThreadState.thinking_expanded,
|
|
83
|
+
on_toggle=ThreadState.toggle_thinking_expanded,
|
|
84
|
+
),
|
|
85
|
+
# Main message content
|
|
86
|
+
rx.cond(
|
|
87
|
+
message.text == "",
|
|
88
|
+
rx.hstack(
|
|
89
|
+
rx.text(
|
|
90
|
+
rx.cond(
|
|
91
|
+
ThreadState.current_activity != "",
|
|
92
|
+
ThreadState.current_activity,
|
|
93
|
+
"Denke nach",
|
|
94
|
+
),
|
|
95
|
+
color=rx.color("gray", 8),
|
|
96
|
+
margin_right="9px",
|
|
97
|
+
),
|
|
98
|
+
rx.hstack(
|
|
99
|
+
rx.el.span(""),
|
|
100
|
+
rx.el.span(""),
|
|
101
|
+
rx.el.span(""),
|
|
102
|
+
rx.el.span(""),
|
|
103
|
+
),
|
|
104
|
+
class_name="loading",
|
|
105
|
+
height="40px",
|
|
106
|
+
color=rx.color("gray", 8),
|
|
107
|
+
background_color=rx.color("gray", 2),
|
|
108
|
+
padding="0.5em",
|
|
109
|
+
border_radius="9px",
|
|
110
|
+
margin_top="16px",
|
|
111
|
+
padding_right="18px",
|
|
112
|
+
),
|
|
113
|
+
# Actual message content
|
|
114
|
+
mn.markdown_preview(
|
|
115
|
+
source=message.text,
|
|
116
|
+
enable_mermaid=message.done,
|
|
117
|
+
enable_katex=message.done,
|
|
118
|
+
security_level="standard",
|
|
119
|
+
padding="0.5em",
|
|
120
|
+
border_radius="9px",
|
|
121
|
+
max_width="90%",
|
|
122
|
+
class_name="markdown",
|
|
123
|
+
),
|
|
124
|
+
# rx.markdown(
|
|
125
|
+
# message.text,
|
|
126
|
+
# padding="0.5em",
|
|
127
|
+
# border_radius="9px",
|
|
128
|
+
# max_width="90%",
|
|
129
|
+
# class_name="markdown",
|
|
130
|
+
# ),
|
|
131
|
+
),
|
|
132
|
+
spacing="3",
|
|
133
|
+
width="100%",
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
return rx.hstack(
|
|
137
|
+
rx.avatar(
|
|
138
|
+
fallback="AI",
|
|
139
|
+
size="3",
|
|
140
|
+
variant="soft",
|
|
141
|
+
radius="full",
|
|
142
|
+
margin_top="16px",
|
|
143
|
+
),
|
|
144
|
+
content_area,
|
|
145
|
+
style=message_styles,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def info_message(message: str) -> rx.Component:
|
|
150
|
+
return rx.hstack(
|
|
151
|
+
rx.avatar(
|
|
152
|
+
fallback="AI",
|
|
153
|
+
size="3",
|
|
154
|
+
variant="soft",
|
|
155
|
+
radius="full",
|
|
156
|
+
margin_top="16px",
|
|
157
|
+
),
|
|
158
|
+
rx.callout(
|
|
159
|
+
message,
|
|
160
|
+
icon="info",
|
|
161
|
+
max_width="90%",
|
|
162
|
+
size="1",
|
|
163
|
+
padding="0.5em",
|
|
164
|
+
border_radius="9px",
|
|
165
|
+
margin_top="18px",
|
|
166
|
+
),
|
|
167
|
+
style=message_styles,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
@staticmethod
|
|
171
|
+
def render_message(
|
|
172
|
+
message: Message,
|
|
173
|
+
) -> rx.Component:
|
|
174
|
+
"""Render message with optional enhanced chunk-based components."""
|
|
175
|
+
return rx.fragment(
|
|
176
|
+
rx.match(
|
|
177
|
+
message.type,
|
|
178
|
+
(
|
|
179
|
+
MessageType.HUMAN,
|
|
180
|
+
MessageComponent.human_message(message.text),
|
|
181
|
+
),
|
|
182
|
+
(
|
|
183
|
+
MessageType.ASSISTANT,
|
|
184
|
+
MessageComponent.assistant_message(message),
|
|
185
|
+
),
|
|
186
|
+
MessageComponent.info_message(message.text),
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class ToolCallComponent:
|
|
192
|
+
"""Component for displaying individual tool calls with green styling."""
|
|
193
|
+
|
|
194
|
+
@staticmethod
|
|
195
|
+
def render(tool_item: Thinking) -> rx.Component:
|
|
196
|
+
return rx.cond(
|
|
197
|
+
tool_item.type == ThinkingType.REASONING,
|
|
198
|
+
ToolCallComponent._render_reasoning(tool_item),
|
|
199
|
+
ToolCallComponent._render_tool_call(tool_item),
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
@staticmethod
|
|
203
|
+
def _render_reasoning(item: Thinking) -> rx.Component:
|
|
204
|
+
return rx.vstack(
|
|
205
|
+
rx.text(item.text, size="1"),
|
|
206
|
+
border_left=f"3px solid {rx.color('gray', 4)}",
|
|
207
|
+
padding="3px 6px",
|
|
208
|
+
margin_bottom="9px",
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
@staticmethod
|
|
212
|
+
def _render_tool_call(item: Thinking) -> rx.Component:
|
|
213
|
+
return rx.vstack(
|
|
214
|
+
rx.hstack(
|
|
215
|
+
rx.icon("wrench", size=14, color=rx.color("green", 8)),
|
|
216
|
+
rx.text(
|
|
217
|
+
f"Werkzeug: {item.tool_name}",
|
|
218
|
+
size="1",
|
|
219
|
+
font_weight="bold",
|
|
220
|
+
color=rx.color("blue", 9),
|
|
221
|
+
),
|
|
222
|
+
rx.spacer(),
|
|
223
|
+
rx.text(
|
|
224
|
+
item.id,
|
|
225
|
+
size="1",
|
|
226
|
+
color=rx.color("gray", 6),
|
|
227
|
+
),
|
|
228
|
+
spacing="1",
|
|
229
|
+
margin_bottom="3px",
|
|
230
|
+
width="100%",
|
|
231
|
+
),
|
|
232
|
+
rx.cond(
|
|
233
|
+
item.text,
|
|
234
|
+
rx.vstack(
|
|
235
|
+
rx.text(
|
|
236
|
+
item.text,
|
|
237
|
+
size="1",
|
|
238
|
+
color=rx.color("gray", 10),
|
|
239
|
+
),
|
|
240
|
+
align="start",
|
|
241
|
+
width="100%",
|
|
242
|
+
),
|
|
243
|
+
),
|
|
244
|
+
rx.cond(
|
|
245
|
+
item.parameters,
|
|
246
|
+
rx.vstack(
|
|
247
|
+
rx.text(
|
|
248
|
+
item.parameters,
|
|
249
|
+
size="1",
|
|
250
|
+
color=rx.color("blue", 9),
|
|
251
|
+
white_space="pre-wrap",
|
|
252
|
+
),
|
|
253
|
+
align="start",
|
|
254
|
+
width="100%",
|
|
255
|
+
spacing="1",
|
|
256
|
+
),
|
|
257
|
+
),
|
|
258
|
+
rx.cond(
|
|
259
|
+
item.status == ThinkingStatus.COMPLETED,
|
|
260
|
+
rx.scroll_area(
|
|
261
|
+
rx.text(
|
|
262
|
+
item.result,
|
|
263
|
+
size="1",
|
|
264
|
+
color=rx.color("gray", 8),
|
|
265
|
+
),
|
|
266
|
+
max_height="60px",
|
|
267
|
+
width="95%",
|
|
268
|
+
scrollbars="vertical",
|
|
269
|
+
),
|
|
270
|
+
),
|
|
271
|
+
rx.cond(
|
|
272
|
+
item.status == ThinkingStatus.ERROR,
|
|
273
|
+
rx.vstack(
|
|
274
|
+
rx.hstack(
|
|
275
|
+
rx.icon("shield-alert", size=14, color=rx.color("red", 10)),
|
|
276
|
+
rx.text(
|
|
277
|
+
"Fehler",
|
|
278
|
+
size="1",
|
|
279
|
+
font_weight="bold",
|
|
280
|
+
color=rx.color("red", 10),
|
|
281
|
+
),
|
|
282
|
+
spacing="1",
|
|
283
|
+
),
|
|
284
|
+
rx.text(
|
|
285
|
+
item.error,
|
|
286
|
+
size="1",
|
|
287
|
+
color=rx.color("red", 9),
|
|
288
|
+
),
|
|
289
|
+
align="start",
|
|
290
|
+
width="100%",
|
|
291
|
+
spacing="1",
|
|
292
|
+
),
|
|
293
|
+
),
|
|
294
|
+
padding="3px 6px",
|
|
295
|
+
border_left=f"3px solid {rx.color('gray', 5)}",
|
|
296
|
+
margin_bottom="9px",
|
|
297
|
+
width="100%",
|
|
298
|
+
spacing="2",
|
|
299
|
+
)
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
|
|
4
|
+
import reflex as rx
|
|
5
|
+
|
|
6
|
+
import appkit_mantine as mn
|
|
7
|
+
from appkit_assistant.components import composer
|
|
8
|
+
from appkit_assistant.components.message import MessageComponent
|
|
9
|
+
from appkit_assistant.components.threadlist import ThreadList
|
|
10
|
+
from appkit_assistant.state.thread_state import (
|
|
11
|
+
Message,
|
|
12
|
+
MessageType,
|
|
13
|
+
ThreadListState,
|
|
14
|
+
ThreadState,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Assistant:
|
|
21
|
+
@staticmethod
|
|
22
|
+
def suggestion(
|
|
23
|
+
prompt: str,
|
|
24
|
+
icon: str | None = None,
|
|
25
|
+
update_prompt: Callable | None = None,
|
|
26
|
+
**props,
|
|
27
|
+
) -> rx.Component:
|
|
28
|
+
"""Component to display a suggestion."""
|
|
29
|
+
|
|
30
|
+
on_click_handler = update_prompt(prompt) if update_prompt else None
|
|
31
|
+
|
|
32
|
+
return rx.button(
|
|
33
|
+
rx.cond(icon, rx.icon(icon), None),
|
|
34
|
+
prompt,
|
|
35
|
+
size="2",
|
|
36
|
+
variant="soft",
|
|
37
|
+
radius="large",
|
|
38
|
+
on_click=on_click_handler,
|
|
39
|
+
**props,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def empty(
|
|
44
|
+
welcome_message: str,
|
|
45
|
+
**props,
|
|
46
|
+
) -> rx.Component:
|
|
47
|
+
"""Component to display when there are no messages."""
|
|
48
|
+
return rx.vstack(
|
|
49
|
+
rx.text(welcome_message, size="8", margin_bottom="0.5em"),
|
|
50
|
+
rx.cond(
|
|
51
|
+
ThreadState.has_suggestions,
|
|
52
|
+
rx.flex(
|
|
53
|
+
rx.foreach(
|
|
54
|
+
ThreadState.suggestions,
|
|
55
|
+
lambda suggestion: Assistant.suggestion(
|
|
56
|
+
prompt=suggestion.prompt,
|
|
57
|
+
icon=suggestion.icon,
|
|
58
|
+
update_prompt=ThreadState.update_prompt,
|
|
59
|
+
),
|
|
60
|
+
),
|
|
61
|
+
spacing="4",
|
|
62
|
+
width="100%",
|
|
63
|
+
direction="row",
|
|
64
|
+
wrap="wrap",
|
|
65
|
+
),
|
|
66
|
+
None,
|
|
67
|
+
),
|
|
68
|
+
**props,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def messages(
|
|
73
|
+
**props,
|
|
74
|
+
) -> rx.Component:
|
|
75
|
+
"""Component to display messages in the thread."""
|
|
76
|
+
|
|
77
|
+
if ThreadState.messages is None:
|
|
78
|
+
messages = [Message(text="👋 Hi!", type=MessageType.ASSISTANT)]
|
|
79
|
+
else:
|
|
80
|
+
messages = ThreadState.messages
|
|
81
|
+
|
|
82
|
+
return rx.fragment(
|
|
83
|
+
mn.scroll_area.stateful(
|
|
84
|
+
rx.foreach(
|
|
85
|
+
messages,
|
|
86
|
+
lambda message: MessageComponent.render_message(message),
|
|
87
|
+
),
|
|
88
|
+
rx.spacer(
|
|
89
|
+
id="scroll-anchor",
|
|
90
|
+
display="hidden",
|
|
91
|
+
min_height="44px",
|
|
92
|
+
wrap="nowrap",
|
|
93
|
+
),
|
|
94
|
+
type="hover",
|
|
95
|
+
autoscroll=True,
|
|
96
|
+
show_controls=True,
|
|
97
|
+
controls="both",
|
|
98
|
+
top_button_text="↑",
|
|
99
|
+
bottom_button_text="↓",
|
|
100
|
+
offset_scrollbars=False,
|
|
101
|
+
scrollbars="y",
|
|
102
|
+
scrollbar_size="6px",
|
|
103
|
+
height="100%",
|
|
104
|
+
min_height="0",
|
|
105
|
+
**props,
|
|
106
|
+
),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def composer(
|
|
111
|
+
with_attachments: bool = False,
|
|
112
|
+
with_model_chooser: bool = False,
|
|
113
|
+
with_tools: bool = False,
|
|
114
|
+
with_clear: bool = True,
|
|
115
|
+
**props,
|
|
116
|
+
) -> rx.Component:
|
|
117
|
+
return composer(
|
|
118
|
+
composer.input(),
|
|
119
|
+
rx.hstack(
|
|
120
|
+
rx.hstack(
|
|
121
|
+
composer.choose_model(show=with_model_chooser),
|
|
122
|
+
),
|
|
123
|
+
rx.hstack(
|
|
124
|
+
composer.tools(
|
|
125
|
+
show=with_tools and ThreadState.current_model_supports_tools
|
|
126
|
+
),
|
|
127
|
+
composer.add_attachment(show=with_attachments),
|
|
128
|
+
composer.clear(show=with_clear),
|
|
129
|
+
composer.submit(),
|
|
130
|
+
width="100%",
|
|
131
|
+
justify="end",
|
|
132
|
+
align="center",
|
|
133
|
+
spacing="4",
|
|
134
|
+
),
|
|
135
|
+
padding="0 12px 12px 12px",
|
|
136
|
+
width="100%",
|
|
137
|
+
align="center",
|
|
138
|
+
),
|
|
139
|
+
on_submit=ThreadState.submit_message,
|
|
140
|
+
**props,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def thread(
|
|
145
|
+
welcome_message: str = "",
|
|
146
|
+
with_attachments: bool = False,
|
|
147
|
+
with_clear: bool = True,
|
|
148
|
+
with_model_chooser: bool = True,
|
|
149
|
+
with_scroll_to_bottom: bool = False,
|
|
150
|
+
with_thread_list: bool = False,
|
|
151
|
+
with_tools: bool = False,
|
|
152
|
+
**props,
|
|
153
|
+
) -> rx.Component:
|
|
154
|
+
# Note: avoid mutating state during component tree building
|
|
155
|
+
# Use ThreadState.set_suggestions() event handler to update suggestions
|
|
156
|
+
# if suggestions is not None:
|
|
157
|
+
# ThreadState.set_suggestions(suggestions)
|
|
158
|
+
|
|
159
|
+
if with_thread_list:
|
|
160
|
+
ThreadState.with_thread_list = with_thread_list
|
|
161
|
+
|
|
162
|
+
return rx.flex(
|
|
163
|
+
rx.cond(
|
|
164
|
+
ThreadState.messages,
|
|
165
|
+
Assistant.messages(
|
|
166
|
+
with_scroll_to_bottom=with_scroll_to_bottom,
|
|
167
|
+
width="100%",
|
|
168
|
+
margin_bottom="-1em",
|
|
169
|
+
flex_grow=1,
|
|
170
|
+
justify_content="start",
|
|
171
|
+
),
|
|
172
|
+
Assistant.empty(
|
|
173
|
+
welcome_message=welcome_message,
|
|
174
|
+
width="100%",
|
|
175
|
+
max_width="880px",
|
|
176
|
+
margin_left="auto",
|
|
177
|
+
margin_right="auto",
|
|
178
|
+
margin_bottom="2em",
|
|
179
|
+
flex_grow=1,
|
|
180
|
+
justify_content="flex-end",
|
|
181
|
+
),
|
|
182
|
+
),
|
|
183
|
+
Assistant.composer(
|
|
184
|
+
with_attachments=with_attachments,
|
|
185
|
+
with_tools=with_tools,
|
|
186
|
+
with_model_chooser=with_model_chooser,
|
|
187
|
+
with_clear=with_clear,
|
|
188
|
+
# styling
|
|
189
|
+
border=rx.color_mode_cond(
|
|
190
|
+
light=f"1px solid {rx.color('gray', 9)}",
|
|
191
|
+
dark=f"1px solid {rx.color('white', 7, alpha=True)}",
|
|
192
|
+
),
|
|
193
|
+
box_shadow=rx.color_mode_cond(
|
|
194
|
+
light="0 1px 10px -0.5px rgba(0, 0, 0, 0.1)",
|
|
195
|
+
dark="0 1px 10px -0.5px rgba(0.8, 0.8, 0.8, 0.1)",
|
|
196
|
+
),
|
|
197
|
+
border_radius="10px",
|
|
198
|
+
background_color=rx.color_mode_cond(
|
|
199
|
+
light=rx.color("white", 9, alpha=True),
|
|
200
|
+
dark=rx.color("white", 2, alpha=False),
|
|
201
|
+
),
|
|
202
|
+
width="100%",
|
|
203
|
+
max_width="880px",
|
|
204
|
+
margin_left="auto",
|
|
205
|
+
margin_right="auto",
|
|
206
|
+
margin_top="1em",
|
|
207
|
+
spacing="0",
|
|
208
|
+
flex_shrink=0,
|
|
209
|
+
z_index=1000,
|
|
210
|
+
on_mount=ThreadState.load_available_mcp_servers,
|
|
211
|
+
),
|
|
212
|
+
**props,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
@staticmethod
|
|
216
|
+
def thread_list(
|
|
217
|
+
*items,
|
|
218
|
+
with_footer: bool = False,
|
|
219
|
+
default_model: str | None = None,
|
|
220
|
+
**props,
|
|
221
|
+
) -> rx.Component:
|
|
222
|
+
if default_model:
|
|
223
|
+
ThreadListState.default_model = default_model
|
|
224
|
+
|
|
225
|
+
return rx.flex(
|
|
226
|
+
rx.flex(
|
|
227
|
+
ThreadList.header(
|
|
228
|
+
title="Neuer Chat",
|
|
229
|
+
margin_bottom="1.5em",
|
|
230
|
+
flex_shrink=0,
|
|
231
|
+
),
|
|
232
|
+
ThreadList.list(
|
|
233
|
+
flex_grow=1,
|
|
234
|
+
min_height="60px",
|
|
235
|
+
),
|
|
236
|
+
rx.cond(
|
|
237
|
+
with_footer,
|
|
238
|
+
ThreadList.footer(
|
|
239
|
+
*items,
|
|
240
|
+
flex_shrink=0,
|
|
241
|
+
min_height="48px",
|
|
242
|
+
),
|
|
243
|
+
None,
|
|
244
|
+
),
|
|
245
|
+
flex_direction=["column"],
|
|
246
|
+
width="100%",
|
|
247
|
+
height="100%",
|
|
248
|
+
overflow="hidden",
|
|
249
|
+
),
|
|
250
|
+
overflow="hidden",
|
|
251
|
+
**props,
|
|
252
|
+
)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import reflex as rx
|
|
2
|
+
|
|
3
|
+
from appkit_assistant.state.thread_state import ThreadListState, ThreadModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ThreadList:
|
|
7
|
+
@staticmethod
|
|
8
|
+
def header(title: str = "Neuer Chat", **props) -> rx.Component:
|
|
9
|
+
"""Header component for the thread list."""
|
|
10
|
+
return rx.flex(
|
|
11
|
+
rx.tooltip(
|
|
12
|
+
rx.button(
|
|
13
|
+
rx.text(title),
|
|
14
|
+
size="2",
|
|
15
|
+
margin_right="28px",
|
|
16
|
+
on_click=ThreadListState.create_thread(),
|
|
17
|
+
width="95%",
|
|
18
|
+
),
|
|
19
|
+
content="Neuen Chat starten",
|
|
20
|
+
),
|
|
21
|
+
direction="row",
|
|
22
|
+
align="center",
|
|
23
|
+
margin_top="9px",
|
|
24
|
+
**props,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def footer(*items, **props) -> rx.Component:
|
|
29
|
+
"""Footer component for the thread list."""
|
|
30
|
+
return rx.flex(
|
|
31
|
+
*items,
|
|
32
|
+
**props,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def thread_list_item(thread: ThreadModel) -> rx.Component:
|
|
37
|
+
return rx.flex(
|
|
38
|
+
rx.text(
|
|
39
|
+
thread.title,
|
|
40
|
+
size="2",
|
|
41
|
+
white_space="nowrap",
|
|
42
|
+
overflow="hidden",
|
|
43
|
+
text_overflow="ellipsis",
|
|
44
|
+
flex_grow="1",
|
|
45
|
+
width="100px",
|
|
46
|
+
min_width="0",
|
|
47
|
+
title=thread.title,
|
|
48
|
+
),
|
|
49
|
+
rx.tooltip(
|
|
50
|
+
rx.button(
|
|
51
|
+
rx.icon(
|
|
52
|
+
"trash",
|
|
53
|
+
size=13,
|
|
54
|
+
stroke_width=1.5,
|
|
55
|
+
),
|
|
56
|
+
variant="ghost",
|
|
57
|
+
size="1",
|
|
58
|
+
margin_left="0px",
|
|
59
|
+
margin_right="0px",
|
|
60
|
+
color_scheme="gray",
|
|
61
|
+
on_click=ThreadListState.delete_thread(thread.thread_id),
|
|
62
|
+
),
|
|
63
|
+
content="Chat löschen",
|
|
64
|
+
flex_shrink=0,
|
|
65
|
+
),
|
|
66
|
+
on_click=ThreadListState.select_thread(thread.thread_id),
|
|
67
|
+
flex_direction=["row"],
|
|
68
|
+
margin_right="10px",
|
|
69
|
+
margin_bottom="8px",
|
|
70
|
+
padding="6px",
|
|
71
|
+
align="center",
|
|
72
|
+
border_radius="8px",
|
|
73
|
+
background_color=rx.cond(
|
|
74
|
+
thread.active,
|
|
75
|
+
rx.color("accent", 3),
|
|
76
|
+
rx.color("gray", 3),
|
|
77
|
+
),
|
|
78
|
+
border=rx.cond(
|
|
79
|
+
thread.active,
|
|
80
|
+
f"1px solid {rx.color('gray', 5)}",
|
|
81
|
+
"0",
|
|
82
|
+
),
|
|
83
|
+
style={
|
|
84
|
+
"_hover": {
|
|
85
|
+
"cursor": "pointer",
|
|
86
|
+
"background_color": rx.cond(
|
|
87
|
+
thread.active,
|
|
88
|
+
rx.color("accent", 4),
|
|
89
|
+
rx.color("gray", 6),
|
|
90
|
+
),
|
|
91
|
+
"color": rx.cond(
|
|
92
|
+
thread.active,
|
|
93
|
+
rx.color("black", 9),
|
|
94
|
+
rx.color("white", 9),
|
|
95
|
+
),
|
|
96
|
+
"opacity": "1",
|
|
97
|
+
},
|
|
98
|
+
"opacity": rx.cond(
|
|
99
|
+
thread.active,
|
|
100
|
+
"1",
|
|
101
|
+
"0.95",
|
|
102
|
+
),
|
|
103
|
+
},
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def list(**props) -> rx.Component:
|
|
108
|
+
"""List component for displaying threads."""
|
|
109
|
+
return rx.scroll_area(
|
|
110
|
+
rx.cond(
|
|
111
|
+
ThreadListState.has_threads,
|
|
112
|
+
rx.foreach(
|
|
113
|
+
ThreadListState.threads,
|
|
114
|
+
ThreadList.thread_list_item,
|
|
115
|
+
),
|
|
116
|
+
rx.text(
|
|
117
|
+
"Keine Chats vorhanden.",
|
|
118
|
+
size="2",
|
|
119
|
+
white_space="nowrap",
|
|
120
|
+
overflow="hidden",
|
|
121
|
+
text_overflow="ellipsis",
|
|
122
|
+
flex_grow="1",
|
|
123
|
+
min_width="0",
|
|
124
|
+
margin_right="10px",
|
|
125
|
+
margin_bottom="8px",
|
|
126
|
+
padding="6px",
|
|
127
|
+
align="center",
|
|
128
|
+
),
|
|
129
|
+
),
|
|
130
|
+
scrollbars="vertical",
|
|
131
|
+
padding_right="3px",
|
|
132
|
+
type="auto",
|
|
133
|
+
**props,
|
|
134
|
+
)
|