|
|
|
|
@@ -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.
|
|
|
|
|
|