From de2d21a0ac69dc999fa5184ec5e965ab19eda6bc Mon Sep 17 00:00:00 2001 From: "jabber.developer2" Date: Thu, 14 May 2026 15:06:06 +0300 Subject: [PATCH] fix(ai): persistence and command-output fixes - Persist per-provider default model in [ai] prefs (set/get/remove helpers, load on init, clear with provider removal). Previously /ai set default-model only updated runtime state. - Stop re-seeding openai/perplexity into [ai] every time the configured provider list is empty. The defaults are now seeded once, guarded by a "defaults_initialized" sentinel, so removing every provider does not silently restore them. - /ai providers (no arg) now lists the configured providers with key status, instead of the hardcoded openai/perplexity blurb. - /ai models (fresh fetch) prints the model names it cached, not just a count. Previously the user had to follow up with --cached to actually see them. - /ai remove provider now also clears the persisted default model, token, and cached model list, leaving no orphan keys in [ai]. - Drop dead ai_models_find declaration from ai_client.h (no implementation in this tree). --- src/ai/ai_client.c | 32 ++++++++++---- src/ai/ai_client.h | 9 ---- src/command/cmd_funcs.c | 22 ++++------ src/config/preferences.c | 93 ++++++++++++++++++++++++++++++---------- src/config/preferences.h | 5 +++ 5 files changed, 107 insertions(+), 54 deletions(-) diff --git a/src/ai/ai_client.c b/src/ai/ai_client.c index c6b20366..00f8d007 100644 --- a/src/ai/ai_client.c +++ b/src/ai/ai_client.c @@ -296,13 +296,16 @@ ai_client_init(void) /* Load saved API keys from config */ ai_load_keys(); - /* Load cached models for all providers */ + /* Load cached models and persisted default-model for all providers */ GList* provider_list = ai_list_providers(); - GList* models_curr = provider_list; - while (models_curr) { - AIProvider* provider = (AIProvider*)models_curr->data; + for (GList* curr = provider_list; curr; curr = g_list_next(curr)) { + AIProvider* provider = (AIProvider*)curr->data; _ai_load_models_for_provider(provider); - models_curr = g_list_next(models_curr); + auto_gchar gchar* dm = prefs_ai_get_default_model(provider->name); + if (dm && dm[0] != '\0') { + g_free(provider->default_model); + provider->default_model = g_strdup(dm); + } } g_list_free(provider_list); @@ -400,6 +403,9 @@ ai_remove_provider(const gchar* name) /* Remove from config before removing from hash table */ prefs_ai_remove_provider(name); + prefs_ai_remove_default_model(name); + prefs_ai_remove_models(name); + prefs_ai_remove_token(name); /* Sync autocomplete before removing */ autocomplete_remove(providers_ac, name); @@ -501,6 +507,7 @@ ai_set_provider_default_model(const gchar* provider_name, const gchar* model) g_free(provider->default_model); provider->default_model = g_strdup(model); + prefs_ai_set_default_model(provider_name, model); log_info("Default model for provider '%s' set to: %s", provider_name, model); } @@ -979,9 +986,18 @@ _models_response_handler(AIProvider* provider, const gchar* response, gpointer u { _parse_and_cache_models(provider, response); - auto_gchar gchar* msg = g_strdup_printf("Cached %d models for provider '%s'. Use '/ai start ' to start a chat.", - g_list_length(provider->models), provider->name); - _aiwin_display_response(user_data, msg); + gint count = g_list_length(provider->models); + GString* msg = g_string_new(NULL); + g_string_append_printf(msg, "Cached %d models for provider '%s':", count, provider->name); + for (GList* curr = provider->models; curr; curr = g_list_next(curr)) { + g_string_append_printf(msg, "\n %s", (const gchar*)curr->data); + } + if (count > 0) { + g_string_append_printf(msg, "\nUse '/ai start %s ' to start a chat.", provider->name); + } + + auto_gchar gchar* out = g_string_free(msg, FALSE); + _aiwin_display_response(user_data, out); } static gpointer diff --git a/src/ai/ai_client.h b/src/ai/ai_client.h index 97ea8a9b..6b24145d 100644 --- a/src/ai/ai_client.h +++ b/src/ai/ai_client.h @@ -106,15 +106,6 @@ GList* ai_list_providers(void); */ gchar* ai_providers_find(const char* const search_str, gboolean previous, void* context); -/** - * Find a model name for autocomplete. - * @param search_str The search string - * @param previous Whether to go to previous match - * @param context ProfAiWin* for the current session - * @return Model name, or NULL if not found - */ -gchar* ai_models_find(const char* const search_str, gboolean previous, void* context); - /** * Reset the provider autocomplete state. * Called from cmd_ac_reset() to clear autocomplete state. diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index 4b089118..57e76e7d 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -11174,30 +11174,24 @@ cmd_ai_clear(ProfWin* window, const char* const command, gchar** args) gboolean cmd_ai_providers(ProfWin* window, const char* const command, gchar** args) { - if (args[1] == NULL || g_strcmp0(args[1], "list") != 0) { - // List all available providers - cons_show("Available AI providers:"); - cons_show(""); - cons_show(" openai - OpenAI API (https://api.openai.com)"); - cons_show(" perplexity - Perplexity API (https://api.perplexity.ai)"); - cons_show(""); - cons_show("Add custom providers with: /ai set provider "); - return TRUE; - } - - // List configured providers with key status cons_show("Configured providers:"); cons_show(""); GList* providers = ai_list_providers(); + if (!providers) { + cons_show(" (none configured)"); + cons_show(""); + cons_show("Add a provider with: /ai set provider "); + return TRUE; + } + for (GList* curr = providers; curr; curr = g_list_next(curr)) { AIProvider* provider = curr->data; - gchar* key = ai_get_provider_key(provider->name); + auto_gchar gchar* key = ai_get_provider_key(provider->name); cons_show(" %s", provider->name); cons_show(" URL: %s", provider->api_url); cons_show(" Key: %s", key ? "configured" : "NOT configured"); cons_show(""); - g_free(key); } g_list_free(providers); diff --git a/src/config/preferences.c b/src/config/preferences.c index 6580cb62..c1415b1d 100644 --- a/src/config/preferences.c +++ b/src/config/preferences.c @@ -1863,6 +1863,53 @@ prefs_free_ai_models(GList* models) } } +/* ======================================================================== + * AI Default Model (per provider) + * ======================================================================== */ + +gboolean +prefs_ai_set_default_model(const char* const provider, const char* const model) +{ + if (!provider || !model || !prefs) + return FALSE; + + if (model[0] == '\0') + return prefs_ai_remove_default_model(provider); + + auto_gchar gchar* key = g_strconcat(provider, "_default_model", NULL); + g_key_file_set_string(prefs, PREF_GROUP_AI, key, model); + _save_prefs(); + return TRUE; +} + +gboolean +prefs_ai_remove_default_model(const char* const provider) +{ + if (!provider || !prefs) + return FALSE; + + auto_gchar gchar* key = g_strconcat(provider, "_default_model", NULL); + if (!g_key_file_has_key(prefs, PREF_GROUP_AI, key, NULL)) + return FALSE; + + g_key_file_remove_key(prefs, PREF_GROUP_AI, key, NULL); + _save_prefs(); + return TRUE; +} + +char* +prefs_ai_get_default_model(const char* const provider) +{ + if (!provider || !prefs) + return NULL; + + auto_gchar gchar* key = g_strconcat(provider, "_default_model", NULL); + if (!g_key_file_has_key(prefs, PREF_GROUP_AI, key, NULL)) + return NULL; + + return g_key_file_get_string(prefs, PREF_GROUP_AI, key, NULL); +} + /* ======================================================================== * AI Provider Management * ======================================================================== */ @@ -1953,35 +2000,35 @@ prefs_free_ai_providers(GList* providers) GList* prefs_ai_get_providers(void) { - /* If user has configured any providers, return those */ GList* configured = prefs_ai_list_providers(); - if (configured && g_list_length(configured) > 0) { - /* Build result list with name, url pairs */ - GList* result = NULL; - GList* curr = configured; - while (curr) { - gchar* name = (gchar*)curr->data; - gchar* url = prefs_ai_get_provider_url(name); - if (url && strlen(url) > 0) { - result = g_list_append(result, g_strdup(name)); - result = g_list_append(result, g_strdup(url)); - } - g_free(url); - curr = g_list_next(curr); + GList* result = NULL; + + for (GList* curr = configured; curr; curr = g_list_next(curr)) { + gchar* name = (gchar*)curr->data; + auto_gchar gchar* url = prefs_ai_get_provider_url(name); + if (url && strlen(url) > 0) { + result = g_list_append(result, g_strdup(name)); + result = g_list_append(result, g_strdup(url)); } - prefs_free_ai_providers(configured); - return result; } prefs_free_ai_providers(configured); - prefs_ai_set_provider("openai", "https://api.openai.com/"); - prefs_ai_set_provider("perplexity", "https://api.perplexity.ai/"); + /* Seed openai/perplexity once on very first init only — driven by the + * "defaults_initialized" sentinel so a user who removes every provider + * does not get them silently re-added. */ + if (prefs && !g_key_file_get_boolean(prefs, PREF_GROUP_AI, "defaults_initialized", NULL)) { + if (!result) { + prefs_ai_set_provider("openai", "https://api.openai.com/"); + prefs_ai_set_provider("perplexity", "https://api.perplexity.ai/"); + result = g_list_append(result, g_strdup("openai")); + result = g_list_append(result, g_strdup("https://api.openai.com/")); + result = g_list_append(result, g_strdup("perplexity")); + result = g_list_append(result, g_strdup("https://api.perplexity.ai/")); + } + g_key_file_set_boolean(prefs, PREF_GROUP_AI, "defaults_initialized", TRUE); + _save_prefs(); + } - GList* result = NULL; - result = g_list_append(result, g_strdup("openai")); - result = g_list_append(result, g_strdup("https://api.openai.com/")); - result = g_list_append(result, g_strdup("perplexity")); - result = g_list_append(result, g_strdup("https://api.perplexity.ai/")); return result; } diff --git a/src/config/preferences.h b/src/config/preferences.h index b62d2e36..16a5dab6 100644 --- a/src/config/preferences.h +++ b/src/config/preferences.h @@ -380,4 +380,9 @@ gboolean prefs_ai_remove_models(const char* const provider); GList* prefs_ai_get_models(const char* const provider); void prefs_free_ai_models(GList* models); +/* AI default model per provider */ +gboolean prefs_ai_set_default_model(const char* const provider, const char* const model); +gboolean prefs_ai_remove_default_model(const char* const provider); +char* prefs_ai_get_default_model(const char* const provider); + #endif