dtSpark 1.0.11__py3-none-any.whl → 1.1.0a2__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/_version.txt +1 -1
- dtSpark/core/application.py +21 -28
- dtSpark/tools/builtin.py +18 -0
- dtSpark/web/endpoints/chat.py +147 -0
- dtSpark/web/templates/chat.html +300 -0
- {dtspark-1.0.11.dist-info → dtspark-1.1.0a2.dist-info}/METADATA +1 -1
- {dtspark-1.0.11.dist-info → dtspark-1.1.0a2.dist-info}/RECORD +11 -11
- {dtspark-1.0.11.dist-info → dtspark-1.1.0a2.dist-info}/WHEEL +0 -0
- {dtspark-1.0.11.dist-info → dtspark-1.1.0a2.dist-info}/entry_points.txt +0 -0
- {dtspark-1.0.11.dist-info → dtspark-1.1.0a2.dist-info}/licenses/LICENSE +0 -0
- {dtspark-1.0.11.dist-info → dtspark-1.1.0a2.dist-info}/top_level.txt +0 -0
dtSpark/_version.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.1.0a2
|
dtSpark/core/application.py
CHANGED
|
@@ -328,34 +328,6 @@ class AWSBedrockCLI(AbstractApp):
|
|
|
328
328
|
|
|
329
329
|
logging.info('Initialising application components')
|
|
330
330
|
|
|
331
|
-
# Debug: Diagnose settings loading for llm_providers
|
|
332
|
-
# Try different access patterns to understand how Settings class works
|
|
333
|
-
logging.info("=== Settings Diagnostics ===")
|
|
334
|
-
|
|
335
|
-
# Try accessing the whole llm_providers section
|
|
336
|
-
llm_providers_raw = self.settings.get('llm_providers', None)
|
|
337
|
-
logging.info(f"settings.get('llm_providers'): {llm_providers_raw} (type: {type(llm_providers_raw).__name__ if llm_providers_raw else 'None'})")
|
|
338
|
-
|
|
339
|
-
# Try accessing aws_bedrock under llm_providers
|
|
340
|
-
aws_bedrock_raw = self.settings.get('llm_providers.aws_bedrock', None)
|
|
341
|
-
logging.info(f"settings.get('llm_providers.aws_bedrock'): {aws_bedrock_raw} (type: {type(aws_bedrock_raw).__name__ if aws_bedrock_raw else 'None'})")
|
|
342
|
-
|
|
343
|
-
# If llm_providers is a dict, try to access nested values directly
|
|
344
|
-
if isinstance(llm_providers_raw, dict):
|
|
345
|
-
logging.info(f"llm_providers keys: {list(llm_providers_raw.keys())}")
|
|
346
|
-
aws_bedrock_dict = llm_providers_raw.get('aws_bedrock', {})
|
|
347
|
-
logging.info(f"llm_providers['aws_bedrock']: {aws_bedrock_dict}")
|
|
348
|
-
if isinstance(aws_bedrock_dict, dict):
|
|
349
|
-
logging.info(f"aws_bedrock['enabled']: {aws_bedrock_dict.get('enabled', 'NOT_FOUND')}")
|
|
350
|
-
|
|
351
|
-
# Check if settings has a _settings or similar internal dict
|
|
352
|
-
if hasattr(self.settings, '_settings'):
|
|
353
|
-
logging.info(f"settings._settings type: {type(self.settings._settings)}")
|
|
354
|
-
if hasattr(self.settings, 'settings'):
|
|
355
|
-
logging.info(f"settings.settings type: {type(self.settings.settings)}")
|
|
356
|
-
|
|
357
|
-
logging.info("=== End Settings Diagnostics ===")
|
|
358
|
-
|
|
359
331
|
# Initialise CLI interface
|
|
360
332
|
self.cli = CLIInterface()
|
|
361
333
|
|
|
@@ -779,6 +751,27 @@ class AWSBedrockCLI(AbstractApp):
|
|
|
779
751
|
'enabled': self.settings.get('embedded_tools.filesystem.enabled', False),
|
|
780
752
|
'allowed_path': self.settings.get('embedded_tools.filesystem.allowed_path', './'),
|
|
781
753
|
'access_mode': self.settings.get('embedded_tools.filesystem.access_mode', 'read')
|
|
754
|
+
},
|
|
755
|
+
'documents': {
|
|
756
|
+
'enabled': self.settings.get('embedded_tools.documents.enabled', False),
|
|
757
|
+
'allowed_path': self.settings.get('embedded_tools.documents.allowed_path', './'),
|
|
758
|
+
'access_mode': self.settings.get('embedded_tools.documents.access_mode', 'read'),
|
|
759
|
+
'max_file_size_mb': self.settings.get('embedded_tools.documents.max_file_size_mb', 50),
|
|
760
|
+
'reading': {
|
|
761
|
+
'max_pdf_pages': self.settings.get('embedded_tools.documents.reading.max_pdf_pages', 100),
|
|
762
|
+
'max_excel_rows': self.settings.get('embedded_tools.documents.reading.max_excel_rows', 10000)
|
|
763
|
+
},
|
|
764
|
+
'creation': {
|
|
765
|
+
'templates_path': self.settings.get('embedded_tools.documents.creation.templates_path'),
|
|
766
|
+
'default_author': self.settings.get('embedded_tools.documents.creation.default_author')
|
|
767
|
+
}
|
|
768
|
+
},
|
|
769
|
+
'archives': {
|
|
770
|
+
'enabled': self.settings.get('embedded_tools.archives.enabled', False),
|
|
771
|
+
'allowed_path': self.settings.get('embedded_tools.archives.allowed_path', './'),
|
|
772
|
+
'access_mode': self.settings.get('embedded_tools.archives.access_mode', 'read'),
|
|
773
|
+
'max_file_size_mb': self.settings.get('embedded_tools.archives.max_file_size_mb', 100),
|
|
774
|
+
'max_files_to_list': self.settings.get('embedded_tools.archives.max_files_to_list', 1000)
|
|
782
775
|
}
|
|
783
776
|
}
|
|
784
777
|
}
|
dtSpark/tools/builtin.py
CHANGED
|
@@ -1630,6 +1630,17 @@ def _execute_create_word_document(tool_input: Dict[str, Any],
|
|
|
1630
1630
|
if content.get('title'):
|
|
1631
1631
|
doc.add_heading(content['title'], 0)
|
|
1632
1632
|
|
|
1633
|
+
# Define valid built-in styles that python-docx supports
|
|
1634
|
+
valid_styles = {
|
|
1635
|
+
'Normal', 'Title', 'Subtitle', 'Quote', 'Intense Quote',
|
|
1636
|
+
'List Paragraph', 'List Bullet', 'List Number',
|
|
1637
|
+
'Heading 1', 'Heading 2', 'Heading 3', 'Heading 4',
|
|
1638
|
+
'Heading 5', 'Heading 6', 'Heading 7', 'Heading 8', 'Heading 9',
|
|
1639
|
+
'Body Text', 'Body Text 2', 'Body Text 3',
|
|
1640
|
+
'Caption', 'Macro Text', 'No Spacing'
|
|
1641
|
+
}
|
|
1642
|
+
invalid_styles_used = set()
|
|
1643
|
+
|
|
1633
1644
|
# Add paragraphs
|
|
1634
1645
|
for para_data in content.get('paragraphs', []):
|
|
1635
1646
|
text = para_data.get('text', '')
|
|
@@ -1638,8 +1649,15 @@ def _execute_create_word_document(tool_input: Dict[str, Any],
|
|
|
1638
1649
|
level = int(style.split()[-1]) if style.split()[-1].isdigit() else 1
|
|
1639
1650
|
doc.add_heading(text, level)
|
|
1640
1651
|
else:
|
|
1652
|
+
# Validate style - fall back to Normal if invalid
|
|
1653
|
+
if style not in valid_styles:
|
|
1654
|
+
invalid_styles_used.add(style)
|
|
1655
|
+
style = 'Normal'
|
|
1641
1656
|
doc.add_paragraph(text, style=style)
|
|
1642
1657
|
|
|
1658
|
+
if invalid_styles_used:
|
|
1659
|
+
logging.warning(f"Invalid styles replaced with 'Normal': {', '.join(sorted(invalid_styles_used))}")
|
|
1660
|
+
|
|
1643
1661
|
doc.save(str(full_path))
|
|
1644
1662
|
|
|
1645
1663
|
result = {
|
dtSpark/web/endpoints/chat.py
CHANGED
|
@@ -252,9 +252,19 @@ async def command_info(
|
|
|
252
252
|
"tokens_sent": conv.get('tokens_sent', 0),
|
|
253
253
|
"tokens_received": conv.get('tokens_received', 0),
|
|
254
254
|
"total_tokens": conv.get('tokens_sent', 0) + conv.get('tokens_received', 0),
|
|
255
|
+
"instructions": conv.get('instructions', ''),
|
|
255
256
|
},
|
|
256
257
|
"model_usage": model_usage,
|
|
257
258
|
"files": [f['filename'] for f in files],
|
|
259
|
+
"attached_files": [
|
|
260
|
+
{
|
|
261
|
+
"id": f['id'],
|
|
262
|
+
"filename": f['filename'],
|
|
263
|
+
"size_bytes": f.get('size_bytes', 0),
|
|
264
|
+
"mime_type": f.get('mime_type', 'application/octet-stream'),
|
|
265
|
+
}
|
|
266
|
+
for f in files
|
|
267
|
+
],
|
|
258
268
|
"mcp_servers": mcp_servers,
|
|
259
269
|
"rollup_settings": rollup_info,
|
|
260
270
|
},
|
|
@@ -570,6 +580,143 @@ async def toggle_mcp_server(
|
|
|
570
580
|
raise HTTPException(status_code=500, detail=str(e))
|
|
571
581
|
|
|
572
582
|
|
|
583
|
+
@router.post("/chat/{conversation_id}/command/instructions")
|
|
584
|
+
async def update_instructions(
|
|
585
|
+
request: Request,
|
|
586
|
+
conversation_id: int,
|
|
587
|
+
instructions: str = Form(""),
|
|
588
|
+
session_id: str = Depends(get_current_session),
|
|
589
|
+
):
|
|
590
|
+
"""
|
|
591
|
+
Update conversation instructions/system prompt.
|
|
592
|
+
|
|
593
|
+
Args:
|
|
594
|
+
request: FastAPI request
|
|
595
|
+
conversation_id: ID of the conversation
|
|
596
|
+
instructions: New instructions text (empty to clear)
|
|
597
|
+
session_id: Current session ID
|
|
598
|
+
|
|
599
|
+
Returns:
|
|
600
|
+
CommandResponse with status
|
|
601
|
+
"""
|
|
602
|
+
try:
|
|
603
|
+
app_instance = request.app.state.app_instance
|
|
604
|
+
|
|
605
|
+
# Check if conversation is predefined
|
|
606
|
+
if app_instance.database.is_conversation_predefined(conversation_id):
|
|
607
|
+
return CommandResponse(
|
|
608
|
+
command="instructions",
|
|
609
|
+
status="error",
|
|
610
|
+
message="Cannot modify instructions for predefined conversations",
|
|
611
|
+
data=None,
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
# Set conversation context if needed
|
|
615
|
+
if app_instance.conversation_manager.current_conversation_id != conversation_id:
|
|
616
|
+
app_instance.conversation_manager.load_conversation(conversation_id)
|
|
617
|
+
|
|
618
|
+
# Update instructions (None to clear, or the text)
|
|
619
|
+
new_instructions = instructions.strip() if instructions.strip() else None
|
|
620
|
+
success = app_instance.conversation_manager.update_instructions(new_instructions)
|
|
621
|
+
|
|
622
|
+
if success:
|
|
623
|
+
return CommandResponse(
|
|
624
|
+
command="instructions",
|
|
625
|
+
status="success",
|
|
626
|
+
message="Instructions updated successfully",
|
|
627
|
+
data={"instructions": new_instructions},
|
|
628
|
+
)
|
|
629
|
+
else:
|
|
630
|
+
return CommandResponse(
|
|
631
|
+
command="instructions",
|
|
632
|
+
status="error",
|
|
633
|
+
message="Failed to update instructions",
|
|
634
|
+
data=None,
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
except Exception as e:
|
|
638
|
+
logger.error(f"Error updating instructions for conversation {conversation_id}: {e}")
|
|
639
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
@router.post("/chat/{conversation_id}/command/deletefiles")
|
|
643
|
+
async def delete_files(
|
|
644
|
+
request: Request,
|
|
645
|
+
conversation_id: int,
|
|
646
|
+
session_id: str = Depends(get_current_session),
|
|
647
|
+
):
|
|
648
|
+
"""
|
|
649
|
+
Delete attached files from a conversation.
|
|
650
|
+
|
|
651
|
+
Args:
|
|
652
|
+
request: FastAPI request
|
|
653
|
+
conversation_id: ID of the conversation
|
|
654
|
+
session_id: Current session ID
|
|
655
|
+
|
|
656
|
+
Returns:
|
|
657
|
+
CommandResponse with deletion status
|
|
658
|
+
"""
|
|
659
|
+
try:
|
|
660
|
+
app_instance = request.app.state.app_instance
|
|
661
|
+
|
|
662
|
+
# Check if conversation is predefined
|
|
663
|
+
if app_instance.database.is_conversation_predefined(conversation_id):
|
|
664
|
+
return CommandResponse(
|
|
665
|
+
command="deletefiles",
|
|
666
|
+
status="error",
|
|
667
|
+
message="Cannot delete files from predefined conversations",
|
|
668
|
+
data=None,
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
# Parse JSON body for file_ids
|
|
672
|
+
body = await request.json()
|
|
673
|
+
file_ids = body.get('file_ids', [])
|
|
674
|
+
|
|
675
|
+
if not file_ids:
|
|
676
|
+
return CommandResponse(
|
|
677
|
+
command="deletefiles",
|
|
678
|
+
status="error",
|
|
679
|
+
message="No file IDs provided",
|
|
680
|
+
data=None,
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
# Delete each file
|
|
684
|
+
deleted_count = 0
|
|
685
|
+
failed_ids = []
|
|
686
|
+
|
|
687
|
+
for file_id in file_ids:
|
|
688
|
+
try:
|
|
689
|
+
if app_instance.database.delete_file(file_id):
|
|
690
|
+
deleted_count += 1
|
|
691
|
+
else:
|
|
692
|
+
failed_ids.append(file_id)
|
|
693
|
+
except Exception as e:
|
|
694
|
+
logger.warning(f"Failed to delete file {file_id}: {e}")
|
|
695
|
+
failed_ids.append(file_id)
|
|
696
|
+
|
|
697
|
+
if deleted_count > 0:
|
|
698
|
+
return CommandResponse(
|
|
699
|
+
command="deletefiles",
|
|
700
|
+
status="success",
|
|
701
|
+
message=f"Deleted {deleted_count} file(s)",
|
|
702
|
+
data={
|
|
703
|
+
"deleted_count": deleted_count,
|
|
704
|
+
"failed_ids": failed_ids,
|
|
705
|
+
},
|
|
706
|
+
)
|
|
707
|
+
else:
|
|
708
|
+
return CommandResponse(
|
|
709
|
+
command="deletefiles",
|
|
710
|
+
status="error",
|
|
711
|
+
message="Failed to delete files",
|
|
712
|
+
data={"failed_ids": failed_ids},
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
except Exception as e:
|
|
716
|
+
logger.error(f"Error deleting files for conversation {conversation_id}: {e}")
|
|
717
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
718
|
+
|
|
719
|
+
|
|
573
720
|
@router.post("/chat/permission/respond")
|
|
574
721
|
async def respond_to_permission_request(
|
|
575
722
|
request: Request,
|
dtSpark/web/templates/chat.html
CHANGED
|
@@ -114,12 +114,28 @@ document.getElementById('infoModal').addEventListener('show.bs.modal', function(
|
|
|
114
114
|
<button class="list-group-item list-group-item-action" onclick="executeCommand('export')">
|
|
115
115
|
<i class="bi bi-download"></i> Export Conversation
|
|
116
116
|
</button>
|
|
117
|
+
<button class="list-group-item list-group-item-action" onclick="executeCommand('attach')">
|
|
118
|
+
<i class="bi bi-paperclip"></i> Attach Files
|
|
119
|
+
</button>
|
|
120
|
+
<button class="list-group-item list-group-item-action" onclick="executeCommand('instructions')">
|
|
121
|
+
<i class="bi bi-card-text"></i> Change Instructions
|
|
122
|
+
</button>
|
|
123
|
+
<button class="list-group-item list-group-item-action" onclick="executeCommand('copy')">
|
|
124
|
+
<i class="bi bi-clipboard"></i> Copy Last Response
|
|
125
|
+
</button>
|
|
117
126
|
<button class="list-group-item list-group-item-action" onclick="executeCommand('mcpaudit')">
|
|
118
127
|
<i class="bi bi-shield-check"></i> MCP Audit Log
|
|
119
128
|
</button>
|
|
120
129
|
<button class="list-group-item list-group-item-action" onclick="executeCommand('mcpservers')">
|
|
121
130
|
<i class="bi bi-hdd-network"></i> MCP Server Management
|
|
122
131
|
</button>
|
|
132
|
+
<hr class="my-2">
|
|
133
|
+
<button class="list-group-item list-group-item-action" onclick="executeCommand('deletefiles')">
|
|
134
|
+
<i class="bi bi-file-earmark-x text-warning"></i> Delete Attached Files
|
|
135
|
+
</button>
|
|
136
|
+
<button class="list-group-item list-group-item-action text-danger" onclick="executeCommand('delete')">
|
|
137
|
+
<i class="bi bi-trash"></i> Delete Conversation
|
|
138
|
+
</button>
|
|
123
139
|
</div>
|
|
124
140
|
</div>
|
|
125
141
|
</div>
|
|
@@ -197,6 +213,21 @@ async function executeCommand(command) {
|
|
|
197
213
|
case 'changemodel':
|
|
198
214
|
await showChangeModelModal();
|
|
199
215
|
break;
|
|
216
|
+
case 'attach':
|
|
217
|
+
await showAttachFilesModal();
|
|
218
|
+
break;
|
|
219
|
+
case 'instructions':
|
|
220
|
+
await showInstructionsModal();
|
|
221
|
+
break;
|
|
222
|
+
case 'copy':
|
|
223
|
+
await copyLastResponse();
|
|
224
|
+
break;
|
|
225
|
+
case 'deletefiles':
|
|
226
|
+
await showDeleteFilesModal();
|
|
227
|
+
break;
|
|
228
|
+
case 'delete':
|
|
229
|
+
await confirmDeleteConversation();
|
|
230
|
+
break;
|
|
200
231
|
case 'mcpaudit':
|
|
201
232
|
await showMCPAuditModal();
|
|
202
233
|
break;
|
|
@@ -810,5 +841,274 @@ async function performAttachFiles() {
|
|
|
810
841
|
function attachFiles() {
|
|
811
842
|
showAttachFilesModal();
|
|
812
843
|
}
|
|
844
|
+
|
|
845
|
+
// Show instructions modal
|
|
846
|
+
async function showInstructionsModal() {
|
|
847
|
+
try {
|
|
848
|
+
// Load current instructions
|
|
849
|
+
const response = await fetch(`/api/chat/${conversationId}/command/info`, {
|
|
850
|
+
method: 'POST'
|
|
851
|
+
});
|
|
852
|
+
const data = await response.json();
|
|
853
|
+
const currentInstructions = data.data.conversation.instructions || '';
|
|
854
|
+
|
|
855
|
+
const modalHTML = `
|
|
856
|
+
<div class="modal fade" id="instructionsModal" tabindex="-1">
|
|
857
|
+
<div class="modal-dialog modal-lg">
|
|
858
|
+
<div class="modal-content">
|
|
859
|
+
<div class="modal-header">
|
|
860
|
+
<h5 class="modal-title"><i class="bi bi-card-text"></i> Change Instructions</h5>
|
|
861
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
862
|
+
</div>
|
|
863
|
+
<div class="modal-body">
|
|
864
|
+
<div class="mb-3">
|
|
865
|
+
<label for="instructions-text" class="form-label">System Instructions / Prompt</label>
|
|
866
|
+
<textarea class="form-control" id="instructions-text" rows="10"
|
|
867
|
+
placeholder="Enter system instructions for this conversation...">${escapeHtml(currentInstructions)}</textarea>
|
|
868
|
+
<div class="form-text">These instructions are sent as a system prompt with each message.</div>
|
|
869
|
+
</div>
|
|
870
|
+
</div>
|
|
871
|
+
<div class="modal-footer">
|
|
872
|
+
<button type="button" class="btn btn-outline-warning" onclick="clearInstructions()">
|
|
873
|
+
<i class="bi bi-eraser"></i> Clear
|
|
874
|
+
</button>
|
|
875
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
876
|
+
<button type="button" class="btn btn-primary" onclick="saveInstructions()">
|
|
877
|
+
<i class="bi bi-check-lg"></i> Save
|
|
878
|
+
</button>
|
|
879
|
+
</div>
|
|
880
|
+
</div>
|
|
881
|
+
</div>
|
|
882
|
+
</div>
|
|
883
|
+
`;
|
|
884
|
+
|
|
885
|
+
// Remove old modal if exists
|
|
886
|
+
const oldModal = document.getElementById('instructionsModal');
|
|
887
|
+
if (oldModal) oldModal.remove();
|
|
888
|
+
|
|
889
|
+
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
|
890
|
+
const modal = new bootstrap.Modal(document.getElementById('instructionsModal'));
|
|
891
|
+
modal.show();
|
|
892
|
+
} catch (error) {
|
|
893
|
+
showToast('Failed to load instructions', 'error');
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
async function saveInstructions() {
|
|
898
|
+
const instructions = document.getElementById('instructions-text').value;
|
|
899
|
+
|
|
900
|
+
try {
|
|
901
|
+
const formData = new FormData();
|
|
902
|
+
formData.append('instructions', instructions);
|
|
903
|
+
|
|
904
|
+
const response = await fetch(`/api/chat/${conversationId}/command/instructions`, {
|
|
905
|
+
method: 'POST',
|
|
906
|
+
body: formData
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
const data = await response.json();
|
|
910
|
+
|
|
911
|
+
if (data.status === 'success') {
|
|
912
|
+
bootstrap.Modal.getInstance(document.getElementById('instructionsModal')).hide();
|
|
913
|
+
showToast('Instructions updated successfully', 'success');
|
|
914
|
+
} else {
|
|
915
|
+
showToast(data.message || 'Failed to update instructions', 'error');
|
|
916
|
+
}
|
|
917
|
+
} catch (error) {
|
|
918
|
+
showToast('Failed to update instructions', 'error');
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
async function clearInstructions() {
|
|
923
|
+
if (!confirm('Are you sure you want to clear the instructions?')) return;
|
|
924
|
+
|
|
925
|
+
try {
|
|
926
|
+
const formData = new FormData();
|
|
927
|
+
formData.append('instructions', '');
|
|
928
|
+
|
|
929
|
+
const response = await fetch(`/api/chat/${conversationId}/command/instructions`, {
|
|
930
|
+
method: 'POST',
|
|
931
|
+
body: formData
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
const data = await response.json();
|
|
935
|
+
|
|
936
|
+
if (data.status === 'success') {
|
|
937
|
+
document.getElementById('instructions-text').value = '';
|
|
938
|
+
showToast('Instructions cleared', 'success');
|
|
939
|
+
} else {
|
|
940
|
+
showToast(data.message || 'Failed to clear instructions', 'error');
|
|
941
|
+
}
|
|
942
|
+
} catch (error) {
|
|
943
|
+
showToast('Failed to clear instructions', 'error');
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// Copy last response to clipboard
|
|
948
|
+
async function copyLastResponse() {
|
|
949
|
+
// Find the last assistant message in the chat
|
|
950
|
+
const chatContainer = document.getElementById('chat-container');
|
|
951
|
+
const assistantMessages = chatContainer.querySelectorAll('.assistant-message');
|
|
952
|
+
|
|
953
|
+
if (assistantMessages.length === 0) {
|
|
954
|
+
showToast('No assistant response to copy', 'warning');
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
const lastMessage = assistantMessages[assistantMessages.length - 1];
|
|
959
|
+
const messageContent = lastMessage.querySelector('.message-content');
|
|
960
|
+
|
|
961
|
+
if (!messageContent) {
|
|
962
|
+
showToast('No content to copy', 'warning');
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
try {
|
|
967
|
+
// Get the text content, preserving some formatting
|
|
968
|
+
const text = messageContent.innerText || messageContent.textContent;
|
|
969
|
+
await navigator.clipboard.writeText(text);
|
|
970
|
+
showToast('Response copied to clipboard', 'success');
|
|
971
|
+
} catch (error) {
|
|
972
|
+
// Fallback for older browsers
|
|
973
|
+
const textarea = document.createElement('textarea');
|
|
974
|
+
textarea.value = messageContent.innerText || messageContent.textContent;
|
|
975
|
+
document.body.appendChild(textarea);
|
|
976
|
+
textarea.select();
|
|
977
|
+
document.execCommand('copy');
|
|
978
|
+
document.body.removeChild(textarea);
|
|
979
|
+
showToast('Response copied to clipboard', 'success');
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// Show delete files modal
|
|
984
|
+
async function showDeleteFilesModal() {
|
|
985
|
+
try {
|
|
986
|
+
// Load attached files
|
|
987
|
+
const response = await fetch(`/api/chat/${conversationId}/command/info`, {
|
|
988
|
+
method: 'POST'
|
|
989
|
+
});
|
|
990
|
+
const data = await response.json();
|
|
991
|
+
const files = data.data.attached_files || [];
|
|
992
|
+
|
|
993
|
+
if (files.length === 0) {
|
|
994
|
+
showToast('No files attached to this conversation', 'info');
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
let filesHTML = '';
|
|
999
|
+
files.forEach(file => {
|
|
1000
|
+
filesHTML += `
|
|
1001
|
+
<div class="form-check">
|
|
1002
|
+
<input class="form-check-input file-delete-checkbox" type="checkbox" value="${file.id}" id="file-${file.id}">
|
|
1003
|
+
<label class="form-check-label" for="file-${file.id}">
|
|
1004
|
+
<i class="bi bi-file-earmark"></i> ${escapeHtml(file.filename)}
|
|
1005
|
+
<span class="text-muted small">(${(file.size_bytes / 1024).toFixed(2)} KB)</span>
|
|
1006
|
+
</label>
|
|
1007
|
+
</div>
|
|
1008
|
+
`;
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
const modalHTML = `
|
|
1012
|
+
<div class="modal fade" id="deleteFilesModal" tabindex="-1">
|
|
1013
|
+
<div class="modal-dialog">
|
|
1014
|
+
<div class="modal-content">
|
|
1015
|
+
<div class="modal-header">
|
|
1016
|
+
<h5 class="modal-title"><i class="bi bi-file-earmark-x text-warning"></i> Delete Attached Files</h5>
|
|
1017
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
1018
|
+
</div>
|
|
1019
|
+
<div class="modal-body">
|
|
1020
|
+
<p>Select files to delete:</p>
|
|
1021
|
+
<div class="mb-3">
|
|
1022
|
+
<button class="btn btn-sm btn-outline-secondary me-2" onclick="toggleAllDeleteFiles(true)">Select All</button>
|
|
1023
|
+
<button class="btn btn-sm btn-outline-secondary" onclick="toggleAllDeleteFiles(false)">Deselect All</button>
|
|
1024
|
+
</div>
|
|
1025
|
+
<div id="delete-files-list">
|
|
1026
|
+
${filesHTML}
|
|
1027
|
+
</div>
|
|
1028
|
+
</div>
|
|
1029
|
+
<div class="modal-footer">
|
|
1030
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
1031
|
+
<button type="button" class="btn btn-danger" onclick="performDeleteFiles()">
|
|
1032
|
+
<i class="bi bi-trash"></i> Delete Selected
|
|
1033
|
+
</button>
|
|
1034
|
+
</div>
|
|
1035
|
+
</div>
|
|
1036
|
+
</div>
|
|
1037
|
+
</div>
|
|
1038
|
+
`;
|
|
1039
|
+
|
|
1040
|
+
// Remove old modal if exists
|
|
1041
|
+
const oldModal = document.getElementById('deleteFilesModal');
|
|
1042
|
+
if (oldModal) oldModal.remove();
|
|
1043
|
+
|
|
1044
|
+
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
|
1045
|
+
const modal = new bootstrap.Modal(document.getElementById('deleteFilesModal'));
|
|
1046
|
+
modal.show();
|
|
1047
|
+
} catch (error) {
|
|
1048
|
+
showToast('Failed to load attached files', 'error');
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function toggleAllDeleteFiles(checked) {
|
|
1053
|
+
document.querySelectorAll('.file-delete-checkbox').forEach(cb => cb.checked = checked);
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
async function performDeleteFiles() {
|
|
1057
|
+
const checkboxes = document.querySelectorAll('.file-delete-checkbox:checked');
|
|
1058
|
+
const fileIds = Array.from(checkboxes).map(cb => cb.value);
|
|
1059
|
+
|
|
1060
|
+
if (fileIds.length === 0) {
|
|
1061
|
+
showToast('Please select at least one file to delete', 'warning');
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
if (!confirm(`Are you sure you want to delete ${fileIds.length} file(s)?`)) return;
|
|
1066
|
+
|
|
1067
|
+
try {
|
|
1068
|
+
const response = await fetch(`/api/chat/${conversationId}/command/deletefiles`, {
|
|
1069
|
+
method: 'POST',
|
|
1070
|
+
headers: {
|
|
1071
|
+
'Content-Type': 'application/json'
|
|
1072
|
+
},
|
|
1073
|
+
body: JSON.stringify({ file_ids: fileIds.map(id => parseInt(id)) })
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
const data = await response.json();
|
|
1077
|
+
|
|
1078
|
+
if (data.status === 'success') {
|
|
1079
|
+
bootstrap.Modal.getInstance(document.getElementById('deleteFilesModal')).hide();
|
|
1080
|
+
showToast(`Deleted ${data.data.deleted_count} file(s)`, 'success');
|
|
1081
|
+
} else {
|
|
1082
|
+
showToast(data.message || 'Failed to delete files', 'error');
|
|
1083
|
+
}
|
|
1084
|
+
} catch (error) {
|
|
1085
|
+
showToast('Failed to delete files', 'error');
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// Confirm and delete conversation
|
|
1090
|
+
async function confirmDeleteConversation() {
|
|
1091
|
+
if (!confirm('Are you sure you want to delete this conversation? This cannot be undone.')) return;
|
|
1092
|
+
|
|
1093
|
+
try {
|
|
1094
|
+
const response = await fetch(`/api/conversations/${conversationId}`, {
|
|
1095
|
+
method: 'DELETE'
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
const data = await response.json();
|
|
1099
|
+
|
|
1100
|
+
if (data.status === 'success') {
|
|
1101
|
+
showToast('Conversation deleted', 'success');
|
|
1102
|
+
// Redirect to main menu after a short delay
|
|
1103
|
+
setTimeout(() => {
|
|
1104
|
+
window.location.href = '/';
|
|
1105
|
+
}, 1000);
|
|
1106
|
+
} else {
|
|
1107
|
+
showToast(data.message || 'Failed to delete conversation', 'error');
|
|
1108
|
+
}
|
|
1109
|
+
} catch (error) {
|
|
1110
|
+
showToast('Failed to delete conversation', 'error');
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
813
1113
|
</script>
|
|
814
1114
|
{% endblock %}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dtSpark
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0a2
|
|
4
4
|
Summary: Secure Personal AI Research Kit - Multi-provider LLM CLI/Web interface with MCP tool integration
|
|
5
5
|
Home-page: https://github.com/digital-thought/dtSpark
|
|
6
6
|
Author: Matthew Westwood-Hill
|
|
@@ -4,7 +4,7 @@ dtSpark/_full_name.txt,sha256=wsMYXtT12WMrY9gT1JHiKdE4k7H59psECS6cSD07giQ,31
|
|
|
4
4
|
dtSpark/_licence.txt,sha256=Mvt5wkOkst8VGlk48vwN3CgHwMHLfmplKSPOUEbTfOw,1071
|
|
5
5
|
dtSpark/_metadata.yaml,sha256=h3PQd2QsY5yUBzS2b6EueTwkmd57svsbAKcwDVVEfIo,188
|
|
6
6
|
dtSpark/_name.txt,sha256=kDZC5_a3iMKIPOUvtLXl0C9N5DiOfgUCsecwTUnkJhs,7
|
|
7
|
-
dtSpark/_version.txt,sha256=
|
|
7
|
+
dtSpark/_version.txt,sha256=YOI1DhuRviyX3bEqj_oAF6ONeo-n8cE_rLAyvB-audY,7
|
|
8
8
|
dtSpark/cli_interface.py,sha256=aB-rFAh6o8GOwwecx-rSHEj3W4bmQHp51OPEBtC2CEw,96911
|
|
9
9
|
dtSpark/conversation_manager.py,sha256=e1aTJpUjxV96G9xrssJl1ujPJMZZwtfufQ8ZDb1WyAI,132270
|
|
10
10
|
dtSpark/launch.py,sha256=j8XYpmDswWUIzhpMc32E0Fhbhk_qhOQUAwJSpqh37f0,892
|
|
@@ -14,7 +14,7 @@ dtSpark/aws/bedrock.py,sha256=j1OknN76LehIMRmoqzx8G3DUSqk2IsO3Fiy5AxQp7Fw,24250
|
|
|
14
14
|
dtSpark/aws/costs.py,sha256=eyjigH_Yef7g5cKqhR2QUx_7y4E6-NsLd9-WL52I2vM,11931
|
|
15
15
|
dtSpark/aws/pricing.py,sha256=pk85e--C5Iz0Y-6iWv_VfENAnrcWQ_9XK0fJQSlk5Xk,23388
|
|
16
16
|
dtSpark/core/__init__.py,sha256=kORX-9C7L91WxMTMUHXr3Yc09rb6oJNzEH5_KDRdgqM,477
|
|
17
|
-
dtSpark/core/application.py,sha256=
|
|
17
|
+
dtSpark/core/application.py,sha256=RSTylove4hTRwq9t6Zpzl_ljxuGoT_9UDgSKpkzwzUY,169755
|
|
18
18
|
dtSpark/core/context_compaction.py,sha256=FWN352EW8n-eWv0MzbOiWIiKcooRxeIAb7611eN-kdY,31898
|
|
19
19
|
dtSpark/daemon/__init__.py,sha256=xdKEyMsNXgIv6nNerpDcwf94c80n-BFoJFaucWxVF64,3300
|
|
20
20
|
dtSpark/daemon/__main__.py,sha256=sRNG4RJ-Ejvd-f7OAU5slPOtxVpCoC2OfazdTJro69Q,170
|
|
@@ -61,7 +61,7 @@ dtSpark/scheduler/execution_queue.py,sha256=7_yXnGxO-arZTl0qPbyE-kDhZDVXQKT2z9zI
|
|
|
61
61
|
dtSpark/scheduler/executor.py,sha256=n42MGNyZOoQohpYh1QFenZzFsv560S0h-o_-7FajkJc,43709
|
|
62
62
|
dtSpark/scheduler/manager.py,sha256=vwtmQadqf3cv1QzdZJzFqC0l3bBzIplGZ79BUjfsh4o,12846
|
|
63
63
|
dtSpark/tools/__init__.py,sha256=TPK-c8CmXheEkoiFzL9PMP8ve0hTpw9iIV2xlGLTsMc,147
|
|
64
|
-
dtSpark/tools/builtin.py,sha256=
|
|
64
|
+
dtSpark/tools/builtin.py,sha256=F2QxhLc7i4nloyAohNnzbX6ry4QLPMGAc5nSew2F-_A,88683
|
|
65
65
|
dtSpark/web/__init__.py,sha256=5OrzA2yIR9NBf9mlTPnrQ0afMJTBuEgnzxq4QmIYe54,428
|
|
66
66
|
dtSpark/web/auth.py,sha256=uSIHwJOiklkjZSpLcznwOL1pVQktKeWifZygt068M9Y,4384
|
|
67
67
|
dtSpark/web/dependencies.py,sha256=ku54-Vb8wYvGVQ8Kluzxj8zm2Re3wDgU5LzFJ0NVIsI,874
|
|
@@ -71,7 +71,7 @@ dtSpark/web/ssl_utils.py,sha256=keDhLzEbc9REUGSdMls0vxBV3yPunR490u4j6EvB8dg,6624
|
|
|
71
71
|
dtSpark/web/web_interface.py,sha256=Jl50HxTatj6yzKFBvOxp51jlhNe3CqOqnBjT1uL8HEw,4713
|
|
72
72
|
dtSpark/web/endpoints/__init__.py,sha256=uOTiOKx6IDZ2kc0-2GS2hqZD2qX6KtwAhMMZQbS7pWc,356
|
|
73
73
|
dtSpark/web/endpoints/autonomous_actions.py,sha256=tHJtFvJJtpr34LujQu-udZvT4vY5TpJB3Sn0WPJ8giM,36588
|
|
74
|
-
dtSpark/web/endpoints/chat.py,sha256=
|
|
74
|
+
dtSpark/web/endpoints/chat.py,sha256=Xb8FOsmHvw2S3ju1wpET1XOAOR0P5pC1qGoYMhNG0xg,25560
|
|
75
75
|
dtSpark/web/endpoints/conversations.py,sha256=uFNpd_9DF1jqTEKFd9qRWK-uffXyTwz4Px9JIur_128,11728
|
|
76
76
|
dtSpark/web/endpoints/main_menu.py,sha256=54KYz86-jj9KXeBLbqEq-OOuxg2Cs4Hbnd_Z48MdB9M,20363
|
|
77
77
|
dtSpark/web/endpoints/streaming.py,sha256=vhpLefhc1PUDlmZ3Lx4IXizZfHy45zPgv5POhb-TdFU,14581
|
|
@@ -82,15 +82,15 @@ dtSpark/web/static/js/main.js,sha256=A5m_VDyEzMcfCL9nnId2oCtFecvWRlpedzzPbt34QGY
|
|
|
82
82
|
dtSpark/web/static/js/sse-client.js,sha256=-YyRXNzSoQWrNLDvT8xGXtbgll_1JiP8p398OtQU7H8,8438
|
|
83
83
|
dtSpark/web/templates/actions.html,sha256=KHJ552XjwPCCOrz0WUkehRzNwuBkS7qVpsAoCx1lGZI,18852
|
|
84
84
|
dtSpark/web/templates/base.html,sha256=ne7kY8mnT5GpGDcxokQhNsn6H4tVksBE9BsXcJAsdmI,3765
|
|
85
|
-
dtSpark/web/templates/chat.html,sha256=
|
|
85
|
+
dtSpark/web/templates/chat.html,sha256=wMDna_73e9kulTR8OKzg-uSotkp9EXP4zIJh0bDe9o0,45073
|
|
86
86
|
dtSpark/web/templates/conversations.html,sha256=URJX3W5-MjUjuUAXixziY1yFG-T6Ho5_u9Up7FcH70E,13004
|
|
87
87
|
dtSpark/web/templates/goodbye.html,sha256=4VnUgq6gHCMeJuty51qq8SZuTtpEMMUOsgTBut37Mc8,2825
|
|
88
88
|
dtSpark/web/templates/login.html,sha256=OkLf_uzMYhl_o9ER1RQZc8ch6-bCaiOokEhKbeEl8E8,3274
|
|
89
89
|
dtSpark/web/templates/main_menu.html,sha256=_tK5QBLzSmJY_j2qHFHk0c1fz9YyRr3i4ZLIhTzNEUU,39246
|
|
90
90
|
dtSpark/web/templates/new_conversation.html,sha256=hoiQ4Ew3lpFWHTsYza0JiBqqWJs7St7jTusKDnxRR8w,6844
|
|
91
|
-
dtspark-1.
|
|
92
|
-
dtspark-1.
|
|
93
|
-
dtspark-1.
|
|
94
|
-
dtspark-1.
|
|
95
|
-
dtspark-1.
|
|
96
|
-
dtspark-1.
|
|
91
|
+
dtspark-1.1.0a2.dist-info/licenses/LICENSE,sha256=jIuWlmbirwQabTwxfzE7SXT1LpCG-y_GRQ3VnoRTKgo,1101
|
|
92
|
+
dtspark-1.1.0a2.dist-info/METADATA,sha256=mT2_qXrZzv_gtKJ-PzLramjjerWfv7ikfJ0a1wwP50k,6262
|
|
93
|
+
dtspark-1.1.0a2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
94
|
+
dtspark-1.1.0a2.dist-info/entry_points.txt,sha256=IpIwa_a6XY8Z2w7DtgYAhpFHHEbha-zhLkyttWd3zpw,76
|
|
95
|
+
dtspark-1.1.0a2.dist-info/top_level.txt,sha256=x-6lMA6vNuxyDNJGNOKI4dyy7L0kOb9V98I5z46bJVY,8
|
|
96
|
+
dtspark-1.1.0a2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|