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,621 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Chat API endpoints.
|
|
3
|
+
|
|
4
|
+
Provides REST API for chat operations:
|
|
5
|
+
- Load conversation
|
|
6
|
+
- Send message
|
|
7
|
+
- Execute commands (history, info, attach, etc.)
|
|
8
|
+
- Export conversation
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import tempfile
|
|
15
|
+
import os
|
|
16
|
+
from typing import Optional, List
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
|
|
19
|
+
from fastapi import APIRouter, Depends, Request, HTTPException, UploadFile, File, Form
|
|
20
|
+
from pydantic import BaseModel
|
|
21
|
+
|
|
22
|
+
from ..dependencies import get_current_session
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
router = APIRouter()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Message(BaseModel):
|
|
31
|
+
"""Chat message."""
|
|
32
|
+
role: str # 'user' or 'assistant'
|
|
33
|
+
content: str
|
|
34
|
+
timestamp: datetime
|
|
35
|
+
tokens: Optional[int] = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ChatHistory(BaseModel):
|
|
39
|
+
"""Chat history for a conversation."""
|
|
40
|
+
conversation_id: int
|
|
41
|
+
conversation_name: str
|
|
42
|
+
messages: List[Message]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class CommandResponse(BaseModel):
|
|
46
|
+
"""Response from a chat command."""
|
|
47
|
+
command: str
|
|
48
|
+
status: str
|
|
49
|
+
data: Optional[dict] = None
|
|
50
|
+
message: Optional[str] = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@router.get("/chat/{conversation_id}/history")
|
|
54
|
+
async def get_chat_history(
|
|
55
|
+
conversation_id: int,
|
|
56
|
+
request: Request,
|
|
57
|
+
session_id: str = Depends(get_current_session),
|
|
58
|
+
) -> ChatHistory:
|
|
59
|
+
"""
|
|
60
|
+
Get chat history for a conversation.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
conversation_id: ID of the conversation
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
ChatHistory with all messages
|
|
67
|
+
"""
|
|
68
|
+
try:
|
|
69
|
+
app_instance = request.app.state.app_instance
|
|
70
|
+
conversation_manager = app_instance.conversation_manager
|
|
71
|
+
database = app_instance.database
|
|
72
|
+
|
|
73
|
+
# Load the conversation (will set it as current)
|
|
74
|
+
conversation_manager.load_conversation(conversation_id)
|
|
75
|
+
|
|
76
|
+
# Get conversation name from database
|
|
77
|
+
conv = database.get_conversation(conversation_id)
|
|
78
|
+
if not conv:
|
|
79
|
+
raise HTTPException(status_code=404, detail="Conversation not found")
|
|
80
|
+
conv_name = conv['name']
|
|
81
|
+
|
|
82
|
+
# Set the model from the conversation and update service references
|
|
83
|
+
model_id = conv['model_id']
|
|
84
|
+
app_instance.llm_manager.set_model(model_id)
|
|
85
|
+
app_instance.bedrock_service = app_instance.llm_manager.get_active_service()
|
|
86
|
+
conversation_manager.update_service(app_instance.bedrock_service)
|
|
87
|
+
|
|
88
|
+
# Get conversation history from conversation manager
|
|
89
|
+
history = conversation_manager.get_conversation_history(include_rolled_up=False)
|
|
90
|
+
|
|
91
|
+
# Format messages
|
|
92
|
+
messages = []
|
|
93
|
+
for msg in history:
|
|
94
|
+
messages.append(
|
|
95
|
+
Message(
|
|
96
|
+
role=msg['role'],
|
|
97
|
+
content=msg['content'],
|
|
98
|
+
timestamp=msg['timestamp'],
|
|
99
|
+
tokens=msg.get('tokens'),
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return ChatHistory(
|
|
104
|
+
conversation_id=conversation_id,
|
|
105
|
+
conversation_name=conv_name,
|
|
106
|
+
messages=messages,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
except HTTPException:
|
|
110
|
+
raise
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.error(f"Error getting chat history for conversation {conversation_id}: {e}")
|
|
113
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@router.post("/chat/{conversation_id}/message")
|
|
117
|
+
async def send_message(
|
|
118
|
+
conversation_id: int,
|
|
119
|
+
request: Request,
|
|
120
|
+
message: str = Form(...),
|
|
121
|
+
session_id: str = Depends(get_current_session),
|
|
122
|
+
) -> dict:
|
|
123
|
+
"""
|
|
124
|
+
Send a message in a conversation.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
conversation_id: ID of the conversation
|
|
128
|
+
message: Message text to send
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Response with assistant's reply
|
|
132
|
+
"""
|
|
133
|
+
try:
|
|
134
|
+
app_instance = request.app.state.app_instance
|
|
135
|
+
conversation_manager = app_instance.conversation_manager
|
|
136
|
+
|
|
137
|
+
# Load conversation in conversation manager
|
|
138
|
+
conversation_manager.load_conversation(conversation_id)
|
|
139
|
+
|
|
140
|
+
# Set the model from the conversation and update service references
|
|
141
|
+
conv_info = conversation_manager.get_current_conversation_info()
|
|
142
|
+
model_id = conv_info['model_id']
|
|
143
|
+
app_instance.llm_manager.set_model(model_id)
|
|
144
|
+
app_instance.bedrock_service = app_instance.llm_manager.get_active_service()
|
|
145
|
+
conversation_manager.update_service(app_instance.bedrock_service)
|
|
146
|
+
|
|
147
|
+
# Send message (this will handle tool use, streaming, etc.)
|
|
148
|
+
response = conversation_manager.send_message(message)
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
"status": "success",
|
|
152
|
+
"response": response,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
except Exception as e:
|
|
156
|
+
logger.error(f"Error sending message in conversation {conversation_id}: {e}")
|
|
157
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@router.post("/chat/{conversation_id}/command/info")
|
|
161
|
+
async def command_info(
|
|
162
|
+
conversation_id: int,
|
|
163
|
+
request: Request,
|
|
164
|
+
session_id: str = Depends(get_current_session),
|
|
165
|
+
) -> CommandResponse:
|
|
166
|
+
"""
|
|
167
|
+
Execute 'info' command to get conversation information.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
conversation_id: ID of the conversation
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
CommandResponse with conversation info
|
|
174
|
+
"""
|
|
175
|
+
try:
|
|
176
|
+
app_instance = request.app.state.app_instance
|
|
177
|
+
database = app_instance.database
|
|
178
|
+
conversation_manager = app_instance.conversation_manager
|
|
179
|
+
|
|
180
|
+
# Get conversation
|
|
181
|
+
conv = database.get_conversation(conversation_id)
|
|
182
|
+
if not conv:
|
|
183
|
+
raise HTTPException(status_code=404, detail="Conversation not found")
|
|
184
|
+
|
|
185
|
+
# Get model usage breakdown
|
|
186
|
+
model_usage = database.get_model_usage_breakdown(conversation_id)
|
|
187
|
+
|
|
188
|
+
# Get attached files
|
|
189
|
+
files = database.get_conversation_files(conversation_id)
|
|
190
|
+
|
|
191
|
+
# Get MCP server states (if available)
|
|
192
|
+
mcp_servers = []
|
|
193
|
+
if app_instance.mcp_manager:
|
|
194
|
+
# Load conversation to set current_conversation_id
|
|
195
|
+
conversation_manager.load_conversation(conversation_id)
|
|
196
|
+
mcp_servers = conversation_manager.get_mcp_server_states()
|
|
197
|
+
|
|
198
|
+
# Handle created_at - could be datetime or string from database
|
|
199
|
+
created_at = conv['created_at']
|
|
200
|
+
if hasattr(created_at, 'isoformat'):
|
|
201
|
+
created_at_str = created_at.isoformat()
|
|
202
|
+
else:
|
|
203
|
+
created_at_str = str(created_at)
|
|
204
|
+
|
|
205
|
+
# Get rollup/compaction settings
|
|
206
|
+
rollup_info = {}
|
|
207
|
+
try:
|
|
208
|
+
model_id = conv.get('model_id', '')
|
|
209
|
+
provider = app_instance.llm_manager.get_active_provider() if app_instance.llm_manager else 'unknown'
|
|
210
|
+
|
|
211
|
+
# Get context limits from resolver
|
|
212
|
+
context_limits = conversation_manager.context_limit_resolver.get_context_limits(model_id, provider)
|
|
213
|
+
context_window = context_limits.get('context_window', 8192)
|
|
214
|
+
max_output = context_limits.get('max_output', 4096)
|
|
215
|
+
|
|
216
|
+
# Get compaction thresholds from compactor
|
|
217
|
+
compaction_threshold = conversation_manager.context_compactor.compaction_threshold
|
|
218
|
+
emergency_threshold = conversation_manager.context_compactor.emergency_threshold
|
|
219
|
+
|
|
220
|
+
# Calculate threshold token counts
|
|
221
|
+
compaction_trigger_tokens = int(context_window * compaction_threshold)
|
|
222
|
+
emergency_trigger_tokens = int(context_window * emergency_threshold)
|
|
223
|
+
|
|
224
|
+
# Get current token count
|
|
225
|
+
current_tokens = conv.get('tokens_sent', 0) + conv.get('tokens_received', 0)
|
|
226
|
+
context_usage_percent = (current_tokens / context_window * 100) if context_window > 0 else 0
|
|
227
|
+
|
|
228
|
+
rollup_info = {
|
|
229
|
+
"context_window": context_window,
|
|
230
|
+
"max_output": max_output,
|
|
231
|
+
"compaction_threshold": compaction_threshold,
|
|
232
|
+
"compaction_trigger_tokens": compaction_trigger_tokens,
|
|
233
|
+
"emergency_threshold": emergency_threshold,
|
|
234
|
+
"emergency_trigger_tokens": emergency_trigger_tokens,
|
|
235
|
+
"current_tokens": current_tokens,
|
|
236
|
+
"context_usage_percent": round(context_usage_percent, 1),
|
|
237
|
+
"provider": provider,
|
|
238
|
+
}
|
|
239
|
+
except Exception as e:
|
|
240
|
+
logger.warning(f"Could not get rollup info: {e}")
|
|
241
|
+
rollup_info = {"error": str(e)}
|
|
242
|
+
|
|
243
|
+
return CommandResponse(
|
|
244
|
+
command="info",
|
|
245
|
+
status="success",
|
|
246
|
+
data={
|
|
247
|
+
"conversation": {
|
|
248
|
+
"id": conv['id'],
|
|
249
|
+
"name": conv['name'],
|
|
250
|
+
"model_id": conv['model_id'],
|
|
251
|
+
"created_at": created_at_str,
|
|
252
|
+
"tokens_sent": conv.get('tokens_sent', 0),
|
|
253
|
+
"tokens_received": conv.get('tokens_received', 0),
|
|
254
|
+
"total_tokens": conv.get('tokens_sent', 0) + conv.get('tokens_received', 0),
|
|
255
|
+
},
|
|
256
|
+
"model_usage": model_usage,
|
|
257
|
+
"files": [f['filename'] for f in files],
|
|
258
|
+
"mcp_servers": mcp_servers,
|
|
259
|
+
"rollup_settings": rollup_info,
|
|
260
|
+
},
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
except HTTPException:
|
|
264
|
+
raise
|
|
265
|
+
except Exception as e:
|
|
266
|
+
logger.error(f"Error getting info for conversation {conversation_id}: {e}")
|
|
267
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@router.post("/chat/{conversation_id}/command/attach")
|
|
271
|
+
async def command_attach(
|
|
272
|
+
conversation_id: int,
|
|
273
|
+
request: Request,
|
|
274
|
+
files: List[UploadFile] = File(...),
|
|
275
|
+
session_id: str = Depends(get_current_session),
|
|
276
|
+
) -> CommandResponse:
|
|
277
|
+
"""
|
|
278
|
+
Execute 'attach' command to attach files to conversation.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
conversation_id: ID of the conversation
|
|
282
|
+
files: Files to attach
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
CommandResponse with status
|
|
286
|
+
"""
|
|
287
|
+
try:
|
|
288
|
+
app_instance = request.app.state.app_instance
|
|
289
|
+
conversation_manager = app_instance.conversation_manager
|
|
290
|
+
database = app_instance.database
|
|
291
|
+
|
|
292
|
+
# Load conversation
|
|
293
|
+
conversation_manager.load_conversation(conversation_id)
|
|
294
|
+
|
|
295
|
+
# Set the model from the conversation and update service references
|
|
296
|
+
conv = database.get_conversation(conversation_id)
|
|
297
|
+
if conv:
|
|
298
|
+
app_instance.llm_manager.set_model(conv['model_id'])
|
|
299
|
+
app_instance.bedrock_service = app_instance.llm_manager.get_active_service()
|
|
300
|
+
conversation_manager.update_service(app_instance.bedrock_service)
|
|
301
|
+
|
|
302
|
+
# Save uploaded files to temporary locations and attach
|
|
303
|
+
temp_files = []
|
|
304
|
+
attached_filenames = []
|
|
305
|
+
|
|
306
|
+
try:
|
|
307
|
+
# Save uploaded files to temporary locations
|
|
308
|
+
for upload_file in files:
|
|
309
|
+
# Create temporary file
|
|
310
|
+
suffix = os.path.splitext(upload_file.filename)[1]
|
|
311
|
+
temp_fd, temp_path = tempfile.mkstemp(suffix=suffix)
|
|
312
|
+
|
|
313
|
+
# Write uploaded content to temp file
|
|
314
|
+
with os.fdopen(temp_fd, 'wb') as f:
|
|
315
|
+
content = await upload_file.read()
|
|
316
|
+
f.write(content)
|
|
317
|
+
|
|
318
|
+
temp_files.append(temp_path)
|
|
319
|
+
attached_filenames.append(upload_file.filename)
|
|
320
|
+
logger.info(f"Saved uploaded file {upload_file.filename} to {temp_path}")
|
|
321
|
+
|
|
322
|
+
# Attach files using conversation manager
|
|
323
|
+
if temp_files:
|
|
324
|
+
success = conversation_manager.attach_files(temp_files)
|
|
325
|
+
|
|
326
|
+
if success:
|
|
327
|
+
logger.info(f"Successfully attached {len(temp_files)} file(s) to conversation {conversation_id}")
|
|
328
|
+
return CommandResponse(
|
|
329
|
+
command="attach",
|
|
330
|
+
status="success",
|
|
331
|
+
message=f"Attached {len(attached_filenames)} file(s)",
|
|
332
|
+
data={"files": attached_filenames},
|
|
333
|
+
)
|
|
334
|
+
else:
|
|
335
|
+
logger.warning("Some files failed to attach")
|
|
336
|
+
return CommandResponse(
|
|
337
|
+
command="attach",
|
|
338
|
+
status="partial",
|
|
339
|
+
message="Some files failed to attach",
|
|
340
|
+
data={"files": attached_filenames},
|
|
341
|
+
)
|
|
342
|
+
else:
|
|
343
|
+
return CommandResponse(
|
|
344
|
+
command="attach",
|
|
345
|
+
status="error",
|
|
346
|
+
message="No files provided",
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
except Exception as e:
|
|
350
|
+
logger.error(f"Error processing file uploads: {e}")
|
|
351
|
+
raise
|
|
352
|
+
|
|
353
|
+
finally:
|
|
354
|
+
# Clean up temporary files
|
|
355
|
+
for temp_path in temp_files:
|
|
356
|
+
try:
|
|
357
|
+
if os.path.exists(temp_path):
|
|
358
|
+
os.unlink(temp_path)
|
|
359
|
+
except:
|
|
360
|
+
pass
|
|
361
|
+
|
|
362
|
+
except Exception as e:
|
|
363
|
+
logger.error(f"Error attaching files to conversation {conversation_id}: {e}")
|
|
364
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
@router.post("/chat/{conversation_id}/command/export")
|
|
368
|
+
async def command_export(
|
|
369
|
+
conversation_id: int,
|
|
370
|
+
request: Request,
|
|
371
|
+
format: str = Form(...), # 'markdown', 'html', or 'csv'
|
|
372
|
+
include_tools: bool = Form(True),
|
|
373
|
+
session_id: str = Depends(get_current_session),
|
|
374
|
+
) -> dict:
|
|
375
|
+
"""
|
|
376
|
+
Execute 'export' command to export conversation.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
conversation_id: ID of the conversation
|
|
380
|
+
format: Export format ('markdown', 'html', or 'csv')
|
|
381
|
+
include_tools: Whether to include tool use details
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
Export data
|
|
385
|
+
"""
|
|
386
|
+
try:
|
|
387
|
+
app_instance = request.app.state.app_instance
|
|
388
|
+
|
|
389
|
+
# Load conversation
|
|
390
|
+
app_instance.conversation_manager.load_conversation(conversation_id)
|
|
391
|
+
|
|
392
|
+
# Export conversation
|
|
393
|
+
if format == 'markdown':
|
|
394
|
+
content = app_instance.conversation_manager.export_to_markdown(
|
|
395
|
+
include_tool_details=include_tools
|
|
396
|
+
)
|
|
397
|
+
elif format == 'html':
|
|
398
|
+
content = app_instance.conversation_manager.export_to_html(
|
|
399
|
+
include_tool_details=include_tools
|
|
400
|
+
)
|
|
401
|
+
elif format == 'csv':
|
|
402
|
+
content = app_instance.conversation_manager.export_to_csv(
|
|
403
|
+
include_tool_details=include_tools
|
|
404
|
+
)
|
|
405
|
+
else:
|
|
406
|
+
raise HTTPException(status_code=400, detail="Invalid export format")
|
|
407
|
+
|
|
408
|
+
return {
|
|
409
|
+
"status": "success",
|
|
410
|
+
"format": format,
|
|
411
|
+
"content": content,
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
except HTTPException:
|
|
415
|
+
raise
|
|
416
|
+
except Exception as e:
|
|
417
|
+
logger.error(f"Error exporting conversation {conversation_id}: {e}")
|
|
418
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
@router.post("/chat/{conversation_id}/command/changemodel")
|
|
422
|
+
async def command_change_model(
|
|
423
|
+
conversation_id: int,
|
|
424
|
+
request: Request,
|
|
425
|
+
model_id: str = Form(...),
|
|
426
|
+
session_id: str = Depends(get_current_session),
|
|
427
|
+
) -> CommandResponse:
|
|
428
|
+
"""
|
|
429
|
+
Execute 'changemodel' command to change conversation model.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
conversation_id: ID of the conversation
|
|
433
|
+
model_id: New model ID
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
CommandResponse with status
|
|
437
|
+
"""
|
|
438
|
+
try:
|
|
439
|
+
app_instance = request.app.state.app_instance
|
|
440
|
+
|
|
441
|
+
# Load conversation
|
|
442
|
+
app_instance.conversation_manager.load_conversation(conversation_id)
|
|
443
|
+
|
|
444
|
+
# Change model
|
|
445
|
+
app_instance.conversation_manager.change_model(model_id)
|
|
446
|
+
|
|
447
|
+
return CommandResponse(
|
|
448
|
+
command="changemodel",
|
|
449
|
+
status="success",
|
|
450
|
+
message=f"Model changed to {model_id}",
|
|
451
|
+
data={"model_id": model_id},
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
except Exception as e:
|
|
455
|
+
logger.error(f"Error changing model for conversation {conversation_id}: {e}")
|
|
456
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
@router.get("/chat/{conversation_id}/command/mcpaudit")
|
|
460
|
+
async def command_mcp_audit(
|
|
461
|
+
conversation_id: int,
|
|
462
|
+
request: Request,
|
|
463
|
+
session_id: str = Depends(get_current_session),
|
|
464
|
+
) -> CommandResponse:
|
|
465
|
+
"""
|
|
466
|
+
Execute 'mcpaudit' command to get MCP transaction audit log.
|
|
467
|
+
|
|
468
|
+
Args:
|
|
469
|
+
conversation_id: ID of the conversation
|
|
470
|
+
|
|
471
|
+
Returns:
|
|
472
|
+
CommandResponse with audit log
|
|
473
|
+
"""
|
|
474
|
+
try:
|
|
475
|
+
app_instance = request.app.state.app_instance
|
|
476
|
+
database = app_instance.database
|
|
477
|
+
|
|
478
|
+
# Get MCP transactions for this conversation
|
|
479
|
+
transactions = database.get_mcp_transactions(conversation_id=conversation_id)
|
|
480
|
+
|
|
481
|
+
return CommandResponse(
|
|
482
|
+
command="mcpaudit",
|
|
483
|
+
status="success",
|
|
484
|
+
data={"transactions": transactions},
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
except Exception as e:
|
|
488
|
+
logger.error(f"Error getting MCP audit for conversation {conversation_id}: {e}")
|
|
489
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
@router.get("/chat/{conversation_id}/command/mcpservers")
|
|
493
|
+
async def command_mcp_servers(
|
|
494
|
+
conversation_id: int,
|
|
495
|
+
request: Request,
|
|
496
|
+
session_id: str = Depends(get_current_session),
|
|
497
|
+
) -> CommandResponse:
|
|
498
|
+
"""
|
|
499
|
+
Execute 'mcpservers' command to get MCP server states.
|
|
500
|
+
|
|
501
|
+
Args:
|
|
502
|
+
conversation_id: ID of the conversation
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
CommandResponse with MCP server states
|
|
506
|
+
"""
|
|
507
|
+
try:
|
|
508
|
+
app_instance = request.app.state.app_instance
|
|
509
|
+
database = app_instance.database
|
|
510
|
+
|
|
511
|
+
# Get all server names from MCP manager
|
|
512
|
+
all_server_names = []
|
|
513
|
+
if app_instance.mcp_manager and hasattr(app_instance.mcp_manager, 'clients'):
|
|
514
|
+
all_server_names = list(app_instance.mcp_manager.clients.keys())
|
|
515
|
+
|
|
516
|
+
# Get MCP server states for this conversation
|
|
517
|
+
servers = database.get_all_mcp_server_states(conversation_id, all_server_names)
|
|
518
|
+
|
|
519
|
+
return CommandResponse(
|
|
520
|
+
command="mcpservers",
|
|
521
|
+
status="success",
|
|
522
|
+
data={"servers": servers},
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
except Exception as e:
|
|
526
|
+
logger.error(f"Error getting MCP servers for conversation {conversation_id}: {e}")
|
|
527
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
@router.post("/chat/{conversation_id}/command/mcpservers/toggle")
|
|
531
|
+
async def toggle_mcp_server(
|
|
532
|
+
conversation_id: int,
|
|
533
|
+
request: Request,
|
|
534
|
+
server_name: str = Form(...),
|
|
535
|
+
enabled: bool = Form(...),
|
|
536
|
+
session_id: str = Depends(get_current_session),
|
|
537
|
+
) -> CommandResponse:
|
|
538
|
+
"""
|
|
539
|
+
Toggle MCP server enabled/disabled state.
|
|
540
|
+
|
|
541
|
+
Args:
|
|
542
|
+
conversation_id: ID of the conversation
|
|
543
|
+
server_name: Name of the server to toggle
|
|
544
|
+
enabled: New enabled state
|
|
545
|
+
|
|
546
|
+
Returns:
|
|
547
|
+
CommandResponse with status
|
|
548
|
+
"""
|
|
549
|
+
try:
|
|
550
|
+
app_instance = request.app.state.app_instance
|
|
551
|
+
|
|
552
|
+
# Load conversation
|
|
553
|
+
app_instance.conversation_manager.load_conversation(conversation_id)
|
|
554
|
+
|
|
555
|
+
# Toggle server
|
|
556
|
+
app_instance.conversation_manager.set_mcp_server_enabled(server_name, enabled)
|
|
557
|
+
|
|
558
|
+
return CommandResponse(
|
|
559
|
+
command="mcpservers",
|
|
560
|
+
status="success",
|
|
561
|
+
message=f"Server '{server_name}' {'enabled' if enabled else 'disabled'}",
|
|
562
|
+
data={
|
|
563
|
+
"server_name": server_name,
|
|
564
|
+
"enabled": enabled,
|
|
565
|
+
},
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
except Exception as e:
|
|
569
|
+
logger.error(f"Error toggling MCP server for conversation {conversation_id}: {e}")
|
|
570
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
@router.post("/chat/permission/respond")
|
|
574
|
+
async def respond_to_permission_request(
|
|
575
|
+
request: Request,
|
|
576
|
+
request_id: str = Form(...),
|
|
577
|
+
response: str = Form(...),
|
|
578
|
+
session_id: str = Depends(get_current_session)
|
|
579
|
+
):
|
|
580
|
+
"""
|
|
581
|
+
Submit a response to a tool permission request.
|
|
582
|
+
|
|
583
|
+
Args:
|
|
584
|
+
request: FastAPI request
|
|
585
|
+
request_id: The permission request ID
|
|
586
|
+
response: User's response ('once', 'allowed', 'denied', or 'cancel')
|
|
587
|
+
session_id: Current session ID
|
|
588
|
+
|
|
589
|
+
Returns:
|
|
590
|
+
CommandResponse with status
|
|
591
|
+
"""
|
|
592
|
+
try:
|
|
593
|
+
app_instance = request.app.state.app_instance
|
|
594
|
+
|
|
595
|
+
# Check if web interface is available
|
|
596
|
+
if not hasattr(app_instance.conversation_manager, 'web_interface') or not app_instance.conversation_manager.web_interface:
|
|
597
|
+
raise HTTPException(status_code=400, detail="Web interface not initialized")
|
|
598
|
+
|
|
599
|
+
# Submit the response
|
|
600
|
+
success = app_instance.conversation_manager.web_interface.submit_permission_response(
|
|
601
|
+
request_id, None if response == 'cancel' else response
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
if not success:
|
|
605
|
+
raise HTTPException(status_code=404, detail="Permission request not found")
|
|
606
|
+
|
|
607
|
+
return CommandResponse(
|
|
608
|
+
command="permission_response",
|
|
609
|
+
status="success",
|
|
610
|
+
message=f"Permission response submitted: {response}",
|
|
611
|
+
data={
|
|
612
|
+
"request_id": request_id,
|
|
613
|
+
"response": response,
|
|
614
|
+
},
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
except HTTPException:
|
|
618
|
+
raise
|
|
619
|
+
except Exception as e:
|
|
620
|
+
logger.error(f"Error submitting permission response: {e}")
|
|
621
|
+
raise HTTPException(status_code=500, detail=str(e))
|