diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index c9006cf957..24ab622427 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -163,6 +163,7 @@ import org.bouncycastle.operator.OutputEncryptor; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import org.bouncycastle.util.Store; +import org.json.JSONObject; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; @@ -2445,7 +2446,18 @@ public class FragmentCompose extends FragmentBase { String model = prefs.getString("openai_model", "gpt-3.5-turbo"); float temperature = prefs.getFloat("openai_temperature", 1.0f); - return OpenAI.completeChat(context, model, result.toArray(new OpenAI.Message[0]), temperature, 1); + OpenAI.Message[] completions = + OpenAI.completeChat(context, model, result.toArray(new OpenAI.Message[0]), temperature, 1); + + try { + Pair usage = OpenAI.getGrants(context); + args.putDouble("used", usage.first); + args.putDouble("granted", usage.second); + } catch (Throwable ex) { + Log.w(ex); + } + + return completions; } @NonNull @@ -2489,6 +2501,12 @@ public class FragmentCompose extends FragmentBase { etBody.setSelection(index + text.length() + 1); StyleHelper.markAsInserted(edit, index, index + text.length() + 1); + + if (args.containsKey("used") && args.containsKey("granted")) { + double used = args.getDouble("used"); + double granted = args.getDouble("granted"); + ToastEx.makeText(getContext(), String.format("$%.2f/%.2f", used, granted), Toast.LENGTH_LONG).show(); + } } @Override diff --git a/app/src/main/java/eu/faircode/email/OpenAI.java b/app/src/main/java/eu/faircode/email/OpenAI.java index b113444b9f..8a62411e2c 100644 --- a/app/src/main/java/eu/faircode/email/OpenAI.java +++ b/app/src/main/java/eu/faircode/email/OpenAI.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.net.Uri; import android.text.TextUtils; +import android.util.Pair; import androidx.annotation.NonNull; import androidx.preference.PreferenceManager; @@ -53,6 +54,34 @@ public class OpenAI { return (enabled && !TextUtils.isEmpty(apikey)); } + static Pair getGrants(Context context) throws JSONException, IOException { + // dashboard/billing/credit_grants + // { + // "object": "credit_summary", + // "total_granted": , + // "total_used": , + // "total_available": , + // "grants": { + // "object": "list", + // "data": [ + // { + // "object": "credit_grant", + // "id": ">", + // "grant_amount": , + // "used_amount": >, + // "effective_at": , + // "expires_at": + // } + // ] + // } + //} + + JSONObject grants = call(context, "GET", "dashboard/billing/credit_grants", null); + return new Pair<>( + grants.getDouble("total_used"), + grants.getDouble("total_granted")); + } + static Message[] completeChat(Context context, String model, Message[] messages, Float temperature, int n) throws JSONException, IOException { // https://platform.openai.com/docs/guides/chat/introduction // https://platform.openai.com/docs/api-reference/chat/create @@ -71,7 +100,7 @@ public class OpenAI { if (temperature != null) jquestion.put("temperature", temperature); jquestion.put("n", n); - JSONObject jresponse = call(context, "v1/chat/completions", jquestion); + JSONObject jresponse = call(context, "POST", "v1/chat/completions", jquestion); JSONArray jchoices = jresponse.getJSONArray("choices"); Message[] choices = new Message[jchoices.length()]; @@ -84,21 +113,18 @@ public class OpenAI { return choices; } - private static JSONObject call(Context context, String method, JSONObject args) throws JSONException, IOException { + private static JSONObject call(Context context, String method, String path, JSONObject args) throws JSONException, IOException { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); String apikey = prefs.getString("openai_apikey", null); // https://platform.openai.com/docs/api-reference/introduction - Uri uri = Uri.parse(URI_ENDPOINT).buildUpon().appendEncodedPath(method).build(); + Uri uri = Uri.parse(URI_ENDPOINT).buildUpon().appendEncodedPath(path).build(); Log.i("OpenAI uri=" + uri); - String json = args.toString(); - Log.i("OpenAI request=" + json); - URL url = new URL(uri.toString()); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("POST"); - connection.setDoOutput(true); + connection.setRequestMethod(method); + connection.setDoOutput(args != null); connection.setDoInput(true); connection.setReadTimeout(TIMEOUT * 1000); connection.setConnectTimeout(TIMEOUT * 1000); @@ -109,7 +135,11 @@ public class OpenAI { connection.connect(); try { - connection.getOutputStream().write(json.getBytes()); + if (args != null) { + String json = args.toString(); + Log.i("OpenAI request=" + json); + connection.getOutputStream().write(json.getBytes()); + } int status = connection.getResponseCode(); if (status != HttpURLConnection.HTTP_OK) {