dtSpark 1.0.4__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.
- dtSpark/__init__.py +0 -0
- dtSpark/_description.txt +1 -0
- dtSpark/_full_name.txt +1 -0
- dtSpark/_licence.txt +21 -0
- dtSpark/_metadata.yaml +6 -0
- dtSpark/_name.txt +1 -0
- dtSpark/_version.txt +1 -0
- dtSpark/aws/__init__.py +7 -0
- dtSpark/aws/authentication.py +296 -0
- dtSpark/aws/bedrock.py +578 -0
- dtSpark/aws/costs.py +318 -0
- dtSpark/aws/pricing.py +580 -0
- dtSpark/cli_interface.py +2645 -0
- dtSpark/conversation_manager.py +3050 -0
- dtSpark/core/__init__.py +12 -0
- dtSpark/core/application.py +3355 -0
- dtSpark/core/context_compaction.py +735 -0
- dtSpark/daemon/__init__.py +104 -0
- dtSpark/daemon/__main__.py +10 -0
- dtSpark/daemon/action_monitor.py +213 -0
- dtSpark/daemon/daemon_app.py +730 -0
- dtSpark/daemon/daemon_manager.py +289 -0
- dtSpark/daemon/execution_coordinator.py +194 -0
- dtSpark/daemon/pid_file.py +169 -0
- dtSpark/database/__init__.py +482 -0
- dtSpark/database/autonomous_actions.py +1191 -0
- dtSpark/database/backends.py +329 -0
- dtSpark/database/connection.py +122 -0
- dtSpark/database/conversations.py +520 -0
- dtSpark/database/credential_prompt.py +218 -0
- dtSpark/database/files.py +205 -0
- dtSpark/database/mcp_ops.py +355 -0
- dtSpark/database/messages.py +161 -0
- dtSpark/database/schema.py +673 -0
- dtSpark/database/tool_permissions.py +186 -0
- dtSpark/database/usage.py +167 -0
- dtSpark/files/__init__.py +4 -0
- dtSpark/files/manager.py +322 -0
- dtSpark/launch.py +39 -0
- dtSpark/limits/__init__.py +10 -0
- dtSpark/limits/costs.py +296 -0
- dtSpark/limits/tokens.py +342 -0
- dtSpark/llm/__init__.py +17 -0
- dtSpark/llm/anthropic_direct.py +446 -0
- dtSpark/llm/base.py +146 -0
- dtSpark/llm/context_limits.py +438 -0
- dtSpark/llm/manager.py +177 -0
- dtSpark/llm/ollama.py +578 -0
- dtSpark/mcp_integration/__init__.py +5 -0
- dtSpark/mcp_integration/manager.py +653 -0
- dtSpark/mcp_integration/tool_selector.py +225 -0
- dtSpark/resources/config.yaml.template +631 -0
- dtSpark/safety/__init__.py +22 -0
- dtSpark/safety/llm_service.py +111 -0
- dtSpark/safety/patterns.py +229 -0
- dtSpark/safety/prompt_inspector.py +442 -0
- dtSpark/safety/violation_logger.py +346 -0
- dtSpark/scheduler/__init__.py +20 -0
- dtSpark/scheduler/creation_tools.py +599 -0
- dtSpark/scheduler/execution_queue.py +159 -0
- dtSpark/scheduler/executor.py +1152 -0
- dtSpark/scheduler/manager.py +395 -0
- dtSpark/tools/__init__.py +4 -0
- dtSpark/tools/builtin.py +833 -0
- dtSpark/web/__init__.py +20 -0
- dtSpark/web/auth.py +152 -0
- dtSpark/web/dependencies.py +37 -0
- dtSpark/web/endpoints/__init__.py +17 -0
- dtSpark/web/endpoints/autonomous_actions.py +1125 -0
- dtSpark/web/endpoints/chat.py +621 -0
- dtSpark/web/endpoints/conversations.py +353 -0
- dtSpark/web/endpoints/main_menu.py +547 -0
- dtSpark/web/endpoints/streaming.py +421 -0
- dtSpark/web/server.py +578 -0
- dtSpark/web/session.py +167 -0
- dtSpark/web/ssl_utils.py +195 -0
- dtSpark/web/static/css/dark-theme.css +427 -0
- dtSpark/web/static/js/actions.js +1101 -0
- dtSpark/web/static/js/chat.js +614 -0
- dtSpark/web/static/js/main.js +496 -0
- dtSpark/web/static/js/sse-client.js +242 -0
- dtSpark/web/templates/actions.html +408 -0
- dtSpark/web/templates/base.html +93 -0
- dtSpark/web/templates/chat.html +814 -0
- dtSpark/web/templates/conversations.html +350 -0
- dtSpark/web/templates/goodbye.html +81 -0
- dtSpark/web/templates/login.html +90 -0
- dtSpark/web/templates/main_menu.html +983 -0
- dtSpark/web/templates/new_conversation.html +191 -0
- dtSpark/web/web_interface.py +137 -0
- dtspark-1.0.4.dist-info/METADATA +187 -0
- dtspark-1.0.4.dist-info/RECORD +96 -0
- dtspark-1.0.4.dist-info/WHEEL +5 -0
- dtspark-1.0.4.dist-info/entry_points.txt +3 -0
- dtspark-1.0.4.dist-info/licenses/LICENSE +21 -0
- dtspark-1.0.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Conversation CRUD operations module.
|
|
3
|
+
|
|
4
|
+
This module handles:
|
|
5
|
+
- Creating new conversations
|
|
6
|
+
- Retrieving conversation records
|
|
7
|
+
- Updating conversation settings
|
|
8
|
+
- Deleting conversations
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sqlite3
|
|
12
|
+
import logging
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from typing import List, Dict, Optional
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def create_conversation(conn: sqlite3.Connection, name: str, model_id: str,
|
|
18
|
+
instructions: Optional[str] = None, user_guid: str = None,
|
|
19
|
+
compaction_threshold: Optional[float] = None) -> int:
|
|
20
|
+
"""
|
|
21
|
+
Create a new conversation.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
conn: Database connection
|
|
25
|
+
name: Name of the conversation
|
|
26
|
+
model_id: ID of the Bedrock model being used
|
|
27
|
+
instructions: Optional instructions/system prompt for the conversation
|
|
28
|
+
user_guid: User GUID for multi-user support
|
|
29
|
+
compaction_threshold: Optional compaction threshold override (0.0-1.0, NULL uses config default)
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
ID of the newly created conversation
|
|
33
|
+
"""
|
|
34
|
+
cursor = conn.cursor()
|
|
35
|
+
now = datetime.now()
|
|
36
|
+
|
|
37
|
+
cursor.execute('''
|
|
38
|
+
INSERT INTO conversations (name, model_id, created_at, last_updated, instructions, user_guid, compaction_threshold)
|
|
39
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
40
|
+
''', (name, model_id, now, now, instructions, user_guid, compaction_threshold))
|
|
41
|
+
|
|
42
|
+
conn.commit()
|
|
43
|
+
conversation_id = cursor.lastrowid
|
|
44
|
+
logging.info(f"Created conversation '{name}' with ID {conversation_id} for user {user_guid} (compaction_threshold: {compaction_threshold})")
|
|
45
|
+
return conversation_id
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_active_conversations(conn: sqlite3.Connection, user_guid: str = None) -> List[Dict]:
|
|
49
|
+
"""
|
|
50
|
+
Retrieve all active conversations for a user.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
conn: Database connection
|
|
54
|
+
user_guid: User GUID for multi-user support
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
List of conversation dictionaries
|
|
58
|
+
"""
|
|
59
|
+
cursor = conn.cursor()
|
|
60
|
+
cursor.execute('''
|
|
61
|
+
SELECT
|
|
62
|
+
c.id,
|
|
63
|
+
c.name,
|
|
64
|
+
c.model_id,
|
|
65
|
+
c.created_at,
|
|
66
|
+
c.last_updated,
|
|
67
|
+
c.total_tokens,
|
|
68
|
+
c.instructions,
|
|
69
|
+
c.tokens_sent,
|
|
70
|
+
c.tokens_received,
|
|
71
|
+
COUNT(m.id) as message_count,
|
|
72
|
+
MAX(m.timestamp) as last_message_at
|
|
73
|
+
FROM conversations c
|
|
74
|
+
LEFT JOIN messages m ON c.id = m.conversation_id AND m.user_guid = ?
|
|
75
|
+
WHERE c.is_active = 1 AND c.user_guid = ?
|
|
76
|
+
GROUP BY c.id
|
|
77
|
+
ORDER BY c.last_updated DESC
|
|
78
|
+
''', (user_guid, user_guid))
|
|
79
|
+
|
|
80
|
+
conversations = []
|
|
81
|
+
for row in cursor.fetchall():
|
|
82
|
+
conversations.append({
|
|
83
|
+
'id': row['id'],
|
|
84
|
+
'name': row['name'],
|
|
85
|
+
'model_id': row['model_id'],
|
|
86
|
+
'created_at': row['created_at'],
|
|
87
|
+
'last_updated': row['last_updated'],
|
|
88
|
+
'total_tokens': row['total_tokens'],
|
|
89
|
+
'instructions': row['instructions'],
|
|
90
|
+
'tokens_sent': row['tokens_sent'] or 0,
|
|
91
|
+
'tokens_received': row['tokens_received'] or 0,
|
|
92
|
+
'message_count': row['message_count'],
|
|
93
|
+
'last_message_at': row['last_message_at']
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
return conversations
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def get_conversation(conn: sqlite3.Connection, conversation_id: int, user_guid: str = None) -> Optional[Dict]:
|
|
100
|
+
"""
|
|
101
|
+
Retrieve a specific conversation for a user.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
conn: Database connection
|
|
105
|
+
conversation_id: ID of the conversation
|
|
106
|
+
user_guid: User GUID for multi-user support
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Conversation dictionary or None if not found
|
|
110
|
+
"""
|
|
111
|
+
cursor = conn.cursor()
|
|
112
|
+
cursor.execute('''
|
|
113
|
+
SELECT id, name, model_id, created_at, last_updated, total_tokens, instructions,
|
|
114
|
+
tokens_sent, tokens_received, max_tokens, compaction_threshold
|
|
115
|
+
FROM conversations
|
|
116
|
+
WHERE id = ? AND user_guid = ?
|
|
117
|
+
''', (conversation_id, user_guid))
|
|
118
|
+
|
|
119
|
+
row = cursor.fetchone()
|
|
120
|
+
if row:
|
|
121
|
+
return {
|
|
122
|
+
'id': row['id'],
|
|
123
|
+
'name': row['name'],
|
|
124
|
+
'model_id': row['model_id'],
|
|
125
|
+
'created_at': row['created_at'],
|
|
126
|
+
'last_updated': row['last_updated'],
|
|
127
|
+
'total_tokens': row['total_tokens'],
|
|
128
|
+
'instructions': row['instructions'],
|
|
129
|
+
'tokens_sent': row['tokens_sent'] or 0,
|
|
130
|
+
'tokens_received': row['tokens_received'] or 0,
|
|
131
|
+
'max_tokens': row['max_tokens'], # NULL means use global default
|
|
132
|
+
'compaction_threshold': row['compaction_threshold'] # NULL means use global default
|
|
133
|
+
}
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def get_conversation_token_count(conn: sqlite3.Connection, conversation_id: int, user_guid: str = None) -> int:
|
|
138
|
+
"""
|
|
139
|
+
Get the total token count for a conversation.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
conn: Database connection
|
|
143
|
+
conversation_id: ID of the conversation
|
|
144
|
+
user_guid: User GUID for multi-user support
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Total token count
|
|
148
|
+
"""
|
|
149
|
+
cursor = conn.cursor()
|
|
150
|
+
cursor.execute('''
|
|
151
|
+
SELECT total_tokens FROM conversations WHERE id = ? AND user_guid = ?
|
|
152
|
+
''', (conversation_id, user_guid))
|
|
153
|
+
|
|
154
|
+
row = cursor.fetchone()
|
|
155
|
+
return row['total_tokens'] if row else 0
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def recalculate_total_tokens(conn: sqlite3.Connection, conversation_id: int,
|
|
159
|
+
user_guid: str = None) -> int:
|
|
160
|
+
"""
|
|
161
|
+
Recalculate and update total_tokens from active (non-rolled-up) messages.
|
|
162
|
+
|
|
163
|
+
This function recalculates total_tokens by summing token_count from all
|
|
164
|
+
messages that are NOT marked as rolled up. This ensures accuracy after
|
|
165
|
+
compaction operations.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
conn: Database connection
|
|
169
|
+
conversation_id: ID of the conversation
|
|
170
|
+
user_guid: User GUID for multi-user support
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
The new total token count
|
|
174
|
+
"""
|
|
175
|
+
cursor = conn.cursor()
|
|
176
|
+
|
|
177
|
+
# Sum tokens from active (non-rolled-up) messages only
|
|
178
|
+
cursor.execute('''
|
|
179
|
+
SELECT COALESCE(SUM(token_count), 0) as active_tokens
|
|
180
|
+
FROM messages
|
|
181
|
+
WHERE conversation_id = ? AND user_guid = ? AND is_rolled_up = 0
|
|
182
|
+
''', (conversation_id, user_guid))
|
|
183
|
+
|
|
184
|
+
row = cursor.fetchone()
|
|
185
|
+
new_total = row['active_tokens'] if row else 0
|
|
186
|
+
|
|
187
|
+
# Update the conversation's total_tokens
|
|
188
|
+
cursor.execute('''
|
|
189
|
+
UPDATE conversations
|
|
190
|
+
SET total_tokens = ?
|
|
191
|
+
WHERE id = ? AND user_guid = ?
|
|
192
|
+
''', (new_total, conversation_id, user_guid))
|
|
193
|
+
|
|
194
|
+
conn.commit()
|
|
195
|
+
logging.info(f"Recalculated total_tokens for conversation {conversation_id}: {new_total}")
|
|
196
|
+
return new_total
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def delete_conversation(conn: sqlite3.Connection, conversation_id: int, user_guid: str = None) -> bool:
|
|
200
|
+
"""
|
|
201
|
+
Delete a conversation and all its messages for a user.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
conn: Database connection
|
|
205
|
+
conversation_id: ID of the conversation to delete
|
|
206
|
+
user_guid: User GUID for multi-user support
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
True if successful, False otherwise
|
|
210
|
+
"""
|
|
211
|
+
try:
|
|
212
|
+
cursor = conn.cursor()
|
|
213
|
+
|
|
214
|
+
# Delete all messages for this conversation (filtered by user_guid for safety)
|
|
215
|
+
cursor.execute('''
|
|
216
|
+
DELETE FROM messages
|
|
217
|
+
WHERE conversation_id = ? AND user_guid = ?
|
|
218
|
+
''', (conversation_id, user_guid))
|
|
219
|
+
|
|
220
|
+
# Delete all rollup history for this conversation
|
|
221
|
+
cursor.execute('''
|
|
222
|
+
DELETE FROM rollup_history
|
|
223
|
+
WHERE conversation_id = ? AND user_guid = ?
|
|
224
|
+
''', (conversation_id, user_guid))
|
|
225
|
+
|
|
226
|
+
# Delete the conversation (filtered by user_guid for security)
|
|
227
|
+
cursor.execute('''
|
|
228
|
+
DELETE FROM conversations
|
|
229
|
+
WHERE id = ? AND user_guid = ?
|
|
230
|
+
''', (conversation_id, user_guid))
|
|
231
|
+
|
|
232
|
+
conn.commit()
|
|
233
|
+
logging.info(f"Deleted conversation {conversation_id} for user {user_guid}")
|
|
234
|
+
return True
|
|
235
|
+
|
|
236
|
+
except Exception as e:
|
|
237
|
+
logging.error(f"Failed to delete conversation {conversation_id}: {e}")
|
|
238
|
+
conn.rollback()
|
|
239
|
+
return False
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def update_conversation_max_tokens(conn: sqlite3.Connection, conversation_id: int,
|
|
243
|
+
max_tokens: int, user_guid: str = None):
|
|
244
|
+
"""
|
|
245
|
+
Update the max_tokens setting for a specific conversation.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
conn: Database connection
|
|
249
|
+
conversation_id: ID of the conversation
|
|
250
|
+
max_tokens: Maximum tokens for this conversation (overrides global default)
|
|
251
|
+
user_guid: User GUID for multi-user support
|
|
252
|
+
"""
|
|
253
|
+
try:
|
|
254
|
+
cursor = conn.cursor()
|
|
255
|
+
cursor.execute('''
|
|
256
|
+
UPDATE conversations
|
|
257
|
+
SET max_tokens = ?
|
|
258
|
+
WHERE id = ? AND user_guid = ?
|
|
259
|
+
''', (max_tokens, conversation_id, user_guid))
|
|
260
|
+
conn.commit()
|
|
261
|
+
logging.info(f"Updated max_tokens for conversation {conversation_id} to {max_tokens}")
|
|
262
|
+
except Exception as e:
|
|
263
|
+
logging.error(f"Failed to update max_tokens: {e}")
|
|
264
|
+
conn.rollback()
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def update_conversation_compaction_threshold(conn: sqlite3.Connection, conversation_id: int,
|
|
268
|
+
compaction_threshold: float, user_guid: str = None):
|
|
269
|
+
"""
|
|
270
|
+
Update the compaction_threshold setting for a specific conversation.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
conn: Database connection
|
|
274
|
+
conversation_id: ID of the conversation
|
|
275
|
+
compaction_threshold: Compaction threshold (0.0-1.0) for this conversation (overrides global default)
|
|
276
|
+
user_guid: User GUID for multi-user support
|
|
277
|
+
"""
|
|
278
|
+
try:
|
|
279
|
+
cursor = conn.cursor()
|
|
280
|
+
cursor.execute('''
|
|
281
|
+
UPDATE conversations
|
|
282
|
+
SET compaction_threshold = ?
|
|
283
|
+
WHERE id = ? AND user_guid = ?
|
|
284
|
+
''', (compaction_threshold, conversation_id, user_guid))
|
|
285
|
+
conn.commit()
|
|
286
|
+
logging.info(f"Updated compaction_threshold for conversation {conversation_id} to {compaction_threshold}")
|
|
287
|
+
except Exception as e:
|
|
288
|
+
logging.error(f"Failed to update compaction_threshold: {e}")
|
|
289
|
+
conn.rollback()
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def update_conversation_instructions(conn: sqlite3.Connection, conversation_id: int,
|
|
293
|
+
instructions: Optional[str], user_guid: str = None):
|
|
294
|
+
"""
|
|
295
|
+
Update the instructions for a specific conversation.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
conn: Database connection
|
|
299
|
+
conversation_id: ID of the conversation
|
|
300
|
+
instructions: New instructions/system prompt (None to clear)
|
|
301
|
+
user_guid: User GUID for multi-user support
|
|
302
|
+
"""
|
|
303
|
+
try:
|
|
304
|
+
cursor = conn.cursor()
|
|
305
|
+
cursor.execute('''
|
|
306
|
+
UPDATE conversations
|
|
307
|
+
SET instructions = ?
|
|
308
|
+
WHERE id = ? AND user_guid = ?
|
|
309
|
+
''', (instructions, conversation_id, user_guid))
|
|
310
|
+
conn.commit()
|
|
311
|
+
if instructions:
|
|
312
|
+
logging.info(f"Updated instructions for conversation {conversation_id}")
|
|
313
|
+
else:
|
|
314
|
+
logging.info(f"Cleared instructions for conversation {conversation_id}")
|
|
315
|
+
except Exception as e:
|
|
316
|
+
logging.error(f"Failed to update instructions: {e}")
|
|
317
|
+
conn.rollback()
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def update_token_usage(conn: sqlite3.Connection, conversation_id: int,
|
|
321
|
+
tokens_sent: int, tokens_received: int, model_id: str = None,
|
|
322
|
+
user_guid: str = None):
|
|
323
|
+
"""
|
|
324
|
+
Update the API token usage for a conversation and track per-model usage.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
conn: Database connection
|
|
328
|
+
conversation_id: ID of the conversation
|
|
329
|
+
tokens_sent: Number of tokens sent to the API (input tokens)
|
|
330
|
+
tokens_received: Number of tokens received from the API (output tokens)
|
|
331
|
+
model_id: Model used for this request (for per-model tracking)
|
|
332
|
+
user_guid: User GUID for multi-user support
|
|
333
|
+
"""
|
|
334
|
+
try:
|
|
335
|
+
cursor = conn.cursor()
|
|
336
|
+
|
|
337
|
+
# Update overall conversation token counts
|
|
338
|
+
cursor.execute('''
|
|
339
|
+
UPDATE conversations
|
|
340
|
+
SET tokens_sent = tokens_sent + ?,
|
|
341
|
+
tokens_received = tokens_received + ?
|
|
342
|
+
WHERE id = ? AND user_guid = ?
|
|
343
|
+
''', (tokens_sent, tokens_received, conversation_id, user_guid))
|
|
344
|
+
|
|
345
|
+
# Update per-model token usage if model_id provided
|
|
346
|
+
if model_id:
|
|
347
|
+
now = datetime.now().isoformat()
|
|
348
|
+
|
|
349
|
+
# Try to update existing record
|
|
350
|
+
cursor.execute('''
|
|
351
|
+
INSERT INTO conversation_model_usage
|
|
352
|
+
(conversation_id, model_id, input_tokens, output_tokens, first_used, last_used, user_guid)
|
|
353
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
354
|
+
ON CONFLICT(conversation_id, model_id) DO UPDATE SET
|
|
355
|
+
input_tokens = input_tokens + excluded.input_tokens,
|
|
356
|
+
output_tokens = output_tokens + excluded.output_tokens,
|
|
357
|
+
last_used = excluded.last_used
|
|
358
|
+
''', (conversation_id, model_id, tokens_sent, tokens_received, now, now, user_guid))
|
|
359
|
+
|
|
360
|
+
conn.commit()
|
|
361
|
+
logging.debug(f"Updated token usage for conversation {conversation_id}: +{tokens_sent} sent, +{tokens_received} received (model: {model_id or 'unknown'})")
|
|
362
|
+
|
|
363
|
+
except Exception as e:
|
|
364
|
+
logging.error(f"Failed to update token usage: {e}")
|
|
365
|
+
conn.rollback()
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def get_model_usage_breakdown(conn: sqlite3.Connection, conversation_id: int,
|
|
369
|
+
user_guid: str = None) -> List[Dict]:
|
|
370
|
+
"""
|
|
371
|
+
Get per-model token usage breakdown for a conversation.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
conn: Database connection
|
|
375
|
+
conversation_id: ID of the conversation
|
|
376
|
+
user_guid: User GUID for multi-user support
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
List of dictionaries with model usage details
|
|
380
|
+
"""
|
|
381
|
+
cursor = conn.cursor()
|
|
382
|
+
cursor.execute('''
|
|
383
|
+
SELECT model_id, input_tokens, output_tokens, first_used, last_used
|
|
384
|
+
FROM conversation_model_usage
|
|
385
|
+
WHERE conversation_id = ? AND user_guid = ?
|
|
386
|
+
ORDER BY first_used ASC
|
|
387
|
+
''', (conversation_id, user_guid))
|
|
388
|
+
|
|
389
|
+
results = []
|
|
390
|
+
for row in cursor.fetchall():
|
|
391
|
+
results.append({
|
|
392
|
+
'model_id': row['model_id'],
|
|
393
|
+
'input_tokens': row['input_tokens'],
|
|
394
|
+
'output_tokens': row['output_tokens'],
|
|
395
|
+
'total_tokens': row['input_tokens'] + row['output_tokens'],
|
|
396
|
+
'first_used': row['first_used'],
|
|
397
|
+
'last_used': row['last_used']
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
return results
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def get_predefined_conversation_by_name(conn: sqlite3.Connection, name: str,
|
|
404
|
+
user_guid: str = None) -> Optional[Dict]:
|
|
405
|
+
"""
|
|
406
|
+
Retrieve a predefined conversation by name for a user.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
conn: Database connection
|
|
410
|
+
name: Name of the predefined conversation
|
|
411
|
+
user_guid: User GUID for multi-user support
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
Conversation dictionary or None if not found
|
|
415
|
+
"""
|
|
416
|
+
cursor = conn.cursor()
|
|
417
|
+
cursor.execute('''
|
|
418
|
+
SELECT id, name, model_id, created_at, last_updated, instructions, config_hash, is_predefined
|
|
419
|
+
FROM conversations
|
|
420
|
+
WHERE name = ? AND is_predefined = 1 AND is_active = 1 AND user_guid = ?
|
|
421
|
+
''', (name, user_guid))
|
|
422
|
+
|
|
423
|
+
row = cursor.fetchone()
|
|
424
|
+
if row:
|
|
425
|
+
return {
|
|
426
|
+
'id': row['id'],
|
|
427
|
+
'name': row['name'],
|
|
428
|
+
'model_id': row['model_id'],
|
|
429
|
+
'created_at': row['created_at'],
|
|
430
|
+
'last_updated': row['last_updated'],
|
|
431
|
+
'instructions': row['instructions'],
|
|
432
|
+
'config_hash': row['config_hash'],
|
|
433
|
+
'is_predefined': row['is_predefined']
|
|
434
|
+
}
|
|
435
|
+
return None
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def create_predefined_conversation(conn: sqlite3.Connection, name: str, model_id: str,
|
|
439
|
+
instructions: Optional[str], config_hash: str,
|
|
440
|
+
user_guid: str = None) -> int:
|
|
441
|
+
"""
|
|
442
|
+
Create a new predefined conversation.
|
|
443
|
+
|
|
444
|
+
Args:
|
|
445
|
+
conn: Database connection
|
|
446
|
+
name: Name of the conversation
|
|
447
|
+
model_id: ID of the model being used
|
|
448
|
+
instructions: Instructions/system prompt for the conversation
|
|
449
|
+
config_hash: Hash of the configuration to detect changes
|
|
450
|
+
user_guid: User GUID for multi-user support
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
ID of the newly created conversation
|
|
454
|
+
"""
|
|
455
|
+
cursor = conn.cursor()
|
|
456
|
+
now = datetime.now()
|
|
457
|
+
|
|
458
|
+
cursor.execute('''
|
|
459
|
+
INSERT INTO conversations (name, model_id, created_at, last_updated, instructions,
|
|
460
|
+
is_predefined, config_hash, user_guid)
|
|
461
|
+
VALUES (?, ?, ?, ?, ?, 1, ?, ?)
|
|
462
|
+
''', (name, model_id, now, now, instructions, config_hash, user_guid))
|
|
463
|
+
|
|
464
|
+
conn.commit()
|
|
465
|
+
conversation_id = cursor.lastrowid
|
|
466
|
+
logging.info(f"Created predefined conversation '{name}' with ID {conversation_id} for user {user_guid}")
|
|
467
|
+
return conversation_id
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def update_predefined_conversation(conn: sqlite3.Connection, conversation_id: int,
|
|
471
|
+
model_id: str, instructions: Optional[str],
|
|
472
|
+
config_hash: str, user_guid: str = None):
|
|
473
|
+
"""
|
|
474
|
+
Update a predefined conversation's settings.
|
|
475
|
+
|
|
476
|
+
Args:
|
|
477
|
+
conn: Database connection
|
|
478
|
+
conversation_id: ID of the conversation
|
|
479
|
+
model_id: New model ID
|
|
480
|
+
instructions: New instructions/system prompt
|
|
481
|
+
config_hash: New config hash
|
|
482
|
+
user_guid: User GUID for multi-user support
|
|
483
|
+
"""
|
|
484
|
+
try:
|
|
485
|
+
cursor = conn.cursor()
|
|
486
|
+
now = datetime.now()
|
|
487
|
+
|
|
488
|
+
cursor.execute('''
|
|
489
|
+
UPDATE conversations
|
|
490
|
+
SET model_id = ?, instructions = ?, config_hash = ?, last_updated = ?
|
|
491
|
+
WHERE id = ? AND is_predefined = 1 AND user_guid = ?
|
|
492
|
+
''', (model_id, instructions, config_hash, now, conversation_id, user_guid))
|
|
493
|
+
|
|
494
|
+
conn.commit()
|
|
495
|
+
logging.info(f"Updated predefined conversation {conversation_id}")
|
|
496
|
+
except Exception as e:
|
|
497
|
+
logging.error(f"Failed to update predefined conversation: {e}")
|
|
498
|
+
conn.rollback()
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def is_conversation_predefined(conn: sqlite3.Connection, conversation_id: int,
|
|
502
|
+
user_guid: str = None) -> bool:
|
|
503
|
+
"""
|
|
504
|
+
Check if a conversation is predefined.
|
|
505
|
+
|
|
506
|
+
Args:
|
|
507
|
+
conn: Database connection
|
|
508
|
+
conversation_id: ID of the conversation
|
|
509
|
+
user_guid: User GUID for multi-user support
|
|
510
|
+
|
|
511
|
+
Returns:
|
|
512
|
+
True if conversation is predefined, False otherwise
|
|
513
|
+
"""
|
|
514
|
+
cursor = conn.cursor()
|
|
515
|
+
cursor.execute('''
|
|
516
|
+
SELECT is_predefined FROM conversations WHERE id = ? AND user_guid = ?
|
|
517
|
+
''', (conversation_id, user_guid))
|
|
518
|
+
|
|
519
|
+
row = cursor.fetchone()
|
|
520
|
+
return bool(row['is_predefined']) if row else False
|