This commit is contained in:
2025-12-03 15:09:30 +01:00
parent c7f949f69a
commit 58359294bc
6 changed files with 186 additions and 73 deletions

1
.gitignore vendored
View File

@@ -168,3 +168,4 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
app_database/

View File

@@ -8,5 +8,6 @@
from semeion.services.llm_service import LLMService
from semeion.services.qdrant_service import QdrantService
from semeion.services.localdb import DatabaseService, AppSettings
__all__ = ["LLMService", "QdrantService"]
__all__ = ["LLMService", "QdrantService", "DatabaseService", "AppSettings"]

View File

@@ -0,0 +1,84 @@
#
# Copyright (c) 2025, mstoeck3
# All rights reserved.
#
# This source code is licensed under the BSD-3-Clause license found in the
# LICENSE file in the root directory of this source tree.
#
import sqlite3
import os
from dotenv import dotenv_values
DB_DIR = "app_database"
DB_FILE = os.path.join(DB_DIR, "management_db.sqlite")
class DatabaseService():
def __init__(self) -> None:
print("DBSV: INITIALIZE DATABASE")
self.conn = self.connect_db()
def connect_db(self) -> sqlite3.Connection:
os.makedirs(DB_DIR, exist_ok=True)
try:
conn = sqlite3.connect(DB_FILE)
print("DBSV: DATABASE AVAILABLE")
return conn
except sqlite3.Error as e:
raise
def check_exist(self) -> bool:
if os.path.exists(DB_FILE):
print("DBSV: DATABASE EXISTS")
return True
else:
return False
class AppSettings(DatabaseService):
def __init__(self)->None:
print("DBSV:APPSETTINGS: SETTINGS AVAILABLE")
self.conn = self.connect_db()
self.create_initial_configs()
def check_exist(self):
try:
with self.conn as conn:
cur = conn.cursor()
statement = """
SELECT key,val FROM settings;
"""
cur.execute(statement)
rows = cur.fetchall()
if len(rows)==6: # expecting 6 initial settings
print("DBSV:APPSETTINGS: SETTINGS EXIST")
return True
else:
return False
except sqlite3.DatabaseError as e:
print("DBSV: DB NOT FOUND. CREATING...")
return e
def create_initial_configs(self)->None:
if self.check_exist()==True:
return
else:
print("DBSV:APPSETTINGS: CREATING INITIAL SETTINGS")
initial_config =dotenv_values()
with self.conn as conn:
cur = conn.cursor()
statement = """
CREATE TABLE IF NOT EXISTS settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key text NOT NULL,
val text NOT NULL
);
"""
cur.execute(statement)
for key,val in initial_config.items():
task = (key,val)
statement = f"""
INSERT INTO settings(key,val) VALUES(?,?);
"""
cur.execute(statement, task)
conn.commit()

View File

@@ -1,49 +0,0 @@
#
# Copyright (c) 2025, mstoeck3
# All rights reserved.
#
# This source code is licensed under the BSD-3-Clause license found in the
# LICENSE file in the root directory of this source tree.
#
class LLMQueryWorker(QThread):
query_finished = Signal(object)
def __init__(self, llm_client, prompt: str):
super().__init__()
self.llm_client = llm_client
self.prompt = prompt
def run(self):
response = self.llm_client.query(self.prompt)
self.query_finished.emit(response)
class LLMQueryService(QObject):
query_finished = Signal(object)
def __init__(self):
super().__init__()
print("SRVC:LLMQUERYSERVICE INIT")
self.llm_client = LLMInterface()
print("SRVC:LLMQUERYSERVICE TARGET")
self._worker = None
@Slot(str)
def query_llm(self, prompt: str):
self._worker = LLMQueryWorker(self.llm_client, prompt)
self._worker.query_finished.connect(self.query_finished.emit)
self._worker.finished.connect(self._worker.deleteLater)
self._worker.start()
class QDrantService(QObject):
collections_discovered = Signal(dict)
def __init__(self):
super().__init__()
self.qdrant_client = QdrantInterface()

View File

@@ -5,44 +5,53 @@
# This source code is licensed under the BSD-3-Clause license found in the
# LICENSE file in the root directory of this source tree.
#
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QLineEdit, QToolBar
from PySide6.QtGui import QAction
from PySide6.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QLabel, QPushButton, QLineEdit, QMenu, QHBoxLayout
from PySide6.QtCore import Qt, Signal
from . import styling
from semeion.services import LLMService, QdrantService
from semeion.services import LLMService, QdrantService, DatabaseService, AppSettings
class MainWindow(QWidget):
class MainWindow(QMainWindow):
main_exec_start = Signal(str)
def __init__(self):
super().__init__()
print("MAIN: INITIALIZING GUI")
self.create_ui()
self.db = DatabaseService()
self.settings = AppSettings()
print("MAIN: CREATING SERVICES")
self.create_services()
print("MAIN: INITIALIZING GUI")
self.create_ui()
self.connect_ui_elements()
def create_ui(self):
self.setWindowTitle("Semeion Interface")
self.setGeometry(100, 100, 800, 600)
self.setStyleSheet(styling.main_window_style)
central_widget = QWidget()
self.setCentralWidget(central_widget)
self.main_layout = QVBoxLayout()
self.setLayout(self.main_layout)
central_widget.setLayout(self.main_layout)
# Toolbar
self.toolbar = QToolBar()
self.main_layout.addWidget(self.toolbar)
action_CaseManager = QAction("Case", self)
action_IngestionManager = QAction("Ingest Artifacts", self)
action_ConnManager = QAction("Server Connections", self)
action_SettingsMenu = QAction("Settings", self)
action_AboutMenu = QAction("About", self)
self.toolbar.addAction(action_CaseManager)
self.toolbar.addAction(action_IngestionManager)
self.toolbar.addAction(action_ConnManager)
self.toolbar.addAction(action_SettingsMenu)
self.toolbar.addAction(action_AboutMenu)
self.create_menuBar()
self.conn_status_layout = QHBoxLayout()
conn_widget_qdrant = QLabel("Qdrant Interface available")
conn_widget_llm = QLabel("LLM Interface available")
conn_widget_embeddings = QLabel("EMB_PLACEHOLDER")
status_widgets = []
status_widgets.append(conn_widget_qdrant)
status_widgets.append(conn_widget_llm)
status_widgets.append(conn_widget_embeddings)
for w in status_widgets:
w.setStyleSheet(styling.status_widget_style)
self.conn_status_layout.addWidget(w)
self.main_layout.addLayout(self.conn_status_layout)
# spacing
self.main_layout.addStretch(1)
@@ -58,7 +67,6 @@ class MainWindow(QWidget):
self.searchInput.setFixedWidth(400)
self.searchInput.setMinimumHeight(30)
self.searchInput.setStyleSheet(styling.input_style)
self.searchInput.returnPressed.connect(self.execute_from_input)
self.main_layout.addWidget(self.searchInput, alignment=Qt.AlignmentFlag.AlignCenter)
self.main_layout.addSpacing(10)
@@ -66,10 +74,24 @@ class MainWindow(QWidget):
self.executeButton.setAttribute(Qt.WidgetAttribute.WA_Hover)
self.executeButton.setStyleSheet(styling.button_style)
self.main_layout.addWidget(self.executeButton, alignment=Qt.AlignmentFlag.AlignCenter)
self.executeButton.clicked.connect(self.execute_from_input)
self.main_layout.addStretch(1)
def create_menuBar(self):
menuBar = self.menuBar()
action_caseManager = menuBar.addAction("&Case Manager", self.open_case_manager)
action_caseManager.setStatusTip("Manage Cases")
action_ingestionManager = menuBar.addAction("&Ingestion Manager", self.open_ingestion_manager)
action_ingestionManager.setStatusTip("Ingest various Artifacts into dataset")
menuBar_settingsMenu = QMenu("&Settings", self)
action_connectionManager = menuBar_settingsMenu.addAction("&Manage Server Connections", self.open_connection_manager)
menuBar.addMenu(menuBar_settingsMenu)
menuBar_settingsMenu.setStatusTip("Application Settings")
menuBar_about = QMenu("&About", self)
action_aboutModal = menuBar_about.addAction("&About...", self.open_about_window)
menuBar.addMenu(menuBar_about)
menuBar.setStyleSheet(styling.toolbar_style)
def create_services(self):
self.llm_service = LLMService()
self.qdrant_service = QdrantService()
@@ -84,15 +106,40 @@ class MainWindow(QWidget):
# Qdrant: execute collection discovery on startup
self.qdrant_service.discover_collections()
def connect_ui_elements(self):
self.searchInput.returnPressed.connect(self.execute_from_input)
self.executeButton.clicked.connect(self.execute_from_input)
def execute_from_input(self) -> str|None:
query = self.searchInput.text().strip()
if not query:
return
self.main_exec_start.emit(query)
self.executeButton.setDisabled(True)
self.searchInput.setDisabled(True)
self.reenable_list = [self.executeButton, self.searchInput]
print("MAIN: LLM QUERY SUBMIT:", query)
def handle_llm_response(self, response):
self.enable_input(self.reenable_list)
print("MAIN: LLM RESPONSE:", response)
def handle_qdrant_response(self, response):
print("MAIN: QDRANT COLLECTIONS:", response)
print("MAIN: QDRANT COLLECTIONS:", response)
def enable_input(self, elements: list):
for e in elements:
e.setEnabled(True)
def open_case_manager(self):
print("clicked case manager")
def open_ingestion_manager(self):
print("clicked ingestion manager")
def open_connection_manager(self):
print("clicked open connection manager")
def open_about_window(self):
print("clicked open about")

View File

@@ -6,6 +6,8 @@
# LICENSE file in the root directory of this source tree.
#
## DISCLAIMER: Styles are mostly AI-generated
main_window_style = """
QWidget {
background-color: #f0f0f0;
@@ -39,8 +41,35 @@ QLineEdit {
padding: 10px 20px;
font-size: 16px;
}
QLineEdit:focus {
QLineEdit:focus {
border: 1px solid #aaa;
outline: none;
}
"""
toolbar_style = """
QMenuBar::item {
padding: 6px 12px;
border-radius: 4px;
}
QMenuBar::item:selected {
background-color: #e8e8e8;
}
QMenu {
border: 2px solid #c0c0c0;
border-radius: 6px;
padding: 6px 4px;
}
QMenu::item {
padding: 8px 24px 8px 12px;
border-radius: 4px;
margin: 2px;
}
QMenu::item:selected {
background-color: #e8e8e8;
}
"""
status_widget_style = """
"""