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. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/
app_database/

View File

@@ -8,5 +8,6 @@
from semeion.services.llm_service import LLMService from semeion.services.llm_service import LLMService
from semeion.services.qdrant_service import QdrantService 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 # This source code is licensed under the BSD-3-Clause license found in the
# LICENSE file in the root directory of this source tree. # LICENSE file in the root directory of this source tree.
# #
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QLineEdit, QToolBar from PySide6.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QLabel, QPushButton, QLineEdit, QMenu, QHBoxLayout
from PySide6.QtGui import QAction
from PySide6.QtCore import Qt, Signal from PySide6.QtCore import Qt, Signal
from . import styling 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) main_exec_start = Signal(str)
def __init__(self): def __init__(self):
super().__init__() super().__init__()
print("MAIN: INITIALIZING GUI") self.db = DatabaseService()
self.create_ui() self.settings = AppSettings()
print("MAIN: CREATING SERVICES") print("MAIN: CREATING SERVICES")
self.create_services() self.create_services()
print("MAIN: INITIALIZING GUI")
self.create_ui()
self.connect_ui_elements()
def create_ui(self): def create_ui(self):
self.setWindowTitle("Semeion Interface") self.setWindowTitle("Semeion Interface")
self.setGeometry(100, 100, 800, 600) self.setGeometry(100, 100, 800, 600)
self.setStyleSheet(styling.main_window_style) self.setStyleSheet(styling.main_window_style)
central_widget = QWidget()
self.setCentralWidget(central_widget)
self.main_layout = QVBoxLayout() self.main_layout = QVBoxLayout()
self.setLayout(self.main_layout) central_widget.setLayout(self.main_layout)
# Toolbar # Toolbar
self.toolbar = QToolBar() self.create_menuBar()
self.main_layout.addWidget(self.toolbar)
action_CaseManager = QAction("Case", self) self.conn_status_layout = QHBoxLayout()
action_IngestionManager = QAction("Ingest Artifacts", self) conn_widget_qdrant = QLabel("Qdrant Interface available")
action_ConnManager = QAction("Server Connections", self) conn_widget_llm = QLabel("LLM Interface available")
action_SettingsMenu = QAction("Settings", self) conn_widget_embeddings = QLabel("EMB_PLACEHOLDER")
action_AboutMenu = QAction("About", self) status_widgets = []
self.toolbar.addAction(action_CaseManager) status_widgets.append(conn_widget_qdrant)
self.toolbar.addAction(action_IngestionManager) status_widgets.append(conn_widget_llm)
self.toolbar.addAction(action_ConnManager) status_widgets.append(conn_widget_embeddings)
self.toolbar.addAction(action_SettingsMenu) for w in status_widgets:
self.toolbar.addAction(action_AboutMenu) w.setStyleSheet(styling.status_widget_style)
self.conn_status_layout.addWidget(w)
self.main_layout.addLayout(self.conn_status_layout)
# spacing # spacing
self.main_layout.addStretch(1) self.main_layout.addStretch(1)
@@ -58,7 +67,6 @@ class MainWindow(QWidget):
self.searchInput.setFixedWidth(400) self.searchInput.setFixedWidth(400)
self.searchInput.setMinimumHeight(30) self.searchInput.setMinimumHeight(30)
self.searchInput.setStyleSheet(styling.input_style) 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.addWidget(self.searchInput, alignment=Qt.AlignmentFlag.AlignCenter)
self.main_layout.addSpacing(10) self.main_layout.addSpacing(10)
@@ -66,10 +74,24 @@ class MainWindow(QWidget):
self.executeButton.setAttribute(Qt.WidgetAttribute.WA_Hover) self.executeButton.setAttribute(Qt.WidgetAttribute.WA_Hover)
self.executeButton.setStyleSheet(styling.button_style) self.executeButton.setStyleSheet(styling.button_style)
self.main_layout.addWidget(self.executeButton, alignment=Qt.AlignmentFlag.AlignCenter) self.main_layout.addWidget(self.executeButton, alignment=Qt.AlignmentFlag.AlignCenter)
self.executeButton.clicked.connect(self.execute_from_input)
self.main_layout.addStretch(1) 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): def create_services(self):
self.llm_service = LLMService() self.llm_service = LLMService()
self.qdrant_service = QdrantService() self.qdrant_service = QdrantService()
@@ -84,15 +106,40 @@ class MainWindow(QWidget):
# Qdrant: execute collection discovery on startup # Qdrant: execute collection discovery on startup
self.qdrant_service.discover_collections() 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: def execute_from_input(self) -> str|None:
query = self.searchInput.text().strip() query = self.searchInput.text().strip()
if not query: if not query:
return return
self.main_exec_start.emit(query) 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) print("MAIN: LLM QUERY SUBMIT:", query)
def handle_llm_response(self, response): def handle_llm_response(self, response):
self.enable_input(self.reenable_list)
print("MAIN: LLM RESPONSE:", response) print("MAIN: LLM RESPONSE:", response)
def handle_qdrant_response(self, 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. # LICENSE file in the root directory of this source tree.
# #
## DISCLAIMER: Styles are mostly AI-generated
main_window_style = """ main_window_style = """
QWidget { QWidget {
background-color: #f0f0f0; background-color: #f0f0f0;
@@ -39,8 +41,35 @@ QLineEdit {
padding: 10px 20px; padding: 10px 20px;
font-size: 16px; font-size: 16px;
} }
QLineEdit:focus { QLineEdit:focus {
border: 1px solid #aaa; border: 1px solid #aaa;
outline: none; 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 = """
""" """