meeting-noter 2.0.0__py3-none-any.whl → 3.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of meeting-noter might be problematic. Click here for more details.

@@ -0,0 +1,232 @@
1
+ """Settings screen for Meeting Noter GUI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from PySide6.QtCore import Qt
8
+ from PySide6.QtWidgets import (
9
+ QCheckBox,
10
+ QComboBox,
11
+ QFileDialog,
12
+ QFrame,
13
+ QGridLayout,
14
+ QHBoxLayout,
15
+ QLabel,
16
+ QLineEdit,
17
+ QPushButton,
18
+ QSpinBox,
19
+ QVBoxLayout,
20
+ QWidget,
21
+ )
22
+
23
+ from meeting_noter.config import get_config
24
+
25
+
26
+ WHISPER_MODELS = [
27
+ ("tiny.en", "tiny.en (fastest)"),
28
+ ("base.en", "base.en (balanced)"),
29
+ ("small.en", "small.en (better)"),
30
+ ("medium.en", "medium.en (high quality)"),
31
+ ("large-v3", "large-v3 (best, multilingual)"),
32
+ ]
33
+
34
+
35
+ class SettingsScreen(QWidget):
36
+ """Screen for configuring application settings."""
37
+
38
+ def __init__(self, parent=None):
39
+ super().__init__(parent)
40
+ self._setup_ui()
41
+ self._connect_signals()
42
+ self._load_settings()
43
+
44
+ def _setup_ui(self):
45
+ """Set up the settings UI."""
46
+ layout = QVBoxLayout(self)
47
+ layout.setContentsMargins(30, 30, 30, 30)
48
+ layout.setSpacing(20)
49
+
50
+ # Title
51
+ title = QLabel("Settings")
52
+ title.setStyleSheet("font-size: 24px; font-weight: 600; color: #ffffff;")
53
+ layout.addWidget(title)
54
+
55
+ # Directories section
56
+ dirs_section = self._create_section("Directories")
57
+ dirs_layout = QGridLayout()
58
+ dirs_layout.setSpacing(10)
59
+
60
+ # Recordings directory
61
+ dirs_layout.addWidget(QLabel("Recordings:"), 0, 0)
62
+ self._recordings_dir = QLineEdit()
63
+ dirs_layout.addWidget(self._recordings_dir, 0, 1)
64
+ recordings_browse = QPushButton("...")
65
+ recordings_browse.setFixedWidth(40)
66
+ recordings_browse.clicked.connect(lambda: self._browse_dir(self._recordings_dir))
67
+ dirs_layout.addWidget(recordings_browse, 0, 2)
68
+
69
+ # Transcripts directory
70
+ dirs_layout.addWidget(QLabel("Transcripts:"), 1, 0)
71
+ self._transcripts_dir = QLineEdit()
72
+ dirs_layout.addWidget(self._transcripts_dir, 1, 1)
73
+ transcripts_browse = QPushButton("...")
74
+ transcripts_browse.setFixedWidth(40)
75
+ transcripts_browse.clicked.connect(lambda: self._browse_dir(self._transcripts_dir))
76
+ dirs_layout.addWidget(transcripts_browse, 1, 2)
77
+
78
+ dirs_section.layout().addLayout(dirs_layout)
79
+ layout.addWidget(dirs_section)
80
+
81
+ # Transcription section
82
+ trans_section = self._create_section("Transcription")
83
+ trans_layout = QGridLayout()
84
+ trans_layout.setSpacing(10)
85
+
86
+ trans_layout.addWidget(QLabel("Whisper model:"), 0, 0)
87
+ self._model_combo = QComboBox()
88
+ for model_id, model_name in WHISPER_MODELS:
89
+ self._model_combo.addItem(model_name, model_id)
90
+ self._model_combo.setMinimumWidth(250)
91
+ trans_layout.addWidget(self._model_combo, 0, 1)
92
+ trans_layout.setColumnStretch(2, 1)
93
+
94
+ trans_section.layout().addLayout(trans_layout)
95
+ layout.addWidget(trans_section)
96
+
97
+ # Options section
98
+ options_section = self._create_section("Options")
99
+ options_layout = QVBoxLayout()
100
+ options_layout.setSpacing(10)
101
+
102
+ self._auto_transcribe = QCheckBox("Auto-transcribe after recording")
103
+ options_layout.addWidget(self._auto_transcribe)
104
+
105
+ self._capture_system = QCheckBox("Capture system audio (meeting participants)")
106
+ options_layout.addWidget(self._capture_system)
107
+
108
+ self._auto_update = QCheckBox("Auto-update when new version available")
109
+ options_layout.addWidget(self._auto_update)
110
+
111
+ # Silence timeout
112
+ timeout_row = QHBoxLayout()
113
+ timeout_row.addWidget(QLabel("Silence timeout (minutes):"))
114
+ self._silence_timeout = QSpinBox()
115
+ self._silence_timeout.setRange(1, 60)
116
+ self._silence_timeout.setFixedWidth(80)
117
+ timeout_row.addWidget(self._silence_timeout)
118
+ timeout_row.addStretch()
119
+ options_layout.addLayout(timeout_row)
120
+
121
+ options_section.layout().addLayout(options_layout)
122
+ layout.addWidget(options_section)
123
+
124
+ # Buttons
125
+ button_row = QHBoxLayout()
126
+ button_row.setSpacing(10)
127
+
128
+ self._save_btn = QPushButton("Save")
129
+ self._save_btn.setProperty("class", "success")
130
+ self._save_btn.setStyleSheet("background-color: #107c10;")
131
+
132
+ self._reset_btn = QPushButton("Reset")
133
+ self._reset_btn.setProperty("class", "secondary")
134
+ self._reset_btn.setStyleSheet("background-color: #3c3c3c;")
135
+
136
+ button_row.addWidget(self._save_btn)
137
+ button_row.addWidget(self._reset_btn)
138
+ button_row.addStretch()
139
+ layout.addLayout(button_row)
140
+
141
+ # Status label
142
+ self._status_label = QLabel("")
143
+ self._status_label.setStyleSheet("color: #4ec9b0;")
144
+ layout.addWidget(self._status_label)
145
+
146
+ # Spacer
147
+ layout.addStretch()
148
+
149
+ def _create_section(self, title: str) -> QFrame:
150
+ """Create a section frame with title."""
151
+ frame = QFrame()
152
+ frame.setStyleSheet(
153
+ "QFrame { background-color: #2d2d2d; border: 1px solid #3c3c3c; border-radius: 8px; }"
154
+ )
155
+
156
+ layout = QVBoxLayout(frame)
157
+ layout.setContentsMargins(15, 15, 15, 15)
158
+ layout.setSpacing(15)
159
+
160
+ section_title = QLabel(title)
161
+ section_title.setStyleSheet(
162
+ "font-size: 12px; font-weight: 600; color: #858585; text-transform: uppercase;"
163
+ )
164
+ layout.addWidget(section_title)
165
+
166
+ return frame
167
+
168
+ def _connect_signals(self):
169
+ """Connect button signals."""
170
+ self._save_btn.clicked.connect(self._save_settings)
171
+ self._reset_btn.clicked.connect(self._load_settings)
172
+
173
+ def _browse_dir(self, line_edit: QLineEdit):
174
+ """Open directory browser."""
175
+ current = line_edit.text() or str(Path.home())
176
+ directory = QFileDialog.getExistingDirectory(self, "Select Directory", current)
177
+ if directory:
178
+ line_edit.setText(directory)
179
+
180
+ def _load_settings(self):
181
+ """Load settings from config."""
182
+ config = get_config()
183
+
184
+ self._recordings_dir.setText(str(config.recordings_dir))
185
+ self._transcripts_dir.setText(str(config.transcripts_dir))
186
+
187
+ # Set model combo
188
+ model = config.whisper_model
189
+ for i in range(self._model_combo.count()):
190
+ if self._model_combo.itemData(i) == model:
191
+ self._model_combo.setCurrentIndex(i)
192
+ break
193
+
194
+ self._auto_transcribe.setChecked(config.auto_transcribe)
195
+ self._capture_system.setChecked(config.capture_system_audio)
196
+ self._auto_update.setChecked(config.auto_update)
197
+ self._silence_timeout.setValue(config.silence_timeout)
198
+
199
+ self._status_label.setText("")
200
+
201
+ def _save_settings(self):
202
+ """Save settings to config."""
203
+ config = get_config()
204
+
205
+ # Validate and create directories
206
+ recordings_path = Path(self._recordings_dir.text()).expanduser()
207
+ transcripts_path = Path(self._transcripts_dir.text()).expanduser()
208
+
209
+ try:
210
+ recordings_path.mkdir(parents=True, exist_ok=True)
211
+ transcripts_path.mkdir(parents=True, exist_ok=True)
212
+ except Exception as e:
213
+ self._status_label.setText(f"Error creating directories: {e}")
214
+ self._status_label.setStyleSheet("color: #f14c4c;")
215
+ return
216
+
217
+ config.recordings_dir = recordings_path
218
+ config.transcripts_dir = transcripts_path
219
+ config.whisper_model = self._model_combo.currentData()
220
+ config.auto_transcribe = self._auto_transcribe.isChecked()
221
+ config.capture_system_audio = self._capture_system.isChecked()
222
+ config.auto_update = self._auto_update.isChecked()
223
+ config.silence_timeout = self._silence_timeout.value()
224
+
225
+ config.save()
226
+
227
+ self._status_label.setText("Settings saved successfully")
228
+ self._status_label.setStyleSheet("color: #4ec9b0;")
229
+
230
+ def refresh(self):
231
+ """Refresh settings."""
232
+ self._load_settings()
@@ -0,0 +1,140 @@
1
+ """Transcript viewer screen for Meeting Noter GUI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from pathlib import Path
7
+
8
+ from PySide6.QtWidgets import (
9
+ QHBoxLayout,
10
+ QLabel,
11
+ QPushButton,
12
+ QTextEdit,
13
+ QVBoxLayout,
14
+ QWidget,
15
+ )
16
+
17
+ from meeting_noter.config import get_config
18
+ from meeting_noter.gui.utils.signals import get_app_state
19
+ from meeting_noter.output.writer import format_size
20
+
21
+
22
+ class ViewerScreen(QWidget):
23
+ """Screen for viewing transcripts."""
24
+
25
+ def __init__(self, parent=None):
26
+ super().__init__(parent)
27
+ self._current_path: Path | None = None
28
+ self._setup_ui()
29
+ self._connect_signals()
30
+
31
+ def _setup_ui(self):
32
+ """Set up the viewer UI."""
33
+ layout = QVBoxLayout(self)
34
+ layout.setContentsMargins(30, 30, 30, 30)
35
+ layout.setSpacing(15)
36
+
37
+ # Title row
38
+ title_row = QHBoxLayout()
39
+
40
+ self._title = QLabel("Transcript Viewer")
41
+ self._title.setStyleSheet("font-size: 24px; font-weight: 600; color: #ffffff;")
42
+ title_row.addWidget(self._title)
43
+
44
+ title_row.addStretch()
45
+
46
+ self._back_btn = QPushButton("Back")
47
+ self._back_btn.setProperty("class", "secondary")
48
+ self._back_btn.setStyleSheet("background-color: #3c3c3c;")
49
+ title_row.addWidget(self._back_btn)
50
+
51
+ layout.addLayout(title_row)
52
+
53
+ # File info
54
+ self._file_info = QLabel("")
55
+ self._file_info.setStyleSheet("color: #858585;")
56
+ layout.addWidget(self._file_info)
57
+
58
+ # Content area
59
+ self._content = QTextEdit()
60
+ self._content.setReadOnly(True)
61
+ self._content.setStyleSheet(
62
+ "font-family: 'Menlo', 'Monaco', 'Courier New', monospace; font-size: 13px;"
63
+ )
64
+ layout.addWidget(self._content, 1)
65
+
66
+ # Action buttons
67
+ button_row = QHBoxLayout()
68
+ button_row.setSpacing(10)
69
+
70
+ self._favorite_btn = QPushButton("\u2605 Favorite")
71
+ button_row.addWidget(self._favorite_btn)
72
+ button_row.addStretch()
73
+
74
+ layout.addLayout(button_row)
75
+
76
+ def _connect_signals(self):
77
+ """Connect button signals."""
78
+ self._back_btn.clicked.connect(self._go_back)
79
+ self._favorite_btn.clicked.connect(self._toggle_favorite)
80
+
81
+ def load_transcript(self, filepath: str | Path):
82
+ """Load and display a transcript."""
83
+ self._current_path = Path(filepath)
84
+
85
+ if not self._current_path.exists():
86
+ self._title.setText("File Not Found")
87
+ self._file_info.setText("")
88
+ self._content.setText(f"Could not find: {filepath}")
89
+ return
90
+
91
+ # Update title
92
+ config = get_config()
93
+ is_favorite = config.is_favorite(self._current_path.name)
94
+ star = "\u2605 " if is_favorite else ""
95
+ self._title.setText(f"{star}{self._current_path.name}")
96
+
97
+ # Update file info
98
+ stat = self._current_path.stat()
99
+ mtime = datetime.fromtimestamp(stat.st_mtime)
100
+ size = format_size(stat.st_size)
101
+ self._file_info.setText(f"Modified: {mtime.strftime('%Y-%m-%d %H:%M')} | Size: {size}")
102
+
103
+ # Update favorite button
104
+ if is_favorite:
105
+ self._favorite_btn.setText("\u2606 Unfavorite")
106
+ self._favorite_btn.setStyleSheet("background-color: #ca5010;")
107
+ else:
108
+ self._favorite_btn.setText("\u2605 Favorite")
109
+ self._favorite_btn.setStyleSheet("")
110
+
111
+ # Load content
112
+ try:
113
+ content = self._current_path.read_text()
114
+ self._content.setText(content)
115
+ except Exception as e:
116
+ self._content.setText(f"Error reading file: {e}")
117
+
118
+ def _go_back(self):
119
+ """Go back to recordings."""
120
+ app_state = get_app_state()
121
+ app_state.navigate_to.emit("recordings")
122
+
123
+ def _toggle_favorite(self):
124
+ """Toggle favorite status."""
125
+ if not self._current_path:
126
+ return
127
+
128
+ config = get_config()
129
+ if config.is_favorite(self._current_path.name):
130
+ config.remove_favorite(self._current_path.name)
131
+ else:
132
+ config.add_favorite(self._current_path.name)
133
+
134
+ # Reload to update display
135
+ self.load_transcript(self._current_path)
136
+
137
+ def refresh(self):
138
+ """Refresh the viewer."""
139
+ if self._current_path:
140
+ self.load_transcript(self._current_path)
@@ -0,0 +1,5 @@
1
+ """Theme package for Meeting Noter GUI."""
2
+
3
+ from meeting_noter.gui.theme.dark_theme import COLORS, apply_theme
4
+
5
+ __all__ = ["COLORS", "apply_theme"]
@@ -0,0 +1,53 @@
1
+ """Dark theme colors and styling for Meeting Noter GUI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from PySide6.QtWidgets import QApplication
8
+
9
+ # Color palette (Discord/VS Code inspired)
10
+ COLORS = {
11
+ # Background colors
12
+ "bg_primary": "#1e1e1e", # Main content background
13
+ "bg_secondary": "#252526", # Sidebar, panels
14
+ "bg_tertiary": "#2d2d2d", # Elevated panels, cards
15
+ "bg_hover": "#3c3c3c", # Hover states
16
+ "bg_selected": "#094771", # Selected items (blue tint)
17
+ # Text colors
18
+ "text_primary": "#cccccc", # Primary text
19
+ "text_secondary": "#858585", # Muted/secondary text
20
+ "text_disabled": "#5a5a5a", # Disabled text
21
+ "text_white": "#ffffff", # White text
22
+ # Accent colors
23
+ "accent_primary": "#0078d4", # Primary actions (blue)
24
+ "accent_success": "#4ec9b0", # Success states (green-cyan)
25
+ "accent_warning": "#dcdcaa", # Warnings (yellow)
26
+ "accent_error": "#f14c4c", # Errors (red)
27
+ "accent_info": "#9cdcfe", # Info (light blue)
28
+ # Status colors
29
+ "status_recording": "#f14c4c", # Red dot when recording
30
+ "status_ready": "#4ec9b0", # Green dot when ready
31
+ "status_idle": "#5a5a5a", # Gray dot when stopped
32
+ # Border colors
33
+ "border_default": "#3c3c3c",
34
+ "border_focus": "#007acc",
35
+ # Sidebar
36
+ "sidebar_bg": "#252526",
37
+ "sidebar_item_hover": "#2a2d2e",
38
+ "sidebar_item_selected": "#37373d",
39
+ }
40
+
41
+
42
+ def get_stylesheet() -> str:
43
+ """Load the QSS stylesheet."""
44
+ stylesheet_path = Path(__file__).parent / "styles.qss"
45
+ if stylesheet_path.exists():
46
+ return stylesheet_path.read_text()
47
+ return ""
48
+
49
+
50
+ def apply_theme(app: QApplication) -> None:
51
+ """Apply the dark theme to the application."""
52
+ stylesheet = get_stylesheet()
53
+ app.setStyleSheet(stylesheet)