Skip to content

Commit 67cbb8e

Browse files
authored
Remove Beta from QC Version
1 parent 9c108f2 commit 67cbb8e

1 file changed

Lines changed: 152 additions & 153 deletions

File tree

‎src/qualcoder/app.py‎

Lines changed: 152 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -23,37 +23,36 @@
2323
"""
2424

2525
import configparser
26-
import datetime
27-
import locale as py_locale
28-
import logging
29-
import os
30-
import platform
31-
import shutil
32-
import sqlite3
33-
import sys
34-
import urllib.error as urllib_err
35-
import urllib.request
36-
import webbrowser
37-
import zipfile
38-
from copy import copy
39-
from pathlib import Path
40-
from typing import Any, Optional
41-
42-
from PyQt6 import QtCore, QtGui, QtWidgets
43-
44-
from qualcoder.ai_mcp_server import AiMcpServer
45-
from qualcoder.ai_llm import get_default_ai_models, update_ai_models
46-
from qualcoder.ai_runtime import (
47-
AI_DISABLED,
48-
AI_INITIALIZING,
49-
AI_READY,
50-
AI_UNLOADED,
51-
VECTORSTORE_DISABLED,
52-
VECTORSTORE_UNLOADED,
53-
vectorstore_required,
54-
)
55-
from qualcoder.helpers import get_default_user_directory, Message
56-
from qualcoder.speakers import speaker_coder_name
26+
import datetime
27+
import locale as py_locale
28+
import logging
29+
import os
30+
import platform
31+
from PyQt6 import QtCore, QtGui, QtWidgets
32+
import shutil
33+
import sqlite3
34+
import sys
35+
import urllib.error as urllib_err
36+
import urllib.request
37+
import webbrowser
38+
import zipfile
39+
from copy import copy
40+
from pathlib import Path
41+
from typing import Any, Optional
42+
43+
from qualcoder.ai_mcp_server import AiMcpServer
44+
from qualcoder.ai_llm import get_default_ai_models, update_ai_models
45+
from qualcoder.ai_runtime import (
46+
AI_DISABLED,
47+
AI_INITIALIZING,
48+
AI_READY,
49+
AI_UNLOADED,
50+
VECTORSTORE_DISABLED,
51+
VECTORSTORE_UNLOADED,
52+
vectorstore_required,
53+
)
54+
from qualcoder.helpers import get_default_user_directory, Message
55+
from qualcoder.speakers import speaker_coder_name
5756

5857
qc_config_folder = Path('~').expanduser() / '.qualcoder'
5958
logger = logging.getLogger(__name__)
@@ -117,7 +116,7 @@ class App(object):
117116
"""
118117

119118
def __init__(self):
120-
self.version = "QualCoder 4.0 Beta" # Must start with 'QualCoder '
119+
self.version = "QualCoder 4.0" # Must start with 'QualCoder '
121120
self.citation = f"Citation:\nCurtain C, Dröge K, Missaghieh--Poncet J, Salomón L. (2026) {self.version} [Computer software].\n"
122121
self.citation += f"Retrieved from https://github.com/ccbogel/QualCoder/releases/tag/{self.version}"
123122
self.conn = None
@@ -131,85 +130,85 @@ def __init__(self):
131130
self.confighome = str(qc_config_folder)
132131
self.configpath = str(qc_config_folder / 'config.ini')
133132
self.persist_path = str(qc_config_folder / 'recent_projects.txt')
134-
self.pending_ai_model_upgrade_offer = None
135-
self.ai_runtime_error = ""
136-
self.settings, self.ai_models = self.load_settings()
137-
self.ai_runtime_state = (
138-
AI_UNLOADED if self.settings['ai_enable'] == 'True' else AI_DISABLED
139-
)
133+
self.pending_ai_model_upgrade_offer = None
134+
self.ai_runtime_error = ""
135+
self.settings, self.ai_models = self.load_settings()
136+
self.ai_runtime_state = (
137+
AI_UNLOADED if self.settings['ai_enable'] == 'True' else AI_DISABLED
138+
)
140139
self.last_export_directory = copy(self.settings['directory'])
141-
self.ai = None
142-
# The project vectorstore is shared by AI and external MCP. Expensive
143-
# dependencies are imported only when a consumer constructs the store.
144-
self.vectorstore = None
145-
self.vectorstore_runtime_state = (
146-
VECTORSTORE_UNLOADED if vectorstore_required(self) else VECTORSTORE_DISABLED
147-
)
148-
self.vectorstore_runtime_error = ""
149-
# Sentence transformer embedding function. It is stored here so it must not be reloaded every time a project is opened.
150-
self.ai_embedding_function = None
151-
self.project_events = ProjectEventBus()
152-
self.ai_mcp_server = AiMcpServer(self)
153-
154-
def get_ai_status(self) -> str:
155-
"""Return one status covering both runtime loading and AI operation."""
156-
157-
if self.ai_runtime_state != AI_READY:
158-
return self.ai_runtime_state
159-
if self.ai is None:
160-
return AI_INITIALIZING
161-
return self.ai.get_status()
162-
163-
def _active_vectorstore(self) -> Optional[Any]:
164-
"""Return the shared vectorstore when a configured consumer needs it."""
165-
166-
if not vectorstore_required(self):
167-
return None
168-
return self.vectorstore
169-
170-
def vectorstore_import_document(
171-
self, source_id: int, source_name: str, text: Optional[str]) -> None:
172-
"""Queue one changed source document for indexing when search is active.
173-
174-
Args:
175-
source_id: Project source id.
176-
source_name: Current source name.
177-
text: Current source text.
178-
"""
179-
180-
vectorstore = self._active_vectorstore()
181-
if vectorstore is None:
182-
return
183-
try:
184-
vectorstore.import_document(source_id, source_name, text)
185-
except Exception as err:
186-
logger.warning("Could not update vectorstore document %s: %s", source_id, err)
187-
188-
def vectorstore_update(self) -> None:
189-
"""Queue a full synchronization of the active shared vectorstore."""
190-
191-
vectorstore = self._active_vectorstore()
192-
if vectorstore is None:
193-
return
194-
try:
195-
vectorstore.update_vectorstore()
196-
except Exception as err:
197-
logger.warning("Could not update vectorstore: %s", err)
198-
199-
def vectorstore_delete_document(self, source_id: int) -> None:
200-
"""Remove one source document from the active shared vectorstore.
201-
202-
Args:
203-
source_id: Project source id.
204-
"""
205-
206-
vectorstore = self._active_vectorstore()
207-
if vectorstore is None:
208-
return
209-
try:
210-
vectorstore.delete_document(source_id)
211-
except Exception as err:
212-
logger.warning("Could not remove vectorstore document %s: %s", source_id, err)
140+
self.ai = None
141+
# The project vectorstore is shared by AI and external MCP. Expensive
142+
# dependencies are imported only when a consumer constructs the store.
143+
self.vectorstore = None
144+
self.vectorstore_runtime_state = (
145+
VECTORSTORE_UNLOADED if vectorstore_required(self) else VECTORSTORE_DISABLED
146+
)
147+
self.vectorstore_runtime_error = ""
148+
# Sentence transformer embedding function. It is stored here so it must not be reloaded every time a project is opened.
149+
self.ai_embedding_function = None
150+
self.project_events = ProjectEventBus()
151+
self.ai_mcp_server = AiMcpServer(self)
152+
153+
def get_ai_status(self) -> str:
154+
"""Return one status covering both runtime loading and AI operation."""
155+
156+
if self.ai_runtime_state != AI_READY:
157+
return self.ai_runtime_state
158+
if self.ai is None:
159+
return AI_INITIALIZING
160+
return self.ai.get_status()
161+
162+
def _active_vectorstore(self) -> Optional[Any]:
163+
"""Return the shared vectorstore when a configured consumer needs it."""
164+
165+
if not vectorstore_required(self):
166+
return None
167+
return self.vectorstore
168+
169+
def vectorstore_import_document(
170+
self, source_id: int, source_name: str, text: Optional[str]) -> None:
171+
"""Queue one changed source document for indexing when search is active.
172+
173+
Args:
174+
source_id: Project source id.
175+
source_name: Current source name.
176+
text: Current source text.
177+
"""
178+
179+
vectorstore = self._active_vectorstore()
180+
if vectorstore is None:
181+
return
182+
try:
183+
vectorstore.import_document(source_id, source_name, text)
184+
except Exception as err:
185+
logger.warning("Could not update vectorstore document %s: %s", source_id, err)
186+
187+
def vectorstore_update(self) -> None:
188+
"""Queue a full synchronization of the active shared vectorstore."""
189+
190+
vectorstore = self._active_vectorstore()
191+
if vectorstore is None:
192+
return
193+
try:
194+
vectorstore.update_vectorstore()
195+
except Exception as err:
196+
logger.warning("Could not update vectorstore: %s", err)
197+
198+
def vectorstore_delete_document(self, source_id: int) -> None:
199+
"""Remove one source document from the active shared vectorstore.
200+
201+
Args:
202+
source_id: Project source id.
203+
"""
204+
205+
vectorstore = self._active_vectorstore()
206+
if vectorstore is None:
207+
return
208+
try:
209+
vectorstore.delete_document(source_id)
210+
except Exception as err:
211+
logger.warning("Could not remove vectorstore document %s: %s", source_id, err)
213212

214213
def read_previous_project_paths(self):
215214
""" Recent project paths are stored in .qualcoder/recent_projects.txt
@@ -974,10 +973,10 @@ def check_and_add_additional_settings(self, settings_data, ai_models):
974973
'stylesheet', 'backup_num',
975974
'report_text_context_characters', 'report_text_context_style',
976975
'ai_enable', 'ai_first_startup', 'ai_model_index', 'ai_chat_sidebar',
977-
'ai_permissions', 'ai_extended_logging', 'ai_model_upgrade_offers_seen',
978-
'ai_model_upgrade_offer_pending',
979-
'mcp_external_enabled', 'external_mcp_notice_acknowledged', 'mcp_external_port',
980-
'ai_chat_sidebar_width', 'ai_chat_splitter_output_bottom'
976+
'ai_permissions', 'ai_extended_logging', 'ai_model_upgrade_offers_seen',
977+
'ai_model_upgrade_offer_pending',
978+
'mcp_external_enabled', 'external_mcp_notice_acknowledged', 'mcp_external_port',
979+
'ai_chat_sidebar_width', 'ai_chat_splitter_output_bottom'
981980
]
982981
for key in keys:
983982
if key not in settings_data:
@@ -1013,16 +1012,16 @@ def check_and_add_additional_settings(self, settings_data, ai_models):
10131012
settings_data[key] = 1
10141013
if key == 'ai_extended_logging':
10151014
settings_data[key] = 'False'
1016-
if key == 'ai_model_upgrade_offers_seen':
1017-
settings_data[key] = ''
1018-
if key == 'ai_model_upgrade_offer_pending':
1019-
settings_data[key] = ''
1020-
if key == 'mcp_external_enabled':
1021-
settings_data[key] = 'False'
1022-
if key == 'external_mcp_notice_acknowledged':
1023-
settings_data[key] = 'False'
1024-
if key == 'mcp_external_port':
1025-
settings_data[key] = 47363
1015+
if key == 'ai_model_upgrade_offers_seen':
1016+
settings_data[key] = ''
1017+
if key == 'ai_model_upgrade_offer_pending':
1018+
settings_data[key] = ''
1019+
if key == 'mcp_external_enabled':
1020+
settings_data[key] = 'False'
1021+
if key == 'external_mcp_notice_acknowledged':
1022+
settings_data[key] = 'False'
1023+
if key == 'mcp_external_port':
1024+
settings_data[key] = 47363
10261025
if key == 'ai_chat_sidebar':
10271026
settings_data[key] = 'False'
10281027
if key == 'ai_chat_sidebar_width':
@@ -1036,18 +1035,18 @@ def check_and_add_additional_settings(self, settings_data, ai_models):
10361035
if key == 'dialogcodepdf_page_view':
10371036
settings_data[key] = 0
10381037

1039-
ai_permissions = settings_data.get('ai_permissions', 1)
1040-
if ai_permissions not in (0, 1, 2):
1041-
settings_data['ai_permissions'] = 1
1042-
settings_updated = True
1043-
1044-
mcp_enabled = str(settings_data.get('mcp_external_enabled', 'False')).lower() == 'true'
1045-
notice_acknowledged = str(
1046-
settings_data.get('external_mcp_notice_acknowledged', 'False')
1047-
).lower() == 'true'
1048-
if mcp_enabled and not notice_acknowledged:
1049-
settings_data['mcp_external_enabled'] = 'False'
1050-
settings_updated = True
1038+
ai_permissions = settings_data.get('ai_permissions', 1)
1039+
if ai_permissions not in (0, 1, 2):
1040+
settings_data['ai_permissions'] = 1
1041+
settings_updated = True
1042+
1043+
mcp_enabled = str(settings_data.get('mcp_external_enabled', 'False')).lower() == 'true'
1044+
notice_acknowledged = str(
1045+
settings_data.get('external_mcp_notice_acknowledged', 'False')
1046+
).lower() == 'true'
1047+
if mcp_enabled and not notice_acknowledged:
1048+
settings_data['mcp_external_enabled'] = 'False'
1049+
settings_updated = True
10511050

10521051
# Check AI models
10531052
if len(ai_models) == 0: # No models loaded, create default
@@ -1306,16 +1305,16 @@ def highlight_color(self):
13061305
return "#303f9f"
13071306
if self.settings['stylesheet'] == "purple":
13081307
return "#ca1b9a"
1309-
if self.settings['stylesheet'] == "native":
1310-
palette = QtWidgets.QApplication.instance().palette()
1311-
color_role = QtGui.QPalette.ColorRole.Highlight
1312-
if platform.system() == "Darwin":
1313-
color_role = getattr(
1314-
QtGui.QPalette.ColorRole,
1315-
"Accent",
1316-
QtGui.QPalette.ColorRole.Highlight,
1317-
)
1318-
return palette.color(color_role).name(QtGui.QColor.NameFormat.HexRgb)
1308+
if self.settings['stylesheet'] == "native":
1309+
palette = QtWidgets.QApplication.instance().palette()
1310+
color_role = QtGui.QPalette.ColorRole.Highlight
1311+
if platform.system() == "Darwin":
1312+
color_role = getattr(
1313+
QtGui.QPalette.ColorRole,
1314+
"Accent",
1315+
QtGui.QPalette.ColorRole.Highlight,
1316+
)
1317+
return palette.color(color_role).name(QtGui.QColor.NameFormat.HexRgb)
13191318
return '#f89407' # Default
13201319

13211320
def qtawesome_icon_color(self):
@@ -1453,13 +1452,13 @@ def default_settings(self):
14531452
'ai_first_startup': 'True',
14541453
'ai_model_index': -1,
14551454
'ai_permissions': 1,
1456-
'ai_extended_logging': 'False',
1457-
'ai_model_upgrade_offers_seen': '',
1458-
'ai_model_upgrade_offer_pending': '',
1459-
'mcp_external_enabled': 'False',
1460-
'external_mcp_notice_acknowledged': 'False',
1461-
'mcp_external_port': 47363,
1462-
'ai_chat_sidebar': 'False',
1455+
'ai_extended_logging': 'False',
1456+
'ai_model_upgrade_offers_seen': '',
1457+
'ai_model_upgrade_offer_pending': '',
1458+
'mcp_external_enabled': 'False',
1459+
'external_mcp_notice_acknowledged': 'False',
1460+
'mcp_external_port': 47363,
1461+
'ai_chat_sidebar': 'False',
14631462
'ai_chat_sidebar_width': 320,
14641463
'ai_chat_splitter_output_bottom': 80
14651464
}

0 commit comments

Comments
 (0)