dtSpark 1.1.0a6__tar.gz → 1.1.0a9__tar.gz

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.
Files changed (123) hide show
  1. {dtspark-1.1.0a6/src/dtSpark.egg-info → dtspark-1.1.0a9}/PKG-INFO +2 -2
  2. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/pyproject.toml +1 -1
  3. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/setup.py +1 -1
  4. dtspark-1.1.0a9/src/dtSpark/_version.txt +1 -0
  5. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/cli_interface.py +8 -6
  6. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/core/application.py +2 -0
  7. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/mcp_integration/tool_selector.py +3 -3
  8. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/resources/config.yaml.template +1 -0
  9. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/tools/builtin.py +11 -1
  10. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/endpoints/conversations.py +7 -0
  11. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/server.py +5 -0
  12. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/static/css/dark-theme.css +55 -3
  13. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/static/js/chat.js +118 -15
  14. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/templates/base.html +2 -0
  15. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/templates/conversations.html +4 -0
  16. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/templates/main_menu.html +2 -0
  17. {dtspark-1.1.0a6 → dtspark-1.1.0a9/src/dtSpark.egg-info}/PKG-INFO +2 -2
  18. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark.egg-info/requires.txt +1 -1
  19. dtspark-1.1.0a6/src/dtSpark/_version.txt +0 -1
  20. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/LICENSE +0 -0
  21. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/MANIFEST.in +0 -0
  22. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/README.md +0 -0
  23. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/setup.cfg +0 -0
  24. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/__init__.py +0 -0
  25. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/_description.txt +0 -0
  26. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/_full_name.txt +0 -0
  27. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/_licence.txt +0 -0
  28. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/_metadata.yaml +0 -0
  29. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/_name.txt +0 -0
  30. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/aws/__init__.py +0 -0
  31. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/aws/authentication.py +0 -0
  32. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/aws/bedrock.py +0 -0
  33. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/aws/costs.py +0 -0
  34. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/aws/pricing.py +0 -0
  35. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/conversation_manager.py +0 -0
  36. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/core/__init__.py +0 -0
  37. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/core/context_compaction.py +0 -0
  38. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/daemon/__init__.py +0 -0
  39. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/daemon/__main__.py +0 -0
  40. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/daemon/action_monitor.py +0 -0
  41. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/daemon/daemon_app.py +0 -0
  42. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/daemon/daemon_manager.py +0 -0
  43. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/daemon/execution_coordinator.py +0 -0
  44. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/daemon/pid_file.py +0 -0
  45. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/__init__.py +0 -0
  46. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/autonomous_actions.py +0 -0
  47. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/backends.py +0 -0
  48. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/connection.py +0 -0
  49. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/conversations.py +0 -0
  50. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/credential_prompt.py +0 -0
  51. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/files.py +0 -0
  52. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/mcp_ops.py +0 -0
  53. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/messages.py +0 -0
  54. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/schema.py +0 -0
  55. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/tool_permissions.py +0 -0
  56. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/usage.py +0 -0
  57. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/files/__init__.py +0 -0
  58. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/files/manager.py +0 -0
  59. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/launch.py +0 -0
  60. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/limits/__init__.py +0 -0
  61. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/limits/costs.py +0 -0
  62. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/limits/tokens.py +0 -0
  63. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/llm/__init__.py +0 -0
  64. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/llm/anthropic_direct.py +0 -0
  65. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/llm/base.py +0 -0
  66. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/llm/context_limits.py +0 -0
  67. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/llm/manager.py +0 -0
  68. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/llm/ollama.py +0 -0
  69. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/mcp_integration/__init__.py +0 -0
  70. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/mcp_integration/manager.py +0 -0
  71. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/safety/__init__.py +0 -0
  72. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/safety/llm_service.py +0 -0
  73. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/safety/patterns.py +0 -0
  74. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/safety/prompt_inspector.py +0 -0
  75. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/safety/violation_logger.py +0 -0
  76. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/scheduler/__init__.py +0 -0
  77. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/scheduler/creation_tools.py +0 -0
  78. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/scheduler/execution_queue.py +0 -0
  79. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/scheduler/executor.py +0 -0
  80. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/scheduler/manager.py +0 -0
  81. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/tools/__init__.py +0 -0
  82. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/__init__.py +0 -0
  83. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/auth.py +0 -0
  84. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/dependencies.py +0 -0
  85. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/endpoints/__init__.py +0 -0
  86. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/endpoints/autonomous_actions.py +0 -0
  87. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/endpoints/chat.py +0 -0
  88. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/endpoints/main_menu.py +0 -0
  89. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/endpoints/streaming.py +0 -0
  90. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/session.py +0 -0
  91. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/ssl_utils.py +0 -0
  92. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/static/js/actions.js +0 -0
  93. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/static/js/main.js +0 -0
  94. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/static/js/sse-client.js +0 -0
  95. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/templates/actions.html +0 -0
  96. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/templates/chat.html +0 -0
  97. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/templates/goodbye.html +0 -0
  98. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/templates/login.html +0 -0
  99. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/templates/new_conversation.html +0 -0
  100. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/web_interface.py +0 -0
  101. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark.egg-info/SOURCES.txt +0 -0
  102. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark.egg-info/dependency_links.txt +0 -0
  103. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark.egg-info/entry_points.txt +0 -0
  104. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark.egg-info/not-zip-safe +0 -0
  105. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark.egg-info/top_level.txt +0 -0
  106. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/README.md +0 -0
  107. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/debug_bulk_api.py +0 -0
  108. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/diagnose_aws_costs.py +0 -0
  109. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_builtin_tools.py +0 -0
  110. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_builtin_tools_integration.py +0 -0
  111. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_bulk_pricing.py +0 -0
  112. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_document_archive_tools.py +0 -0
  113. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_filesystem_tools.py +0 -0
  114. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_mcp_server.py +0 -0
  115. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_ollama_context.py +0 -0
  116. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_ollama_conversation.py +0 -0
  117. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_ollama_integration.py +0 -0
  118. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_pricing_integration.py +0 -0
  119. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_prompt_inspection.py +0 -0
  120. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_status_indicator.py +0 -0
  121. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_tool_selector.py +0 -0
  122. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_web_auth.py +0 -0
  123. {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_web_session.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dtSpark
3
- Version: 1.1.0a6
3
+ Version: 1.1.0a9
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
@@ -42,7 +42,7 @@ Requires-Dist: httpx>=0.24.0
42
42
  Requires-Dist: aiohttp>=3.8.0
43
43
  Requires-Dist: mcp>=0.9.0
44
44
  Requires-Dist: pyyaml>=6.0
45
- Requires-Dist: dtPyAppFramework>=4.1.2
45
+ Requires-Dist: dtPyAppFramework>=4.2.1
46
46
  Requires-Dist: tiktoken>=0.5.0
47
47
  Requires-Dist: ollama>=0.2.0
48
48
  Requires-Dist: cryptography>=41.0.0
@@ -57,7 +57,7 @@ dependencies = [
57
57
  "aiohttp>=3.8.0",
58
58
  "mcp>=0.9.0",
59
59
  "pyyaml>=6.0",
60
- "dtPyAppFramework>=4.1.2",
60
+ "dtPyAppFramework>=4.2.1",
61
61
  "tiktoken>=0.5.0",
62
62
  "ollama>=0.2.0",
63
63
  "cryptography>=41.0.0",
@@ -98,7 +98,7 @@ setup(
98
98
  # Data handling
99
99
  "pyyaml>=6.0",
100
100
  # Application framework
101
- "dtPyAppFramework>=4.1.2",
101
+ "dtPyAppFramework>=4.2.1",
102
102
  # Cryptography for SSL
103
103
  "cryptography>=41.0.0",
104
104
  # Anthropic direct API (optional but commonly used)
@@ -0,0 +1 @@
1
+ 1.1.0a9
@@ -233,6 +233,7 @@ class CLIInterface:
233
233
  self.model_changing_enabled = True # Can be disabled if model is locked via config
234
234
  self.cost_tracking_enabled = False # Can be enabled via config
235
235
  self.actions_enabled = False # Can be enabled via autonomous_actions.enabled config
236
+ self.new_conversations_allowed = True # Can be disabled via predefined_conversations.allow_new_conversations
236
237
  self._active_status_indicator = None # Track active status indicator for pause/resume
237
238
 
238
239
  def print_splash_screen(self, full_name: str, description: str, version: str): # noqa: S1172
@@ -383,12 +384,13 @@ class CLIInterface:
383
384
  choice_map[str(option_num)] = 'costs'
384
385
  option_num += 1
385
386
 
386
- # Start New Conversation
387
- menu_content.append(" ", style="")
388
- menu_content.append(str(option_num), style="cyan")
389
- menu_content.append(". Start New Conversation\n", style="")
390
- choice_map[str(option_num)] = 'new'
391
- option_num += 1
387
+ # Start New Conversation (only when new conversations are allowed)
388
+ if self.new_conversations_allowed:
389
+ menu_content.append(" ", style="")
390
+ menu_content.append(str(option_num), style="cyan")
391
+ menu_content.append(". Start New Conversation\n", style="")
392
+ choice_map[str(option_num)] = 'new'
393
+ option_num += 1
392
394
 
393
395
  # List and Select Conversation
394
396
  menu_content.append(" ", style="")
@@ -386,6 +386,7 @@ class AWSBedrockCLI(AbstractApp):
386
386
  cost_tracking_enabled = self.settings.get(_SETTING_COST_TRACKING, False)
387
387
  self.cli.cost_tracking_enabled = cost_tracking_enabled
388
388
  self.cli.actions_enabled = self._get_nested_setting('autonomous_actions.enabled', False)
389
+ self.cli.new_conversations_allowed = self._get_nested_setting('predefined_conversations.allow_new_conversations', True)
389
390
 
390
391
  progress.update(task_config, advance=100)
391
392
 
@@ -809,6 +810,7 @@ class AWSBedrockCLI(AbstractApp):
809
810
 
810
811
  # Task 7: Initialise autonomous action scheduler (if enabled)
811
812
  self.actions_enabled = self._get_nested_setting('autonomous_actions.enabled', False)
813
+ self.new_conversations_allowed = self._get_nested_setting('predefined_conversations.allow_new_conversations', True)
812
814
  task_scheduler = progress.add_task("[cyan]Initialising action scheduler...", total=100)
813
815
 
814
816
  if not self.actions_enabled:
@@ -21,7 +21,7 @@ class ToolSelector:
21
21
  'aws_infrastructure': ['ec2', 's3', 'lambda', 'cloudwatch', 'iam', 'vpc', 'rds', 'dynamodb', 'diagram'],
22
22
  'elasticsearch': ['elasticsearch', 'search', 'index', 'query', 'aggregation'],
23
23
  'ragstore': ['ragstore', 'rag', 'embedding', 'vector', 'semantic'],
24
- 'documents': ['word', 'excel', 'powerpoint', 'pdf', 'document', 'docx', 'xlsx', 'pptx', 'spreadsheet'],
24
+ 'documents': ['word', 'excel', 'powerpoint', 'pdf', 'docx', 'xlsx', 'pptx', 'spreadsheet'],
25
25
  'archives': ['archive', 'zip', 'tar', 'extract', 'compress', 'tgz'],
26
26
  }
27
27
 
@@ -38,8 +38,8 @@ class ToolSelector:
38
38
  'vpc', 'subnet', 'instance', 'bucket', 'function', 'diagram'],
39
39
  'elasticsearch': ['elasticsearch', 'search', 'query', 'index', 'log', 'aggregate'],
40
40
  'ragstore': ['ragstore', 'rag', 'embedding', 'semantic', 'vector', 'similarity'],
41
- 'documents': ['document', 'word', 'excel', 'powerpoint', 'pdf', 'docx', 'xlsx', 'pptx',
42
- 'spreadsheet', 'presentation', 'template', 'office'],
41
+ 'documents': ['word doc', 'excel', 'powerpoint', 'pdf', 'docx', 'xlsx', 'pptx',
42
+ 'spreadsheet', 'presentation', 'office'],
43
43
  'archives': ['archive', 'zip', 'tar', 'extract', 'unzip', 'compressed', 'tgz'],
44
44
  }
45
45
 
@@ -420,6 +420,7 @@ aws_comprehend:
420
420
  #
421
421
  predefined_conversations:
422
422
  enabled: false # Set to true to enable predefined conversations
423
+ allow_new_conversations: true # Set to false to restrict users to predefined/existing conversations only
423
424
 
424
425
  conversations:
425
426
  # Example 1: Code Review Assistant with inline instructions
@@ -361,7 +361,11 @@ def _get_filesystem_tools(fs_config: Dict[str, Any]) -> List[Dict[str, Any]]:
361
361
  tools.extend([
362
362
  {
363
363
  "name": "write_file",
364
- "description": f"Write content to a file within the allowed path ({allowed_path}). "
364
+ "description": f"Write text content to a file within the allowed path ({allowed_path}). "
365
+ "Use this tool for creating or updating ANY text-based file including HTML, CSS, JavaScript, "
366
+ "JSON, YAML, XML, Python, Markdown, plain text, config files, and all other non-binary formats. "
367
+ "Do NOT use the Microsoft Office tools (create_word_document, create_excel_document, "
368
+ "create_powerpoint_document) for text-based file formats. "
365
369
  "Creates the file if it doesn't exist, or overwrites if it exists. "
366
370
  "Parent directories must already exist (use create_directories first if needed).",
367
371
  "input_schema": {
@@ -1037,6 +1041,8 @@ def _get_document_tools(doc_config: Dict[str, Any]) -> List[Dict[str, Any]]:
1037
1041
  {
1038
1042
  "name": "create_word_document",
1039
1043
  "description": f"Create a Microsoft Word document (.docx) within the allowed path ({allowed_path}). "
1044
+ "ONLY use this tool when the user specifically requests a Word/.docx file. "
1045
+ "Do NOT use this for HTML, plain text, Markdown, or other text-based formats — use write_file instead. "
1040
1046
  "Supports creating from scratch with structured content, or using a template with placeholder replacement. "
1041
1047
  "When using a template, placeholders in the format {{{{placeholder_name}}}} will be replaced with provided values.",
1042
1048
  "input_schema": {
@@ -1080,6 +1086,8 @@ def _get_document_tools(doc_config: Dict[str, Any]) -> List[Dict[str, Any]]:
1080
1086
  {
1081
1087
  "name": "create_excel_document",
1082
1088
  "description": f"Create a Microsoft Excel document (.xlsx) within the allowed path ({allowed_path}). "
1089
+ "ONLY use this tool when the user specifically requests an Excel/.xlsx spreadsheet. "
1090
+ "Do NOT use this for CSV or other text-based tabular formats — use write_file instead. "
1083
1091
  "Creates spreadsheets from structured data. Supports multiple sheets.",
1084
1092
  "input_schema": {
1085
1093
  "type": "object",
@@ -1112,6 +1120,8 @@ def _get_document_tools(doc_config: Dict[str, Any]) -> List[Dict[str, Any]]:
1112
1120
  {
1113
1121
  "name": "create_powerpoint_document",
1114
1122
  "description": f"Create a Microsoft PowerPoint document (.pptx) within the allowed path ({allowed_path}). "
1123
+ "ONLY use this tool when the user specifically requests a PowerPoint/.pptx presentation. "
1124
+ "Do NOT use this for HTML presentations or other text-based formats — use write_file instead. "
1115
1125
  "Creates presentations with title and content slides. Supports templates with placeholder replacement.",
1116
1126
  "input_schema": {
1117
1127
  "type": "object",
@@ -177,6 +177,13 @@ async def create_conversation(
177
177
  ConversationDetail for the created conversation
178
178
  """
179
179
  try:
180
+ # Check if new conversations are allowed
181
+ if not getattr(request.app.state, 'new_conversations_allowed', True):
182
+ raise HTTPException(
183
+ status_code=403,
184
+ detail="Creating new conversations is disabled by configuration"
185
+ )
186
+
180
187
  app_instance = request.app.state.app_instance
181
188
  database = app_instance.database
182
189
  conversation_manager = app_instance.conversation_manager
@@ -305,6 +305,7 @@ def create_app(
305
305
 
306
306
  # Determine feature flags from app instance
307
307
  actions_enabled = getattr(app_instance, 'actions_enabled', False)
308
+ new_conversations_allowed = getattr(app_instance, 'new_conversations_allowed', True)
308
309
 
309
310
  # Read heartbeat configuration
310
311
  from dtPyAppFramework.settings import Settings as _Settings
@@ -319,6 +320,7 @@ def create_app(
319
320
  templates.env.globals['app_description'] = description()
320
321
  templates.env.globals['agent_name'] = agent_name()
321
322
  templates.env.globals['actions_enabled'] = actions_enabled
323
+ templates.env.globals['new_conversations_allowed'] = new_conversations_allowed
322
324
  templates.env.globals['heartbeat_enabled'] = heartbeat_enabled
323
325
  templates.env.globals['heartbeat_interval_ms'] = heartbeat_interval * 1000
324
326
 
@@ -332,6 +334,7 @@ def create_app(
332
334
  app.state.templates = templates
333
335
  app.state.dark_theme = dark_theme
334
336
  app.state.cost_tracking_enabled = cost_tracking_enabled
337
+ app.state.new_conversations_allowed = new_conversations_allowed
335
338
 
336
339
  # Session dependency
337
340
  async def get_session(session_id: Optional[str] = Cookie(default=None)) -> str:
@@ -538,6 +541,8 @@ def create_app(
538
541
  @app.get("/conversations/new", response_class=HTMLResponse)
539
542
  async def new_conversation_page(request: Request, session_id: str = Depends(get_session)):
540
543
  """Display new conversation creation page."""
544
+ if not new_conversations_allowed:
545
+ return RedirectResponse(url="/conversations", status_code=303)
541
546
  return templates.TemplateResponse(
542
547
  "new_conversation.html",
543
548
  {
@@ -101,19 +101,71 @@ body {
101
101
  overflow-x: auto;
102
102
  }
103
103
 
104
- /* Tool calls */
104
+ /* Tool calls - aligned left like assistant messages */
105
105
  .tool-call {
106
106
  background-color: #2a3a4a;
107
107
  padding: 0.75rem;
108
108
  margin: 0.5rem 0;
109
- border-radius: 0.25rem;
109
+ margin-right: 20%;
110
+ border-radius: 0.5rem;
110
111
  }
111
112
 
113
+ /* Tool results - aligned right like user messages */
112
114
  .tool-result {
113
115
  background-color: #3a3a2a;
114
116
  padding: 0.75rem;
115
117
  margin: 0.5rem 0;
116
- border-radius: 0.25rem;
118
+ margin-left: 20%;
119
+ border-radius: 0.5rem;
120
+ }
121
+
122
+ /* Tool message header with icons */
123
+ .tool-header {
124
+ display: flex;
125
+ justify-content: space-between;
126
+ align-items: center;
127
+ cursor: pointer;
128
+ }
129
+
130
+ .tool-header-left {
131
+ display: flex;
132
+ align-items: center;
133
+ gap: 0.5rem;
134
+ }
135
+
136
+ .tool-header-icons {
137
+ display: flex;
138
+ gap: 0.25rem;
139
+ }
140
+
141
+ .tool-header-icons button {
142
+ background: none;
143
+ border: none;
144
+ color: #aaa;
145
+ padding: 0.25rem;
146
+ cursor: pointer;
147
+ font-size: 0.9rem;
148
+ transition: color 0.2s;
149
+ }
150
+
151
+ .tool-header-icons button:hover {
152
+ color: #fff;
153
+ }
154
+
155
+ /* Tool content - collapsible */
156
+ .tool-content {
157
+ display: none;
158
+ margin-top: 0.5rem;
159
+ }
160
+
161
+ .tool-content.expanded {
162
+ display: block;
163
+ }
164
+
165
+ .tool-content pre {
166
+ margin: 0;
167
+ white-space: pre-wrap;
168
+ word-wrap: break-word;
117
169
  }
118
170
 
119
171
  /* Rollup summary */
@@ -167,19 +167,34 @@ function appendToolResults(content, timestamp = null) {
167
167
 
168
168
  if (Array.isArray(results)) {
169
169
  results.forEach((result, index) => {
170
+ const toolId = generateToolId();
170
171
  const resultDiv = document.createElement('div');
171
172
  resultDiv.className = 'tool-result';
173
+ resultDiv.id = toolId;
172
174
 
173
- const toolId = result.tool_use_id || 'unknown';
175
+ const toolUseId = result.tool_use_id || 'unknown';
174
176
  const resultContent = result.content || JSON.stringify(result);
177
+ const formattedContent = typeof resultContent === 'string' ? resultContent : JSON.stringify(resultContent, null, 2);
175
178
 
176
179
  resultDiv.innerHTML = `
177
- <div class="small">
178
- <strong><i class="bi bi-check-circle-fill"></i> Tool Result ${index + 1}:</strong>
179
- <code>${escapeHtml(toolId)}</code>
180
- ${timestamp ? `<span class="ms-2">${formatTimestamp(timestamp)}</span>` : ''}
180
+ <div class="tool-header small" onclick="toggleToolContent('${toolId}')">
181
+ <div class="tool-header-left">
182
+ <strong><i class="bi bi-check-circle-fill"></i> Tool Result ${index + 1}:</strong>
183
+ <code>${escapeHtml(toolUseId)}</code>
184
+ ${timestamp ? `<span class="ms-2">${formatTimestamp(timestamp)}</span>` : ''}
185
+ </div>
186
+ <div class="tool-header-icons">
187
+ <button class="tool-copy-btn" onclick="event.stopPropagation(); copyToolContent('${toolId}')" title="Copy to clipboard">
188
+ <i class="bi bi-clipboard"></i>
189
+ </button>
190
+ <button onclick="event.stopPropagation(); toggleToolContent('${toolId}')" title="Expand/Collapse">
191
+ <i class="bi bi-chevron-down" id="${toolId}-toggle-icon"></i>
192
+ </button>
193
+ </div>
194
+ </div>
195
+ <div class="tool-content" id="${toolId}-content">
196
+ <pre class="small mb-0">${escapeHtml(formattedContent)}</pre>
181
197
  </div>
182
- <pre class="small mb-0 mt-1">${escapeHtml(typeof resultContent === 'string' ? resultContent : JSON.stringify(resultContent, null, 2))}</pre>
183
198
  `;
184
199
 
185
200
  messagesContainer.appendChild(resultDiv);
@@ -188,11 +203,27 @@ function appendToolResults(content, timestamp = null) {
188
203
  } catch (e) {
189
204
  console.error('Error parsing tool results:', e);
190
205
  // Fall back to displaying the raw content
206
+ const toolId = generateToolId();
191
207
  const resultDiv = document.createElement('div');
192
208
  resultDiv.className = 'tool-result';
209
+ resultDiv.id = toolId;
193
210
  resultDiv.innerHTML = `
194
- <div class="small"><strong><i class="bi bi-check-circle-fill"></i> Tool Results</strong></div>
195
- <pre class="small mb-0 mt-1">${escapeHtml(content)}</pre>
211
+ <div class="tool-header small" onclick="toggleToolContent('${toolId}')">
212
+ <div class="tool-header-left">
213
+ <strong><i class="bi bi-check-circle-fill"></i> Tool Results</strong>
214
+ </div>
215
+ <div class="tool-header-icons">
216
+ <button class="tool-copy-btn" onclick="event.stopPropagation(); copyToolContent('${toolId}')" title="Copy to clipboard">
217
+ <i class="bi bi-clipboard"></i>
218
+ </button>
219
+ <button onclick="event.stopPropagation(); toggleToolContent('${toolId}')" title="Expand/Collapse">
220
+ <i class="bi bi-chevron-down" id="${toolId}-toggle-icon"></i>
221
+ </button>
222
+ </div>
223
+ </div>
224
+ <div class="tool-content" id="${toolId}-content">
225
+ <pre class="small mb-0">${escapeHtml(content)}</pre>
226
+ </div>
196
227
  `;
197
228
  messagesContainer.appendChild(resultDiv);
198
229
  }
@@ -225,6 +256,50 @@ function appendRollupSummary(content, timestamp = null) {
225
256
  scrollToBottom();
226
257
  }
227
258
 
259
+ /**
260
+ * Generate a unique ID for tool elements
261
+ * @returns {string} Unique ID
262
+ */
263
+ function generateToolId() {
264
+ return 'tool-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
265
+ }
266
+
267
+ /**
268
+ * Toggle tool content visibility
269
+ * @param {string} toolId - ID of the tool element
270
+ */
271
+ function toggleToolContent(toolId) {
272
+ const content = document.getElementById(toolId + '-content');
273
+ const icon = document.getElementById(toolId + '-toggle-icon');
274
+ if (content && icon) {
275
+ content.classList.toggle('expanded');
276
+ icon.className = content.classList.contains('expanded')
277
+ ? 'bi bi-chevron-up'
278
+ : 'bi bi-chevron-down';
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Copy tool content to clipboard
284
+ * @param {string} toolId - ID of the tool element
285
+ */
286
+ function copyToolContent(toolId) {
287
+ const content = document.getElementById(toolId + '-content');
288
+ if (content) {
289
+ const pre = content.querySelector('pre');
290
+ if (pre) {
291
+ navigator.clipboard.writeText(pre.textContent).then(() => {
292
+ const btn = document.querySelector(`#${toolId} .tool-copy-btn`);
293
+ if (btn) {
294
+ const originalIcon = btn.innerHTML;
295
+ btn.innerHTML = '<i class="bi bi-check"></i>';
296
+ setTimeout(() => { btn.innerHTML = originalIcon; }, 1500);
297
+ }
298
+ });
299
+ }
300
+ }
301
+ }
302
+
228
303
  /**
229
304
  * Append a tool call message
230
305
  * @param {string} toolName - Tool name
@@ -232,14 +307,28 @@ function appendRollupSummary(content, timestamp = null) {
232
307
  */
233
308
  function appendToolCall(toolName, toolInput) {
234
309
  const messagesContainer = document.getElementById('chat-messages');
310
+ const toolId = generateToolId();
235
311
 
236
312
  const toolDiv = document.createElement('div');
237
313
  toolDiv.className = 'tool-call';
314
+ toolDiv.id = toolId;
238
315
  toolDiv.innerHTML = `
239
- <div class="small">
240
- <strong><i class="bi bi-tools"></i> Tool Call:</strong> <code>${escapeHtml(toolName)}</code>
316
+ <div class="tool-header small" onclick="toggleToolContent('${toolId}')">
317
+ <div class="tool-header-left">
318
+ <strong><i class="bi bi-tools"></i> Tool Call:</strong> <code>${escapeHtml(toolName)}</code>
319
+ </div>
320
+ <div class="tool-header-icons">
321
+ <button class="tool-copy-btn" onclick="event.stopPropagation(); copyToolContent('${toolId}')" title="Copy to clipboard">
322
+ <i class="bi bi-clipboard"></i>
323
+ </button>
324
+ <button onclick="event.stopPropagation(); toggleToolContent('${toolId}')" title="Expand/Collapse">
325
+ <i class="bi bi-chevron-down" id="${toolId}-toggle-icon"></i>
326
+ </button>
327
+ </div>
328
+ </div>
329
+ <div class="tool-content" id="${toolId}-content">
330
+ <pre class="small mb-0">${escapeHtml(JSON.stringify(toolInput, null, 2))}</pre>
241
331
  </div>
242
- <pre class="small mb-0 mt-1">${escapeHtml(JSON.stringify(toolInput, null, 2))}</pre>
243
332
  `;
244
333
 
245
334
  messagesContainer.appendChild(toolDiv);
@@ -248,19 +337,33 @@ function appendToolCall(toolName, toolInput) {
248
337
 
249
338
  /**
250
339
  * Append a tool result message
251
- * @param {string} toolName - Tool name
340
+ * @param {string} toolName - Tool name or tool_use_id
252
341
  * @param {object} toolResult - Tool result data
253
342
  */
254
343
  function appendToolResult(toolName, toolResult) {
255
344
  const messagesContainer = document.getElementById('chat-messages');
345
+ const toolId = generateToolId();
256
346
 
257
347
  const resultDiv = document.createElement('div');
258
348
  resultDiv.className = 'tool-result';
349
+ resultDiv.id = toolId;
259
350
  resultDiv.innerHTML = `
260
- <div class="small">
261
- <strong><i class="bi bi-check-circle-fill"></i> Tool Result:</strong> <code>${escapeHtml(toolName)}</code>
351
+ <div class="tool-header small" onclick="toggleToolContent('${toolId}')">
352
+ <div class="tool-header-left">
353
+ <strong><i class="bi bi-check-circle-fill"></i> Tool Result:</strong> <code>${escapeHtml(toolName)}</code>
354
+ </div>
355
+ <div class="tool-header-icons">
356
+ <button class="tool-copy-btn" onclick="event.stopPropagation(); copyToolContent('${toolId}')" title="Copy to clipboard">
357
+ <i class="bi bi-clipboard"></i>
358
+ </button>
359
+ <button onclick="event.stopPropagation(); toggleToolContent('${toolId}')" title="Expand/Collapse">
360
+ <i class="bi bi-chevron-down" id="${toolId}-toggle-icon"></i>
361
+ </button>
362
+ </div>
363
+ </div>
364
+ <div class="tool-content" id="${toolId}-content">
365
+ <pre class="small mb-0">${escapeHtml(JSON.stringify(toolResult, null, 2))}</pre>
262
366
  </div>
263
- <pre class="small mb-0 mt-1">${escapeHtml(JSON.stringify(toolResult, null, 2))}</pre>
264
367
  `;
265
368
 
266
369
  messagesContainer.appendChild(resultDiv);
@@ -48,11 +48,13 @@
48
48
  <i class="bi bi-list-ul"></i> Conversations
49
49
  </a>
50
50
  </li>
51
+ {% if new_conversations_allowed %}
51
52
  <li class="nav-item">
52
53
  <a class="nav-link" href="/conversations/new">
53
54
  <i class="bi bi-plus-circle"></i> New
54
55
  </a>
55
56
  </li>
57
+ {% endif %}
56
58
  {% if actions_enabled %}
57
59
  <li class="nav-item">
58
60
  <a class="nav-link" href="/actions">
@@ -19,9 +19,11 @@
19
19
  <div class="col-12">
20
20
  <h2 class="mb-4">
21
21
  <i class="bi bi-list-ul"></i> Conversations
22
+ {% if new_conversations_allowed %}
22
23
  <button class="btn btn-primary float-end" onclick="showNewConversationModal()">
23
24
  <i class="bi bi-plus-circle-fill"></i> New Conversation
24
25
  </button>
26
+ {% endif %}
25
27
  </h2>
26
28
  </div>
27
29
  </div>
@@ -69,6 +71,7 @@
69
71
  </div>
70
72
  </div>
71
73
 
74
+ {% if new_conversations_allowed %}
72
75
  <!-- New Conversation Modal -->
73
76
  <div class="modal fade" id="newConversationModal" tabindex="-1">
74
77
  <div class="modal-dialog modal-lg">
@@ -141,6 +144,7 @@
141
144
  </div>
142
145
  </div>
143
146
  </div>
147
+ {% endif %}
144
148
  {% endblock %}
145
149
 
146
150
  {% block extra_scripts %}
@@ -239,9 +239,11 @@
239
239
  <div class="col-12">
240
240
  <h5 class="mb-3"><i class="bi bi-lightning-fill"></i> Quick Actions</h5>
241
241
  <div class="d-grid gap-3 d-md-flex">
242
+ {% if new_conversations_allowed %}
242
243
  <a href="/conversations/new" class="btn btn-primary quick-action-btn flex-fill">
243
244
  <i class="bi bi-plus-circle-fill"></i> Start New Conversation
244
245
  </a>
246
+ {% endif %}
245
247
  <a href="/conversations" class="btn btn-outline-primary quick-action-btn flex-fill">
246
248
  <i class="bi bi-list-ul"></i> View Conversations
247
249
  </a>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dtSpark
3
- Version: 1.1.0a6
3
+ Version: 1.1.0a9
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
@@ -42,7 +42,7 @@ Requires-Dist: httpx>=0.24.0
42
42
  Requires-Dist: aiohttp>=3.8.0
43
43
  Requires-Dist: mcp>=0.9.0
44
44
  Requires-Dist: pyyaml>=6.0
45
- Requires-Dist: dtPyAppFramework>=4.1.2
45
+ Requires-Dist: dtPyAppFramework>=4.2.1
46
46
  Requires-Dist: tiktoken>=0.5.0
47
47
  Requires-Dist: ollama>=0.2.0
48
48
  Requires-Dist: cryptography>=41.0.0
@@ -11,7 +11,7 @@ httpx>=0.24.0
11
11
  aiohttp>=3.8.0
12
12
  mcp>=0.9.0
13
13
  pyyaml>=6.0
14
- dtPyAppFramework>=4.1.2
14
+ dtPyAppFramework>=4.2.1
15
15
  tiktoken>=0.5.0
16
16
  ollama>=0.2.0
17
17
  cryptography>=41.0.0
@@ -1 +0,0 @@
1
- 1.1.0a6
File without changes
File without changes
File without changes
File without changes
File without changes