From cead417e7851ec5f479008fef9e22e9df4694387 Mon Sep 17 00:00:00 2001 From: Jabber Developer Date: Fri, 1 May 2026 13:34:00 +0000 Subject: [PATCH] refactor(ai): remove redundant callback layer from AI client The ai_response_cb and ai_error_cb parameters were always passed the same functions (aiwin_display_response and aiwin_display_error), making the callback indirection redundant and complicating the call chain. Remove ai_callback_data_t, _ai_callback_invoke(), and _ai_invoke_callback(). Simplify _ai_request_thread and ai_send_prompt to directly call aiwin_display_error() and aiwin_display_response() when user_data is a valid ProfAiWin*. --- src/ai/ai_client.c | 194 ++++++++++++++++----------------------------- src/ai/ai_client.h | 16 +--- 2 files changed, 70 insertions(+), 140 deletions(-) diff --git a/src/ai/ai_client.c b/src/ai/ai_client.c index aadc1e46..bd920b72 100644 --- a/src/ai/ai_client.c +++ b/src/ai/ai_client.c @@ -67,56 +67,6 @@ _write_callback(void* ptr, size_t size, size_t nmemb, void* userdata) return realsize; } -/* ======================================================================== - * Thread-safe UI callback helpers - * ======================================================================== */ - -typedef struct -{ - ai_response_cb response_cb; - ai_error_cb error_cb; - gpointer user_data; - gchar* response; - gchar* error; - gboolean is_error; -} ai_callback_data_t; - -static gboolean -_ai_callback_invoke(gpointer data) -{ - log_debug("[AI-CALLBACK] _ai_callback_invoke ENTER"); - ai_callback_data_t* cb_data = (ai_callback_data_t*)data; - - if (cb_data->is_error) { - log_debug("[AI-CALLBACK] Invoking error_cb: %s", cb_data->error); - if (cb_data->error_cb) { - cb_data->error_cb(cb_data->error, cb_data->user_data); - } - } else { - log_debug("[AI-CALLBACK] Invoking response_cb: %s", cb_data->response); - if (cb_data->response_cb) { - cb_data->response_cb(cb_data->response, cb_data->user_data); - } - } - - g_free(cb_data->response); - g_free(cb_data->error); - g_free(cb_data); - log_debug("[AI-CALLBACK] _ai_callback_invoke EXIT"); - return G_SOURCE_REMOVE; -} - -static void -_ai_invoke_callback(ai_callback_data_t* cb_data) -{ - log_debug("[AI-CALLBACK] _ai_invoke_callback: is_error=%d, response_cb=%p, error_cb=%p", - cb_data->is_error, (void*)cb_data->response_cb, (void*)cb_data->error_cb); - /* Take the global lock to ensure thread-safety when invoking callbacks that may access UI state. */ - pthread_mutex_lock(&lock); - _ai_callback_invoke(cb_data); - pthread_mutex_unlock(&lock); -} - /* ======================================================================== * JSON helpers * ======================================================================== */ @@ -651,13 +601,11 @@ _ai_request_thread(gpointer data) { log_debug("[AI-THREAD] Starting AI request thread"); - /* Data is an array: [0]=session, [1]=prompt, [2]=response_cb, [3]=error_cb, [4]=user_data */ + /* Data is an array: [0]=session, [1]=prompt, [2]=user_data */ gpointer* args = (gpointer*)data; AISession* session = (AISession*)args[0]; auto_gchar gchar* prompt = args[1]; - // ai_response_cb response_cb = (ai_response_cb)args[2]; - ai_error_cb error_cb = (ai_error_cb)args[3]; - gpointer user_data = args[4]; + gpointer user_data = args[2]; log_debug("[AI-THREAD] Session: %s/%s", session->provider_name, session->model); log_debug("[AI-THREAD] API key length: %zu", session->api_key ? strlen(session->api_key) : 0); @@ -666,12 +614,16 @@ _ai_request_thread(gpointer data) if (!session->api_key || strlen(session->api_key) == 0) { auto_gchar gchar* error_msg = g_strdup_printf("No API key set for provider '%s'. Use '/ai set token %s ' to configure.", session->provider_name, session->provider_name); - ai_callback_data_t* cb_data = g_new0(ai_callback_data_t, 1); - cb_data->error_cb = error_cb; - cb_data->user_data = user_data; - cb_data->error = g_strdup(error_msg); - cb_data->is_error = TRUE; - _ai_invoke_callback(cb_data); + log_error("AI request failed for %s/%s: %s", session->provider_name, session->model, error_msg); + + /* Display error directly in AI window */ + pthread_mutex_lock(&lock); + ProfAiWin* aiwin = (ProfAiWin*)user_data; + if (aiwin && aiwin->memcheck == PROFAIWIN_MEMCHECK) { + aiwin_display_error(aiwin, error_msg); + } + pthread_mutex_unlock(&lock); + g_free(args); return NULL; } @@ -679,13 +631,17 @@ _ai_request_thread(gpointer data) CURL* curl = curl_easy_init(); log_debug("[AI-THREAD] Curl initialized: %s", curl ? "OK" : "FAILED"); if (!curl) { - ai_callback_data_t* cb_data = g_new0(ai_callback_data_t, 1); - cb_data->error_cb = error_cb; - cb_data->user_data = user_data; - cb_data->error = g_strdup("Failed to initialize curl."); - cb_data->is_error = TRUE; - log_debug("[AI-THREAD] Invoking error callback (curl init failed)"); - _ai_invoke_callback(cb_data); + log_error("AI request failed for %s/%s: Failed to initialize curl", + session->provider_name, session->model); + + /* Display error directly in AI window */ + pthread_mutex_lock(&lock); + ProfAiWin* aiwin = (ProfAiWin*)user_data; + if (aiwin && aiwin->memcheck == PROFAIWIN_MEMCHECK) { + aiwin_display_error(aiwin, "Failed to initialize curl."); + } + pthread_mutex_unlock(&lock); + g_free(args); return NULL; } @@ -740,66 +696,57 @@ _ai_request_thread(gpointer data) if (res != CURLE_OK) { auto_gchar gchar* error_msg = g_strdup(curl_easy_strerror(res)); log_error("AI request failed for %s/%s: %s", session->provider_name, session->model, error_msg); - log_debug("[AI-THREAD] Preparing error callback invocation..."); - ai_callback_data_t* cb_data = g_new0(ai_callback_data_t, 1); - cb_data->error_cb = error_cb; - cb_data->user_data = user_data; - cb_data->error = g_strdup(error_msg); - cb_data->is_error = TRUE; - log_debug("[AI-THREAD] Calling _ai_invoke_callback (error)"); - _ai_invoke_callback(cb_data); - log_debug("[AI-THREAD] _ai_invoke_callback (error) returned"); - } else { - /* Handle HTTP errors */ - if (http_code >= 400) { - log_debug("[AI-THREAD] HTTP error response body (%zu bytes): %s", - response.size, response.data ? response.data : "NULL"); - auto_gchar gchar* error_msg = g_strdup_printf("HTTP %ld: %s", http_code, - response.data ? response.data : "Unknown error"); - log_error("AI request failed for %s/%s: %s", session->provider_name, session->model, error_msg); - /* Display error directly in AI window using aiwin_display_error */ + /* Display error directly in AI window */ + pthread_mutex_lock(&lock); + ProfAiWin* aiwin = (ProfAiWin*)user_data; + if (aiwin && aiwin->memcheck == PROFAIWIN_MEMCHECK) { + aiwin_display_error(aiwin, error_msg); + } + pthread_mutex_unlock(&lock); + } else if (http_code >= 400) { + /* Handle HTTP errors */ + log_debug("[AI-THREAD] HTTP error response body (%zu bytes): %s", + response.size, response.data ? response.data : "NULL"); + auto_gchar gchar* error_msg = g_strdup_printf("HTTP %ld: %s", http_code, + response.data ? response.data : "Unknown error"); + log_error("AI request failed for %s/%s: %s", session->provider_name, session->model, error_msg); + + /* Display error directly in AI window */ + pthread_mutex_lock(&lock); + ProfAiWin* aiwin = (ProfAiWin*)user_data; + if (aiwin && aiwin->memcheck == PROFAIWIN_MEMCHECK) { + aiwin_display_error(aiwin, error_msg); + } + pthread_mutex_unlock(&lock); + } else { + /* Parse response - transfer ownership to auto_gchar for cleanup */ + log_debug("[AI-THREAD] Raw API response (%zu bytes): %s", response.size, response.data ? response.data : "NULL"); + auto_gchar gchar* response_data = response.data; + response.data = NULL; + auto_gchar gchar* content = _parse_ai_response(response_data); + if (content) { + /* Add assistant response to history */ + ai_session_add_message(session, "assistant", content); + + /* Display response directly in AI window */ pthread_mutex_lock(&lock); ProfAiWin* aiwin = (ProfAiWin*)user_data; if (aiwin && aiwin->memcheck == PROFAIWIN_MEMCHECK) { - aiwin_display_error(aiwin, error_msg); + aiwin_display_response(aiwin, content); } pthread_mutex_unlock(&lock); - - log_debug("[AI-THREAD] Displayed HTTP error via aiwin_display_error"); } else { - /* Parse response - transfer ownership to auto_gchar for cleanup */ - log_debug("[AI-THREAD] Raw API response (%zu bytes): %s", response.size, response.data ? response.data : "NULL"); - auto_gchar gchar* response_data = response.data; - response.data = NULL; - auto_gchar gchar* content = _parse_ai_response(response_data); - if (content) { - /* Add assistant response to history */ - ai_session_add_message(session, "assistant", content); + log_error("AI response parse failed for %s/%s: %.200s...", + session->provider_name, session->model, response_data); - /* Display response directly in AI window using aiwin_display_response */ - pthread_mutex_lock(&lock); - ProfAiWin* aiwin = (ProfAiWin*)user_data; - if (aiwin && aiwin->memcheck == PROFAIWIN_MEMCHECK) { - aiwin_display_response(aiwin, content); - } - pthread_mutex_unlock(&lock); - - log_debug("[AI-THREAD] Displayed AI response via aiwin_display_response"); - } else { - log_error("AI response parse failed for %s/%s: %.200s...", - session->provider_name, session->model, response_data); - - /* Display parse error directly in AI window using aiwin_display_error */ - pthread_mutex_lock(&lock); - ProfAiWin* aiwin = (ProfAiWin*)user_data; - if (aiwin && aiwin->memcheck == PROFAIWIN_MEMCHECK) { - aiwin_display_error(aiwin, "Failed to parse AI response."); - } - pthread_mutex_unlock(&lock); - - log_debug("[AI-THREAD] Displayed parse error via aiwin_display_error"); + /* Display parse error directly in AI window */ + pthread_mutex_lock(&lock); + ProfAiWin* aiwin = (ProfAiWin*)user_data; + if (aiwin && aiwin->memcheck == PROFAIWIN_MEMCHECK) { + aiwin_display_error(aiwin, "Failed to parse AI response."); } + pthread_mutex_unlock(&lock); } } @@ -822,14 +769,11 @@ ai_send_prompt(AISession* session, const gchar* prompt, gpointer user_data) return FALSE; } - /* Prepare thread arguments - add message to history inside thread after building payload */ - gpointer* args = g_new0(gpointer, 6); + /* Prepare thread arguments: [0]=session, [1]=prompt, [2]=user_data */ + gpointer* args = g_new0(gpointer, 3); args[0] = ai_session_ref(session); args[1] = g_strdup(prompt); - args[2] = (ai_response_cb)aiwin_display_response; - args[3] = (ai_error_cb)aiwin_display_error; - args[4] = user_data; - args[5] = NULL; /* placeholder for built payload */ + args[2] = user_data; log_debug("[AI-PROMPT] Prepared args, creating thread..."); GThread* thread = g_thread_new("ai_request", _ai_request_thread, args); @@ -844,4 +788,4 @@ ai_send_prompt(AISession* session, const gchar* prompt, gpointer user_data) log_debug("[AI-PROMPT] Thread created successfully: %p", (void*)thread); g_thread_unref(thread); return TRUE; -} \ No newline at end of file +} diff --git a/src/ai/ai_client.h b/src/ai/ai_client.h index f61d6356..17083c39 100644 --- a/src/ai/ai_client.h +++ b/src/ai/ai_client.h @@ -3,20 +3,6 @@ #include -/** - * @brief Callback function for successful AI responses. - * @param response The AI response text. - * @param user_data User-provided data passed to the callback. - */ -typedef void (*ai_response_cb)(const gchar* response, gpointer user_data); - -/** - * @brief Callback function for AI errors. - * @param error_msg The error message. - * @param user_data User-provided data passed to the callback. - */ -typedef void (*ai_error_cb)(const gchar* error_msg, gpointer user_data); - /** * @brief AI message structure for conversation history. */ @@ -186,7 +172,7 @@ void ai_session_set_model(AISession* session, const gchar* model); * Send a prompt to the AI provider asynchronously. * @param session The AI session containing provider and model * @param prompt The prompt to send - * @param user_data User data to be passed to the callbacks + * @param user_data User data (ProfAiWin* for UI display) * @return TRUE if the request was successfully queued, FALSE otherwise */ gboolean ai_send_prompt(AISession* session, const gchar* prompt,