refactor(ai): remove redundant callback layer from AI client
Some checks failed
CI Code / Check spelling (pull_request) Successful in 53s
CI Code / Check coding style (pull_request) Successful in 1m8s
CI Code / Linux (debian) (pull_request) Failing after 2m55s
CI Code / Linux (ubuntu) (pull_request) Failing after 3m4s
CI Code / Linux (arch) (pull_request) Failing after 3m7s
CI Code / Code Coverage (pull_request) Successful in 2m42s

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*.
This commit is contained in:
2026-05-01 13:34:00 +00:00
parent 00f11eb704
commit cead417e78
2 changed files with 70 additions and 140 deletions

View File

@@ -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 <key>' 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;
}
}

View File

@@ -3,20 +3,6 @@
#include <glib.h>
/**
* @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,