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.
- {dtspark-1.1.0a6/src/dtSpark.egg-info → dtspark-1.1.0a9}/PKG-INFO +2 -2
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/pyproject.toml +1 -1
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/setup.py +1 -1
- dtspark-1.1.0a9/src/dtSpark/_version.txt +1 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/cli_interface.py +8 -6
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/core/application.py +2 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/mcp_integration/tool_selector.py +3 -3
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/resources/config.yaml.template +1 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/tools/builtin.py +11 -1
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/endpoints/conversations.py +7 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/server.py +5 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/static/css/dark-theme.css +55 -3
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/static/js/chat.js +118 -15
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/templates/base.html +2 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/templates/conversations.html +4 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/templates/main_menu.html +2 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9/src/dtSpark.egg-info}/PKG-INFO +2 -2
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark.egg-info/requires.txt +1 -1
- dtspark-1.1.0a6/src/dtSpark/_version.txt +0 -1
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/LICENSE +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/MANIFEST.in +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/README.md +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/setup.cfg +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/__init__.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/_description.txt +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/_full_name.txt +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/_licence.txt +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/_metadata.yaml +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/_name.txt +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/aws/__init__.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/aws/authentication.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/aws/bedrock.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/aws/costs.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/aws/pricing.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/conversation_manager.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/core/__init__.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/core/context_compaction.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/daemon/__init__.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/daemon/__main__.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/daemon/action_monitor.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/daemon/daemon_app.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/daemon/daemon_manager.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/daemon/execution_coordinator.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/daemon/pid_file.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/__init__.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/autonomous_actions.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/backends.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/connection.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/conversations.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/credential_prompt.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/files.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/mcp_ops.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/messages.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/schema.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/tool_permissions.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/database/usage.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/files/__init__.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/files/manager.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/launch.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/limits/__init__.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/limits/costs.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/limits/tokens.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/llm/__init__.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/llm/anthropic_direct.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/llm/base.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/llm/context_limits.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/llm/manager.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/llm/ollama.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/mcp_integration/__init__.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/mcp_integration/manager.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/safety/__init__.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/safety/llm_service.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/safety/patterns.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/safety/prompt_inspector.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/safety/violation_logger.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/scheduler/__init__.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/scheduler/creation_tools.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/scheduler/execution_queue.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/scheduler/executor.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/scheduler/manager.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/tools/__init__.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/__init__.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/auth.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/dependencies.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/endpoints/__init__.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/endpoints/autonomous_actions.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/endpoints/chat.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/endpoints/main_menu.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/endpoints/streaming.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/session.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/ssl_utils.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/static/js/actions.js +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/static/js/main.js +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/static/js/sse-client.js +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/templates/actions.html +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/templates/chat.html +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/templates/goodbye.html +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/templates/login.html +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/templates/new_conversation.html +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark/web/web_interface.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark.egg-info/SOURCES.txt +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark.egg-info/dependency_links.txt +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark.egg-info/entry_points.txt +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark.egg-info/not-zip-safe +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/src/dtSpark.egg-info/top_level.txt +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/README.md +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/debug_bulk_api.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/diagnose_aws_costs.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_builtin_tools.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_builtin_tools_integration.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_bulk_pricing.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_document_archive_tools.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_filesystem_tools.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_mcp_server.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_ollama_context.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_ollama_conversation.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_ollama_integration.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_pricing_integration.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_prompt_inspection.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_status_indicator.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_tool_selector.py +0 -0
- {dtspark-1.1.0a6 → dtspark-1.1.0a9}/tests/test_web_auth.py +0 -0
- {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.
|
|
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
|
|
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
|
|
@@ -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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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', '
|
|
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': ['
|
|
42
|
-
'spreadsheet', 'presentation', '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
<
|
|
179
|
-
|
|
180
|
-
|
|
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"
|
|
195
|
-
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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.
|
|
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
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.1.0a6
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|