local-deep-research 0.2.3__py3-none-any.whl → 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. local_deep_research/__init__.py +1 -1
  2. local_deep_research/__version__.py +1 -0
  3. local_deep_research/advanced_search_system/filters/cross_engine_filter.py +5 -1
  4. local_deep_research/advanced_search_system/strategies/base_strategy.py +5 -2
  5. local_deep_research/advanced_search_system/strategies/iterdrag_strategy.py +23 -16
  6. local_deep_research/advanced_search_system/strategies/parallel_search_strategy.py +13 -6
  7. local_deep_research/advanced_search_system/strategies/rapid_search_strategy.py +4 -3
  8. local_deep_research/advanced_search_system/strategies/source_based_strategy.py +57 -62
  9. local_deep_research/advanced_search_system/strategies/standard_strategy.py +8 -4
  10. local_deep_research/api/research_functions.py +0 -46
  11. local_deep_research/citation_handler.py +2 -5
  12. local_deep_research/config/llm_config.py +25 -68
  13. local_deep_research/config/search_config.py +8 -21
  14. local_deep_research/defaults/default_settings.json +3996 -0
  15. local_deep_research/search_system.py +34 -31
  16. local_deep_research/utilities/db_utils.py +22 -3
  17. local_deep_research/utilities/search_utilities.py +10 -7
  18. local_deep_research/web/app.py +3 -23
  19. local_deep_research/web/app_factory.py +1 -25
  20. local_deep_research/web/database/migrations.py +20 -418
  21. local_deep_research/web/routes/settings_routes.py +75 -364
  22. local_deep_research/web/services/research_service.py +43 -43
  23. local_deep_research/web/services/settings_manager.py +108 -315
  24. local_deep_research/web/services/settings_service.py +3 -56
  25. local_deep_research/web/static/js/components/research.js +1 -1
  26. local_deep_research/web/static/js/components/settings.js +16 -4
  27. local_deep_research/web/static/js/research_form.js +106 -0
  28. local_deep_research/web/templates/pages/research.html +3 -2
  29. local_deep_research/web_search_engines/engines/meta_search_engine.py +56 -21
  30. local_deep_research/web_search_engines/engines/search_engine_local.py +11 -2
  31. local_deep_research/web_search_engines/engines/search_engine_local_all.py +7 -11
  32. local_deep_research/web_search_engines/search_engine_factory.py +12 -64
  33. local_deep_research/web_search_engines/search_engines_config.py +123 -64
  34. {local_deep_research-0.2.3.dist-info → local_deep_research-0.3.1.dist-info}/METADATA +16 -1
  35. {local_deep_research-0.2.3.dist-info → local_deep_research-0.3.1.dist-info}/RECORD +38 -39
  36. local_deep_research/config/config_files.py +0 -245
  37. local_deep_research/defaults/local_collections.toml +0 -53
  38. local_deep_research/defaults/main.toml +0 -80
  39. local_deep_research/defaults/search_engines.toml +0 -291
  40. {local_deep_research-0.2.3.dist-info → local_deep_research-0.3.1.dist-info}/WHEEL +0 -0
  41. {local_deep_research-0.2.3.dist-info → local_deep_research-0.3.1.dist-info}/entry_points.txt +0 -0
  42. {local_deep_research-0.2.3.dist-info → local_deep_research-0.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,15 +1,14 @@
1
+ import importlib.resources as pkg_resources
2
+ import json
1
3
  import logging
2
4
  import os
3
- from pathlib import Path
4
5
  from typing import Any, Dict, Optional, Union
5
6
 
6
- import toml
7
7
  from sqlalchemy import func
8
8
  from sqlalchemy.exc import SQLAlchemyError
9
9
  from sqlalchemy.orm import Session
10
10
 
11
- from ...config.config_files import get_config_dir
12
- from ...config.config_files import settings as dynaconf_settings
11
+ from ... import defaults
13
12
  from ..database.models import Setting, SettingType
14
13
  from ..models.settings import (
15
14
  AppSetting,
@@ -23,6 +22,25 @@ from ..models.settings import (
23
22
  logger = logging.getLogger(__name__)
24
23
 
25
24
 
25
+ def check_env_setting(key: str) -> str | None:
26
+ """
27
+ Checks environment variables for a particular setting.
28
+
29
+ Args:
30
+ key: The database key for the setting.
31
+
32
+ Returns:
33
+ The setting from the environment variables, or None if the variable
34
+ is not set.
35
+
36
+ """
37
+ env_variable_name = f"LDR_{'_'.join(key.split('.')).upper()}"
38
+ env_value = os.getenv(env_variable_name)
39
+ if env_value is not None:
40
+ logger.debug(f"Overriding {key} setting from environment variable.")
41
+ return env_value
42
+
43
+
26
44
  class SettingsManager:
27
45
  """
28
46
  Manager for handling application settings with database storage and file fallback.
@@ -37,30 +55,29 @@ class SettingsManager:
37
55
  db_session: SQLAlchemy session for database operations
38
56
  """
39
57
  self.db_session = db_session
40
- self.config_dir = get_config_dir() / "config"
41
- self.settings_file = self.config_dir / "settings.toml"
42
- self.search_engines_file = self.config_dir / "search_engines.toml"
43
- self.collections_file = self.config_dir / "local_collections.toml"
44
- self.secrets_file = self.config_dir / ".secrets.toml"
45
58
  self.db_first = True # Always prioritize DB settings
46
59
 
47
- # In-memory cache for settings
48
- self._settings_cache: Dict[str, Any] = {}
60
+ # Load default settings.
61
+ default_settings = pkg_resources.read_text(defaults, "default_settings.json")
62
+ self.default_settings = json.loads(default_settings)
49
63
 
50
- def get_setting(self, key: str, default: Any = None) -> Any:
64
+ def get_setting(self, key: str, default: Any = None, check_env: bool = True) -> Any:
51
65
  """
52
66
  Get a setting value
53
67
 
54
68
  Args:
55
69
  key: Setting key
56
70
  default: Default value if setting is not found
71
+ check_env: If true, it will check the environment variable for
72
+ this setting before reading from the DB.
57
73
 
58
74
  Returns:
59
75
  Setting value or default if not found
60
76
  """
61
- # Check in-memory cache first (highest priority)
62
- if key in self._settings_cache:
63
- return self._settings_cache[key]
77
+ if check_env:
78
+ env_value = check_env_setting(key)
79
+ if env_value is not None:
80
+ return env_value
64
81
 
65
82
  # If using database first approach and session available, check database
66
83
  if self.db_first and self.db_session:
@@ -73,7 +90,6 @@ class SettingsManager:
73
90
  if len(settings) == 1:
74
91
  # This is a bottom-level key.
75
92
  value = settings[0].value
76
- self._settings_cache[key] = value
77
93
  return value
78
94
  elif len(settings) > 1:
79
95
  # This is a higher-level key.
@@ -86,22 +102,6 @@ class SettingsManager:
86
102
  except SQLAlchemyError as e:
87
103
  logger.error(f"Error retrieving setting {key} from database: {e}")
88
104
 
89
- # Fall back to Dynaconf settings
90
- try:
91
- # Split the key into sections
92
- parts = key.split(".")
93
- if len(parts) == 2:
94
- section, setting = parts
95
- if hasattr(dynaconf_settings, section) and hasattr(
96
- getattr(dynaconf_settings, section), setting
97
- ):
98
- value = getattr(getattr(dynaconf_settings, section), setting)
99
- # Update cache and return
100
- self._settings_cache[key] = value
101
- return value
102
- except Exception as e:
103
- logger.debug(f"Error retrieving setting {key} from Dynaconf: {e}")
104
-
105
105
  # Return default if not found
106
106
  return default
107
107
 
@@ -117,9 +117,6 @@ class SettingsManager:
117
117
  Returns:
118
118
  True if successful, False otherwise
119
119
  """
120
- # Always update cache
121
- self._settings_cache[key] = value
122
-
123
120
  # Always update database if available
124
121
  if self.db_session:
125
122
  try:
@@ -172,29 +169,36 @@ class SettingsManager:
172
169
  """
173
170
  result = {}
174
171
 
175
- # Start with memory cache (highest priority)
176
- result.update(self._settings_cache)
177
-
178
172
  # Add database settings if available
179
173
  if self.db_session:
180
174
  try:
181
175
  for setting in self.db_session.query(Setting).all():
182
- result[setting.key] = setting.value
176
+ result[setting.key] = dict(
177
+ value=setting.value,
178
+ type=setting.type.name,
179
+ name=setting.name,
180
+ description=setting.description,
181
+ category=setting.category,
182
+ ui_element=setting.ui_element,
183
+ options=setting.options,
184
+ min_value=setting.min_value,
185
+ max_value=setting.max_value,
186
+ step=setting.step,
187
+ visible=setting.visible,
188
+ editable=setting.editable,
189
+ )
190
+
191
+ # Override from the environment variables if needed.
192
+ env_value = check_env_setting(setting.key)
193
+ if env_value is not None:
194
+ result[setting.key]["value"] = env_value
195
+ # Mark it as non-editable, because changes to the DB
196
+ # value have no effect as long as the environment
197
+ # variable is set.
198
+ result[setting.key]["editable"] = False
183
199
  except SQLAlchemyError as e:
184
200
  logger.error(f"Error retrieving all settings from database: {e}")
185
201
 
186
- # Fill in missing values from Dynaconf (lowest priority)
187
- for section in ["llm", "search", "report", "app", "web"]:
188
- if hasattr(dynaconf_settings, section):
189
- section_obj = getattr(dynaconf_settings, section)
190
- for key in dir(section_obj):
191
- if not key.startswith("_") and not callable(
192
- getattr(section_obj, key)
193
- ):
194
- full_key = f"{section}.{key}"
195
- if full_key not in result:
196
- result[full_key] = getattr(section_obj, key)
197
-
198
202
  return result
199
203
 
200
204
  def create_or_update_setting(
@@ -278,9 +282,6 @@ class SettingsManager:
278
282
  )
279
283
  self.db_session.add(db_setting)
280
284
 
281
- # Update cache
282
- self._settings_cache[setting_obj.key] = setting_obj.value
283
-
284
285
  if commit:
285
286
  self.db_session.commit()
286
287
 
@@ -307,10 +308,6 @@ class SettingsManager:
307
308
  return False
308
309
 
309
310
  try:
310
- # Remove from cache
311
- if key in self._settings_cache:
312
- del self._settings_cache[key]
313
-
314
311
  # Remove from database
315
312
  result = self.db_session.query(Setting).filter(Setting.key == key).delete()
316
313
 
@@ -323,181 +320,32 @@ class SettingsManager:
323
320
  self.db_session.rollback()
324
321
  return False
325
322
 
326
- def export_to_file(self, setting_type: Optional[SettingType] = None) -> bool:
323
+ def load_from_defaults_file(self, commit: bool = True, **kwargs: Any) -> None:
327
324
  """
328
- Export settings to file
325
+ Import settings from the defaults settings file.
329
326
 
330
327
  Args:
331
- setting_type: Type of settings to export (or all if None)
328
+ commit: Whether to commit changes to database
329
+ **kwargs: Will be passed to `import_settings`.
332
330
 
333
- Returns:
334
- True if successful, False otherwise
335
331
  """
336
- try:
337
- # Get settings
338
- settings = self.get_all_settings()
339
-
340
- # Group by section
341
- sections = {}
342
- for key, value in settings.items():
343
- # Split key into section and name
344
- parts = key.split(".", 1)
345
- if len(parts) == 2:
346
- section, name = parts
347
- if section not in sections:
348
- sections[section] = {}
349
- sections[section][name] = value
350
-
351
- # Write to appropriate file
352
- if setting_type == SettingType.LLM:
353
- file_path = self.settings_file
354
- section_name = "llm"
355
- elif setting_type == SettingType.SEARCH:
356
- file_path = self.search_engines_file
357
- section_name = "search"
358
- elif setting_type == SettingType.REPORT:
359
- file_path = self.settings_file
360
- section_name = "report"
361
- else:
362
- # Write all sections to appropriate files
363
- for section_name, section_data in sections.items():
364
- if section_name == "search":
365
- self._write_section_to_file(
366
- self.search_engines_file, section_name, section_data
367
- )
368
- else:
369
- self._write_section_to_file(
370
- self.settings_file, section_name, section_data
371
- )
372
- return True
373
-
374
- # Write specific section
375
- if section_name in sections:
376
- return self._write_section_to_file(
377
- file_path, section_name, sections[section_name]
378
- )
379
-
380
- return False
381
-
382
- except Exception as e:
383
- logger.error(f"Error exporting settings to file: {e}")
384
- return False
332
+ self.import_settings(self.default_settings, commit=commit, **kwargs)
385
333
 
386
- def import_from_file(
387
- self, setting_type: Optional[SettingType] = None, commit: bool = True
388
- ) -> bool:
334
+ def db_version_matches_defaults(self) -> bool:
389
335
  """
390
- Import settings from file
391
-
392
- Args:
393
- setting_type: Type of settings to import (or all if None)
394
- commit: Whether to commit changes to database
395
-
396
336
  Returns:
397
- True if successful, False otherwise
398
- """
399
- try:
400
- # Determine file path
401
- if (
402
- setting_type == SettingType.LLM
403
- or setting_type == SettingType.APP
404
- or setting_type == SettingType.REPORT
405
- ):
406
- file_path = self.settings_file
407
- elif setting_type == SettingType.SEARCH:
408
- file_path = self.search_engines_file
409
- else:
410
- # Import from all files
411
- success = True
412
- success &= self.import_from_file(SettingType.LLM, commit=False)
413
- success &= self.import_from_file(SettingType.SEARCH, commit=False)
414
- success &= self.import_from_file(SettingType.REPORT, commit=False)
415
- success &= self.import_from_file(SettingType.APP, commit=False)
416
-
417
- # Commit all changes at once
418
- if commit and self.db_session:
419
- self.db_session.commit()
420
-
421
- return success
422
-
423
- # Read from file
424
- if not os.path.exists(file_path):
425
- logger.warning(f"Settings file does not exist: {file_path}")
426
- return False
427
-
428
- # Parse TOML file
429
- with open(file_path, "r") as f:
430
- file_data = toml.load(f)
431
-
432
- # Extract section based on setting type
433
- section_name = setting_type.value.lower() if setting_type else None
434
- if section_name and section_name in file_data:
435
- section_data = file_data[section_name]
436
- else:
437
- section_data = file_data
438
-
439
- # Import settings
440
- for key, value in section_data.items():
441
- if section_name:
442
- full_key = f"{section_name}.{key}"
443
- else:
444
- # Try to determine section from key structure
445
- if "." in key:
446
- full_key = key
447
- else:
448
- # Assume it's an app setting
449
- full_key = f"app.{key}"
450
-
451
- self.set_setting(full_key, value, commit=False)
452
-
453
- # Commit if requested
454
- if commit and self.db_session:
455
- self.db_session.commit()
456
-
457
- return True
337
+ True if the version saved in the DB matches that in the default
338
+ settings file.
458
339
 
459
- except Exception as e:
460
- logger.error(f"Error importing settings from file: {e}")
461
- if self.db_session:
462
- self.db_session.rollback()
463
- return False
464
-
465
- def _write_section_to_file(
466
- self, file_path: Path, section: str, data: Dict[str, Any]
467
- ) -> bool:
468
340
  """
469
- Write a section of settings to a TOML file
470
-
471
- Args:
472
- file_path: Path to the file
473
- section: Section name
474
- data: Section data
341
+ db_version = self.get_setting("app.version")
342
+ default_version = self.default_settings["app.version"]["value"]
343
+ logger.debug(
344
+ f"App version saved in DB is {db_version}, have default "
345
+ f"settings from version {default_version}."
346
+ )
475
347
 
476
- Returns:
477
- True if successful, False otherwise
478
- """
479
- try:
480
- # Create file if it doesn't exist
481
- if not os.path.exists(file_path):
482
- file_path.parent.mkdir(parents=True, exist_ok=True)
483
- with open(file_path, "w") as f:
484
- f.write(f"[{section}]\n")
485
-
486
- # Read existing file
487
- with open(file_path, "r") as f:
488
- file_data = toml.load(f)
489
-
490
- # Update section
491
- file_data[section] = data
492
-
493
- # Write back to file
494
- with open(file_path, "w") as f:
495
- toml.dump(file_data, f)
496
-
497
- return True
498
- except Exception as e:
499
- logger.error(f"Error writing section {section} to {file_path}: {e}")
500
- return False
348
+ return db_version == default_version
501
349
 
502
350
  @classmethod
503
351
  def get_instance(cls, db_session: Optional[Session] = None) -> "SettingsManager":
@@ -518,100 +366,49 @@ class SettingsManager:
518
366
 
519
367
  return cls._instance
520
368
 
521
- def import_default_settings(
522
- self, main_settings_file, search_engines_file, collections_file
523
- ):
369
+ def import_settings(
370
+ self,
371
+ settings_data: Dict[str, Any],
372
+ commit: bool = True,
373
+ overwrite: bool = True,
374
+ delete_extra: bool = False,
375
+ ) -> None:
524
376
  """
525
- Import settings directly from default files
377
+ Import settings directly from the export format. This can be used to
378
+ re-import settings that have been exported with `get_all_settings()`.
526
379
 
527
380
  Args:
528
- main_settings_file: Path to the main settings.toml file
529
- search_engines_file: Path to the search_engines.toml file
530
- collections_file: Path to the local_collections.toml file
381
+ settings_data: The raw settings data to import.
382
+ commit: Whether to commit the DB after loading the settings.
383
+ overwrite: If true, it will overwrite the value of settings that
384
+ are already in the database.
385
+ delete_extra: If true, it will delete any settings that are in
386
+ the database but don't have a corresponding entry in
387
+ `settings_data`.
531
388
 
532
- Returns:
533
- True if successful, False otherwise
534
389
  """
535
- if not self.db_session:
536
- logger.warning(
537
- "No database session available, cannot import default settings"
538
- )
539
- return False
540
-
541
- try:
542
- # Import settings from main settings file
543
- if os.path.exists(main_settings_file):
544
- with open(main_settings_file, "r") as f:
545
- main_data = toml.load(f)
546
-
547
- # Process each section in the main settings file
548
- for section, values in main_data.items():
549
- if section in ["web", "llm", "general", "app"]:
550
- setting_type = None
551
- if section == "web" or section == "app":
552
- setting_type = SettingType.APP
553
- prefix = "app"
554
- elif section == "llm":
555
- setting_type = SettingType.LLM
556
- prefix = "llm"
557
- else: # general section
558
- # Map general settings to appropriate types
559
- prefix = None
560
- for key, value in values.items():
561
- if key in [
562
- "enable_fact_checking",
563
- "knowledge_accumulation",
564
- "knowledge_accumulation_context_limit",
565
- "output_dir",
566
- ]:
567
- self._create_setting(
568
- f"report.{key}", value, SettingType.REPORT
569
- )
570
-
571
- # Add settings with correct prefix
572
- if prefix:
573
- for key, value in values.items():
574
- self._create_setting(
575
- f"{prefix}.{key}", value, setting_type
576
- )
577
-
578
- elif section == "search":
579
- # Search settings go to search type
580
- for key, value in values.items():
581
- self._create_setting(
582
- f"search.{key}", value, SettingType.SEARCH
583
- )
584
-
585
- elif section == "report":
586
- # Report settings
587
- for key, value in values.items():
588
- self._create_setting(
589
- f"report.{key}", value, SettingType.REPORT
590
- )
591
-
592
- # Import settings from search engines file
593
- if os.path.exists(search_engines_file):
594
- with open(search_engines_file, "r") as f:
595
- search_data = toml.load(f)
596
-
597
- # Find search section in search engines file
598
- if "search" in search_data:
599
- for key, value in search_data["search"].items():
600
- # Skip complex sections that are nested
601
- if not isinstance(value, dict):
602
- self._create_setting(
603
- f"search.{key}", value, SettingType.SEARCH
604
- )
605
-
606
- # Commit changes
390
+ for key, setting_values in settings_data.items():
391
+ if not overwrite:
392
+ existing_value = self.get_setting(key)
393
+ if existing_value is not None:
394
+ # Preserve the value from this setting.
395
+ setting_values["value"] = existing_value
396
+
397
+ # Delete any existing setting so we can completely overwrite it.
398
+ self.delete_setting(key, commit=False)
399
+
400
+ setting = Setting(key=key, **setting_values)
401
+ self.db_session.add(setting)
402
+
403
+ if delete_extra:
404
+ all_settings = self.get_all_settings()
405
+ for key in all_settings:
406
+ if key not in settings_data:
407
+ logger.debug(f"Deleting extraneous setting: {key}")
408
+ self.delete_setting(key, commit=False)
409
+
410
+ if commit:
607
411
  self.db_session.commit()
608
- return True
609
-
610
- except Exception as e:
611
- logger.error(f"Error importing default settings: {e}")
612
- if self.db_session:
613
- self.db_session.rollback()
614
- return False
615
412
 
616
413
  def _create_setting(self, key, value, setting_type):
617
414
  """Create a setting with appropriate metadata"""
@@ -662,8 +459,4 @@ class SettingsManager:
662
459
  }
663
460
 
664
461
  # Create the setting in the database
665
- db_setting = self.create_or_update_setting(setting_dict, commit=False)
666
-
667
- # Also update cache
668
- if db_setting:
669
- self._settings_cache[key] = value
462
+ self.create_or_update_setting(setting_dict, commit=False)
@@ -1,8 +1,7 @@
1
1
  import logging
2
- from pathlib import Path
3
2
  from typing import Any, Dict, Optional, Union
4
3
 
5
- from ..database.models import Setting, SettingType
4
+ from ..database.models import Setting
6
5
  from .settings_manager import SettingsManager
7
6
 
8
7
  # Initialize logger
@@ -55,46 +54,18 @@ def set_setting(key: str, value: Any, commit: bool = True, db_session=None) -> b
55
54
  return manager.set_setting(key, value, commit)
56
55
 
57
56
 
58
- def get_all_settings(
59
- setting_type: Optional[SettingType] = None, db_session=None
60
- ) -> Dict[str, Any]:
57
+ def get_all_settings(db_session=None) -> Dict[str, Any]:
61
58
  """
62
59
  Get all settings, optionally filtered by type
63
60
 
64
61
  Args:
65
- setting_type: Optional filter by type
66
62
  db_session: Optional database session
67
63
 
68
64
  Returns:
69
65
  Dict[str, Any]: Dictionary of settings
70
66
  """
71
67
  manager = get_settings_manager(db_session)
72
- return manager.get_all_settings(setting_type)
73
-
74
-
75
- def get_all_settings_as_dict(db_session=None) -> Dict[str, Dict[str, Any]]:
76
- """
77
- Get all settings as a structured dictionary
78
-
79
- Args:
80
- db_session: Optional database session
81
-
82
- Returns:
83
- Dict[str, Dict[str, Any]]: Dictionary of settings grouped by type
84
- """
85
- # Get settings manager
86
- manager = get_settings_manager(db_session)
87
-
88
- # Get all settings
89
- all_settings = {}
90
-
91
- for setting_type in SettingType:
92
- type_key = setting_type.value.lower()
93
- type_settings = manager.get_all_settings(setting_type)
94
- if type_settings:
95
- all_settings[type_key] = type_settings
96
-
97
- return all_settings
68
+ return manager.get_all_settings()
98
69
 
99
70
 
100
71
  def create_or_update_setting(
@@ -147,30 +118,6 @@ def bulk_update_settings(
147
118
  return success
148
119
 
149
120
 
150
- def import_settings_from_file(
151
- main_settings_file: Union[str, Path],
152
- search_engines_file: Union[str, Path],
153
- collections_file: Union[str, Path],
154
- db_session=None,
155
- ) -> bool:
156
- """
157
- Import settings from default configuration files
158
-
159
- Args:
160
- main_settings_file: Path to the main settings file
161
- search_engines_file: Path to the search engines file
162
- collections_file: Path to the collections file
163
- db_session: Optional database session
164
-
165
- Returns:
166
- bool: True if import was successful
167
- """
168
- manager = get_settings_manager(db_session)
169
- return manager.import_default_settings(
170
- main_settings_file, search_engines_file, collections_file
171
- )
172
-
173
-
174
121
  def validate_setting(setting: Setting, value: Any) -> tuple[bool, Optional[str]]:
175
122
  """
176
123
  Validate a setting value based on its type and constraints
@@ -1043,7 +1043,7 @@
1043
1043
  // Find the provider and model settings
1044
1044
  const providerSetting = data.settings.value["provider"];
1045
1045
  const modelSetting = data.settings.value["model"];
1046
- const customEndpointUrl = data.settings.value["openai_endpoint_url"];
1046
+ const customEndpointUrl = data.settings.value["openai_endpoint.url"];
1047
1047
 
1048
1048
  // Update provider dropdown if we have a valid provider
1049
1049
  if (providerSetting && modelProviderSelect) {