From 7f26f9d6f278b9f9e4b97a8859cb19f229579a12 Mon Sep 17 00:00:00 2001 From: M66B Date: Sat, 29 Aug 2026 09:25:45 +0200 Subject: [PATCH] Added model selector --- app/src/main/java/eu/faircode/email/AI.java | 10 ++++ .../email/FragmentOptionsIntegrations.java | 50 +++++++++++++++++++ .../main/java/eu/faircode/email/OpenAI.java | 11 ++++ .../layout/fragment_options_integrations.xml | 13 ++++- 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/eu/faircode/email/AI.java b/app/src/main/java/eu/faircode/email/AI.java index c83db45e63..5d4ba0042a 100644 --- a/app/src/main/java/eu/faircode/email/AI.java +++ b/app/src/main/java/eu/faircode/email/AI.java @@ -35,6 +35,7 @@ import org.jsoup.nodes.Document; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class AI { @@ -243,4 +244,13 @@ public class AI { Document doc = HtmlHelper.sanitizeView(context, JsoupEx.parse(html), false); return HtmlHelper.fromDocument(context, doc, null, null); } + + static List getModelList(Context context) throws JSONException, IOException { + if (OpenAI.isAvailable(context)) { + List models = OpenAI.getModelList(context); + Collections.sort(models); + return models; + } else + return new ArrayList<>(); + } } diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsIntegrations.java b/app/src/main/java/eu/faircode/email/FragmentOptionsIntegrations.java index 2f9f57d21f..9754620570 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptionsIntegrations.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptionsIntegrations.java @@ -19,6 +19,8 @@ package eu.faircode.email; Copyright 2018-2026 by Marcel Bokhorst (M66B) */ +import android.content.Context; +import android.content.DialogInterface; import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; @@ -39,6 +41,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.SwitchCompat; import androidx.cardview.widget.CardView; import androidx.preference.PreferenceManager; @@ -80,6 +83,7 @@ public class FragmentOptionsIntegrations extends FragmentBase implements SharedP private EditText etOpenAi; private TextInputLayout tilOpenAi; private EditText etOpenAiModel; + private ImageButton ibOpenAiModel; private EditText etOpenAiMaxTokens; private SwitchCompat swOpenMultiModal; private TextView tvOpenAiTemperature; @@ -157,6 +161,7 @@ public class FragmentOptionsIntegrations extends FragmentBase implements SharedP etOpenAi = view.findViewById(R.id.etOpenAi); tilOpenAi = view.findViewById(R.id.tilOpenAi); etOpenAiModel = view.findViewById(R.id.etOpenAiModel); + ibOpenAiModel = view.findViewById(R.id.ibOpenAiModel); etOpenAiMaxTokens = view.findViewById(R.id.etOpenAiMaxTokens); swOpenMultiModal = view.findViewById(R.id.swOpenMultiModal); tvOpenAiTemperature = view.findViewById(R.id.tvOpenAiTemperature); @@ -429,6 +434,7 @@ public class FragmentOptionsIntegrations extends FragmentBase implements SharedP public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { prefs.edit().putBoolean("openai_enabled", checked).apply(); etOpenAiModel.setEnabled(checked); + ibOpenAiModel.setEnabled(checked); etOpenAiMaxTokens.setEnabled(checked); swOpenMultiModal.setEnabled(checked); sbOpenAiTemperature.setEnabled(checked); @@ -513,6 +519,50 @@ public class FragmentOptionsIntegrations extends FragmentBase implements SharedP } }); + ibOpenAiModel.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + new SimpleTask>() { + @Override + protected List onExecute(Context context, Bundle args) throws Throwable { + return AI.getModelList(context); + } + + @Override + protected void onExecuted(Bundle args, List data) { + String current = prefs.getString("openai_model", null); + + CharSequence[] models = new CharSequence[data.size()]; + int selected = -1; + for (int i = 0; i < data.size(); i++) { + String model = data.get(i); + models[i] = model; + if (current != null && current.equals(model)) + selected = i; + } + + new AlertDialog.Builder(v.getContext()) + .setIcon(R.drawable.twotone_search_24) + .setTitle(R.string.title_advanced_openai_model) + .setSingleChoiceItems(models, selected, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String model = models[which].toString(); + etOpenAiModel.setText(model); + dialog.dismiss(); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + + @Override + protected void onException(Bundle args, Throwable ex) { + } + }.execute(FragmentOptionsIntegrations.this, new Bundle(), "ai:models"); + } + }); + etOpenAiMaxTokens.addTextChangedListener(new TextWatcher() { @Override public void afterTextChanged(Editable s) { diff --git a/app/src/main/java/eu/faircode/email/OpenAI.java b/app/src/main/java/eu/faircode/email/OpenAI.java index 0f3aefa58c..61dfe5f547 100644 --- a/app/src/main/java/eu/faircode/email/OpenAI.java +++ b/app/src/main/java/eu/faircode/email/OpenAI.java @@ -169,6 +169,17 @@ public class OpenAI { return choices; } + static List getModelList(Context context) throws JSONException, IOException { + JSONObject jresponse = call(context, "GET", "models", null); + List models = new ArrayList<>(); + JSONArray data = jresponse.getJSONArray("data"); + for (int i = 0; i < data.length(); i++) { + JSONObject item = data.getJSONObject(i); + models.add(item.getString("id")); + } + return models; + } + private static String getUri(Context context) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); String endpoint = prefs.getString("openai_uri", BuildConfig.OPENAI_ENDPOINT); diff --git a/app/src/main/res/layout/fragment_options_integrations.xml b/app/src/main/res/layout/fragment_options_integrations.xml index f11aa94e27..c6e9cb107f 100644 --- a/app/src/main/res/layout/fragment_options_integrations.xml +++ b/app/src/main/res/layout/fragment_options_integrations.xml @@ -663,10 +663,21 @@ android:layout_marginStart="12dp" android:inputType="text" android:textAppearance="@style/TextAppearance.AppCompat.Small" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toStartOf="@id/ibOpenAiModel" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tvOpenAiModel" /> + +