database
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -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/
|
||||||
@@ -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"]
|
||||||
84
src/semeion/services/localdb.py
Normal file
84
src/semeion/services/localdb.py
Normal 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()
|
||||||
|
|
||||||
@@ -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()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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")
|
||||||
|
|
||||||
@@ -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 = """
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Reference in New Issue
Block a user