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,96 @@
|
|
|
1
|
+
"""Repository for MCP server data access operations."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
import reflex as rx
|
|
6
|
+
|
|
7
|
+
from appkit_assistant.backend.models import MCPServer
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MCPServerRepository:
|
|
13
|
+
"""Repository class for MCP server database operations."""
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
async def get_all() -> list[MCPServer]:
|
|
17
|
+
"""Retrieve all MCP servers ordered by name."""
|
|
18
|
+
async with rx.asession() as session:
|
|
19
|
+
result = await session.exec(MCPServer.select().order_by(MCPServer.name))
|
|
20
|
+
return result.all()
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
async def get_by_id(server_id: int) -> MCPServer | None:
|
|
24
|
+
"""Retrieve an MCP server by ID."""
|
|
25
|
+
async with rx.asession() as session:
|
|
26
|
+
result = await session.exec(
|
|
27
|
+
MCPServer.select().where(MCPServer.id == server_id)
|
|
28
|
+
)
|
|
29
|
+
return result.first()
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
async def create(
|
|
33
|
+
name: str,
|
|
34
|
+
url: str,
|
|
35
|
+
headers: str,
|
|
36
|
+
description: str | None = None,
|
|
37
|
+
prompt: str | None = None,
|
|
38
|
+
) -> MCPServer:
|
|
39
|
+
"""Create a new MCP server."""
|
|
40
|
+
async with rx.asession() as session:
|
|
41
|
+
server = MCPServer(
|
|
42
|
+
name=name,
|
|
43
|
+
url=url,
|
|
44
|
+
headers=headers,
|
|
45
|
+
description=description,
|
|
46
|
+
prompt=prompt,
|
|
47
|
+
)
|
|
48
|
+
session.add(server)
|
|
49
|
+
await session.commit()
|
|
50
|
+
await session.refresh(server)
|
|
51
|
+
logger.debug("Created MCP server: %s", name)
|
|
52
|
+
return server
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
async def update(
|
|
56
|
+
server_id: int,
|
|
57
|
+
name: str,
|
|
58
|
+
url: str,
|
|
59
|
+
headers: str,
|
|
60
|
+
description: str | None = None,
|
|
61
|
+
prompt: str | None = None,
|
|
62
|
+
) -> MCPServer | None:
|
|
63
|
+
"""Update an existing MCP server."""
|
|
64
|
+
async with rx.asession() as session:
|
|
65
|
+
result = await session.exec(
|
|
66
|
+
MCPServer.select().where(MCPServer.id == server_id)
|
|
67
|
+
)
|
|
68
|
+
server = result.first()
|
|
69
|
+
if server:
|
|
70
|
+
server.name = name
|
|
71
|
+
server.url = url
|
|
72
|
+
server.headers = headers
|
|
73
|
+
server.description = description
|
|
74
|
+
server.prompt = prompt
|
|
75
|
+
await session.commit()
|
|
76
|
+
await session.refresh(server)
|
|
77
|
+
logger.debug("Updated MCP server: %s", name)
|
|
78
|
+
return server
|
|
79
|
+
logger.warning("MCP server with ID %s not found for update", server_id)
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
async def delete(server_id: int) -> bool:
|
|
84
|
+
"""Delete an MCP server by ID."""
|
|
85
|
+
async with rx.asession() as session:
|
|
86
|
+
result = await session.exec(
|
|
87
|
+
MCPServer.select().where(MCPServer.id == server_id)
|
|
88
|
+
)
|
|
89
|
+
server = result.first()
|
|
90
|
+
if server:
|
|
91
|
+
await session.delete(server)
|
|
92
|
+
await session.commit()
|
|
93
|
+
logger.debug("Deleted MCP server: %s", server.name)
|
|
94
|
+
return True
|
|
95
|
+
logger.warning("MCP server with ID %s not found for deletion", server_id)
|
|
96
|
+
return False
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from typing import Final
|
|
2
|
+
|
|
3
|
+
SYSTEM_PROMPT: Final[str] = """
|
|
4
|
+
# System Prompt: Kontextbewusster, Tool-orientierter Chat-Client
|
|
5
|
+
|
|
6
|
+
## 1) Auftrag
|
|
7
|
+
Interpretiere Benutzereingaben semantisch, berücksichtige Kontext (Verlauf, Metadaten, Projekte) und führe die geeignetsten Tools aus, um präzise, nachvollziehbare Ergebnisse zu liefern.
|
|
8
|
+
|
|
9
|
+
## 2) Prioritäten
|
|
10
|
+
1. Korrektheit und Prägnanz vor Länge.
|
|
11
|
+
2. Tool-Einsatz, wenn verfügbar und sinnvoll; ansonsten fundierte Eigenableitung.
|
|
12
|
+
3. Ergebnisse stets direkt anzeigen (kein Warten/Platzhalter).
|
|
13
|
+
|
|
14
|
+
## 3) Ausgabeformate
|
|
15
|
+
- **Code:** In Markdown-Codeblöcken mit korrektem Sprach-Tag.
|
|
16
|
+
- **Diagramme:** Immer in Mermaid-Syntax als korrekter Markdown Source.
|
|
17
|
+
- **Analysen/Vergleiche:** Datengetrieben; Tabellen verwenden.
|
|
18
|
+
- **Bilder (wichtig):** Mit Bilderzeugungs-Tools generieren und **immer inline** im Chat anzeigen. Bei realen Personen nur nach vorgängiger Zustimmung.
|
|
19
|
+
|
|
20
|
+
## 4) Tool-Nutzung
|
|
21
|
+
- Wähle genau **ein** primäres Tool pro Aufgabe (falls mehrere möglich, wähle das mit größtem Nutzen).
|
|
22
|
+
- Nutze Capability-Deskriptoren, führe Tool(s) deterministisch aus, zeige Output unmittelbar.
|
|
23
|
+
- Exploratives Vorgehen ist erlaubt, sofern Ziel und Kontext klar sind.
|
|
24
|
+
- Falls kein Tool passt: direkt antworten (strukturierte Begründung implizit, nicht ausgeben).
|
|
25
|
+
|
|
26
|
+
{mcp_prompts}
|
|
27
|
+
|
|
28
|
+
## 5) Kontext
|
|
29
|
+
- Berücksichtige durchgehend Gesprächsverlauf, Nutzerrolle, Organisation und laufende Projekte.
|
|
30
|
+
- Halte Kohärenz über mehrere Turns; vermeide Wiederholungen.
|
|
31
|
+
|
|
32
|
+
## 6) Fehler- & Ausnahmebehandlung
|
|
33
|
+
- **Toolfehler/Unverfügbarkeit:** Kurz informieren und sofort eine belastbare Alternative liefern (z. B. lokale Schätzung/Analyse).
|
|
34
|
+
- **Mehrdeutigkeit:** Triff eine nachvollziehbare Annahme und liefere ein vollständiges Ergebnis.
|
|
35
|
+
- **Kein geeignetes Tool:** Antwort mit eigener Inferenz, klar strukturiert.
|
|
36
|
+
|
|
37
|
+
## 7) Qualitätskriterien
|
|
38
|
+
- Präzise, testbare Aussagen; wenn sinnvoll, mit Zahlen/Tabellen.
|
|
39
|
+
- Klare Struktur (Überschriften, Listen, Tabellen, Codeblöcke, Diagramme).
|
|
40
|
+
- Konsistente Terminologie; keine redundanten Passagen.
|
|
41
|
+
|
|
42
|
+
## 8) Beispiele (Format)
|
|
43
|
+
```python
|
|
44
|
+
def hello_world():
|
|
45
|
+
print("Hello, world!")
|
|
46
|
+
```
|
|
47
|
+
```mermaid
|
|
48
|
+
flowchart TD
|
|
49
|
+
A["LLM/Chat-Client"] --> B["MCP Client"]
|
|
50
|
+
B --> C{{"Transport"}}
|
|
51
|
+
C -->|stdio| D["FastMCP Server (lokal)"]
|
|
52
|
+
C -->|http| E["FastMCP Server (remote)"]
|
|
53
|
+
D --> F["@mcp.tool web_search()"]
|
|
54
|
+
E --> F
|
|
55
|
+
```
|
|
56
|
+
"""
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from appkit_assistant.backend.models import Suggestion
|
|
2
|
+
from appkit_assistant.components.composer import composer
|
|
3
|
+
from appkit_assistant.components.thread import Assistant
|
|
4
|
+
from appkit_assistant.components.message import MessageComponent
|
|
5
|
+
from appkit_assistant.backend.models import (
|
|
6
|
+
AIModel,
|
|
7
|
+
Chunk,
|
|
8
|
+
ChunkType,
|
|
9
|
+
MCPServer,
|
|
10
|
+
Message,
|
|
11
|
+
MessageType,
|
|
12
|
+
ThreadModel,
|
|
13
|
+
ThreadStatus,
|
|
14
|
+
)
|
|
15
|
+
from appkit_assistant.state.thread_state import (
|
|
16
|
+
ThreadState,
|
|
17
|
+
ThreadListState,
|
|
18
|
+
)
|
|
19
|
+
from appkit_assistant.components.mcp_server_table import mcp_servers_table
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"AIModel",
|
|
23
|
+
"Assistant",
|
|
24
|
+
"Chunk",
|
|
25
|
+
"ChunkType",
|
|
26
|
+
"MCPServer",
|
|
27
|
+
"Message",
|
|
28
|
+
"MessageComponent",
|
|
29
|
+
"MessageType",
|
|
30
|
+
"Suggestion",
|
|
31
|
+
"ThreadList",
|
|
32
|
+
"ThreadListState",
|
|
33
|
+
"ThreadModel",
|
|
34
|
+
"ThreadState",
|
|
35
|
+
"ThreadStatus",
|
|
36
|
+
"composer",
|
|
37
|
+
"mcp_servers_table",
|
|
38
|
+
]
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
|
|
3
|
+
import reflex as rx
|
|
4
|
+
|
|
5
|
+
import appkit_mantine as mn
|
|
6
|
+
from appkit_assistant.components.tools_modal import tools_popover
|
|
7
|
+
from appkit_assistant.state.thread_state import ThreadState
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def render_model_option(model: dict) -> rx.Component:
|
|
11
|
+
return rx.hstack(
|
|
12
|
+
rx.cond(
|
|
13
|
+
model.icon,
|
|
14
|
+
rx.image(
|
|
15
|
+
src=rx.color_mode_cond(
|
|
16
|
+
light=f"/icons/{model.icon}.svg",
|
|
17
|
+
dark=f"/icons/{model.icon}_dark.svg",
|
|
18
|
+
),
|
|
19
|
+
width="13px",
|
|
20
|
+
margin_right="8px",
|
|
21
|
+
),
|
|
22
|
+
None,
|
|
23
|
+
),
|
|
24
|
+
rx.text(model.text),
|
|
25
|
+
align="center",
|
|
26
|
+
spacing="0",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def composer_input(placeholder: str = "Frage etwas...") -> rx.Component:
|
|
31
|
+
return rx.text_area(
|
|
32
|
+
id="composer-area",
|
|
33
|
+
name="composer_prompt",
|
|
34
|
+
placeholder=placeholder,
|
|
35
|
+
value=ThreadState.prompt,
|
|
36
|
+
auto_height=True,
|
|
37
|
+
enter_key_submit=True,
|
|
38
|
+
# stil
|
|
39
|
+
border="0",
|
|
40
|
+
outline="none",
|
|
41
|
+
variant="soft",
|
|
42
|
+
background_color=rx.color("white", 1, alpha=False),
|
|
43
|
+
padding="9px 3px",
|
|
44
|
+
size="3",
|
|
45
|
+
min_height="24px",
|
|
46
|
+
max_height="244px",
|
|
47
|
+
resize="none",
|
|
48
|
+
rows="1",
|
|
49
|
+
width="100%",
|
|
50
|
+
on_change=ThreadState.set_prompt,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def submit() -> rx.Component:
|
|
55
|
+
return rx.fragment(
|
|
56
|
+
rx.button(
|
|
57
|
+
rx.icon("arrow-right", size=18),
|
|
58
|
+
id="composer-submit",
|
|
59
|
+
name="composer_submit",
|
|
60
|
+
type="submit",
|
|
61
|
+
loading=ThreadState.processing,
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def add_attachment(show: bool = False) -> rx.Component | None:
|
|
67
|
+
if not show:
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
return rx.tooltip(
|
|
71
|
+
rx.button(
|
|
72
|
+
rx.icon("paperclip", size=18),
|
|
73
|
+
rx.text("2 files", size="1", color="gray.2"),
|
|
74
|
+
id="composer-attachment",
|
|
75
|
+
variant="ghost",
|
|
76
|
+
padding="8px",
|
|
77
|
+
access_key="s",
|
|
78
|
+
),
|
|
79
|
+
content="Manage Attachments…",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def choose_model(show: bool = False) -> rx.Component | None:
|
|
84
|
+
if not show:
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
return rx.cond(
|
|
88
|
+
ThreadState.ai_models,
|
|
89
|
+
mn.rich_select(
|
|
90
|
+
mn.rich_select.map(
|
|
91
|
+
ThreadState.ai_models,
|
|
92
|
+
renderer=render_model_option,
|
|
93
|
+
value=lambda model: model.id,
|
|
94
|
+
),
|
|
95
|
+
placeholder="Wähle ein Modell",
|
|
96
|
+
value=ThreadState.selected_model,
|
|
97
|
+
on_change=ThreadState.set_selected_model,
|
|
98
|
+
name="model-select",
|
|
99
|
+
width="252px",
|
|
100
|
+
position="top",
|
|
101
|
+
),
|
|
102
|
+
None,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def tools(show: bool = False) -> rx.Component:
|
|
107
|
+
"""Render tools button with conditional visibility."""
|
|
108
|
+
return rx.cond(
|
|
109
|
+
show,
|
|
110
|
+
rx.hstack(
|
|
111
|
+
tools_popover(),
|
|
112
|
+
spacing="1",
|
|
113
|
+
align="center",
|
|
114
|
+
),
|
|
115
|
+
rx.fragment(), # Empty fragment when hidden
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def clear(show: bool = True) -> rx.Component | None:
|
|
120
|
+
if not show:
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
return rx.tooltip(
|
|
124
|
+
rx.button(
|
|
125
|
+
rx.icon("paintbrush", size=17),
|
|
126
|
+
variant="ghost",
|
|
127
|
+
padding="8px",
|
|
128
|
+
on_click=ThreadState.clear,
|
|
129
|
+
),
|
|
130
|
+
content="Chatverlauf löschen",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def composer(*children, on_submit: Callable, **kwargs) -> rx.Component:
|
|
135
|
+
return rx.vstack(
|
|
136
|
+
rx.form.root(
|
|
137
|
+
*children,
|
|
138
|
+
on_submit=on_submit,
|
|
139
|
+
),
|
|
140
|
+
**kwargs,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class ComposerComponent(rx.ComponentNamespace):
|
|
145
|
+
__call__ = staticmethod(composer)
|
|
146
|
+
add_attachment = staticmethod(add_attachment)
|
|
147
|
+
choose_model = staticmethod(choose_model)
|
|
148
|
+
clear = staticmethod(clear)
|
|
149
|
+
input = staticmethod(composer_input)
|
|
150
|
+
submit = staticmethod(submit)
|
|
151
|
+
tools = staticmethod(tools)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
composer = ComposerComponent()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import reflex as rx
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class KeyboardShortcuts(rx.Component):
|
|
5
|
+
library = None # No external library needed
|
|
6
|
+
tag = None
|
|
7
|
+
|
|
8
|
+
def add_imports(self) -> dict:
|
|
9
|
+
return {
|
|
10
|
+
"react": [rx.ImportVar(tag="useEffect")],
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
def add_hooks(self) -> list[str]:
|
|
14
|
+
return [
|
|
15
|
+
"""
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const textarea = document.getElementById('composer-area');
|
|
18
|
+
const submitBtn = document.getElementById('composer-submit');
|
|
19
|
+
|
|
20
|
+
if (textarea && submitBtn) {
|
|
21
|
+
const handleKeydown = (e) => {
|
|
22
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
23
|
+
e.preventDefault();
|
|
24
|
+
if (textarea.value.trim() && !submitBtn.disabled) {
|
|
25
|
+
submitBtn.click();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
textarea.addEventListener('keydown', handleKeydown);
|
|
30
|
+
return () => textarea.removeEventListener('keydown', handleKeydown);
|
|
31
|
+
}
|
|
32
|
+
}, []);
|
|
33
|
+
"""
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Create an instance function
|
|
38
|
+
keyboard_shortcuts = KeyboardShortcuts.create
|