Skip to content

Commit 8e208b0

Browse files
authored
Merge pull request #3 from pcphil/feat/widgets
Feat/widgets
2 parents 1f956d5 + 644842f commit 8e208b0

13 files changed

Lines changed: 174 additions & 101 deletions

File tree

.claude/settings.local.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"Bash(cd /c/Users/chung/projects/bite-size-notes && .venv/Scripts/python.exe -m pytest 2>&1)",
1919
"Bash(cd /c/Users/chung/projects/bite-size-notes && .venv/Scripts/ruff.exe check src/ 2>&1)",
2020
"Bash(uv run:*)",
21-
"Bash(uv:*)"
21+
"Bash(uv:*)",
22+
"Bash(rm:*)"
2223
]
2324
}
2425
}

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ dependencies = [
1212
"PySide6>=6.6,<7",
1313
"sounddevice>=0.4.6",
1414
"numpy>=1.24",
15-
"soundfile>=0.12",
16-
"faster-whisper>=1.0.0",
15+
"faster-whisper>=1.0.0",
1716
"pyaudiowpatch>=0.2.12; sys_platform == 'win32'",
1817
"llama-cpp-python>=0.3",
1918
"huggingface-hub>=0.20",

src/bite_size_notes/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
def main():
1515
app = QApplication(sys.argv)
1616
app.setApplicationName("Bite-Size Notes")
17-
app.setOrganizationName("BiteSize")
17+
app.setOrganizationName("pcphil")
1818

1919
# Apply theme
2020
config = AppConfig()

src/bite_size_notes/gui/chat_bubble.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Transcript line widget for displaying a single transcript segment."""
22

3-
from PySide6.QtCore import Qt, Signal
3+
from PySide6.QtCore import Qt, QTimer, Signal
44
from PySide6.QtGui import QFont, QTextOption
55
from PySide6.QtWidgets import (
66
QFrame,
@@ -30,16 +30,18 @@ def __init__(self, parent=None):
3030
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
3131
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
3232
self.setWordWrapMode(QTextOption.WrapMode.WordWrap)
33+
self.setMinimumWidth(0)
3334

3435
def _adjust_height(self):
3536
doc_height = int(self.document().size().height())
3637
margins = self.contentsMargins()
3738
height = doc_height + margins.top() + margins.bottom() + 4
3839
self.setFixedHeight(max(height, 28))
40+
self.updateGeometry()
3941

4042
def resizeEvent(self, event):
4143
super().resizeEvent(event)
42-
self._adjust_height()
44+
QTimer.singleShot(0, self._adjust_height)
4345

4446
def showEvent(self, event):
4547
super().showEvent(event)

src/bite_size_notes/gui/export_dialog.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Export dialog for saving transcripts."""
1+
"""Export dialog for saving transcripts and output text."""
22

33
from pathlib import Path
44

@@ -46,6 +46,29 @@ def export_transcript(session: TranscriptSession, parent: QWidget | None = None)
4646
content = exporters.get(ext, session.to_text)()
4747
path.write_text(content, encoding="utf-8")
4848

49-
QMessageBox.information(
50-
parent, "Export", f"Transcript exported to:\n{path}"
49+
QMessageBox.information(parent, "Export", f"Transcript exported to:\n{path}")
50+
51+
52+
def export_output(text: str, parent: QWidget | None = None):
53+
"""Show a file save dialog and export the summarization output as plain text."""
54+
if not text.strip():
55+
QMessageBox.information(parent, "Export", "No output to export.")
56+
return
57+
58+
file_path, _ = QFileDialog.getSaveFileName(
59+
parent,
60+
"Export Output",
61+
"output",
62+
"Plain Text (*.txt)",
5163
)
64+
65+
if not file_path:
66+
return
67+
68+
path = Path(file_path)
69+
if not path.suffix:
70+
path = path.with_suffix(".txt")
71+
72+
path.write_text(text, encoding="utf-8")
73+
74+
QMessageBox.information(parent, "Export", f"Output exported to:\n{path}")

src/bite_size_notes/gui/main_window.py

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
import time
55

66
from PySide6.QtCore import QThread, QTimer, Qt, Signal
7-
from PySide6.QtGui import QKeySequence, QShortcut
7+
from PySide6.QtGui import QFont, QKeySequence, QShortcut
88
from PySide6.QtWidgets import (
9+
QInputDialog,
910
QLabel,
1011
QMainWindow,
1112
QMessageBox,
@@ -18,7 +19,7 @@
1819
from bite_size_notes.audio.capture import AudioCaptureThread
1920
from bite_size_notes.gui.themes import build_stylesheet, get_palette
2021
from bite_size_notes.audio.devices import get_default_mic, get_loopback_device
21-
from bite_size_notes.gui.export_dialog import export_transcript
22+
from bite_size_notes.gui.export_dialog import export_output, export_transcript
2223
from bite_size_notes.gui.notes_panel import NotesPanel
2324
from bite_size_notes.gui.output_panel import OutputPanel
2425
from bite_size_notes.gui.settings_dialog import SettingsDialog
@@ -147,20 +148,22 @@ def _setup_ui(self):
147148

148149
layout.addWidget(self._splitter, 1)
149150

150-
# Connect sidebar collapse to redistribute splitter space
151-
self._saved_splitter_sizes = self._splitter.sizes()
152151
self._saved_output_sizes = self._splitter.sizes()
153-
self.sidebar.collapse_toggled.connect(self._on_sidebar_collapse_toggled)
154152

155153
# Connect transcript view control signals
156154
self.transcript_view.record_clicked.connect(self._on_record_clicked)
157155
self.transcript_view.clear_clicked.connect(self._on_clear_clicked)
156+
self.transcript_view.export_clicked.connect(self._on_export_clicked)
158157

159158
# Connect output panel signals
160159
self.output_panel.notes_toggled.connect(self._toggle_notes)
161-
self.output_panel.export_clicked.connect(self._on_export_clicked)
160+
self.output_panel.export_output_clicked.connect(self._on_export_output_clicked)
162161
self.output_panel.collapse_toggled.connect(self._on_output_collapse_toggled)
163162
self.output_panel.bite_size_clicked.connect(self._on_bite_size_clicked)
163+
self._splitter.splitterMoved.connect(self._on_splitter_moved)
164+
165+
# Connect sidebar rename signal
166+
self.sidebar.rename_session_requested.connect(self._on_rename_session)
164167

165168
self.setCentralWidget(central)
166169

@@ -197,6 +200,10 @@ def _setup_status_bar(self):
197200
self.loopback_level.setTextVisible(True)
198201
self.statusBar().addPermanentWidget(self.loopback_level)
199202

203+
author_label = QLabel("By Philip Chung")
204+
author_label.setFont(QFont("Segoe UI", 8))
205+
self.statusBar().addPermanentWidget(author_label)
206+
200207
def _setup_timers(self):
201208
# Timer to update duration and audio levels
202209
self.ui_timer = QTimer(self)
@@ -374,6 +381,9 @@ def _on_error(self, message: str):
374381
def _on_export_clicked(self):
375382
export_transcript(self.transcript_session, self)
376383

384+
def _on_export_output_clicked(self):
385+
export_output(self.output_panel.text(), self)
386+
377387
def _on_clear_clicked(self):
378388
self.transcript_session.clear()
379389
self.transcript_view.clear_transcript()
@@ -392,6 +402,9 @@ def _on_bite_size_clicked(self):
392402
return
393403

394404
text = self.transcript_session.to_text()
405+
notes = self.notes_panel.get_text().strip()
406+
if notes:
407+
text += f"\n\nNotes:\n{notes}"
395408
if not text.strip():
396409
QMessageBox.information(
397410
self, "No Transcript", "There is no transcript to summarize."
@@ -410,6 +423,9 @@ def _on_bite_size_clicked(self):
410423
def _on_summarize_finished(self, result: str):
411424
self._summarize_thread = None
412425
self.output_panel.set_text(result)
426+
self.transcript_session.summary = result
427+
if self.transcript_session.segments:
428+
self.session_store.save_session(self.transcript_session)
413429

414430
def _on_summarize_error(self, message: str):
415431
self._summarize_thread = None
@@ -438,6 +454,10 @@ def _on_session_selected(self, session_id: str):
438454
for segment in session.segments:
439455
self.transcript_view.append_segment(segment)
440456
self.transcript_view.set_editable(True)
457+
if session.summary:
458+
self.output_panel.set_text(session.summary)
459+
else:
460+
self.output_panel.clear()
441461
self.sidebar.set_active_session(session_id)
442462

443463
def _on_new_session(self):
@@ -447,9 +467,31 @@ def _on_new_session(self):
447467
if self.transcript_session.segments:
448468
self.session_store.save_session(self.transcript_session)
449469
self.transcript_session = TranscriptSession()
470+
self.session_store.save_session(self.transcript_session)
450471
self.transcript_view.clear_transcript()
451472
self.transcript_view.set_editable(False)
452473
self.notes_panel.clear()
474+
self.output_panel.clear()
475+
self.sidebar.refresh_sessions(active_id=self.transcript_session.id)
476+
477+
def _on_rename_session(self, session_id: str):
478+
"""Prompt the user to rename a session."""
479+
try:
480+
session = self.session_store.load_session(session_id)
481+
except Exception:
482+
return
483+
new_name, ok = QInputDialog.getText(
484+
self,
485+
"Rename Session",
486+
"New name:",
487+
text=session.title,
488+
)
489+
if not ok or not new_name.strip():
490+
return
491+
session.title = new_name.strip()
492+
self.session_store.save_session(session)
493+
if session_id == self.transcript_session.id:
494+
self.transcript_session.title = session.title
453495
self.sidebar.refresh_sessions(active_id=self.transcript_session.id)
454496

455497
def _on_delete_session(self, session_id: str):
@@ -478,16 +520,9 @@ def _toggle_notes(self):
478520
self.notes_panel.show()
479521
self.notes_panel.raise_()
480522

481-
def _on_sidebar_collapse_toggled(self, collapsed: bool):
482-
"""Redistribute splitter space when the sidebar collapses/expands."""
483-
sizes = self._splitter.sizes()
484-
total = sum(sizes)
485-
if collapsed:
486-
self._saved_splitter_sizes = sizes
487-
self._splitter.setSizes([40, total - 40 - sizes[2], sizes[2]])
488-
else:
489-
# Restore previous proportions
490-
self._splitter.setSizes(self._saved_splitter_sizes)
523+
def _on_splitter_moved(self):
524+
"""Refresh bubble heights after the user drags the splitter."""
525+
QTimer.singleShot(0, self.transcript_view._refresh_bubble_heights)
491526

492527
def _on_output_collapse_toggled(self, collapsed: bool):
493528
"""Redistribute splitter space when the output panel collapses/expands."""

src/bite_size_notes/gui/output_panel.py

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class OutputPanel(QWidget):
1616

1717
bite_size_clicked = Signal()
1818
notes_toggled = Signal()
19-
export_clicked = Signal()
19+
export_output_clicked = Signal()
2020
collapse_toggled = Signal(bool) # emits True when collapsed
2121

2222
def __init__(self, parent=None):
@@ -35,26 +35,23 @@ def _setup_ui(self):
3535
self._btn_row.setSpacing(4)
3636
self._btn_row.setAlignment(Qt.AlignmentFlag.AlignVCenter)
3737

38-
self._action_btns = []
39-
for label in ("Bite Size It", "Tone", "Model"):
40-
btn = QPushButton(label)
41-
btn.setFixedHeight(28)
42-
self._btn_row.addWidget(btn)
43-
self._action_btns.append(btn)
44-
45-
self._action_btns[0].clicked.connect(self.bite_size_clicked.emit)
38+
self._bite_size_btn = QPushButton("Bite Size It")
39+
self._bite_size_btn.setFixedHeight(28)
40+
self._btn_row.addWidget(self._bite_size_btn)
41+
self._bite_size_btn.clicked.connect(self.bite_size_clicked.emit)
4642

4743
self._copy_btn = QPushButton("\U0001f4cb")
4844
self._copy_btn.setObjectName("iconBtn")
4945
self._copy_btn.setFixedSize(28, 28)
5046
self._copy_btn.setToolTip("Copy")
47+
self._copy_btn.clicked.connect(self._copy_to_clipboard)
5148
self._btn_row.addWidget(self._copy_btn)
5249

5350
self._export_btn = QPushButton("\U0001f4e5")
5451
self._export_btn.setObjectName("iconBtn")
5552
self._export_btn.setFixedSize(28, 28)
56-
self._export_btn.setToolTip("Export")
57-
self._export_btn.clicked.connect(self.export_clicked.emit)
53+
self._export_btn.setToolTip("Export output")
54+
self._export_btn.clicked.connect(self.export_output_clicked.emit)
5855
self._btn_row.addWidget(self._export_btn)
5956

6057
self._btn_row.addStretch()
@@ -90,8 +87,7 @@ def _setup_ui(self):
9087
def toggle_collapse(self):
9188
self._collapsed = not self._collapsed
9289
if self._collapsed:
93-
for btn in self._action_btns:
94-
btn.hide()
90+
self._bite_size_btn.hide()
9591
self._copy_btn.hide()
9692
self._export_btn.hide()
9793
self._text_edit.hide()
@@ -101,8 +97,7 @@ def toggle_collapse(self):
10197
self.setMinimumWidth(40)
10298
self.setMaximumWidth(40)
10399
else:
104-
for btn in self._action_btns:
105-
btn.show()
100+
self._bite_size_btn.show()
106101
self._copy_btn.show()
107102
self._export_btn.show()
108103
self._text_edit.show()
@@ -123,5 +118,14 @@ def append_text(self, text: str):
123118
def set_text(self, text: str):
124119
self._text_edit.setPlainText(text)
125120

121+
def text(self) -> str:
122+
return self._text_edit.toPlainText()
123+
124+
def _copy_to_clipboard(self):
125+
from PySide6.QtWidgets import QApplication
126+
127+
clipboard = QApplication.clipboard()
128+
clipboard.setText(self._text_edit.toPlainText())
129+
126130
def clear(self):
127131
self._text_edit.clear()

0 commit comments

Comments
 (0)