diff --git a/src/ai/ai_client.c b/src/ai/ai_client.c index c40189c8..30a02f9d 100644 --- a/src/ai/ai_client.c +++ b/src/ai/ai_client.c @@ -121,18 +121,27 @@ _aiwin_display_error(gpointer user_data, const gchar* error_msg) } /** - * Display a response message in the AI window (thread-safe). + * Display a response message. If aiwin is provided and valid, display in AI window. + * Otherwise, display in console (for commands run from console). * @param user_data The original user_data pointer (may be NULL) * @param response The response message to display */ static void _aiwin_display_response(gpointer user_data, const gchar* response) { - ProfAiWin* aiwin = _aiwin_validate(user_data); - if (!aiwin) { - log_warning("[AI-THREAD] Cannot display response: aiwin is invalid or window was closed"); + if (!response || strlen(response) == 0) { return; } + + /* Try to display in AI window if available */ + ProfAiWin* aiwin = _aiwin_validate(user_data); + if (!aiwin) { + pthread_mutex_lock(&lock); + cons_show("%s", response); + pthread_mutex_unlock(&lock); + return; + } + pthread_mutex_lock(&lock); aiwin_display_response(aiwin, response); pthread_mutex_unlock(&lock); @@ -1270,6 +1279,64 @@ _parse_ai_response(const gchar* response_json) return NULL; } +/** + * Extract error message from an API error JSON response. + * Handles format: {"error":{"message":"...","type":"...","code":...}} + */ +static gchar* +_parse_error_response(const gchar* error_json) +{ + if (!error_json || strlen(error_json) == 0) + return NULL; + + /* Look for "message":"..." inside "error" object */ + const gchar* error_obj = strstr(error_json, "\"error\":"); + if (!error_obj) + return NULL; + + const gchar* message_key = strstr(error_obj, "\"message\":\""); + if (!message_key) + return NULL; + + message_key += strlen("\"message\":\""); + const gchar* msg_start = message_key; + const gchar* p = msg_start; + while (*p) { + if (*p == '\\' && *(p + 1) == '"') { + p += 2; + continue; + } + if (*p == '"') { + gsize len = p - msg_start; + gchar* result = g_new0(gchar, len + 1); + gchar* out = result; + const gchar* in = msg_start; + while (in < p) { + if (*in == '\\' && *(in + 1) == '"') { + *out++ = '"'; + in += 2; + } else if (*in == '\\' && *(in + 1) == 'n') { + *out++ = '\n'; + in += 2; + } else if (*in == '\\' && *(in + 1) == '\\') { + *out++ = '\\'; + in += 2; + } else if (*in == '\\' && *(in + 1) == 't') { + *out++ = '\t'; + in += 2; + } else { + *out++ = *in++; + } + } + *out = '\0'; + return result; + } + p++; + } + + return NULL; +} + static gpointer _ai_request_thread(gpointer data) { @@ -1328,7 +1395,11 @@ _ai_request_thread(gpointer data) log_debug("[AI-THREAD] API Request URL: %s", request_url); log_debug("[AI-THREAD] Model: %s", session->model); - /* Execute request with POST fields */ + /* Set URL and execute request */ + curl_easy_setopt(curl, CURLOPT_URL, request_url); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_payload); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60L); @@ -1348,8 +1419,9 @@ _ai_request_thread(gpointer data) /* 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"); + /* Try to extract the actual error message from the JSON response */ + auto_gchar gchar* parsed_error = _parse_error_response(response.data); + auto_gchar gchar* error_msg = parsed_error ? g_strdup_printf("HTTP %ld: %s", http_code, parsed_error) : g_strdup_printf("HTTP %ld: %s", http_code, response.data && strlen(response.data) > 0 ? response.data : "Unknown error"); log_error("AI request failed for %s/%s: %s", session->provider_name, session->model, error_msg); _aiwin_display_error(user_data, error_msg); } else {