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.
@@ -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