mirror of
https://git.jabber.space/devs/cproof.git
synced 2026-07-18 11:06:21 +00:00
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*.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user