dtSpark 1.1.0a1__tar.gz → 1.1.0a3__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.0a1 → dtspark-1.1.0a3}/PKG-INFO +1 -1
- dtspark-1.1.0a3/src/dtSpark/_version.txt +1 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/core/application.py +39 -48
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/tools/builtin.py +18 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/endpoints/chat.py +10 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/endpoints/conversations.py +46 -8
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/static/js/actions.js +2 -1
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/templates/chat.html +42 -40
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/templates/conversations.html +48 -20
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/templates/new_conversation.html +51 -20
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark.egg-info/PKG-INFO +1 -1
- dtspark-1.1.0a1/src/dtSpark/_version.txt +0 -1
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/LICENSE +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/MANIFEST.in +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/README.md +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/pyproject.toml +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/setup.cfg +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/setup.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/__init__.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/_description.txt +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/_full_name.txt +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/_licence.txt +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/_metadata.yaml +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/_name.txt +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/aws/__init__.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/aws/authentication.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/aws/bedrock.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/aws/costs.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/aws/pricing.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/cli_interface.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/conversation_manager.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/core/__init__.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/core/context_compaction.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/daemon/__init__.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/daemon/__main__.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/daemon/action_monitor.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/daemon/daemon_app.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/daemon/daemon_manager.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/daemon/execution_coordinator.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/daemon/pid_file.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/database/__init__.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/database/autonomous_actions.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/database/backends.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/database/connection.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/database/conversations.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/database/credential_prompt.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/database/files.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/database/mcp_ops.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/database/messages.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/database/schema.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/database/tool_permissions.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/database/usage.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/files/__init__.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/files/manager.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/launch.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/limits/__init__.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/limits/costs.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/limits/tokens.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/llm/__init__.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/llm/anthropic_direct.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/llm/base.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/llm/context_limits.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/llm/manager.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/llm/ollama.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/mcp_integration/__init__.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/mcp_integration/manager.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/mcp_integration/tool_selector.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/resources/config.yaml.template +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/safety/__init__.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/safety/llm_service.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/safety/patterns.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/safety/prompt_inspector.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/safety/violation_logger.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/scheduler/__init__.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/scheduler/creation_tools.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/scheduler/execution_queue.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/scheduler/executor.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/scheduler/manager.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/tools/__init__.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/__init__.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/auth.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/dependencies.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/endpoints/__init__.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/endpoints/autonomous_actions.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/endpoints/main_menu.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/endpoints/streaming.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/server.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/session.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/ssl_utils.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/static/css/dark-theme.css +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/static/js/chat.js +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/static/js/main.js +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/static/js/sse-client.js +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/templates/actions.html +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/templates/base.html +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/templates/goodbye.html +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/templates/login.html +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/templates/main_menu.html +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark/web/web_interface.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark.egg-info/SOURCES.txt +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark.egg-info/dependency_links.txt +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark.egg-info/entry_points.txt +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark.egg-info/not-zip-safe +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark.egg-info/requires.txt +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/src/dtSpark.egg-info/top_level.txt +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/tests/README.md +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/tests/debug_bulk_api.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/tests/diagnose_aws_costs.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/tests/test_builtin_tools.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/tests/test_builtin_tools_integration.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/tests/test_bulk_pricing.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/tests/test_document_archive_tools.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/tests/test_filesystem_tools.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/tests/test_mcp_server.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/tests/test_ollama_context.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/tests/test_ollama_conversation.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/tests/test_ollama_integration.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/tests/test_pricing_integration.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/tests/test_prompt_inspection.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/tests/test_status_indicator.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/tests/test_tool_selector.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/tests/test_web_auth.py +0 -0
- {dtspark-1.1.0a1 → dtspark-1.1.0a3}/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.0a3
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.1.0a3
|
|
@@ -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
|
|
|
@@ -1044,8 +1016,9 @@ class AWSBedrockCLI(AbstractApp):
|
|
|
1044
1016
|
logging.debug("Predefined conversations not enabled in config")
|
|
1045
1017
|
return
|
|
1046
1018
|
|
|
1047
|
-
# Get the mandatory model
|
|
1019
|
+
# Get the mandatory model and provider settings
|
|
1048
1020
|
mandatory_model = self._get_nested_setting('llm_providers.mandatory_model', None)
|
|
1021
|
+
mandatory_provider = self._get_nested_setting('llm_providers.mandatory_provider', None)
|
|
1049
1022
|
|
|
1050
1023
|
# Get list of predefined conversations
|
|
1051
1024
|
predefined_convs = self.settings.get('predefined_conversations.conversations', [])
|
|
@@ -1163,27 +1136,45 @@ class AWSBedrockCLI(AbstractApp):
|
|
|
1163
1136
|
if not source or not source.strip():
|
|
1164
1137
|
return source
|
|
1165
1138
|
|
|
1166
|
-
|
|
1167
|
-
try:
|
|
1168
|
-
resource_content = ResourceManager().load_resource(source)
|
|
1169
|
-
if resource_content is not None:
|
|
1170
|
-
logging.info(f"Loaded {description} via ResourceManager from: {source}")
|
|
1171
|
-
return resource_content
|
|
1172
|
-
except Exception as e:
|
|
1173
|
-
logging.debug(f"ResourceManager could not load {description} from '{source}': {e}")
|
|
1139
|
+
import os
|
|
1174
1140
|
|
|
1175
|
-
#
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1141
|
+
# Determine if source looks like a file path or resource name
|
|
1142
|
+
# (contains path separators, has a file extension, or doesn't contain spaces)
|
|
1143
|
+
looks_like_path = (
|
|
1144
|
+
os.sep in source
|
|
1145
|
+
or '/' in source
|
|
1146
|
+
or '\\' in source
|
|
1147
|
+
or (
|
|
1148
|
+
'.' in source
|
|
1149
|
+
and not source.strip().endswith('.')
|
|
1150
|
+
and ' ' not in source.strip()
|
|
1151
|
+
)
|
|
1152
|
+
)
|
|
1153
|
+
|
|
1154
|
+
if looks_like_path:
|
|
1155
|
+
# Try ResourceManager first (for package resources)
|
|
1156
|
+
try:
|
|
1157
|
+
resource_content = ResourceManager().load_resource(source)
|
|
1158
|
+
if resource_content is not None:
|
|
1159
|
+
logging.info(f"Loaded {description} via ResourceManager from: {source}")
|
|
1160
|
+
return resource_content
|
|
1161
|
+
except Exception as e:
|
|
1162
|
+
logging.debug(f"ResourceManager could not load {description} from '{source}': {e}")
|
|
1163
|
+
|
|
1164
|
+
# Try direct file path
|
|
1165
|
+
try:
|
|
1166
|
+
if os.path.isfile(source):
|
|
1167
|
+
with open(source, 'r', encoding='utf-8') as f:
|
|
1168
|
+
content = f.read()
|
|
1169
|
+
logging.info(f"Loaded {description} from file path: {source}")
|
|
1170
|
+
return content
|
|
1171
|
+
except Exception as e:
|
|
1172
|
+
logging.debug(f"Could not load {description} from file path '{source}': {e}")
|
|
1173
|
+
|
|
1174
|
+
# Path-like source but couldn't load - log warning and return as-is
|
|
1175
|
+
logging.warning(f"Could not load {description} from path '{source}', using as inline text")
|
|
1185
1176
|
|
|
1186
|
-
#
|
|
1177
|
+
# Treat as inline text
|
|
1187
1178
|
logging.debug(f"Using inline text for {description}")
|
|
1188
1179
|
return source
|
|
1189
1180
|
|
|
@@ -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 = {
|
|
@@ -448,6 +448,16 @@ async def command_change_model(
|
|
|
448
448
|
try:
|
|
449
449
|
app_instance = request.app.state.app_instance
|
|
450
450
|
|
|
451
|
+
# Check if model is locked via configuration
|
|
452
|
+
mandatory_model = getattr(app_instance, 'configured_model_id', None)
|
|
453
|
+
if mandatory_model:
|
|
454
|
+
return CommandResponse(
|
|
455
|
+
command="changemodel",
|
|
456
|
+
status="error",
|
|
457
|
+
message=f"Model changing is disabled - model is locked to '{mandatory_model}' via configuration",
|
|
458
|
+
data=None,
|
|
459
|
+
)
|
|
460
|
+
|
|
451
461
|
# Load conversation
|
|
452
462
|
app_instance.conversation_manager.load_conversation(conversation_id)
|
|
453
463
|
|
|
@@ -179,6 +179,14 @@ async def create_conversation(
|
|
|
179
179
|
database = app_instance.database
|
|
180
180
|
conversation_manager = app_instance.conversation_manager
|
|
181
181
|
|
|
182
|
+
# Enforce mandatory model if configured
|
|
183
|
+
mandatory_model = getattr(app_instance, 'configured_model_id', None)
|
|
184
|
+
mandatory_provider = getattr(app_instance, 'configured_provider', None)
|
|
185
|
+
if mandatory_model:
|
|
186
|
+
model_id = mandatory_model
|
|
187
|
+
logger.info(f"Mandatory model enforced: {model_id}"
|
|
188
|
+
f"{f' via {mandatory_provider}' if mandatory_provider else ''}")
|
|
189
|
+
|
|
182
190
|
# Create conversation in database
|
|
183
191
|
conversation_id = database.create_conversation(
|
|
184
192
|
name=name,
|
|
@@ -190,7 +198,7 @@ async def create_conversation(
|
|
|
190
198
|
conversation_manager.load_conversation(conversation_id)
|
|
191
199
|
|
|
192
200
|
# Set the model from the conversation and update service references
|
|
193
|
-
app_instance.llm_manager.set_model(model_id)
|
|
201
|
+
app_instance.llm_manager.set_model(model_id, mandatory_provider)
|
|
194
202
|
app_instance.bedrock_service = app_instance.llm_manager.get_active_service()
|
|
195
203
|
conversation_manager.update_service(app_instance.bedrock_service)
|
|
196
204
|
|
|
@@ -325,29 +333,59 @@ async def delete_conversation(
|
|
|
325
333
|
async def list_models(
|
|
326
334
|
request: Request,
|
|
327
335
|
session_id: str = Depends(get_current_session),
|
|
328
|
-
) ->
|
|
336
|
+
) -> dict:
|
|
329
337
|
"""
|
|
330
|
-
Get available models.
|
|
338
|
+
Get available models and mandatory model configuration.
|
|
331
339
|
|
|
332
340
|
Returns:
|
|
333
|
-
|
|
341
|
+
Dictionary with models list and mandatory model info
|
|
334
342
|
"""
|
|
335
343
|
try:
|
|
336
344
|
app_instance = request.app.state.app_instance
|
|
337
345
|
|
|
346
|
+
mandatory_model = getattr(app_instance, 'configured_model_id', None)
|
|
347
|
+
mandatory_provider = getattr(app_instance, 'configured_provider', None)
|
|
348
|
+
|
|
338
349
|
# Get available models from LLM manager
|
|
339
|
-
|
|
350
|
+
all_models = app_instance.llm_manager.list_all_models()
|
|
340
351
|
|
|
341
|
-
|
|
352
|
+
models = [
|
|
342
353
|
{
|
|
343
354
|
"id": model.get('id', model.get('name', 'unknown')),
|
|
344
355
|
"name": model.get('name', 'Unknown'),
|
|
345
356
|
"provider": model.get('provider', 'Unknown'),
|
|
346
|
-
"model_maker": model.get('model_maker'),
|
|
357
|
+
"model_maker": model.get('model_maker'),
|
|
347
358
|
}
|
|
348
|
-
for model in
|
|
359
|
+
for model in all_models
|
|
349
360
|
]
|
|
350
361
|
|
|
362
|
+
# If mandatory model is set, filter to matching models only
|
|
363
|
+
if mandatory_model:
|
|
364
|
+
filtered = [
|
|
365
|
+
m for m in models
|
|
366
|
+
if m['id'] == mandatory_model
|
|
367
|
+
or m['name'] == mandatory_model
|
|
368
|
+
]
|
|
369
|
+
|
|
370
|
+
# Further filter by provider if mandatory_provider is set
|
|
371
|
+
if mandatory_provider and filtered:
|
|
372
|
+
provider_filtered = [
|
|
373
|
+
m for m in filtered
|
|
374
|
+
if m['provider'] == mandatory_provider
|
|
375
|
+
]
|
|
376
|
+
if provider_filtered:
|
|
377
|
+
filtered = provider_filtered
|
|
378
|
+
|
|
379
|
+
if filtered:
|
|
380
|
+
models = filtered
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
"models": models,
|
|
384
|
+
"mandatory_model": mandatory_model,
|
|
385
|
+
"mandatory_provider": mandatory_provider,
|
|
386
|
+
"model_locked": mandatory_model is not None,
|
|
387
|
+
}
|
|
388
|
+
|
|
351
389
|
except Exception as e:
|
|
352
390
|
logger.error(f"Error listing models: {e}")
|
|
353
391
|
raise HTTPException(status_code=500, detail=str(e))
|
|
@@ -624,7 +624,8 @@ async function loadModels() {
|
|
|
624
624
|
const response = await fetch('/api/models');
|
|
625
625
|
if (!response.ok) throw new Error('Failed to load models');
|
|
626
626
|
|
|
627
|
-
|
|
627
|
+
const data = await response.json();
|
|
628
|
+
availableModels = data.models || data;
|
|
628
629
|
|
|
629
630
|
const select = document.getElementById('actionModel');
|
|
630
631
|
select.innerHTML = '<option value="">Select a model...</option>';
|
|
@@ -324,70 +324,72 @@ async function performExport() {
|
|
|
324
324
|
|
|
325
325
|
// Change model
|
|
326
326
|
async function showChangeModelModal() {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
327
|
+
// Check if model is locked first
|
|
328
|
+
try {
|
|
329
|
+
const configResponse = await fetch('/api/models');
|
|
330
|
+
const configData = await configResponse.json();
|
|
331
|
+
|
|
332
|
+
if (configData.model_locked) {
|
|
333
|
+
showToast(`Model changing is disabled - model is locked to '${configData.mandatory_model}' via configuration`, 'error');
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const models = configData.models || configData;
|
|
338
|
+
|
|
339
|
+
const modalHTML = `
|
|
340
|
+
<div class="modal fade" id="changeModelModal" tabindex="-1">
|
|
341
|
+
<div class="modal-dialog">
|
|
342
|
+
<div class="modal-content">
|
|
343
|
+
<div class="modal-header">
|
|
344
|
+
<h5 class="modal-title"><i class="bi bi-arrow-repeat"></i> Change Model</h5>
|
|
345
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
346
|
+
</div>
|
|
347
|
+
<div class="modal-body">
|
|
348
|
+
<div class="mb-3">
|
|
349
|
+
<label class="form-label">Select New Model</label>
|
|
350
|
+
<select class="form-select" id="new-model-select">
|
|
351
|
+
<option value="">Select a model...</option>
|
|
352
|
+
</select>
|
|
353
|
+
</div>
|
|
354
|
+
</div>
|
|
355
|
+
<div class="modal-footer">
|
|
356
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
357
|
+
<button type="button" class="btn btn-primary" onclick="performChangeModel()">
|
|
358
|
+
<i class="bi bi-check-circle-fill"></i> Change Model
|
|
359
|
+
</button>
|
|
341
360
|
</div>
|
|
342
|
-
</div>
|
|
343
|
-
<div class="modal-footer">
|
|
344
|
-
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
345
|
-
<button type="button" class="btn btn-primary" onclick="performChangeModel()">
|
|
346
|
-
<i class="bi bi-check-circle-fill"></i> Change Model
|
|
347
|
-
</button>
|
|
348
361
|
</div>
|
|
349
362
|
</div>
|
|
350
363
|
</div>
|
|
351
|
-
|
|
352
|
-
`;
|
|
353
|
-
|
|
354
|
-
// Remove old modal if exists
|
|
355
|
-
const oldModal = document.getElementById('changeModelModal');
|
|
356
|
-
if (oldModal) oldModal.remove();
|
|
364
|
+
`;
|
|
357
365
|
|
|
358
|
-
|
|
366
|
+
// Remove old modal if exists
|
|
367
|
+
const oldModal = document.getElementById('changeModelModal');
|
|
368
|
+
if (oldModal) oldModal.remove();
|
|
359
369
|
|
|
360
|
-
|
|
361
|
-
try {
|
|
362
|
-
const response = await fetch('/api/models');
|
|
363
|
-
const models = await response.json();
|
|
370
|
+
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
|
364
371
|
|
|
365
372
|
const select = document.getElementById('new-model-select');
|
|
366
|
-
select.innerHTML = '<option value="">Select a model...</option>';
|
|
367
|
-
|
|
368
373
|
models.forEach(model => {
|
|
369
374
|
const option = document.createElement('option');
|
|
370
375
|
option.value = model.id;
|
|
371
376
|
|
|
372
|
-
// Show service provider and optionally model maker
|
|
373
377
|
let displayText = model.name;
|
|
374
378
|
if (model.model_maker && model.provider !== model.model_maker) {
|
|
375
|
-
// Show both: "Model Name [Model Maker via Service Provider]"
|
|
376
379
|
displayText += ` [${model.model_maker} via ${model.provider}]`;
|
|
377
380
|
} else {
|
|
378
|
-
// Show service only: "Model Name [Service Provider]"
|
|
379
381
|
displayText += ` [${model.provider}]`;
|
|
380
382
|
}
|
|
381
383
|
|
|
382
384
|
option.textContent = displayText;
|
|
383
385
|
select.appendChild(option);
|
|
384
386
|
});
|
|
387
|
+
|
|
388
|
+
const modal = new bootstrap.Modal(document.getElementById('changeModelModal'));
|
|
389
|
+
modal.show();
|
|
385
390
|
} catch (error) {
|
|
386
|
-
|
|
391
|
+
showToast('Failed to load models', 'error');
|
|
387
392
|
}
|
|
388
|
-
|
|
389
|
-
const modal = new bootstrap.Modal(document.getElementById('changeModelModal'));
|
|
390
|
-
modal.show();
|
|
391
393
|
}
|
|
392
394
|
|
|
393
395
|
async function performChangeModel() {
|
|
@@ -226,28 +226,49 @@ async function showNewConversationModal() {
|
|
|
226
226
|
async function loadModels() {
|
|
227
227
|
try {
|
|
228
228
|
const response = await fetch('/api/models');
|
|
229
|
-
const
|
|
229
|
+
const data = await response.json();
|
|
230
|
+
|
|
231
|
+
const models = data.models || data;
|
|
232
|
+
const modelLocked = data.model_locked || false;
|
|
230
233
|
|
|
231
234
|
const select = document.getElementById('model-select');
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
235
|
+
|
|
236
|
+
if (modelLocked) {
|
|
237
|
+
select.innerHTML = '';
|
|
238
|
+
models.forEach(model => {
|
|
239
|
+
const option = document.createElement('option');
|
|
240
|
+
option.value = model.id;
|
|
241
|
+
let displayText = model.name;
|
|
242
|
+
if (model.provider) {
|
|
243
|
+
displayText += ` [${model.provider}]`;
|
|
244
|
+
}
|
|
245
|
+
option.textContent = displayText;
|
|
246
|
+
option.selected = true;
|
|
247
|
+
select.appendChild(option);
|
|
248
|
+
});
|
|
249
|
+
select.disabled = true;
|
|
250
|
+
|
|
251
|
+
const infoDiv = document.createElement('div');
|
|
252
|
+
infoDiv.className = 'form-text text-info';
|
|
253
|
+
infoDiv.innerHTML = '<i class="bi bi-lock-fill"></i> Model is locked via configuration';
|
|
254
|
+
select.parentNode.appendChild(infoDiv);
|
|
255
|
+
} else {
|
|
256
|
+
select.innerHTML = '<option value="">Select a model...</option>';
|
|
257
|
+
models.forEach(model => {
|
|
258
|
+
const option = document.createElement('option');
|
|
259
|
+
option.value = model.id;
|
|
260
|
+
|
|
261
|
+
let displayText = model.name;
|
|
262
|
+
if (model.model_maker && model.provider !== model.model_maker) {
|
|
263
|
+
displayText += ` [${model.model_maker} via ${model.provider}]`;
|
|
264
|
+
} else {
|
|
265
|
+
displayText += ` [${model.provider}]`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
option.textContent = displayText;
|
|
269
|
+
select.appendChild(option);
|
|
270
|
+
});
|
|
271
|
+
}
|
|
251
272
|
|
|
252
273
|
} catch (error) {
|
|
253
274
|
document.getElementById('model-select').innerHTML = `
|
|
@@ -259,6 +280,13 @@ async function loadModels() {
|
|
|
259
280
|
// Create conversation
|
|
260
281
|
async function createConversation() {
|
|
261
282
|
const form = document.getElementById('new-conversation-form');
|
|
283
|
+
|
|
284
|
+
// Re-enable disabled select so its value is included in form data
|
|
285
|
+
const modelSelect = document.getElementById('model-select');
|
|
286
|
+
if (modelSelect.disabled) {
|
|
287
|
+
modelSelect.disabled = false;
|
|
288
|
+
}
|
|
289
|
+
|
|
262
290
|
const formData = new FormData(form);
|
|
263
291
|
|
|
264
292
|
try {
|
|
@@ -92,28 +92,53 @@
|
|
|
92
92
|
async function loadModels() {
|
|
93
93
|
try {
|
|
94
94
|
const response = await fetch('/api/models');
|
|
95
|
-
const
|
|
95
|
+
const data = await response.json();
|
|
96
|
+
|
|
97
|
+
const models = data.models || data;
|
|
98
|
+
const modelLocked = data.model_locked || false;
|
|
96
99
|
|
|
97
100
|
const select = document.getElementById('model-select');
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
101
|
+
|
|
102
|
+
if (modelLocked) {
|
|
103
|
+
// Model is locked - show only the mandatory model and disable selection
|
|
104
|
+
select.innerHTML = '';
|
|
105
|
+
models.forEach(model => {
|
|
106
|
+
const option = document.createElement('option');
|
|
107
|
+
option.value = model.id;
|
|
108
|
+
|
|
109
|
+
let displayText = model.name;
|
|
110
|
+
if (model.provider) {
|
|
111
|
+
displayText += ` [${model.provider}]`;
|
|
112
|
+
}
|
|
113
|
+
option.textContent = displayText;
|
|
114
|
+
option.selected = true;
|
|
115
|
+
select.appendChild(option);
|
|
116
|
+
});
|
|
117
|
+
select.disabled = true;
|
|
118
|
+
|
|
119
|
+
// Add info message below the select
|
|
120
|
+
const infoDiv = document.createElement('div');
|
|
121
|
+
infoDiv.className = 'form-text text-info';
|
|
122
|
+
infoDiv.innerHTML = '<i class="bi bi-lock-fill"></i> Model is locked via configuration';
|
|
123
|
+
select.parentNode.appendChild(infoDiv);
|
|
124
|
+
} else {
|
|
125
|
+
select.innerHTML = '<option value="">Select a model...</option>';
|
|
126
|
+
models.forEach(model => {
|
|
127
|
+
const option = document.createElement('option');
|
|
128
|
+
option.value = model.id;
|
|
129
|
+
|
|
130
|
+
// Show service provider and optionally model maker
|
|
131
|
+
let displayText = model.name;
|
|
132
|
+
if (model.model_maker && model.provider !== model.model_maker) {
|
|
133
|
+
displayText += ` [${model.model_maker} via ${model.provider}]`;
|
|
134
|
+
} else {
|
|
135
|
+
displayText += ` [${model.provider}]`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
option.textContent = displayText;
|
|
139
|
+
select.appendChild(option);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
117
142
|
} catch (error) {
|
|
118
143
|
console.error('Error loading models:', error);
|
|
119
144
|
showToast('Failed to load models', 'error');
|
|
@@ -146,6 +171,12 @@ function showToast(message, type = 'info') {
|
|
|
146
171
|
document.getElementById('new-conversation-form').addEventListener('submit', async (e) => {
|
|
147
172
|
e.preventDefault();
|
|
148
173
|
|
|
174
|
+
// Re-enable disabled select so its value is included in form data
|
|
175
|
+
const modelSelect = document.getElementById('model-select');
|
|
176
|
+
if (modelSelect.disabled) {
|
|
177
|
+
modelSelect.disabled = false;
|
|
178
|
+
}
|
|
179
|
+
|
|
149
180
|
const formData = new FormData(e.target);
|
|
150
181
|
|
|
151
182
|
try {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dtSpark
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.0a3
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.1.0a1
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|