feat(export): export transcripts to .txt and .vtt (#441)#478
Open
tayyub-ai wants to merge 9 commits into
Open
Conversation
Empty skeleton + module registration so subsequent TDD steps compile. Pure-function validation runs in a throwaway cargo project at /tmp/meetily-441-validate (host machine lacks full Xcode so the meetily crate cannot build locally; full-crate verification is deferred to the fork CI).
Seven unit tests covering happy path with timestamp, NULL timestamp, internal-newline collapse, ordering by audio_start_time, HH:MM:SS hour rollover, the empty-input case, and trailing-newline invariant. Validated locally in a throwaway cargo project (the real crate cannot build on this machine without full Xcode); full-crate verification is deferred to the fork CI run before opening the upstream PR.
WebVTT v1 output. Eight unit tests cover happy path, end-time fallback chain (audio_end_time -> start+duration -> start+5s), NULL start-time skipping (reported via tuple), special-char escaping, overlapping cues preserved as distinct entries, hour-boundary timestamp formatting, and the empty-input case.
Strips path separators (/, \, :, *, ?, ", <, >, |) and control chars, collapses runs of whitespace to single spaces, trims whitespace and dots, truncates to 80 chars, and falls back to meeting-<id-prefix> when the title is empty. Five unit tests.
Non-paginated read ordered by audio_start_time with NULLs sorted last (SQLite-friendly via the `IS NULL` trick rather than NULLS LAST). The upcoming export command streams the whole transcript in one query.
…ions#441) Wires the pure formatters to MeetingsRepository::get_all_transcripts_for_meeting and writes the formatted content to a user-supplied path via std::fs::write. Returns ExportResult so the frontend can render segment counts (and the skipped-segment count for VTT) in its success toast. Registered in lib.rs alongside the other api:: commands.
…ackriya-Solutions#441) - tauri.conf.json: add "dialog:allow-save" to main capability so the renderer can pop a native save-as dialog. The Rust plugin (tauri-plugin-dialog 2.3.0) was already in Cargo.toml and registered in lib.rs:411. - package.json: add "@tauri-apps/plugin-dialog" ~2.3.0 (pnpm resolved to 2.3.3). - pnpm-lock.yaml: contains the new plugin-dialog entries plus a small overrides-config reconciliation that was already needed against the current devtest state (pnpm install --frozen-lockfile failed against the pre-existing lockfile, so the non-frozen install fixed both things together).
…tions#441) Adds a third button between Recording and Enhance: an Export dropdown with .txt and .vtt items. Each item: - builds a default filename `<sanitized title> - <YYYY-MM-DD>.<ext>` (date from meeting.created_at) - opens the native save-as dialog via @tauri-apps/plugin-dialog - invokes api_export_transcript with the chosen path - shows a sonner success toast (or warning when VTT skipped any cues for missing timing); error toast on failure; silent no-op when the user cancels the dialog - disabled while recording, while in-flight, or when transcripts.length is zero (tooltip explains which case applies) Threads meetingTitle, meetingCreatedAt, and isRecording through TranscriptPanel; meeting-details/page-content passes them from the meeting object. Verified: pnpm run build succeeds (TypeScript + Next.js static generation pass).
…ions#441) Tauri requires the npm and Rust plugin packages to share the same major.minor. Cargo.lock pins tauri-plugin-dialog to 2.7.1; the previous ~2.3.0 npm range resolved to 2.3.3 which the tauri build refused with "Found version mismatched Tauri packages". Bumping the JS range to ^2.7.0 lets pnpm resolve to 2.7.1 to match. Caught by build-devtest CI run Zackriya-Solutions#1.
bobby-claw
pushed a commit
to NERV-es/meetily
that referenced
this pull request
Jun 1, 2026
…ackriya-Solutions#457 Zackriya-Solutions#430 Zackriya-Solutions#470 Zackriya-Solutions#420 Ported from upstream Zackriya-Solutions/meetily: - Zackriya-Solutions#475: Whisper vocabulary hints (meeting domain support) - Zackriya-Solutions#478: Export transcripts as TXT/VTT with file-save dialog - Zackriya-Solutions#457: Integration API via deep-link URL scheme + distributed notifications - Zackriya-Solutions#430: Apple Speech engine (cfg-gated behind 'apple-speech' feature) - Zackriya-Solutions#470: BlockNote v0.16 editor upgrade - Zackriya-Solutions#420: Monochrome tray icon (proper macOS template image) Apple Speech engine gated behind feature flag due to upstream Send/Sync issues with objc2 types — enable with: cargo build --features apple-speech
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds an Export dropdown to the meeting transcript toolbar with two options: plain text (
.txt) and WebVTT (.vtt). Both formats are built in Rust (unit-tested) and written to a user-chosen path via the existingtauri-plugin-dialogsave dialog. Disabled while recording or when no transcripts exist..txt format
One line per segment, sorted by
audio_start_time. Timestamp bracket omitted when the segment has no timing (e.g. imported audio). Internal newlines collapsed..vtt format
Standard WebVTT v1 with
&,<,>escaping. End-time fallback chain:audio_end_time→audio_start_time + duration→audio_start_time + 5.0. Segments without a start time are skipped and the count is surfaced in a sonner warning toast. Overlapping mic/system audio segments are kept as distinct cues.Default filename
<sanitized meeting title> - <YYYY-MM-DD>.{txt|vtt}(date frommeeting.created_at).Related Issue
Fixes #441
Type of Change
Testing
frontend/src-tauri/src/api/export.rs:format_txt(7): happy path, NULL timestamp, newline collapse, ordering, hour rollover, empty input, trailing newline invariantformat_vtt(8): happy path, duration fallback, 5s fallback, NULL-start skipping, character escaping, overlapping cues, hour-boundary formatting, empty inputsanitize_filename_stem(5): banned characters, whitespace collapse, empty fallback, whitespace-only fallback, 80-char truncationpnpm run buildpasses (TypeScript + Next.js static generation).vttinto VLC against a real recording to confirm timing sync.Documentation
Notes for reviewers
Windows CI failure is pre-existing and unrelated. The Windows job fails inside
llama-cpp-sys-2 v0.1.146's build script when CMake tries to build the bundledvulkan-shaders-gentool (Not a file: cmake_install.cmake). This is in thellama-helpersidecar's dependency chain — a path my changes don't touch. Evidence:Speaker labels were intentionally excluded from v1. The
transcripts.speakercolumn exists from migration20251110000001_add_speaker_field.sql, but no writer in the live code populates it. Emitting<v Speaker>cues today would only ever produce blanks. Deferred to a follow-up paired with whoever wires diarization end-to-end.pnpm-lock.yamldiff includes a small overrides reconciliation.pnpm install --frozen-lockfilewas already failing against the pre-existing lockfile ondevtest(overrides-config mismatch). The non-frozen install fixed that drift in the same hunk as the new@tauri-apps/plugin-dialogdep. Happy to split into a separatechore(deps)commit if you'd prefer.dialog:allow-saveis the only new capability. The Rusttauri-plugin-dialogwas already inCargo.toml(2.7.1perCargo.lock) and registered inlib.rs:411. The JS package was pinned to match (^2.7.0) — see commitfix(export): bump @tauri-apps/plugin-dialog to ^2.7.0for the version-mismatch fix discovered in CI.Checklist
devtest