Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OpenAI ChatGPT compatible integration #96

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions data/net.sapples.LiveCaptions.gschema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,20 @@
<default>false</default>
<summary>Enable DBus API for external applications to use caption output</summary>
</key>

<key name="auto-refresh" type="b">
<default>false</default>
<summary>Auto-refresh history</summary>
</key>

<key name="openai-key" type="s">
<default>''</default>
<summary>OpenAI API Token</summary>
</key>

<key name="openai-url" type="s">
<default>''</default>
<summary>OpenAI API Token</summary>
</key>
</schema>
</schemalist>
4 changes: 4 additions & 0 deletions src/history-symbolic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 45 additions & 0 deletions src/livecaptions-application.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions src/livecaptions-application.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <adwaita.h>
#include "audiocap.h"
#include "livecaptions-window.h"
#include "livecaptions-history-window.h"
#include "dbus-interface.h"

struct _LiveCaptionsApplication {
Expand All @@ -32,6 +33,8 @@ struct _LiveCaptionsApplication {
LiveCaptionsWindow *window;
GtkWindow *welcome;

LiveCaptionsHistoryWindow *history_window;

asr_thread asr;
audio_thread audio;

Expand Down
156 changes: 121 additions & 35 deletions src/livecaptions-history-window.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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);

Expand Down Expand Up @@ -302,18 +270,130 @@ 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);

gtk_widget_class_set_template_from_resource(widget_class, "/net/sapples/LiveCaptions/livecaptions-history-window.ui");

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
Expand All @@ -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);
}
6 changes: 6 additions & 0 deletions src/livecaptions-history-window.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

Expand Down
Loading