#pragma once #include #include #define GLFW_INCLUDE_NONE // Don't include OpenGL headers (GLAD provides them) #include namespace secondvoice { struct TranslationMessage { std::string chinese; std::string french; }; class TranslationUI { public: TranslationUI(int width, int height); ~TranslationUI(); bool initialize(); void makeContextCurrent(); // Make OpenGL context current in calling thread void render(); bool shouldClose() const; void addTranslation(const std::string& chinese, const std::string& french); void setAccumulatedText(const std::string& chinese, const std::string& french); bool isStopRequested() const { return stop_requested_; } void resetStopRequest() { stop_requested_ = false; } bool isClearRequested() const { return clear_requested_; } void resetClearRequest() { clear_requested_ = false; } // Export transcript to file bool exportTranscript(const std::string& filename = "") const; bool isExportRequested() const { return export_requested_; } void resetExportRequest() { export_requested_ = false; } void setRecordingDuration(int seconds) { recording_duration_ = seconds; } void setProcessingStatus(const std::string& status) { processing_status_ = status; } // Audio level monitoring void setCurrentRMS(float rms) { current_rms_ = rms; } void setCurrentPeak(float peak) { current_peak_ = peak; } float getVadThreshold() const { return vad_threshold_; } float getVadPeakThreshold() const { return vad_peak_threshold_; } private: void renderAccumulated(); void renderTranslations(); void renderControls(); void renderStatus(); void renderAudioPanel(); int width_; int height_; GLFWwindow* window_ = nullptr; std::vector messages_; std::string accumulated_chinese_; std::string accumulated_french_; bool stop_requested_ = false; bool clear_requested_ = false; bool export_requested_ = false; bool auto_scroll_ = true; int recording_duration_ = 0; std::string processing_status_; // Audio monitoring float current_rms_ = 0.0f; float current_peak_ = 0.0f; float vad_threshold_ = 0.02f; // 2x higher to avoid false triggers float vad_peak_threshold_ = 0.08f; // 2x higher // Cost tracking float total_audio_seconds_ = 0.0f; int whisper_calls_ = 0; int claude_calls_ = 0; public: void addAudioCost(float seconds) { total_audio_seconds_ += seconds; whisper_calls_++; } void addClaudeCost() { claude_calls_++; } float getTotalAudioSeconds() const { return total_audio_seconds_; } float getEstimatedCost() const { // gpt-4o-mini-transcribe: $0.006/min = $0.0001/sec // Haiku: ~$0.001 per short translation return (total_audio_seconds_ * 0.0001f) + (claude_calls_ * 0.001f); } }; } // namespace secondvoice