refactor(ai): consolidate per-provider prefs into [ai/<name>] sections
Per-provider state used to live as flat keys in [ai] with naming conventions to avoid collisions: <name>_url, <name>_models, <name>_default_model, and the bare <name> for the API token. Custom settings (/ai set custom) never made it to disk at all — they only lived in the runtime AIProvider.settings hash table and were lost on restart. Move every per-provider field into a [ai/<name>] subsection of the keyfile with stable key names: [ai/openai] url=https://api.openai.com/ key=sk-... default_model=gpt-4 models=gpt-4;gpt-3.5-turbo setting.tools=enabled [ai] now keeps only the global "defaults_initialized" sentinel. Migration runs once in _prefs_load: collect provider names from the old <name>_url keys, then move url/models/default_model/token of each provider into its subsection and drop the flat keys. Idempotent on subsequent loads. prefs_ai_* API surface kept compatible; storage backend changes only. Adds: prefs_ai_remove_section() — wipe whole subsection in one call prefs_ai_{set,get,remove,list}_setting() — persist custom settings ai_client wiring: - ai_set_provider_setting now writes to prefs as well as the runtime hash table; ai_client_init loads them back on startup. - ai_remove_provider replaces four prefs_ai_remove_* calls with a single prefs_ai_remove_section.
This commit is contained in:
@@ -352,16 +352,31 @@ ai_client_init(void)
|
||||
/* Load saved API keys from config */
|
||||
ai_load_keys();
|
||||
|
||||
/* Load cached models and persisted default-model for all providers */
|
||||
/* Load cached models, persisted default-model and custom settings for
|
||||
* all providers. */
|
||||
GList* provider_list = ai_list_providers();
|
||||
for (GList* curr = provider_list; curr; curr = g_list_next(curr)) {
|
||||
AIProvider* provider = (AIProvider*)curr->data;
|
||||
_ai_load_models_for_provider(provider);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
GList* settings = prefs_ai_list_settings(provider->name);
|
||||
for (GList* s = settings; s; s = g_list_next(s)) {
|
||||
const gchar* name = (const gchar*)s->data;
|
||||
auto_gchar gchar* val = prefs_ai_get_setting(provider->name, name);
|
||||
if (val) {
|
||||
if (!provider->settings) {
|
||||
provider->settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
|
||||
}
|
||||
g_hash_table_insert(provider->settings, g_strdup(name), g_strdup(val));
|
||||
}
|
||||
}
|
||||
prefs_free_ai_settings(settings);
|
||||
}
|
||||
g_list_free(provider_list);
|
||||
|
||||
@@ -457,11 +472,9 @@ ai_remove_provider(const gchar* name)
|
||||
if (!name || !providers)
|
||||
return FALSE;
|
||||
|
||||
/* 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);
|
||||
/* Wipe the whole [ai/<name>] subsection from config — url, key,
|
||||
* default_model, models, custom settings — in one call. */
|
||||
prefs_ai_remove_section(name);
|
||||
|
||||
/* Sync autocomplete before removing */
|
||||
autocomplete_remove(providers_ac, name);
|
||||
@@ -598,9 +611,11 @@ ai_set_provider_setting(const gchar* provider_name, const gchar* setting, const
|
||||
|
||||
if (value) {
|
||||
g_hash_table_insert(provider->settings, g_strdup(setting), g_strdup(value));
|
||||
prefs_ai_set_setting(provider_name, setting, value);
|
||||
log_info("Setting '%s' for provider '%s' set to: %s", setting, provider_name, value);
|
||||
} else {
|
||||
g_hash_table_remove(provider->settings, setting);
|
||||
prefs_ai_remove_setting(provider_name, setting);
|
||||
log_info("Setting '%s' removed for provider '%s'", setting, provider_name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,8 @@
|
||||
#define PREF_GROUP_PLUGINS "plugins"
|
||||
#define PREF_GROUP_EXECUTABLES "executables"
|
||||
#define PREF_GROUP_AI "ai"
|
||||
#define PREF_GROUP_AI_PREFIX "ai/" /* per-provider subsection prefix: [ai/<provider>] */
|
||||
#define PREF_AI_SETTING_PREFIX "setting." /* per-provider custom setting key prefix */
|
||||
|
||||
#define INPBLOCK_DEFAULT 1000
|
||||
|
||||
@@ -243,6 +245,69 @@ _prefs_load(void)
|
||||
g_key_file_remove_key(prefs, PREF_GROUP_UI, "titlebar.muc.title.jid", NULL);
|
||||
g_key_file_remove_key(prefs, PREF_GROUP_UI, "titlebar.muc.title.name", NULL);
|
||||
}
|
||||
|
||||
/* Migrate legacy flat [ai] layout
|
||||
* openai_url=..., openai=<token>, openai_models=..., openai_default_model=...
|
||||
* into per-provider subsections
|
||||
* [ai/openai] url=..., key=..., models=..., default_model=...
|
||||
* Idempotent: once the flat keys are gone, this is a no-op. */
|
||||
if (g_key_file_has_group(prefs, PREF_GROUP_AI)) {
|
||||
gsize key_count = 0;
|
||||
auto_gcharv gchar** all_keys = g_key_file_get_keys(prefs, PREF_GROUP_AI, &key_count, NULL);
|
||||
|
||||
/* Collect provider names from <name>_url keys first so we can migrate
|
||||
* the whole bundle (url + token + models + default_model) atomically
|
||||
* per provider, without misclassifying unrelated flat keys. */
|
||||
GHashTable* to_migrate = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
|
||||
for (gsize i = 0; i < key_count; i++) {
|
||||
const gchar* k = all_keys[i];
|
||||
if (g_str_has_suffix(k, "_url") && !g_str_has_suffix(k, "_models_url")) {
|
||||
gsize klen = strlen(k);
|
||||
if (klen > 4) {
|
||||
g_hash_table_add(to_migrate, g_strndup(k, klen - 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GList* provider_names = g_hash_table_get_keys(to_migrate);
|
||||
for (GList* curr = provider_names; curr; curr = g_list_next(curr)) {
|
||||
const gchar* name = (const gchar*)curr->data;
|
||||
auto_gchar gchar* group = g_strconcat(PREF_GROUP_AI_PREFIX, name, NULL);
|
||||
|
||||
const struct
|
||||
{
|
||||
const gchar* flat_suffix;
|
||||
const gchar* new_key;
|
||||
} map[] = {
|
||||
{ "_url", "url" },
|
||||
{ "_models", "models" },
|
||||
{ "_default_model", "default_model" },
|
||||
{ NULL, NULL },
|
||||
};
|
||||
for (int j = 0; map[j].flat_suffix; j++) {
|
||||
auto_gchar gchar* flat_key = g_strconcat(name, map[j].flat_suffix, NULL);
|
||||
if (g_key_file_has_key(prefs, PREF_GROUP_AI, flat_key, NULL)) {
|
||||
auto_gchar gchar* val = g_key_file_get_string(prefs, PREF_GROUP_AI, flat_key, NULL);
|
||||
if (val) {
|
||||
g_key_file_set_string(prefs, group, map[j].new_key, val);
|
||||
}
|
||||
g_key_file_remove_key(prefs, PREF_GROUP_AI, flat_key, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Token was stored as the bare provider name in [ai]. */
|
||||
if (g_key_file_has_key(prefs, PREF_GROUP_AI, name, NULL)) {
|
||||
auto_gchar gchar* val = g_key_file_get_string(prefs, PREF_GROUP_AI, name, NULL);
|
||||
if (val) {
|
||||
g_key_file_set_string(prefs, group, "key", val);
|
||||
}
|
||||
g_key_file_remove_key(prefs, PREF_GROUP_AI, name, NULL);
|
||||
}
|
||||
}
|
||||
g_list_free(provider_names);
|
||||
g_hash_table_destroy(to_migrate);
|
||||
}
|
||||
|
||||
_save_prefs();
|
||||
|
||||
boolean_choice_ac = autocomplete_new();
|
||||
@@ -1708,73 +1773,103 @@ _save_prefs(void)
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
* AI Token Management
|
||||
* AI provider preferences
|
||||
*
|
||||
* Each provider lives in its own [ai/<name>] subsection of the keyfile with:
|
||||
* url=...
|
||||
* key=<api token>
|
||||
* default_model=...
|
||||
* models=model1;model2;...
|
||||
* setting.<custom>=value (e.g. setting.tools=enabled)
|
||||
* The bare [ai] section keeps only the global "defaults_initialized" sentinel.
|
||||
* Legacy flat-key layouts are migrated on first load in _prefs_load().
|
||||
* ======================================================================== */
|
||||
|
||||
static gchar*
|
||||
_ai_section(const char* const provider)
|
||||
{
|
||||
return g_strconcat(PREF_GROUP_AI_PREFIX, provider, NULL);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_ai_set_string(const char* const provider, const char* const key, const char* const value)
|
||||
{
|
||||
if (!provider || !key || !value || !prefs)
|
||||
return FALSE;
|
||||
|
||||
auto_gchar gchar* group = _ai_section(provider);
|
||||
g_key_file_set_string(prefs, group, key, value);
|
||||
_save_prefs();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static char*
|
||||
_ai_get_string(const char* const provider, const char* const key)
|
||||
{
|
||||
if (!provider || !key || !prefs)
|
||||
return NULL;
|
||||
|
||||
auto_gchar gchar* group = _ai_section(provider);
|
||||
if (!g_key_file_has_key(prefs, group, key, NULL))
|
||||
return NULL;
|
||||
return g_key_file_get_string(prefs, group, key, NULL);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_ai_remove_string(const char* const provider, const char* const key)
|
||||
{
|
||||
if (!provider || !key || !prefs)
|
||||
return FALSE;
|
||||
|
||||
auto_gchar gchar* group = _ai_section(provider);
|
||||
if (!g_key_file_has_key(prefs, group, key, NULL))
|
||||
return FALSE;
|
||||
|
||||
g_key_file_remove_key(prefs, group, key, NULL);
|
||||
_save_prefs();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* --- Token --- */
|
||||
|
||||
gboolean
|
||||
prefs_ai_set_token(const char* const provider, const char* const token)
|
||||
{
|
||||
if (!provider)
|
||||
if (!provider || !prefs)
|
||||
return FALSE;
|
||||
|
||||
if (!prefs)
|
||||
return FALSE;
|
||||
|
||||
if (token) {
|
||||
g_key_file_set_string(prefs, PREF_GROUP_AI, provider, token);
|
||||
_save_prefs();
|
||||
return TRUE;
|
||||
} else {
|
||||
if (!token)
|
||||
return prefs_ai_remove_token(provider);
|
||||
}
|
||||
return _ai_set_string(provider, "key", token);
|
||||
}
|
||||
|
||||
gboolean
|
||||
prefs_ai_remove_token(const char* const provider)
|
||||
{
|
||||
if (!provider)
|
||||
return FALSE;
|
||||
|
||||
if (!prefs)
|
||||
return FALSE;
|
||||
|
||||
if (!g_key_file_has_key(prefs, PREF_GROUP_AI, provider, NULL)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_key_file_remove_key(prefs, PREF_GROUP_AI, provider, NULL);
|
||||
_save_prefs();
|
||||
return TRUE;
|
||||
return _ai_remove_string(provider, "key");
|
||||
}
|
||||
|
||||
char*
|
||||
prefs_ai_get_token(const char* const provider)
|
||||
{
|
||||
if (!provider || !prefs)
|
||||
return NULL;
|
||||
|
||||
return g_key_file_get_string(prefs, PREF_GROUP_AI, provider, NULL);
|
||||
return _ai_get_string(provider, "key");
|
||||
}
|
||||
|
||||
GList*
|
||||
prefs_ai_list_tokens(void)
|
||||
{
|
||||
if (!prefs || !g_key_file_has_group(prefs, PREF_GROUP_AI)) {
|
||||
if (!prefs)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GList* result = NULL;
|
||||
gsize len;
|
||||
auto_gcharv gchar** keys = g_key_file_get_keys(prefs, PREF_GROUP_AI, &len, NULL);
|
||||
|
||||
for (gsize i = 0; i < len; i++) {
|
||||
char* name = keys[i];
|
||||
auto_gchar gchar* value = g_key_file_get_string(prefs, PREF_GROUP_AI, name, NULL);
|
||||
if (value) {
|
||||
GList* providers = prefs_ai_list_providers();
|
||||
for (GList* curr = providers; curr; curr = g_list_next(curr)) {
|
||||
const gchar* name = (const gchar*)curr->data;
|
||||
auto_gchar gchar* tok = prefs_ai_get_token(name);
|
||||
if (tok && tok[0] != '\0') {
|
||||
result = g_list_append(result, g_strdup(name));
|
||||
}
|
||||
}
|
||||
|
||||
prefs_free_ai_providers(providers);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1786,72 +1881,45 @@ prefs_free_ai_tokens(GList* tokens)
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Models cache --- */
|
||||
|
||||
gboolean
|
||||
prefs_ai_set_models(const char* const provider, const gchar* const* models, gint count)
|
||||
{
|
||||
if (!provider || count <= 0)
|
||||
if (!provider || count <= 0 || !prefs)
|
||||
return FALSE;
|
||||
|
||||
if (!prefs)
|
||||
return FALSE;
|
||||
|
||||
/* Build semicolon-separated model list */
|
||||
GString* model_str = g_string_new("");
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (i > 0) {
|
||||
if (i > 0)
|
||||
g_string_append_c(model_str, ';');
|
||||
}
|
||||
g_string_append(model_str, models[i]);
|
||||
}
|
||||
|
||||
g_key_file_set_string(prefs, PREF_GROUP_AI, g_strconcat(provider, "_models", NULL), model_str->str);
|
||||
gboolean ok = _ai_set_string(provider, "models", model_str->str);
|
||||
g_string_free(model_str, TRUE);
|
||||
_save_prefs();
|
||||
return TRUE;
|
||||
return ok;
|
||||
}
|
||||
|
||||
gboolean
|
||||
prefs_ai_remove_models(const char* const provider)
|
||||
{
|
||||
if (!provider)
|
||||
return FALSE;
|
||||
|
||||
if (!prefs)
|
||||
return FALSE;
|
||||
|
||||
auto_gchar gchar* key = g_strconcat(provider, "_models", 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;
|
||||
return _ai_remove_string(provider, "models");
|
||||
}
|
||||
|
||||
GList*
|
||||
prefs_ai_get_models(const char* const provider)
|
||||
{
|
||||
if (!provider || !prefs)
|
||||
auto_gchar gchar* model_str = _ai_get_string(provider, "models");
|
||||
if (!model_str || model_str[0] == '\0')
|
||||
return NULL;
|
||||
|
||||
auto_gchar gchar* key = g_strconcat(provider, "_models", NULL);
|
||||
if (!g_key_file_has_key(prefs, PREF_GROUP_AI, key, NULL)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
auto_gchar gchar* model_str = g_key_file_get_string(prefs, PREF_GROUP_AI, key, NULL);
|
||||
if (!model_str || strlen(model_str) == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GList* result = NULL;
|
||||
gchar** models = g_strsplit(model_str, ";", -1);
|
||||
for (int i = 0; models[i] != NULL; i++) {
|
||||
result = g_list_append(result, g_strdup(models[i]));
|
||||
}
|
||||
g_strfreev(models);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1863,125 +1931,89 @@ prefs_free_ai_models(GList* models)
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
* AI Default Model (per provider)
|
||||
* ======================================================================== */
|
||||
/* --- Default model --- */
|
||||
|
||||
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;
|
||||
return _ai_set_string(provider, "default_model", model);
|
||||
}
|
||||
|
||||
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;
|
||||
return _ai_remove_string(provider, "default_model");
|
||||
}
|
||||
|
||||
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);
|
||||
return _ai_get_string(provider, "default_model");
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
* AI Provider Management
|
||||
* ======================================================================== */
|
||||
/* --- Provider URL --- */
|
||||
|
||||
gboolean
|
||||
prefs_ai_set_provider(const char* const provider, const char* const url)
|
||||
{
|
||||
if (!provider || !url)
|
||||
return FALSE;
|
||||
|
||||
if (!prefs)
|
||||
return FALSE;
|
||||
|
||||
/* Store URL with "_url" suffix to avoid collision with API key storage */
|
||||
g_key_file_set_string(prefs, PREF_GROUP_AI, g_strconcat(provider, "_url", NULL), url);
|
||||
_save_prefs();
|
||||
return TRUE;
|
||||
return _ai_set_string(provider, "url", url);
|
||||
}
|
||||
|
||||
gboolean
|
||||
prefs_ai_remove_provider(const char* const provider)
|
||||
{
|
||||
if (!provider)
|
||||
return FALSE;
|
||||
|
||||
if (!prefs)
|
||||
return FALSE;
|
||||
|
||||
auto_gchar gchar* url_key = g_strconcat(provider, "_url", NULL);
|
||||
if (!g_key_file_has_key(prefs, PREF_GROUP_AI, url_key, NULL)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_key_file_remove_key(prefs, PREF_GROUP_AI, url_key, NULL);
|
||||
_save_prefs();
|
||||
return TRUE;
|
||||
/* Provider presence is identified by the url key; removing it is what
|
||||
* makes prefs_ai_list_providers stop returning this name. The remaining
|
||||
* sub-keys are wiped through prefs_ai_remove_section(). */
|
||||
return _ai_remove_string(provider, "url");
|
||||
}
|
||||
|
||||
char*
|
||||
prefs_ai_get_provider_url(const char* const provider)
|
||||
{
|
||||
return _ai_get_string(provider, "url");
|
||||
}
|
||||
|
||||
gboolean
|
||||
prefs_ai_remove_section(const char* const provider)
|
||||
{
|
||||
if (!provider || !prefs)
|
||||
return NULL;
|
||||
return FALSE;
|
||||
|
||||
auto_gchar gchar* key = g_strconcat(provider, "_url", NULL);
|
||||
if (!g_key_file_has_key(prefs, PREF_GROUP_AI, key, NULL)) {
|
||||
return NULL;
|
||||
}
|
||||
auto_gchar gchar* group = _ai_section(provider);
|
||||
if (!g_key_file_has_group(prefs, group))
|
||||
return FALSE;
|
||||
|
||||
return g_key_file_get_string(prefs, PREF_GROUP_AI, key, NULL);
|
||||
g_key_file_remove_group(prefs, group, NULL);
|
||||
_save_prefs();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
GList*
|
||||
prefs_ai_list_providers(void)
|
||||
{
|
||||
if (!prefs || !g_key_file_has_group(prefs, PREF_GROUP_AI)) {
|
||||
if (!prefs)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GList* result = NULL;
|
||||
gsize len = 0;
|
||||
auto_gcharv gchar** keys = g_key_file_get_keys(prefs, PREF_GROUP_AI, &len, NULL);
|
||||
auto_gcharv gchar** groups = g_key_file_get_groups(prefs, &len);
|
||||
if (!groups)
|
||||
return NULL;
|
||||
|
||||
gsize prefix_len = strlen(PREF_GROUP_AI_PREFIX);
|
||||
for (gsize i = 0; i < len; i++) {
|
||||
char* key = keys[i];
|
||||
/* Match keys ending with "_url" that are not "_models" suffix */
|
||||
if (g_str_has_suffix(key, "_url") && !g_str_has_suffix(key, "_models_url")) {
|
||||
/* Extract provider name by removing "_url" suffix */
|
||||
gsize key_len = strlen(key);
|
||||
if (key_len > 4) {
|
||||
gchar* provider_name = g_strndup(key, key_len - 4);
|
||||
result = g_list_append(result, provider_name);
|
||||
const gchar* g = groups[i];
|
||||
if (g_str_has_prefix(g, PREF_GROUP_AI_PREFIX) && strlen(g) > prefix_len) {
|
||||
/* Only count groups that actually identify a provider (have url). */
|
||||
if (g_key_file_has_key(prefs, g, "url", NULL)) {
|
||||
result = g_list_append(result, g_strdup(g + prefix_len));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2004,7 +2036,7 @@ prefs_ai_get_providers(void)
|
||||
GList* result = NULL;
|
||||
|
||||
for (GList* curr = configured; curr; curr = g_list_next(curr)) {
|
||||
gchar* name = (gchar*)curr->data;
|
||||
const gchar* name = (const 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));
|
||||
@@ -2032,6 +2064,67 @@ prefs_ai_get_providers(void)
|
||||
return result;
|
||||
}
|
||||
|
||||
/* --- Custom per-provider settings (setting.<name>) --- */
|
||||
|
||||
gboolean
|
||||
prefs_ai_set_setting(const char* const provider, const char* const setting, const char* const value)
|
||||
{
|
||||
if (!provider || !setting)
|
||||
return FALSE;
|
||||
auto_gchar gchar* key = g_strconcat(PREF_AI_SETTING_PREFIX, setting, NULL);
|
||||
if (!value)
|
||||
return _ai_remove_string(provider, key);
|
||||
return _ai_set_string(provider, key, value);
|
||||
}
|
||||
|
||||
gboolean
|
||||
prefs_ai_remove_setting(const char* const provider, const char* const setting)
|
||||
{
|
||||
if (!provider || !setting)
|
||||
return FALSE;
|
||||
auto_gchar gchar* key = g_strconcat(PREF_AI_SETTING_PREFIX, setting, NULL);
|
||||
return _ai_remove_string(provider, key);
|
||||
}
|
||||
|
||||
char*
|
||||
prefs_ai_get_setting(const char* const provider, const char* const setting)
|
||||
{
|
||||
if (!provider || !setting)
|
||||
return NULL;
|
||||
auto_gchar gchar* key = g_strconcat(PREF_AI_SETTING_PREFIX, setting, NULL);
|
||||
return _ai_get_string(provider, key);
|
||||
}
|
||||
|
||||
GList*
|
||||
prefs_ai_list_settings(const char* const provider)
|
||||
{
|
||||
if (!provider || !prefs)
|
||||
return NULL;
|
||||
|
||||
auto_gchar gchar* group = _ai_section(provider);
|
||||
if (!g_key_file_has_group(prefs, group))
|
||||
return NULL;
|
||||
|
||||
GList* result = NULL;
|
||||
gsize len = 0;
|
||||
auto_gcharv gchar** keys = g_key_file_get_keys(prefs, group, &len, NULL);
|
||||
gsize prefix_len = strlen(PREF_AI_SETTING_PREFIX);
|
||||
for (gsize i = 0; i < len; i++) {
|
||||
if (g_str_has_prefix(keys[i], PREF_AI_SETTING_PREFIX) && strlen(keys[i]) > prefix_len) {
|
||||
result = g_list_append(result, g_strdup(keys[i] + prefix_len));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
prefs_free_ai_settings(GList* settings)
|
||||
{
|
||||
if (settings) {
|
||||
g_list_free_full(settings, g_free);
|
||||
}
|
||||
}
|
||||
|
||||
// get the preference group for a specific preference
|
||||
// for example the PREF_BEEP setting ("beep" in .profrc, see _get_key) belongs
|
||||
// to the [ui] section.
|
||||
|
||||
@@ -374,6 +374,10 @@ GList* prefs_ai_list_providers(void);
|
||||
void prefs_free_ai_providers(GList* providers);
|
||||
GList* prefs_ai_get_providers(void);
|
||||
|
||||
/* Wipe the whole [ai/<provider>] subsection (url, key, models, default_model,
|
||||
* custom settings) in one call. */
|
||||
gboolean prefs_ai_remove_section(const char* const provider);
|
||||
|
||||
/* AI model cache management */
|
||||
gboolean prefs_ai_set_models(const char* const provider, const gchar* const* models, gint count);
|
||||
gboolean prefs_ai_remove_models(const char* const provider);
|
||||
@@ -385,4 +389,12 @@ gboolean prefs_ai_set_default_model(const char* const provider, const char* cons
|
||||
gboolean prefs_ai_remove_default_model(const char* const provider);
|
||||
char* prefs_ai_get_default_model(const char* const provider);
|
||||
|
||||
/* AI per-provider custom settings (e.g. tools, search) — persisted as
|
||||
* setting.<name>=<value> inside [ai/<provider>]. Pass NULL value to remove. */
|
||||
gboolean prefs_ai_set_setting(const char* const provider, const char* const setting, const char* const value);
|
||||
gboolean prefs_ai_remove_setting(const char* const provider, const char* const setting);
|
||||
char* prefs_ai_get_setting(const char* const provider, const char* const setting);
|
||||
GList* prefs_ai_list_settings(const char* const provider);
|
||||
void prefs_free_ai_settings(GList* settings);
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user