diff --git a/src/ai/ai_client.c b/src/ai/ai_client.c index bd920b72..48f14ce1 100644 --- a/src/ai/ai_client.c +++ b/src/ai/ai_client.c @@ -67,6 +67,44 @@ _write_callback(void* ptr, size_t size, size_t nmemb, void* userdata) return realsize; } +/* ======================================================================== + * Thread-safe UI display helpers + * ======================================================================== */ + +/** + * Display an error message in the AI window (thread-safe). + * @param aiwin The AI window (may be NULL) + * @param error_msg The error message to display + */ +static void +_aiwin_display_error(ProfAiWin* aiwin, const gchar* error_msg) +{ + if (!aiwin || aiwin->memcheck != PROFAIWIN_MEMCHECK) { + log_warning("[AI-THREAD] Cannot display error: aiwin is NULL or invalid (msg: %s)", error_msg); + return; + } + pthread_mutex_lock(&lock); + aiwin_display_error(aiwin, error_msg); + pthread_mutex_unlock(&lock); +} + +/** + * Display a response message in the AI window (thread-safe). + * @param aiwin The AI window (may be NULL) + * @param response The response message to display + */ +static void +_aiwin_display_response(ProfAiWin* aiwin, const gchar* response) +{ + if (!aiwin || aiwin->memcheck != PROFAIWIN_MEMCHECK) { + log_warning("[AI-THREAD] Cannot display response: aiwin is NULL or invalid"); + return; + } + pthread_mutex_lock(&lock); + aiwin_display_response(aiwin, response); + pthread_mutex_unlock(&lock); +} + /* ======================================================================== * JSON helpers * ======================================================================== */ @@ -615,15 +653,7 @@ _ai_request_thread(gpointer data) 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); 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); - + _aiwin_display_error((ProfAiWin*)user_data, error_msg); g_free(args); return NULL; } @@ -633,15 +663,7 @@ _ai_request_thread(gpointer data) if (!curl) { 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); - + _aiwin_display_error((ProfAiWin*)user_data, "Failed to initialize curl."); g_free(args); return NULL; } @@ -696,14 +718,7 @@ _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); - - /* 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); + _aiwin_display_error((ProfAiWin*)user_data, error_msg); } else if (http_code >= 400) { /* Handle HTTP errors */ log_debug("[AI-THREAD] HTTP error response body (%zu bytes): %s", @@ -711,14 +726,7 @@ _ai_request_thread(gpointer data) 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); + _aiwin_display_error((ProfAiWin*)user_data, error_msg); } 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"); @@ -728,25 +736,11 @@ _ai_request_thread(gpointer 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_response(aiwin, content); - } - pthread_mutex_unlock(&lock); + _aiwin_display_response((ProfAiWin*)user_data, content); } 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 */ - 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); + _aiwin_display_error((ProfAiWin*)user_data, "Failed to parse AI response."); } }