217 lines
9.1 KiB
Python
217 lines
9.1 KiB
Python
from __future__ import annotations
|
|
from PyQt6.QtWidgets import (
|
|
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton,
|
|
QTabWidget, QWidget, QFormLayout, QGroupBox, QMessageBox, QCheckBox
|
|
)
|
|
from PyQt6.QtCore import Qt
|
|
from PyQt6.QtGui import QFont
|
|
|
|
|
|
class SettingsDialog(QDialog):
|
|
def __init__(self, config, parent=None):
|
|
super().__init__(parent)
|
|
self._config = config
|
|
self.setWindowTitle("Settings")
|
|
self.setMinimumWidth(480)
|
|
self.setMinimumHeight(440)
|
|
self._setup_ui()
|
|
self._load_values()
|
|
|
|
def _setup_ui(self):
|
|
root = QVBoxLayout(self)
|
|
root.setContentsMargins(16, 16, 16, 16)
|
|
root.setSpacing(12)
|
|
|
|
tabs = QTabWidget()
|
|
tabs.addTab(self._build_api_tab(), "API Keys")
|
|
tabs.addTab(self._build_brokers_tab(), "Brokers")
|
|
tabs.addTab(self._build_general_tab(), "General")
|
|
root.addWidget(tabs)
|
|
|
|
btn_row = QHBoxLayout()
|
|
btn_row.addStretch()
|
|
save_btn = QPushButton("Save")
|
|
save_btn.setObjectName("primary_btn")
|
|
save_btn.clicked.connect(self._save)
|
|
cancel_btn = QPushButton("Cancel")
|
|
cancel_btn.clicked.connect(self.reject)
|
|
btn_row.addWidget(cancel_btn)
|
|
btn_row.addWidget(save_btn)
|
|
root.addLayout(btn_row)
|
|
|
|
def _build_api_tab(self) -> QWidget:
|
|
tab = QWidget()
|
|
layout = QVBoxLayout(tab)
|
|
layout.setContentsMargins(8, 8, 8, 8)
|
|
layout.setSpacing(12)
|
|
|
|
# Anthropic
|
|
anthropic_box = QGroupBox("Anthropic (Claude AI)")
|
|
anthropic_form = QFormLayout(anthropic_box)
|
|
self._anthropic_key = QLineEdit()
|
|
self._anthropic_key.setEchoMode(QLineEdit.EchoMode.Password)
|
|
self._anthropic_key.setPlaceholderText("sk-ant-…")
|
|
test_anthropic = QPushButton("Test")
|
|
test_anthropic.setMaximumWidth(60)
|
|
test_anthropic.clicked.connect(self._test_anthropic)
|
|
row = QHBoxLayout()
|
|
row.addWidget(self._anthropic_key)
|
|
row.addWidget(test_anthropic)
|
|
anthropic_form.addRow("API Key:", row)
|
|
anthropic_form.addRow("", QLabel("Get key: console.anthropic.com | Pay per token"))
|
|
layout.addWidget(anthropic_box)
|
|
|
|
# NewsAPI
|
|
news_box = QGroupBox("NewsAPI")
|
|
news_form = QFormLayout(news_box)
|
|
self._news_key = QLineEdit()
|
|
self._news_key.setEchoMode(QLineEdit.EchoMode.Password)
|
|
self._news_key.setPlaceholderText("Your NewsAPI key…")
|
|
test_news = QPushButton("Test")
|
|
test_news.setMaximumWidth(60)
|
|
test_news.clicked.connect(self._test_news)
|
|
row2 = QHBoxLayout()
|
|
row2.addWidget(self._news_key)
|
|
row2.addWidget(test_news)
|
|
news_form.addRow("API Key:", row2)
|
|
news_form.addRow("", QLabel("Get key: newsapi.org | 100 req/day free"))
|
|
layout.addWidget(news_box)
|
|
layout.addStretch()
|
|
return tab
|
|
|
|
def _build_brokers_tab(self) -> QWidget:
|
|
tab = QWidget()
|
|
layout = QVBoxLayout(tab)
|
|
layout.setContentsMargins(8, 8, 8, 8)
|
|
layout.setSpacing(12)
|
|
|
|
# Schwab
|
|
schwab_box = QGroupBox("Charles Schwab")
|
|
schwab_form = QFormLayout(schwab_box)
|
|
self._schwab_key = QLineEdit()
|
|
self._schwab_key.setPlaceholderText("App Key…")
|
|
self._schwab_secret = QLineEdit()
|
|
self._schwab_secret.setEchoMode(QLineEdit.EchoMode.Password)
|
|
self._schwab_secret.setPlaceholderText("App Secret…")
|
|
test_schwab = QPushButton("Test Connection")
|
|
test_schwab.clicked.connect(self._test_schwab)
|
|
schwab_form.addRow("App Key:", self._schwab_key)
|
|
schwab_form.addRow("App Secret:", self._schwab_secret)
|
|
schwab_form.addRow("", test_schwab)
|
|
schwab_form.addRow("", QLabel("Register at: developer.schwab.com\nCallback URL: https://127.0.0.1"))
|
|
layout.addWidget(schwab_box)
|
|
|
|
# Robinhood
|
|
rh_box = QGroupBox("Robinhood")
|
|
rh_form = QFormLayout(rh_box)
|
|
self._rh_username = QLineEdit()
|
|
self._rh_username.setPlaceholderText("Email…")
|
|
self._rh_password = QLineEdit()
|
|
self._rh_password.setEchoMode(QLineEdit.EchoMode.Password)
|
|
self._rh_password.setPlaceholderText("Password…")
|
|
test_rh = QPushButton("Test Login")
|
|
test_rh.clicked.connect(self._test_robinhood)
|
|
rh_form.addRow("Username:", self._rh_username)
|
|
rh_form.addRow("Password:", self._rh_password)
|
|
rh_form.addRow("", test_rh)
|
|
layout.addWidget(rh_box)
|
|
layout.addStretch()
|
|
return tab
|
|
|
|
def _build_general_tab(self) -> QWidget:
|
|
tab = QWidget()
|
|
layout = QVBoxLayout(tab)
|
|
layout.setContentsMargins(8, 8, 8, 8)
|
|
layout.setSpacing(12)
|
|
|
|
general_box = QGroupBox("Appearance & Behavior")
|
|
general_form = QFormLayout(general_box)
|
|
self._theme_dark = QCheckBox("Dark theme")
|
|
self._theme_dark.setChecked(True)
|
|
general_form.addRow("Theme:", self._theme_dark)
|
|
layout.addWidget(general_box)
|
|
layout.addStretch()
|
|
return tab
|
|
|
|
def _load_values(self):
|
|
self._anthropic_key.setText(self._config.get_setting("anthropic_api_key") or self._config.get("ANTHROPIC_API_KEY"))
|
|
self._news_key.setText(self._config.get_setting("news_api_key") or self._config.get("NEWS_API_KEY"))
|
|
self._schwab_key.setText(self._config.get_setting("schwab_app_key") or self._config.get("SCHWAB_APP_KEY"))
|
|
self._schwab_secret.setText(self._config.get_setting("schwab_app_secret") or self._config.get("SCHWAB_APP_SECRET"))
|
|
self._rh_username.setText(self._config.get_setting("robinhood_username") or self._config.get("ROBINHOOD_USERNAME"))
|
|
self._rh_password.setText(self._config.get_setting("robinhood_password") or self._config.get("ROBINHOOD_PASSWORD"))
|
|
theme = self._config.get_setting("theme", "dark")
|
|
self._theme_dark.setChecked(theme == "dark")
|
|
|
|
def _save(self):
|
|
self._config.set_setting("anthropic_api_key", self._anthropic_key.text().strip())
|
|
self._config.set_setting("news_api_key", self._news_key.text().strip())
|
|
self._config.set_setting("schwab_app_key", self._schwab_key.text().strip())
|
|
self._config.set_setting("schwab_app_secret", self._schwab_secret.text().strip())
|
|
self._config.set_setting("robinhood_username", self._rh_username.text().strip())
|
|
self._config.set_setting("robinhood_password", self._rh_password.text().strip())
|
|
self._config.set_setting("theme", "dark" if self._theme_dark.isChecked() else "light")
|
|
QMessageBox.information(self, "Saved", "Settings saved. Restart to apply theme changes.")
|
|
self.accept()
|
|
|
|
def _test_anthropic(self):
|
|
key = self._anthropic_key.text().strip()
|
|
if not key:
|
|
QMessageBox.warning(self, "Test Failed", "Enter an API key first.")
|
|
return
|
|
try:
|
|
import anthropic
|
|
client = anthropic.Anthropic(api_key=key)
|
|
response = client.messages.create(
|
|
model="claude-haiku-4-5-20251001",
|
|
max_tokens=10,
|
|
messages=[{"role": "user", "content": "ping"}],
|
|
)
|
|
QMessageBox.information(self, "Success", "Anthropic API key is valid!")
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "Test Failed", str(e))
|
|
|
|
def _test_news(self):
|
|
key = self._news_key.text().strip()
|
|
if not key:
|
|
QMessageBox.warning(self, "Test Failed", "Enter an API key first.")
|
|
return
|
|
try:
|
|
from newsapi import NewsApiClient
|
|
client = NewsApiClient(api_key=key)
|
|
result = client.get_top_headlines(language="en", page_size=1)
|
|
if result.get("status") == "ok":
|
|
QMessageBox.information(self, "Success", "NewsAPI key is valid!")
|
|
else:
|
|
QMessageBox.critical(self, "Test Failed", result.get("message", "Unknown error"))
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "Test Failed", str(e))
|
|
|
|
def _test_schwab(self):
|
|
key = self._schwab_key.text().strip()
|
|
secret = self._schwab_secret.text().strip()
|
|
if not key or not secret:
|
|
QMessageBox.warning(self, "Test Failed", "Enter both App Key and Secret first.")
|
|
return
|
|
try:
|
|
from brokers.schwab import SchwabBroker
|
|
broker = SchwabBroker(key, secret)
|
|
broker.connect()
|
|
QMessageBox.information(self, "Success", "Schwab connected! Browser may open for OAuth if first time.")
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "Test Failed", str(e))
|
|
|
|
def _test_robinhood(self):
|
|
user = self._rh_username.text().strip()
|
|
pwd = self._rh_password.text().strip()
|
|
if not user or not pwd:
|
|
QMessageBox.warning(self, "Test Failed", "Enter username and password first.")
|
|
return
|
|
try:
|
|
from brokers.robinhood import RobinhoodBroker
|
|
broker = RobinhoodBroker(user, pwd)
|
|
broker.connect()
|
|
QMessageBox.information(self, "Success", "Robinhood login successful!")
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "Test Failed", str(e))
|