appkit-assistant 0.13.0__py3-none-any.whl → 0.14.0__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/repositories.py +124 -285
- appkit_assistant/backend/system_prompt_cache.py +14 -4
- appkit_assistant/components/mcp_server_dialogs.py +1 -1
- appkit_assistant/state/mcp_server_state.py +55 -28
- appkit_assistant/state/system_prompt_state.py +51 -27
- appkit_assistant/state/thread_list_state.py +25 -4
- appkit_assistant/state/thread_state.py +72 -13
- {appkit_assistant-0.13.0.dist-info → appkit_assistant-0.14.0.dist-info}/METADATA +26 -9
- {appkit_assistant-0.13.0.dist-info → appkit_assistant-0.14.0.dist-info}/RECORD +10 -10
- {appkit_assistant-0.13.0.dist-info → appkit_assistant-0.14.0.dist-info}/WHEEL +0 -0
|
@@ -3,321 +3,160 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
from datetime import UTC, datetime
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
from sqlalchemy import select
|
|
7
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
7
8
|
from sqlalchemy.orm import defer
|
|
8
9
|
|
|
9
10
|
from appkit_assistant.backend.models import (
|
|
10
11
|
AssistantThread,
|
|
11
12
|
MCPServer,
|
|
12
|
-
Message,
|
|
13
13
|
SystemPrompt,
|
|
14
|
-
ThreadModel,
|
|
15
|
-
ThreadStatus,
|
|
16
14
|
)
|
|
15
|
+
from appkit_commons.database.base_repository import BaseRepository
|
|
17
16
|
|
|
18
17
|
logger = logging.getLogger(__name__)
|
|
19
18
|
|
|
20
19
|
|
|
21
|
-
class MCPServerRepository:
|
|
20
|
+
class MCPServerRepository(BaseRepository[MCPServer, AsyncSession]):
|
|
22
21
|
"""Repository class for MCP server database operations."""
|
|
23
22
|
|
|
24
|
-
@
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
async with rx.asession() as session:
|
|
28
|
-
result = await session.exec(MCPServer.select().order_by(MCPServer.name))
|
|
29
|
-
return result.all()
|
|
30
|
-
|
|
31
|
-
@staticmethod
|
|
32
|
-
async def get_by_id(server_id: int) -> MCPServer | None:
|
|
33
|
-
"""Retrieve an MCP server by ID."""
|
|
34
|
-
async with rx.asession() as session:
|
|
35
|
-
result = await session.exec(
|
|
36
|
-
MCPServer.select().where(MCPServer.id == server_id)
|
|
37
|
-
)
|
|
38
|
-
return result.first()
|
|
39
|
-
|
|
40
|
-
@staticmethod
|
|
41
|
-
async def create(
|
|
42
|
-
name: str,
|
|
43
|
-
url: str,
|
|
44
|
-
headers: str,
|
|
45
|
-
description: str | None = None,
|
|
46
|
-
prompt: str | None = None,
|
|
47
|
-
) -> MCPServer:
|
|
48
|
-
"""Create a new MCP server."""
|
|
49
|
-
async with rx.asession() as session:
|
|
50
|
-
server = MCPServer(
|
|
51
|
-
name=name,
|
|
52
|
-
url=url,
|
|
53
|
-
headers=headers,
|
|
54
|
-
description=description,
|
|
55
|
-
prompt=prompt,
|
|
56
|
-
)
|
|
57
|
-
session.add(server)
|
|
58
|
-
await session.commit()
|
|
59
|
-
await session.refresh(server)
|
|
60
|
-
logger.debug("Created MCP server: %s", name)
|
|
61
|
-
return server
|
|
23
|
+
@property
|
|
24
|
+
def model_class(self) -> type[MCPServer]:
|
|
25
|
+
return MCPServer
|
|
62
26
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
headers: str,
|
|
69
|
-
description: str | None = None,
|
|
70
|
-
prompt: str | None = None,
|
|
71
|
-
) -> MCPServer | None:
|
|
72
|
-
"""Update an existing MCP server."""
|
|
73
|
-
async with rx.asession() as session:
|
|
74
|
-
result = await session.exec(
|
|
75
|
-
MCPServer.select().where(MCPServer.id == server_id)
|
|
76
|
-
)
|
|
77
|
-
server = result.first()
|
|
78
|
-
if server:
|
|
79
|
-
server.name = name
|
|
80
|
-
server.url = url
|
|
81
|
-
server.headers = headers
|
|
82
|
-
server.description = description
|
|
83
|
-
server.prompt = prompt
|
|
84
|
-
await session.commit()
|
|
85
|
-
await session.refresh(server)
|
|
86
|
-
logger.debug("Updated MCP server: %s", name)
|
|
87
|
-
return server
|
|
88
|
-
logger.warning("MCP server with ID %s not found for update", server_id)
|
|
89
|
-
return None
|
|
90
|
-
|
|
91
|
-
@staticmethod
|
|
92
|
-
async def delete(server_id: int) -> bool:
|
|
93
|
-
"""Delete an MCP server by ID."""
|
|
94
|
-
async with rx.asession() as session:
|
|
95
|
-
result = await session.exec(
|
|
96
|
-
MCPServer.select().where(MCPServer.id == server_id)
|
|
97
|
-
)
|
|
98
|
-
server = result.first()
|
|
99
|
-
if server:
|
|
100
|
-
await session.delete(server)
|
|
101
|
-
await session.commit()
|
|
102
|
-
logger.debug("Deleted MCP server: %s", server.name)
|
|
103
|
-
return True
|
|
104
|
-
logger.warning("MCP server with ID %s not found for deletion", server_id)
|
|
105
|
-
return False
|
|
27
|
+
async def find_all_ordered_by_name(self, session: AsyncSession) -> list[MCPServer]:
|
|
28
|
+
"""Retrieve all MCP servers ordered by name."""
|
|
29
|
+
stmt = select(MCPServer).order_by(MCPServer.name)
|
|
30
|
+
result = await session.execute(stmt)
|
|
31
|
+
return list(result.scalars().all())
|
|
106
32
|
|
|
107
33
|
|
|
108
|
-
class SystemPromptRepository:
|
|
34
|
+
class SystemPromptRepository(BaseRepository[SystemPrompt, AsyncSession]):
|
|
109
35
|
"""Repository class for system prompt database operations.
|
|
110
36
|
|
|
111
37
|
Implements append-only versioning with full CRUD capabilities.
|
|
112
38
|
"""
|
|
113
39
|
|
|
114
|
-
@
|
|
115
|
-
|
|
40
|
+
@property
|
|
41
|
+
def model_class(self) -> type[SystemPrompt]:
|
|
42
|
+
return SystemPrompt
|
|
43
|
+
|
|
44
|
+
async def find_all_ordered_by_version_desc(
|
|
45
|
+
self, session: AsyncSession
|
|
46
|
+
) -> list[SystemPrompt]:
|
|
116
47
|
"""Retrieve all system prompt versions ordered by version descending."""
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
)
|
|
121
|
-
return result.all()
|
|
48
|
+
stmt = select(SystemPrompt).order_by(SystemPrompt.version.desc())
|
|
49
|
+
result = await session.execute(stmt)
|
|
50
|
+
return list(result.scalars().all())
|
|
122
51
|
|
|
123
|
-
|
|
124
|
-
async def get_latest() -> SystemPrompt | None:
|
|
52
|
+
async def find_latest(self, session: AsyncSession) -> SystemPrompt | None:
|
|
125
53
|
"""Retrieve the latest system prompt version."""
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
)
|
|
130
|
-
return result.first()
|
|
54
|
+
stmt = select(SystemPrompt).order_by(SystemPrompt.version.desc()).limit(1)
|
|
55
|
+
result = await session.execute(stmt)
|
|
56
|
+
return result.scalars().first()
|
|
131
57
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
async with rx.asession() as session:
|
|
136
|
-
result = await session.exec(
|
|
137
|
-
SystemPrompt.select().where(SystemPrompt.id == prompt_id)
|
|
138
|
-
)
|
|
139
|
-
return result.first()
|
|
140
|
-
|
|
141
|
-
@staticmethod
|
|
142
|
-
async def create(prompt: str, user_id: int) -> SystemPrompt:
|
|
58
|
+
async def create_next_version(
|
|
59
|
+
self, session: AsyncSession, prompt: str, user_id: int
|
|
60
|
+
) -> SystemPrompt:
|
|
143
61
|
"""Neue System Prompt Version anlegen.
|
|
144
62
|
|
|
145
63
|
Version ist fortlaufende Ganzzahl, beginnend bei 1.
|
|
146
64
|
"""
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
@staticmethod
|
|
175
|
-
async def delete(prompt_id: int) -> bool:
|
|
176
|
-
"""Delete a system prompt version by ID."""
|
|
177
|
-
async with rx.asession() as session:
|
|
178
|
-
result = await session.exec(
|
|
179
|
-
SystemPrompt.select().where(SystemPrompt.id == prompt_id)
|
|
180
|
-
)
|
|
181
|
-
prompt = result.first()
|
|
182
|
-
if prompt:
|
|
183
|
-
await session.delete(prompt)
|
|
184
|
-
await session.commit()
|
|
185
|
-
logger.info("Deleted system prompt version: %s", prompt.version)
|
|
186
|
-
return True
|
|
187
|
-
logger.warning(
|
|
188
|
-
"System prompt with ID %s not found for deletion",
|
|
189
|
-
prompt_id,
|
|
190
|
-
)
|
|
191
|
-
return False
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
class ThreadRepository:
|
|
65
|
+
stmt = select(SystemPrompt).order_by(SystemPrompt.version.desc()).limit(1)
|
|
66
|
+
result = await session.execute(stmt)
|
|
67
|
+
latest = result.scalars().first()
|
|
68
|
+
next_version = (latest.version + 1) if latest else 1
|
|
69
|
+
|
|
70
|
+
name = f"Version {next_version}"
|
|
71
|
+
|
|
72
|
+
system_prompt = SystemPrompt(
|
|
73
|
+
name=name,
|
|
74
|
+
prompt=prompt,
|
|
75
|
+
version=next_version,
|
|
76
|
+
user_id=user_id,
|
|
77
|
+
created_at=datetime.now(UTC),
|
|
78
|
+
)
|
|
79
|
+
session.add(system_prompt)
|
|
80
|
+
await session.flush()
|
|
81
|
+
await session.refresh(system_prompt)
|
|
82
|
+
|
|
83
|
+
logger.info(
|
|
84
|
+
"Created system prompt version %s for user %s",
|
|
85
|
+
next_version,
|
|
86
|
+
user_id,
|
|
87
|
+
)
|
|
88
|
+
return system_prompt
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class ThreadRepository(BaseRepository[AssistantThread, AsyncSession]):
|
|
195
92
|
"""Repository class for Thread database operations."""
|
|
196
93
|
|
|
197
|
-
@
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
async with rx.asession() as session:
|
|
201
|
-
result = await session.exec(
|
|
202
|
-
AssistantThread.select()
|
|
203
|
-
.where(AssistantThread.user_id == user_id)
|
|
204
|
-
.order_by(AssistantThread.updated_at.desc())
|
|
205
|
-
)
|
|
206
|
-
threads = result.all()
|
|
207
|
-
return [
|
|
208
|
-
ThreadModel(
|
|
209
|
-
thread_id=t.thread_id,
|
|
210
|
-
title=t.title,
|
|
211
|
-
state=ThreadStatus(t.state),
|
|
212
|
-
ai_model=t.ai_model,
|
|
213
|
-
active=t.active,
|
|
214
|
-
messages=[Message(**m) for m in t.messages],
|
|
215
|
-
)
|
|
216
|
-
for t in threads
|
|
217
|
-
]
|
|
218
|
-
|
|
219
|
-
@staticmethod
|
|
220
|
-
async def save_thread(thread: ThreadModel, user_id: int) -> None:
|
|
221
|
-
"""Save or update a thread."""
|
|
222
|
-
async with rx.asession() as session:
|
|
223
|
-
result = await session.exec(
|
|
224
|
-
AssistantThread.select().where(
|
|
225
|
-
AssistantThread.thread_id == thread.thread_id
|
|
226
|
-
)
|
|
227
|
-
)
|
|
228
|
-
db_thread = result.first()
|
|
229
|
-
|
|
230
|
-
messages_dict = [m.dict() for m in thread.messages]
|
|
231
|
-
|
|
232
|
-
if db_thread:
|
|
233
|
-
# Ensure user owns the thread or handle shared threads logic if needed
|
|
234
|
-
# For now, we assume thread_id is unique enough,
|
|
235
|
-
# but checking user_id is safer
|
|
236
|
-
if db_thread.user_id != user_id:
|
|
237
|
-
logger.warning(
|
|
238
|
-
"User %s tried to update thread %s belonging to user %s",
|
|
239
|
-
user_id,
|
|
240
|
-
thread.thread_id,
|
|
241
|
-
db_thread.user_id,
|
|
242
|
-
)
|
|
243
|
-
return
|
|
94
|
+
@property
|
|
95
|
+
def model_class(self) -> type[AssistantThread]:
|
|
96
|
+
return AssistantThread
|
|
244
97
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
98
|
+
async def find_by_user(
|
|
99
|
+
self, session: AsyncSession, user_id: int
|
|
100
|
+
) -> list[AssistantThread]:
|
|
101
|
+
"""Retrieve all threads for a user."""
|
|
102
|
+
stmt = (
|
|
103
|
+
select(AssistantThread)
|
|
104
|
+
.where(AssistantThread.user_id == user_id)
|
|
105
|
+
.order_by(AssistantThread.updated_at.desc())
|
|
106
|
+
)
|
|
107
|
+
result = await session.execute(stmt)
|
|
108
|
+
return list(result.scalars().all())
|
|
109
|
+
|
|
110
|
+
async def find_by_thread_id(
|
|
111
|
+
self, session: AsyncSession, thread_id: str
|
|
112
|
+
) -> AssistantThread | None:
|
|
113
|
+
"""Retrieve a thread by its thread_id."""
|
|
114
|
+
stmt = select(AssistantThread).where(AssistantThread.thread_id == thread_id)
|
|
115
|
+
result = await session.execute(stmt)
|
|
116
|
+
return result.scalars().first()
|
|
117
|
+
|
|
118
|
+
async def find_by_thread_id_and_user(
|
|
119
|
+
self, session: AsyncSession, thread_id: str, user_id: int
|
|
120
|
+
) -> AssistantThread | None:
|
|
121
|
+
"""Retrieve a thread by thread_id and user_id."""
|
|
122
|
+
stmt = select(AssistantThread).where(
|
|
123
|
+
AssistantThread.thread_id == thread_id,
|
|
124
|
+
AssistantThread.user_id == user_id,
|
|
125
|
+
)
|
|
126
|
+
result = await session.execute(stmt)
|
|
127
|
+
return result.scalars().first()
|
|
128
|
+
|
|
129
|
+
async def delete_by_thread_id_and_user(
|
|
130
|
+
self, session: AsyncSession, thread_id: str, user_id: int
|
|
131
|
+
) -> bool:
|
|
132
|
+
"""Delete a thread by thread_id and user_id."""
|
|
133
|
+
stmt = select(AssistantThread).where(
|
|
134
|
+
AssistantThread.thread_id == thread_id,
|
|
135
|
+
AssistantThread.user_id == user_id,
|
|
136
|
+
)
|
|
137
|
+
result = await session.execute(stmt)
|
|
138
|
+
thread = result.scalars().first()
|
|
139
|
+
if thread:
|
|
140
|
+
await session.delete(thread)
|
|
141
|
+
await session.flush()
|
|
142
|
+
return True
|
|
143
|
+
return False
|
|
144
|
+
|
|
145
|
+
async def find_summaries_by_user(
|
|
146
|
+
self, session: AsyncSession, user_id: int
|
|
147
|
+
) -> list[AssistantThread]:
|
|
282
148
|
"""Retrieve thread summaries (no messages) for a user."""
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
active=t.active,
|
|
298
|
-
messages=[], # Empty messages for summary
|
|
299
|
-
)
|
|
300
|
-
for t in threads
|
|
301
|
-
]
|
|
302
|
-
|
|
303
|
-
@staticmethod
|
|
304
|
-
async def get_thread_by_id(thread_id: str, user_id: int) -> ThreadModel | None:
|
|
305
|
-
"""Retrieve a full thread by ID."""
|
|
306
|
-
async with rx.asession() as session:
|
|
307
|
-
result = await session.exec(
|
|
308
|
-
AssistantThread.select().where(
|
|
309
|
-
AssistantThread.thread_id == thread_id,
|
|
310
|
-
AssistantThread.user_id == user_id,
|
|
311
|
-
)
|
|
312
|
-
)
|
|
313
|
-
t = result.first()
|
|
314
|
-
if not t:
|
|
315
|
-
return None
|
|
316
|
-
return ThreadModel(
|
|
317
|
-
thread_id=t.thread_id,
|
|
318
|
-
title=t.title,
|
|
319
|
-
state=ThreadStatus(t.state),
|
|
320
|
-
ai_model=t.ai_model,
|
|
321
|
-
active=t.active,
|
|
322
|
-
messages=[Message(**m) for m in t.messages],
|
|
323
|
-
)
|
|
149
|
+
stmt = (
|
|
150
|
+
select(AssistantThread)
|
|
151
|
+
.where(AssistantThread.user_id == user_id)
|
|
152
|
+
.options(defer(AssistantThread.messages))
|
|
153
|
+
.order_by(AssistantThread.updated_at.desc())
|
|
154
|
+
)
|
|
155
|
+
result = await session.execute(stmt)
|
|
156
|
+
return list(result.scalars().all())
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# Export instances
|
|
160
|
+
mcp_server_repo = MCPServerRepository()
|
|
161
|
+
system_prompt_repo = SystemPromptRepository()
|
|
162
|
+
thread_repo = ThreadRepository()
|
|
@@ -3,7 +3,8 @@ import logging
|
|
|
3
3
|
from datetime import UTC, datetime, timedelta
|
|
4
4
|
from typing import Final
|
|
5
5
|
|
|
6
|
-
from appkit_assistant.backend.repositories import
|
|
6
|
+
from appkit_assistant.backend.repositories import system_prompt_repo
|
|
7
|
+
from appkit_commons.database.session import get_asyncdb_session
|
|
7
8
|
|
|
8
9
|
logger = logging.getLogger(__name__)
|
|
9
10
|
|
|
@@ -81,15 +82,24 @@ class SystemPromptCache:
|
|
|
81
82
|
# Cache miss or expired - fetch from database
|
|
82
83
|
logger.info("Cache miss - fetching latest prompt from database")
|
|
83
84
|
|
|
84
|
-
|
|
85
|
+
async with get_asyncdb_session() as session:
|
|
86
|
+
latest_prompt = await system_prompt_repo.find_latest(session)
|
|
87
|
+
|
|
88
|
+
if latest_prompt is None:
|
|
89
|
+
# Raise inside to exit or handle outside
|
|
90
|
+
pass
|
|
91
|
+
else:
|
|
92
|
+
# Capture values while attached
|
|
93
|
+
prompt_text = latest_prompt.prompt
|
|
94
|
+
prompt_version = latest_prompt.version
|
|
85
95
|
|
|
86
96
|
if latest_prompt is None:
|
|
87
97
|
msg = "No system prompt found in database"
|
|
88
98
|
logger.error(msg)
|
|
89
99
|
raise ValueError(msg)
|
|
90
100
|
|
|
91
|
-
self._cached_prompt =
|
|
92
|
-
self._cached_version =
|
|
101
|
+
self._cached_prompt = prompt_text
|
|
102
|
+
self._cached_version = prompt_version
|
|
93
103
|
self._cache_timestamp = datetime.now(UTC)
|
|
94
104
|
|
|
95
105
|
logger.info(
|
|
@@ -130,7 +130,7 @@ class ValidationState(rx.State):
|
|
|
130
130
|
@var_operation
|
|
131
131
|
def json(obj: rx.Var, indent: int = 4) -> CustomVarOperationReturn[RETURN]:
|
|
132
132
|
return var_operation_return(
|
|
133
|
-
js_expression=f"JSON.stringify(JSON.parse({obj}), null, {indent})",
|
|
133
|
+
js_expression=f"JSON.stringify(JSON.parse({obj} || '{{}}'), null, {indent})",
|
|
134
134
|
var_type=Any,
|
|
135
135
|
)
|
|
136
136
|
|
|
@@ -9,8 +9,9 @@ import reflex as rx
|
|
|
9
9
|
|
|
10
10
|
from appkit_assistant.backend.models import MCPServer
|
|
11
11
|
from appkit_assistant.backend.repositories import (
|
|
12
|
-
|
|
12
|
+
mcp_server_repo,
|
|
13
13
|
)
|
|
14
|
+
from appkit_commons.database.session import get_asyncdb_session
|
|
14
15
|
|
|
15
16
|
logger = logging.getLogger(__name__)
|
|
16
17
|
|
|
@@ -29,7 +30,9 @@ class MCPServerState(rx.State):
|
|
|
29
30
|
"""
|
|
30
31
|
self.loading = True
|
|
31
32
|
try:
|
|
32
|
-
|
|
33
|
+
async with get_asyncdb_session() as session:
|
|
34
|
+
servers = await mcp_server_repo.find_all_ordered_by_name(session)
|
|
35
|
+
self.servers = [MCPServer(**s.model_dump()) for s in servers]
|
|
33
36
|
logger.debug("Loaded %d MCP servers", len(self.servers))
|
|
34
37
|
except Exception as e:
|
|
35
38
|
logger.error("Failed to load MCP servers: %s", e)
|
|
@@ -50,7 +53,13 @@ class MCPServerState(rx.State):
|
|
|
50
53
|
async def get_server(self, server_id: int) -> None:
|
|
51
54
|
"""Get a specific MCP server by ID."""
|
|
52
55
|
try:
|
|
53
|
-
|
|
56
|
+
async with get_asyncdb_session() as session:
|
|
57
|
+
server = await mcp_server_repo.find_by_id(session, server_id)
|
|
58
|
+
if server:
|
|
59
|
+
self.current_server = MCPServer(**server.model_dump())
|
|
60
|
+
else:
|
|
61
|
+
self.current_server = None
|
|
62
|
+
|
|
54
63
|
if not self.current_server:
|
|
55
64
|
logger.warning("MCP server with ID %d not found", server_id)
|
|
56
65
|
except Exception as e:
|
|
@@ -64,7 +73,7 @@ class MCPServerState(rx.State):
|
|
|
64
73
|
"""Add a new MCP server."""
|
|
65
74
|
try:
|
|
66
75
|
headers = self._parse_headers_from_form(form_data)
|
|
67
|
-
|
|
76
|
+
server_entity = MCPServer(
|
|
68
77
|
name=form_data["name"],
|
|
69
78
|
url=form_data["url"],
|
|
70
79
|
headers=headers,
|
|
@@ -72,12 +81,17 @@ class MCPServerState(rx.State):
|
|
|
72
81
|
prompt=form_data.get("prompt") or None,
|
|
73
82
|
)
|
|
74
83
|
|
|
84
|
+
async with get_asyncdb_session() as session:
|
|
85
|
+
server = await mcp_server_repo.save(session, server_entity)
|
|
86
|
+
# Ensure we have the name before session closes if used later
|
|
87
|
+
server_name = server.name
|
|
88
|
+
|
|
75
89
|
await self.load_servers()
|
|
76
90
|
yield rx.toast.info(
|
|
77
91
|
"MCP Server {} wurde hinzugefügt.".format(form_data["name"]),
|
|
78
92
|
position="top-right",
|
|
79
93
|
)
|
|
80
|
-
logger.debug("Added MCP server: %s",
|
|
94
|
+
logger.debug("Added MCP server: %s", server_name)
|
|
81
95
|
|
|
82
96
|
except ValueError as e:
|
|
83
97
|
logger.error("Invalid form data for MCP server: %s", e)
|
|
@@ -105,22 +119,34 @@ class MCPServerState(rx.State):
|
|
|
105
119
|
|
|
106
120
|
try:
|
|
107
121
|
headers = self._parse_headers_from_form(form_data)
|
|
108
|
-
|
|
109
|
-
server_id=self.current_server.id,
|
|
110
|
-
name=form_data["name"],
|
|
111
|
-
url=form_data["url"],
|
|
112
|
-
headers=headers,
|
|
113
|
-
description=form_data.get("description") or None,
|
|
114
|
-
prompt=form_data.get("prompt") or None,
|
|
115
|
-
)
|
|
122
|
+
updated_name = ""
|
|
116
123
|
|
|
117
|
-
|
|
124
|
+
async with get_asyncdb_session() as session:
|
|
125
|
+
# Re-fetch server to ensure we have the latest and bound to session
|
|
126
|
+
existing_server = await mcp_server_repo.find_by_id(
|
|
127
|
+
session, self.current_server.id
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
updated_server = None
|
|
131
|
+
if existing_server:
|
|
132
|
+
existing_server.name = form_data["name"]
|
|
133
|
+
existing_server.url = form_data["url"]
|
|
134
|
+
existing_server.headers = headers
|
|
135
|
+
existing_server.description = form_data.get("description") or None
|
|
136
|
+
existing_server.prompt = form_data.get("prompt") or None
|
|
137
|
+
|
|
138
|
+
updated_server = await mcp_server_repo.save(
|
|
139
|
+
session, existing_server
|
|
140
|
+
)
|
|
141
|
+
updated_name = updated_server.name
|
|
142
|
+
|
|
143
|
+
if updated_name:
|
|
118
144
|
await self.load_servers()
|
|
119
145
|
yield rx.toast.info(
|
|
120
146
|
"MCP Server {} wurde aktualisiert.".format(form_data["name"]),
|
|
121
147
|
position="top-right",
|
|
122
148
|
)
|
|
123
|
-
logger.debug("Updated MCP server: %s",
|
|
149
|
+
logger.debug("Updated MCP server: %s", updated_name)
|
|
124
150
|
else:
|
|
125
151
|
yield rx.toast.error(
|
|
126
152
|
"MCP Server konnte nicht gefunden werden.",
|
|
@@ -143,19 +169,20 @@ class MCPServerState(rx.State):
|
|
|
143
169
|
async def delete_server(self, server_id: int) -> AsyncGenerator[Any, Any]:
|
|
144
170
|
"""Delete an MCP server."""
|
|
145
171
|
try:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
172
|
+
async with get_asyncdb_session() as session:
|
|
173
|
+
# Get server name for the success message
|
|
174
|
+
server = await mcp_server_repo.find_by_id(session, server_id)
|
|
175
|
+
if not server:
|
|
176
|
+
yield rx.toast.error(
|
|
177
|
+
"MCP Server nicht gefunden.",
|
|
178
|
+
position="top-right",
|
|
179
|
+
)
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
server_name = server.name
|
|
183
|
+
|
|
184
|
+
# Delete server using repository
|
|
185
|
+
success = await mcp_server_repo.delete_by_id(session, server_id)
|
|
159
186
|
|
|
160
187
|
if success:
|
|
161
188
|
await self.load_servers()
|
|
@@ -5,8 +5,9 @@ from typing import Any, Final
|
|
|
5
5
|
import reflex as rx
|
|
6
6
|
from reflex.state import State
|
|
7
7
|
|
|
8
|
-
from appkit_assistant.backend.repositories import
|
|
8
|
+
from appkit_assistant.backend.repositories import system_prompt_repo
|
|
9
9
|
from appkit_assistant.backend.system_prompt_cache import invalidate_prompt_cache
|
|
10
|
+
from appkit_commons.database.session import get_asyncdb_session
|
|
10
11
|
from appkit_user.authentication.states import UserSession
|
|
11
12
|
|
|
12
13
|
logger = logging.getLogger(__name__)
|
|
@@ -32,31 +33,41 @@ class SystemPromptState(State):
|
|
|
32
33
|
self.is_loading = True
|
|
33
34
|
self.error_message = ""
|
|
34
35
|
try:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
self.
|
|
36
|
+
async with get_asyncdb_session() as session:
|
|
37
|
+
prompts = await system_prompt_repo.find_all_ordered_by_version_desc(
|
|
38
|
+
session
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
self.versions = [
|
|
42
|
+
{
|
|
43
|
+
"value": str(p.version),
|
|
44
|
+
"label": (
|
|
45
|
+
f"Version {p.version} - "
|
|
46
|
+
f"{p.created_at.strftime('%d.%m.%Y %H:%M')}"
|
|
47
|
+
),
|
|
48
|
+
}
|
|
49
|
+
for p in prompts
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
self.prompt_map = {str(p.version): p.prompt for p in prompts}
|
|
53
|
+
|
|
54
|
+
latest_prompt = None
|
|
55
|
+
if prompts:
|
|
56
|
+
latest_prompt = prompts[0]
|
|
57
|
+
# Access attributes to ensure they are loaded/available
|
|
58
|
+
self.selected_version_id = latest_prompt.version
|
|
59
|
+
latest_prompt_text = latest_prompt.prompt
|
|
60
|
+
else:
|
|
61
|
+
self.selected_version_id = 0
|
|
62
|
+
latest_prompt_text = None
|
|
52
63
|
|
|
64
|
+
if latest_prompt_text is not None:
|
|
53
65
|
if not self.current_prompt:
|
|
54
|
-
self.current_prompt =
|
|
55
|
-
self.last_saved_prompt =
|
|
66
|
+
self.current_prompt = latest_prompt_text
|
|
67
|
+
self.last_saved_prompt = latest_prompt_text
|
|
56
68
|
else:
|
|
57
|
-
self.last_saved_prompt =
|
|
69
|
+
self.last_saved_prompt = latest_prompt_text
|
|
58
70
|
else:
|
|
59
|
-
self.selected_version_id = 0
|
|
60
71
|
if not self.current_prompt:
|
|
61
72
|
self.current_prompt = ""
|
|
62
73
|
self.last_saved_prompt = self.current_prompt
|
|
@@ -93,10 +104,12 @@ class SystemPromptState(State):
|
|
|
93
104
|
user_session: UserSession = await self.get_state(UserSession)
|
|
94
105
|
user_id = user_session.user_id
|
|
95
106
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
107
|
+
async with get_asyncdb_session() as session:
|
|
108
|
+
await system_prompt_repo.create_next_version(
|
|
109
|
+
session,
|
|
110
|
+
prompt=self.current_prompt,
|
|
111
|
+
user_id=user_id,
|
|
112
|
+
)
|
|
100
113
|
|
|
101
114
|
self.last_saved_prompt = self.current_prompt
|
|
102
115
|
|
|
@@ -124,7 +137,18 @@ class SystemPromptState(State):
|
|
|
124
137
|
self.is_loading = True
|
|
125
138
|
self.error_message = ""
|
|
126
139
|
try:
|
|
127
|
-
|
|
140
|
+
async with get_asyncdb_session() as session:
|
|
141
|
+
if self.selected_version_id:
|
|
142
|
+
prompt = await system_prompt_repo.find_by_id(
|
|
143
|
+
session, self.selected_version_id
|
|
144
|
+
)
|
|
145
|
+
if prompt:
|
|
146
|
+
success = await system_prompt_repo.delete(session, prompt)
|
|
147
|
+
else:
|
|
148
|
+
success = False
|
|
149
|
+
else:
|
|
150
|
+
success = False
|
|
151
|
+
|
|
128
152
|
if success:
|
|
129
153
|
self.selected_version_id = 0
|
|
130
154
|
|
|
@@ -15,8 +15,9 @@ from typing import TYPE_CHECKING, Any
|
|
|
15
15
|
|
|
16
16
|
import reflex as rx
|
|
17
17
|
|
|
18
|
-
from appkit_assistant.backend.models import ThreadModel
|
|
19
|
-
from appkit_assistant.backend.repositories import
|
|
18
|
+
from appkit_assistant.backend.models import ThreadModel, ThreadStatus
|
|
19
|
+
from appkit_assistant.backend.repositories import thread_repo
|
|
20
|
+
from appkit_commons.database.session import get_asyncdb_session
|
|
20
21
|
from appkit_user.authentication.states import UserSession
|
|
21
22
|
|
|
22
23
|
if TYPE_CHECKING:
|
|
@@ -124,7 +125,24 @@ class ThreadListState(rx.State):
|
|
|
124
125
|
|
|
125
126
|
# Fetch threads from database
|
|
126
127
|
try:
|
|
127
|
-
|
|
128
|
+
async with get_asyncdb_session() as session:
|
|
129
|
+
thread_entities = await thread_repo.find_summaries_by_user(
|
|
130
|
+
session, user_id
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Convert entities to models inside the session context
|
|
134
|
+
threads = [
|
|
135
|
+
ThreadModel(
|
|
136
|
+
thread_id=t.thread_id,
|
|
137
|
+
title=t.title,
|
|
138
|
+
state=ThreadStatus(t.state),
|
|
139
|
+
ai_model=t.ai_model,
|
|
140
|
+
active=t.active,
|
|
141
|
+
messages=[],
|
|
142
|
+
)
|
|
143
|
+
for t in thread_entities
|
|
144
|
+
]
|
|
145
|
+
|
|
128
146
|
async with self:
|
|
129
147
|
self.threads = threads
|
|
130
148
|
self._initialized = True
|
|
@@ -208,7 +226,10 @@ class ThreadListState(rx.State):
|
|
|
208
226
|
|
|
209
227
|
try:
|
|
210
228
|
# Delete from database
|
|
211
|
-
|
|
229
|
+
async with get_asyncdb_session() as session:
|
|
230
|
+
await thread_repo.delete_by_thread_id_and_user(
|
|
231
|
+
session, thread_id, user_id
|
|
232
|
+
)
|
|
212
233
|
|
|
213
234
|
async with self:
|
|
214
235
|
# Remove from list
|
|
@@ -22,6 +22,7 @@ from pydantic import BaseModel
|
|
|
22
22
|
from appkit_assistant.backend.model_manager import ModelManager
|
|
23
23
|
from appkit_assistant.backend.models import (
|
|
24
24
|
AIModel,
|
|
25
|
+
AssistantThread,
|
|
25
26
|
Chunk,
|
|
26
27
|
ChunkType,
|
|
27
28
|
MCPServer,
|
|
@@ -31,8 +32,9 @@ from appkit_assistant.backend.models import (
|
|
|
31
32
|
ThreadModel,
|
|
32
33
|
ThreadStatus,
|
|
33
34
|
)
|
|
34
|
-
from appkit_assistant.backend.repositories import
|
|
35
|
+
from appkit_assistant.backend.repositories import mcp_server_repo, thread_repo
|
|
35
36
|
from appkit_assistant.state.thread_list_state import ThreadListState
|
|
37
|
+
from appkit_commons.database.session import get_asyncdb_session
|
|
36
38
|
from appkit_user.authentication.states import UserSession
|
|
37
39
|
|
|
38
40
|
logger = logging.getLogger(__name__)
|
|
@@ -268,20 +270,41 @@ class ThreadState(rx.State):
|
|
|
268
270
|
return
|
|
269
271
|
|
|
270
272
|
try:
|
|
271
|
-
|
|
273
|
+
async with get_asyncdb_session() as session:
|
|
274
|
+
thread_entity = await thread_repo.find_by_thread_id_and_user(
|
|
275
|
+
session, thread_id, user_id
|
|
276
|
+
)
|
|
272
277
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
+
if not thread_entity:
|
|
279
|
+
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
|
+
|
|
283
|
+
# Convert to ThreadModel if found
|
|
284
|
+
full_thread = None
|
|
285
|
+
if thread_entity:
|
|
286
|
+
full_thread = ThreadModel(
|
|
287
|
+
thread_id=thread_entity.thread_id,
|
|
288
|
+
title=thread_entity.title,
|
|
289
|
+
state=ThreadStatus(thread_entity.state),
|
|
290
|
+
ai_model=thread_entity.ai_model,
|
|
291
|
+
active=thread_entity.active,
|
|
292
|
+
messages=[Message(**m) for m in thread_entity.messages],
|
|
278
293
|
)
|
|
279
|
-
|
|
280
|
-
|
|
294
|
+
|
|
295
|
+
if not full_thread:
|
|
296
|
+
if not thread_entity: # it was not found
|
|
297
|
+
async with self:
|
|
298
|
+
threadlist_state: ThreadListState = await self.get_state(
|
|
299
|
+
ThreadListState
|
|
300
|
+
)
|
|
301
|
+
threadlist_state.loading_thread_id = ""
|
|
302
|
+
return
|
|
281
303
|
|
|
282
304
|
# Mark all messages as done (loaded from DB)
|
|
283
|
-
|
|
284
|
-
msg
|
|
305
|
+
if full_thread:
|
|
306
|
+
for msg in full_thread.messages:
|
|
307
|
+
msg.done = True
|
|
285
308
|
|
|
286
309
|
async with self:
|
|
287
310
|
# Update self with loaded thread
|
|
@@ -355,7 +378,10 @@ class ThreadState(rx.State):
|
|
|
355
378
|
@rx.event
|
|
356
379
|
async def load_mcp_servers(self) -> None:
|
|
357
380
|
"""Load available MCP servers from the database."""
|
|
358
|
-
|
|
381
|
+
async with get_asyncdb_session() as session:
|
|
382
|
+
servers = await mcp_server_repo.find_all_ordered_by_name(session)
|
|
383
|
+
# Create detached copies
|
|
384
|
+
self.available_mcp_servers = [MCPServer(**s.model_dump()) for s in servers]
|
|
359
385
|
|
|
360
386
|
@rx.event
|
|
361
387
|
def toogle_tools_modal(self, show: bool) -> None:
|
|
@@ -598,7 +624,40 @@ class ThreadState(rx.State):
|
|
|
598
624
|
|
|
599
625
|
if user_id:
|
|
600
626
|
try:
|
|
601
|
-
|
|
627
|
+
# Prepare entity data
|
|
628
|
+
messages_dict = [m.dict() for m in self._thread.messages]
|
|
629
|
+
|
|
630
|
+
async with get_asyncdb_session() as session:
|
|
631
|
+
# Check if exists
|
|
632
|
+
existing = await thread_repo.find_by_thread_id_and_user(
|
|
633
|
+
session, self._thread.thread_id, user_id
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
if existing:
|
|
637
|
+
existing.title = self._thread.title
|
|
638
|
+
existing.state = (
|
|
639
|
+
self._thread.state.value
|
|
640
|
+
if hasattr(self._thread.state, "value")
|
|
641
|
+
else self._thread.state
|
|
642
|
+
)
|
|
643
|
+
existing.ai_model = self._thread.ai_model
|
|
644
|
+
existing.active = self._thread.active
|
|
645
|
+
existing.messages = messages_dict
|
|
646
|
+
await thread_repo.save(session, existing)
|
|
647
|
+
else:
|
|
648
|
+
new_thread = AssistantThread(
|
|
649
|
+
thread_id=self._thread.thread_id,
|
|
650
|
+
user_id=user_id,
|
|
651
|
+
title=self._thread.title,
|
|
652
|
+
state=self._thread.state.value
|
|
653
|
+
if hasattr(self._thread.state, "value")
|
|
654
|
+
else self._thread.state,
|
|
655
|
+
ai_model=self._thread.ai_model,
|
|
656
|
+
active=self._thread.active,
|
|
657
|
+
messages=messages_dict,
|
|
658
|
+
)
|
|
659
|
+
await thread_repo.save(session, new_thread)
|
|
660
|
+
|
|
602
661
|
logger.debug("Saved thread to DB: %s", self._thread.thread_id)
|
|
603
662
|
except Exception as e:
|
|
604
663
|
logger.error("Error saving thread %s: %s", self._thread.thread_id, e)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: appkit-assistant
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.14.0
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Project-URL: Homepage, https://github.com/jenreh/appkit
|
|
6
6
|
Project-URL: Documentation, https://github.com/jenreh/appkit/tree/main/docs
|
|
@@ -40,6 +40,7 @@ appkit-assistant provides a complete conversational AI interface built on Reflex
|
|
|
40
40
|
|
|
41
41
|
- **Multi-Model Support** - OpenAI Chat Completions, OpenAI Responses API, Perplexity, and fallback Lorem Ipsum processor
|
|
42
42
|
- **MCP Server Integration** - Manage and connect to Model Context Protocol servers as tools
|
|
43
|
+
- **System Prompt Management** - Versioned system prompts with admin editor interface
|
|
43
44
|
- **Secure Credential Management** - Encrypted storage and handling of API keys and server credentials
|
|
44
45
|
- **Reflex UI Components** - Pre-built assistant interface with composer, thread management, and message display
|
|
45
46
|
- **Streaming Responses** - Real-time streaming of AI responses with chunked content
|
|
@@ -114,11 +115,11 @@ manager.register_processor("perplexity", PerplexityProcessor(assistant_config))
|
|
|
114
115
|
|
|
115
116
|
```python
|
|
116
117
|
import reflex as rx
|
|
117
|
-
|
|
118
|
+
from appkit_assistant.components import Assistant
|
|
118
119
|
|
|
119
120
|
def assistant_page():
|
|
120
121
|
return rx.container(
|
|
121
|
-
|
|
122
|
+
Assistant(),
|
|
122
123
|
height="100vh"
|
|
123
124
|
)
|
|
124
125
|
```
|
|
@@ -188,10 +189,10 @@ async for chunk in processor.process(messages, "gpt-4", mcp_servers=[mcp_server]
|
|
|
188
189
|
The main `Assistant` component provides a complete chat interface:
|
|
189
190
|
|
|
190
191
|
```python
|
|
191
|
-
|
|
192
|
+
from appkit_assistant.components import Assistant
|
|
192
193
|
|
|
193
194
|
def chat_page():
|
|
194
|
-
return
|
|
195
|
+
return Assistant()
|
|
195
196
|
```
|
|
196
197
|
|
|
197
198
|
#### Individual Components
|
|
@@ -199,12 +200,13 @@ def chat_page():
|
|
|
199
200
|
Use individual components for custom layouts:
|
|
200
201
|
|
|
201
202
|
```python
|
|
202
|
-
import
|
|
203
|
+
import reflex as rx
|
|
204
|
+
from appkit_assistant.components import ThreadList, composer
|
|
203
205
|
|
|
204
206
|
def custom_assistant():
|
|
205
207
|
return rx.vstack(
|
|
206
|
-
|
|
207
|
-
|
|
208
|
+
ThreadList(),
|
|
209
|
+
composer(),
|
|
208
210
|
spacing="4"
|
|
209
211
|
)
|
|
210
212
|
```
|
|
@@ -214,8 +216,21 @@ def custom_assistant():
|
|
|
214
216
|
Display and manage MCP servers:
|
|
215
217
|
|
|
216
218
|
```python
|
|
219
|
+
from appkit_assistant.components import mcp_servers_table
|
|
220
|
+
|
|
217
221
|
def servers_page():
|
|
218
|
-
return
|
|
222
|
+
return mcp_servers_table()
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### System Prompt Editor
|
|
226
|
+
|
|
227
|
+
Admin interface for managing versioned system prompts:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from appkit_assistant.components.system_prompt_editor import system_prompt_editor
|
|
231
|
+
|
|
232
|
+
def prompt_editor_page():
|
|
233
|
+
return system_prompt_editor()
|
|
219
234
|
```
|
|
220
235
|
|
|
221
236
|
---
|
|
@@ -279,6 +294,8 @@ manager.register_processor("lorem", LoremIpsumProcessor())
|
|
|
279
294
|
- `ThreadList` - Conversation thread list
|
|
280
295
|
- `MessageComponent` - Individual message display
|
|
281
296
|
- `mcp_servers_table` - MCP server management table
|
|
297
|
+
- `system_prompt_editor` - Admin interface for system prompts
|
|
298
|
+
|
|
282
299
|
|
|
283
300
|
### State Management
|
|
284
301
|
|
|
@@ -2,8 +2,8 @@ appkit_assistant/configuration.py,sha256=3nBL-dEGYsvSnRDNpxtikZn4QMMkMlNbb4VqGOP
|
|
|
2
2
|
appkit_assistant/backend/model_manager.py,sha256=7f65UbZ51qOYM6F73eKbZ8hMnCzxdanFZKgdKF8bbCk,4366
|
|
3
3
|
appkit_assistant/backend/models.py,sha256=-sr10xChq_lMlLpt7Jbmq4VGweuW4_UUwYsRtIY7HFY,6015
|
|
4
4
|
appkit_assistant/backend/processor.py,sha256=dhBg3pYXdmpj9JtAJc-d83SeUA1NsICj1C_YI0M2QYE,1289
|
|
5
|
-
appkit_assistant/backend/repositories.py,sha256=
|
|
6
|
-
appkit_assistant/backend/system_prompt_cache.py,sha256=
|
|
5
|
+
appkit_assistant/backend/repositories.py,sha256=arYagqYDgn65uFvnFTXP6zaXOn1c3zoAYuPO69-qrSk,5546
|
|
6
|
+
appkit_assistant/backend/system_prompt_cache.py,sha256=4a0sX3qzz3XTF7MX4YJuH29eaekmEx6jciY1zDKcuQ8,5509
|
|
7
7
|
appkit_assistant/backend/processors/lorem_ipsum_processor.py,sha256=j-MZhzibrtabzbGB2Pf4Xcdlr1TlTYWNRdE22LsDp9Q,4635
|
|
8
8
|
appkit_assistant/backend/processors/openai_base.py,sha256=IQS4m375BOD_K0PBFOk4i7wL1z5MEiPFxbSmC-HBNgU,4414
|
|
9
9
|
appkit_assistant/backend/processors/openai_chat_completion_processor.py,sha256=nTxouoXDU6VcQr8UhA2KiMNt60KvIwM8cH9Z8lo4dXY,4218
|
|
@@ -12,17 +12,17 @@ appkit_assistant/backend/processors/perplexity_processor.py,sha256=weHukv78MSCF_
|
|
|
12
12
|
appkit_assistant/components/__init__.py,sha256=5tzK5VjX9FGKK-qTUHLjr8-ohT4ykb4a-zC-I3yeRLY,916
|
|
13
13
|
appkit_assistant/components/composer.py,sha256=F4VPxWp4P6fvTW4rQ7S-YWn0eje5c3jGsWrpC1aewss,3885
|
|
14
14
|
appkit_assistant/components/composer_key_handler.py,sha256=KyZYyhxzFR8DH_7F_DrvTFNT6v5kG6JihlGTmCv2wv0,1028
|
|
15
|
-
appkit_assistant/components/mcp_server_dialogs.py,sha256=
|
|
15
|
+
appkit_assistant/components/mcp_server_dialogs.py,sha256=00BL6xdHeWcW1MMGJrmUFjiZ-Ksa22L-lydqPihZJwk,11451
|
|
16
16
|
appkit_assistant/components/mcp_server_table.py,sha256=1dziN7hDDvE8Y3XcdIs0wUPv1H64kP9gRAEjgH9Yvzo,2323
|
|
17
17
|
appkit_assistant/components/message.py,sha256=Tr19CsRqKiMP_fhSByVlUtigeXF13duR6Rp2mQ08IeQ,9636
|
|
18
18
|
appkit_assistant/components/system_prompt_editor.py,sha256=REl33zFmcpYRe9kxvFrBRYg40dV4L4FtVC_3ibLsmrU,2940
|
|
19
19
|
appkit_assistant/components/thread.py,sha256=rYM4LA6PVdEvZ5oz5ZtheVfQfFuvTXTIskTt15kI1kg,7886
|
|
20
20
|
appkit_assistant/components/threadlist.py,sha256=1xVakSTQYi5-wgED3fTJVggeIjL_fkthehce0wKUYtM,4896
|
|
21
21
|
appkit_assistant/components/tools_modal.py,sha256=12iiAVahy3j4JwjGfRlegVEa4ePhGsEu7Bq92JLn1ZI,3353
|
|
22
|
-
appkit_assistant/state/mcp_server_state.py,sha256=
|
|
23
|
-
appkit_assistant/state/system_prompt_state.py,sha256=
|
|
24
|
-
appkit_assistant/state/thread_list_state.py,sha256=
|
|
25
|
-
appkit_assistant/state/thread_state.py,sha256=
|
|
26
|
-
appkit_assistant-0.
|
|
27
|
-
appkit_assistant-0.
|
|
28
|
-
appkit_assistant-0.
|
|
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.0.dist-info/METADATA,sha256=pG1dZK1hqKJf-suHF4nAnq9kJUw8bbqMyJiUIyV1sXg,9403
|
|
27
|
+
appkit_assistant-0.14.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
28
|
+
appkit_assistant-0.14.0.dist-info/RECORD,,
|
|
File without changes
|