diff --git a/data/net.sapples.LiveCaptions.gschema.xml b/data/net.sapples.LiveCaptions.gschema.xml
index 04aad72..2b67a06 100644
--- a/data/net.sapples.LiveCaptions.gschema.xml
+++ b/data/net.sapples.LiveCaptions.gschema.xml
@@ -73,5 +73,20 @@
false
Enable DBus API for external applications to use caption output
+
+
+ false
+ Auto-refresh history
+
+
+
+ ''
+ OpenAI API Token
+
+
+
+ ''
+ OpenAI API Token
+
diff --git a/src/history-symbolic.svg b/src/history-symbolic.svg
new file mode 100644
index 0000000..49279c5
--- /dev/null
+++ b/src/history-symbolic.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/src/livecaptions-application.c b/src/livecaptions-application.c
index 2517f2c..98a5f8c 100644
--- a/src/livecaptions-application.c
+++ b/src/livecaptions-application.c
@@ -19,6 +19,7 @@
#include "livecaptions-application.h"
#include "livecaptions-settings.h"
#include "livecaptions-window.h"
+#include "livecaptions-history-window.h"
#include "livecaptions-welcome.h"
#include "window-helper.h"
#include "asrproc.h"
@@ -251,6 +252,46 @@ livecaptions_application_show_preferences(G_GNUC_UNUSED GSimpleAction *action,
}
+static void history_window_destroy_cb(GtkWidget *widget, gpointer data)
+{
+ LiveCaptionsApplication *self = (LiveCaptionsApplication *)data;
+ self->history_window = NULL;
+}
+
+static void
+livecaptions_application_show_history(G_GNUC_UNUSED GSimpleAction *action,
+ G_GNUC_UNUSED GVariant *parameter,
+ gpointer user_data)
+{
+ LiveCaptionsApplication *self = LIVECAPTIONS_APPLICATION(user_data);
+ if (self->welcome != NULL) return;
+
+ if (self->history_window != NULL) {
+ // If history window already exists, just present it
+ gtk_window_present(GTK_WINDOW(self->history_window));
+ return;
+ }
+
+ // Get the active window
+ GtkWindow *window = gtk_application_get_active_window(GTK_APPLICATION(self));
+ if (!GTK_IS_WINDOW(window)) {
+ g_warning("No active window found or invalid active window.");
+ return;
+ }
+
+ // Create a new history window with the active window as its transient parent
+ self->history_window = g_object_new(LIVECAPTIONS_TYPE_HISTORY_WINDOW, "transient-for", window, NULL);
+ if (!LIVECAPTIONS_IS_HISTORY_WINDOW(self->history_window)) {
+ g_warning("Failed to create LiveCaptionsHistoryWindow.");
+ self->history_window = NULL;
+ return;
+ }
+
+ // Connect to the destroy signal to reset the reference when the window is closed
+ g_signal_connect(self->history_window, "destroy", G_CALLBACK(history_window_destroy_cb), self);
+
+ gtk_window_present(GTK_WINDOW(self->history_window));
+}
static void on_settings_change(G_GNUC_UNUSED GSettings *settings,
char *key,
@@ -328,6 +369,10 @@ static void livecaptions_application_init(LiveCaptionsApplication *self) {
g_signal_connect(prefs_action, "activate", G_CALLBACK(livecaptions_application_show_preferences), self);
g_action_map_add_action(G_ACTION_MAP(self), G_ACTION(prefs_action));
+ g_autoptr(GSimpleAction) history_action = g_simple_action_new("history", NULL);
+ g_signal_connect(history_action, "activate", G_CALLBACK(livecaptions_application_show_history), self); // Corrected line
+ g_action_map_add_action(G_ACTION_MAP(self), G_ACTION(history_action));
+
gboolean use_microphone = g_settings_get_boolean(self->settings, "microphone");
g_autoptr(GSimpleAction) mic_action = g_simple_action_new_stateful("microphone", NULL, g_variant_new_boolean(use_microphone));
g_signal_connect(mic_action, "change-state", G_CALLBACK(livecaptions_application_toggle_microphone), self);
diff --git a/src/livecaptions-application.h b/src/livecaptions-application.h
index 610700a..c14f11f 100644
--- a/src/livecaptions-application.h
+++ b/src/livecaptions-application.h
@@ -21,6 +21,7 @@
#include
#include "audiocap.h"
#include "livecaptions-window.h"
+#include "livecaptions-history-window.h"
#include "dbus-interface.h"
struct _LiveCaptionsApplication {
@@ -32,6 +33,8 @@ struct _LiveCaptionsApplication {
LiveCaptionsWindow *window;
GtkWindow *welcome;
+ LiveCaptionsHistoryWindow *history_window;
+
asr_thread asr;
audio_thread audio;
diff --git a/src/livecaptions-history-window.c b/src/livecaptions-history-window.c
index 3f4edbd..58a87a1 100644
--- a/src/livecaptions-history-window.c
+++ b/src/livecaptions-history-window.c
@@ -26,6 +26,7 @@
#include "common.h"
#include "window-helper.h"
#include "line-gen.h"
+#include "openai.h"
G_DEFINE_TYPE(LiveCaptionsHistoryWindow, livecaptions_history_window, GTK_TYPE_WINDOW)
@@ -39,39 +40,6 @@ static gboolean close_self_window(gpointer userdata) {
return G_SOURCE_REMOVE;
}
-static void message_cb(AdwMessageDialog *dialog, gchar *response, gpointer userdata){
- if(g_str_equal(response, "delete")){
- erase_all_history();
-
- g_idle_add(close_self_window, userdata);
- }
-}
-
-static void warn_deletion_cb(LiveCaptionsHistoryWindow *self){
- GtkWindow *parent = GTK_WINDOW(gtk_widget_get_root(GTK_WIDGET(self)));
- GtkWidget *dialog;
-
- dialog = adw_message_dialog_new(parent,
- _("Erase History?"),
- _("Everything in history will be erased. You may wish to export your history before erasing!"));
-
- adw_message_dialog_add_responses(ADW_MESSAGE_DIALOG(dialog),
- "cancel", _("_Cancel"),
- "delete", _("_Erase Everything"),
- NULL);
-
- adw_message_dialog_set_response_appearance(ADW_MESSAGE_DIALOG(dialog), "delete", ADW_RESPONSE_DESTRUCTIVE);
-
- adw_message_dialog_set_default_response(ADW_MESSAGE_DIALOG(dialog), "cancel");
- adw_message_dialog_set_close_response(ADW_MESSAGE_DIALOG(dialog), "cancel");
-
- g_signal_connect(ADW_MESSAGE_DIALOG(dialog), "response", G_CALLBACK(message_cb), self);
-
- gtk_window_present(GTK_WINDOW(dialog));
-}
-
-
-
static gboolean force_bottom(gpointer userdata) {
LiveCaptionsHistoryWindow *self = LIVECAPTIONS_HISTORY_WINDOW(userdata);
@@ -302,6 +270,113 @@ static void refresh_cb(LiveCaptionsHistoryWindow *self) {
g_idle_add(force_bottom, self);
}
+static void message_cb(AdwMessageDialog *dialog, gchar *response, gpointer userdata){
+ if(g_str_equal(response, "delete")){
+ erase_all_history();
+
+ LiveCaptionsHistoryWindow *self = LIVECAPTIONS_HISTORY_WINDOW(userdata);
+ refresh_cb(self);
+ //g_idle_add(close_self_window, userdata);
+ }
+}
+
+static void warn_deletion_cb(LiveCaptionsHistoryWindow *self){
+ GtkWindow *parent = GTK_WINDOW(gtk_widget_get_root(GTK_WIDGET(self)));
+ GtkWidget *dialog;
+
+ dialog = adw_message_dialog_new(parent,
+ _("Erase History?"),
+ _("Everything in history will be erased. You may wish to export your history before erasing!"));
+
+ adw_message_dialog_add_responses(ADW_MESSAGE_DIALOG(dialog),
+ "cancel", _("_Cancel"),
+ "delete", _("_Erase Everything"),
+ NULL);
+
+ adw_message_dialog_set_response_appearance(ADW_MESSAGE_DIALOG(dialog), "delete", ADW_RESPONSE_DESTRUCTIVE);
+
+ adw_message_dialog_set_default_response(ADW_MESSAGE_DIALOG(dialog), "cancel");
+ adw_message_dialog_set_close_response(ADW_MESSAGE_DIALOG(dialog), "cancel");
+
+ g_signal_connect(ADW_MESSAGE_DIALOG(dialog), "response", G_CALLBACK(message_cb), self);
+
+ gtk_window_present(GTK_WINDOW(dialog));
+}
+
+char* get_full_conversation_history(void) {
+ const struct history_session *session;
+ GString *full_history = g_string_new(NULL);
+ size_t idx = 0;
+
+ // First, determine the total number of sessions
+ while (get_history_session(idx) != NULL) {
+ idx++;
+ }
+
+ // Now iterate over the sessions in forward order
+ for (size_t i = idx; i > 0; i--) {
+ session = get_history_session(i - 1);
+ for (size_t j = 0; j < session->entries_count; j++) {
+ const struct history_entry *entry = &session->entries[j];
+ for (size_t k = 0; k < entry->tokens_count; k++) {
+ g_string_append(full_history, entry->tokens[k].token);
+ }
+ g_string_append_c(full_history, '\n');
+ }
+ }
+
+ return g_string_free(full_history, FALSE); // FALSE means don't deallocate, return the data with a terminating null byte
+}
+
+
+static void send_message_cb(GtkButton *button, gpointer userdata) {
+ LiveCaptionsHistoryWindow *self = LIVECAPTIONS_HISTORY_WINDOW(userdata);
+
+ GtkLabel *response_label = GTK_LABEL(self->ai_response_label);
+ gtk_label_set_text(GTK_LABEL(self->ai_response_label), "Processing...");
+
+ OpenAI_Config config;
+ config.api_url = g_settings_get_string(self->settings, "openai-url");
+ config.api_key = g_settings_get_string(self->settings, "openai-key");
+
+ char *session_text = get_full_conversation_history();
+ char *system_text = g_strdup_printf("The user will ask questions relative the converation history: %s", session_text);
+
+ OpenAI_Message messages[] = {
+ {"system", system_text},
+ {"user", gtk_editable_get_text(GTK_EDITABLE(self->chat_input_entry))},
+ };
+
+ OpenAI_Response response;
+ if (openai_chat(&config, "gpt-4o-mini", messages, sizeof(messages) / sizeof(messages[0]), 0.7, &response)) {
+ gtk_label_set_text(GTK_LABEL(self->ai_response_label), response.message_content);
+ }
+
+ free_openai_response(&response);
+
+ gtk_editable_set_text(GTK_EDITABLE(self->chat_input_entry), "");
+}
+
+// This function will be called every second
+static gboolean refresh_history_callback(gpointer userdata) {
+ LiveCaptionsHistoryWindow *self = LIVECAPTIONS_HISTORY_WINDOW(userdata);
+
+ // Check if auto-refresh is enabled
+ if (g_settings_get_boolean(self->settings, "auto-refresh")) {
+ refresh_cb(self);
+ }
+
+ // Returning TRUE so the function gets called again
+ return G_SOURCE_CONTINUE;
+}
+
+// Callback function for when the checkbox is toggled
+static void auto_refresh_toggled_cb(GtkCheckButton *button, gpointer userdata) {
+ LiveCaptionsHistoryWindow *self = LIVECAPTIONS_HISTORY_WINDOW(userdata);
+ gboolean active = gtk_check_button_get_active(button);
+ g_settings_set_boolean(self->settings, "auto-refresh", active);
+}
+
static void livecaptions_history_window_class_init(LiveCaptionsHistoryWindowClass *klass) {
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
@@ -309,11 +384,16 @@ static void livecaptions_history_window_class_init(LiveCaptionsHistoryWindowClas
gtk_widget_class_bind_template_child(widget_class, LiveCaptionsHistoryWindow, scroll);
gtk_widget_class_bind_template_child(widget_class, LiveCaptionsHistoryWindow, main_box);
+ gtk_widget_class_bind_template_child(widget_class, LiveCaptionsHistoryWindow, ai_response_label);
+ gtk_widget_class_bind_template_child(widget_class, LiveCaptionsHistoryWindow, chat_input_entry);
+ gtk_widget_class_bind_template_child(widget_class, LiveCaptionsHistoryWindow, auto_refresh_checkbox);
gtk_widget_class_bind_template_callback(widget_class, load_more_cb);
gtk_widget_class_bind_template_callback(widget_class, export_cb);
gtk_widget_class_bind_template_callback(widget_class, warn_deletion_cb);
gtk_widget_class_bind_template_callback(widget_class, refresh_cb);
+ gtk_widget_class_bind_template_callback(widget_class, send_message_cb);
+ gtk_widget_class_bind_template_callback(widget_class, auto_refresh_toggled_cb);
}
// TODO: ctrl+f search
@@ -322,11 +402,17 @@ static void livecaptions_history_window_init(LiveCaptionsHistoryWindow *self) {
self->settings = g_settings_new("net.sapples.LiveCaptions");
-
self->session_load = 0;
load_to(self, ++self->session_load);
g_idle_add(force_bottom, self);
g_idle_add(deferred_update_keep_above, self);
-}
+ // Bind the checkbox state to GSettings key
+ GtkCheckButton *check_button = GTK_CHECK_BUTTON(self->auto_refresh_checkbox);
+ gboolean active = g_settings_get_boolean(self->settings, "auto-refresh");
+ gtk_check_button_set_active(check_button, active);
+
+ // Add a timeout to call refresh_history_callback every second
+ g_timeout_add_seconds(1, refresh_history_callback, self);
+}
\ No newline at end of file
diff --git a/src/livecaptions-history-window.h b/src/livecaptions-history-window.h
index 4af4cd9..e9307ea 100644
--- a/src/livecaptions-history-window.h
+++ b/src/livecaptions-history-window.h
@@ -26,10 +26,16 @@ struct _LiveCaptionsHistoryWindow {
GSettings *settings;
+ GtkCheckButton *auto_refresh_checkbox;
+
GtkBox *main_box;
GtkScrolledWindow *scroll;
+ GtkLabel *ai_response_label;
+
+ GtkEntry *chat_input_entry;
+
size_t session_load;
};
diff --git a/src/livecaptions-history-window.ui b/src/livecaptions-history-window.ui
index 8d4113f..1ea582c 100644
--- a/src/livecaptions-history-window.ui
+++ b/src/livecaptions-history-window.ui
@@ -10,6 +10,13 @@
-
-
-
+
\ No newline at end of file
diff --git a/src/livecaptions-settings.c b/src/livecaptions-settings.c
index d23a8ab..46c6ce7 100644
--- a/src/livecaptions-settings.c
+++ b/src/livecaptions-settings.c
@@ -202,6 +202,9 @@ static void livecaptions_settings_class_init(LiveCaptionsSettingsClass *klass) {
gtk_widget_class_bind_template_child (widget_class, LiveCaptionsSettings, models_list);
gtk_widget_class_bind_template_child (widget_class, LiveCaptionsSettings, radio_button_1);
gtk_widget_class_bind_template_child (widget_class, LiveCaptionsSettings, file_filter);
+
+ gtk_widget_class_bind_template_child (widget_class, LiveCaptionsSettings, openai_url_entry);
+ gtk_widget_class_bind_template_child (widget_class, LiveCaptionsSettings, openai_key_entry);
gtk_widget_class_bind_template_callback (widget_class, report_cb);
gtk_widget_class_bind_template_callback (widget_class, about_cb);
@@ -444,4 +447,7 @@ static void livecaptions_settings_init(LiveCaptionsSettings *self) {
}
init_models_page(self);
+
+ g_settings_bind(self->settings, "openai-key", self->openai_key_entry, "text", G_SETTINGS_BIND_DEFAULT);
+ g_settings_bind(self->settings, "openai-url", self->openai_url_entry, "text", G_SETTINGS_BIND_DEFAULT);
}
diff --git a/src/livecaptions-settings.h b/src/livecaptions-settings.h
index 0c68318..875e5e3 100644
--- a/src/livecaptions-settings.h
+++ b/src/livecaptions-settings.h
@@ -53,6 +53,9 @@ struct _LiveCaptionsSettings {
GtkCheckButton *radio_button_1;
GtkFileFilter *file_filter;
+
+ GtkEntry *openai_url_entry;
+ GtkEntry *openai_key_entry;
};
G_BEGIN_DECLS
diff --git a/src/livecaptions-settings.ui b/src/livecaptions-settings.ui
index 9f3ac91..e2b23ac 100644
--- a/src/livecaptions-settings.ui
+++ b/src/livecaptions-settings.ui
@@ -281,6 +281,37 @@
+
+
+
+
+ openai_url_entry
+ OpenAI URL
+
+
+ center
+ OpenAI Endpoint URL
+ 50
+
+
+
+
+
+
+ openai_key_entry
+ OpenAI Key
+
+
+ center
+ false
+ OpenAI Key
+ 50
+
+
+
+
+
+
diff --git a/src/livecaptions-window.ui b/src/livecaptions-window.ui
index e050345..85b9d12 100644
--- a/src/livecaptions-window.ui
+++ b/src/livecaptions-window.ui
@@ -22,6 +22,17 @@
+
+
+
+
+ <Control>H
+ action(app.history)
+
+
+
+
+
+
+
+