OpenAI improvements

This commit is contained in:
M66B 2023-03-07 20:41:51 +01:00
parent 673897deb5
commit 025c2b752c
5 changed files with 92 additions and 61 deletions

10
FAQ.md
View File

@ -5238,14 +5238,16 @@ Cloud sync is an experimental feature. It is not available for the Play Store ve
**Usage** **Usage**
Tap on the conversation button in the top action bar of the message editor. Tap on the robot button in the top action bar of the message editor.
The selected text in the message editor and the first three paragraphs of the first three messages in the conversation will be used for [chat completion](https://platform.openai.com/docs/guides/chat/introduction). The text in the message editor and the first part of the message being replied to (maximum of 1,000 characters)
will be used for [chat completion](https://platform.openai.com/docs/guides/chat/introduction).
If text is selected in the message editor, only the selected text will be used.
For example: create a new draft and enter the text "*How far is the sun?*", and tap on the conversation button in the top action bar. For example: create a new draft and enter the text "*How far is the sun?*", and tap on the robot button in the top action bar.
OpenAI isn't very fast, so be patient. OpenAI isn't very fast, so be patient.
This feature is available in the GitHub version only and requires version 1.2052 or later. This feature is experimental and available in the GitHub version only and requires version 1.2052 or later.
<br> <br>

View File

@ -277,7 +277,6 @@ public class FragmentCompose extends FragmentBase {
private Group grpSignature; private Group grpSignature;
private Group grpReferenceHint; private Group grpReferenceHint;
private ImageButton ibOpenAi;
private ContentResolver resolver; private ContentResolver resolver;
private AdapterAttachment adapter; private AdapterAttachment adapter;
@ -300,6 +299,7 @@ public class FragmentCompose extends FragmentBase {
private List<EntityAttachment> last_attachments = null; private List<EntityAttachment> last_attachments = null;
private boolean saved = false; private boolean saved = false;
private String subject = null; private String subject = null;
private boolean chatting = false;
private Uri photoURI = null; private Uri photoURI = null;
@ -1759,9 +1759,9 @@ public class FragmentCompose extends FragmentBase {
}); });
menu.findItem(R.id.menu_translate).setActionView(ibTranslate); menu.findItem(R.id.menu_translate).setActionView(ibTranslate);
ibOpenAi = (ImageButton) infl.inflate(R.layout.action_button, null); ImageButton ibOpenAi = (ImageButton) infl.inflate(R.layout.action_button, null);
ibOpenAi.setId(View.generateViewId()); ibOpenAi.setId(View.generateViewId());
ibOpenAi.setImageResource(R.drawable.twotone_question_answer_24); ibOpenAi.setImageResource(R.drawable.twotone_smart_toy_24);
ibOpenAi.setContentDescription(getString(R.string.title_openai)); ibOpenAi.setContentDescription(getString(R.string.title_openai));
ibOpenAi.setOnClickListener(new View.OnClickListener() { ibOpenAi.setOnClickListener(new View.OnClickListener() {
@Override @Override
@ -1797,7 +1797,8 @@ public class FragmentCompose extends FragmentBase {
menu.findItem(R.id.menu_encrypt).setEnabled(state == State.LOADED); menu.findItem(R.id.menu_encrypt).setEnabled(state == State.LOADED);
menu.findItem(R.id.menu_translate).setEnabled(state == State.LOADED); menu.findItem(R.id.menu_translate).setEnabled(state == State.LOADED);
menu.findItem(R.id.menu_translate).setVisible(DeepL.isAvailable(context)); menu.findItem(R.id.menu_translate).setVisible(DeepL.isAvailable(context));
menu.findItem(R.id.menu_openai).setEnabled(state == State.LOADED); menu.findItem(R.id.menu_openai).setEnabled(state == State.LOADED && !chatting);
((ImageButton) menu.findItem(R.id.menu_openai).getActionView()).setEnabled(!chatting);
menu.findItem(R.id.menu_openai).setVisible(OpenAI.isAvailable(context)); menu.findItem(R.id.menu_openai).setVisible(OpenAI.isAvailable(context));
menu.findItem(R.id.menu_zoom).setEnabled(state == State.LOADED); menu.findItem(R.id.menu_zoom).setEnabled(state == State.LOADED);
menu.findItem(R.id.menu_style).setEnabled(state == State.LOADED); menu.findItem(R.id.menu_style).setEnabled(state == State.LOADED);
@ -2564,97 +2565,101 @@ public class FragmentCompose extends FragmentBase {
private void onOpenAi(View anchor) { private void onOpenAi(View anchor) {
int start = etBody.getSelectionStart(); int start = etBody.getSelectionStart();
int end = etBody.getSelectionEnd(); int end = etBody.getSelectionEnd();
boolean selection = (start >= 0 && end > start);
Editable edit = etBody.getText(); Editable edit = etBody.getText();
String body = (start >= 0 && end > start ? edit.subSequence(start, end) : edit) String body = (selection ? edit.subSequence(start, end) : edit).toString().trim();
.toString().trim();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putLong("id", working); args.putLong("id", working);
args.putString("body", body); args.putString("body", body);
args.putBoolean("selection", selection);
new SimpleTask<OpenAI.Message[]>() { new SimpleTask<OpenAI.Message[]>() {
@Override @Override
protected void onPreExecute(Bundle args) { protected void onPreExecute(Bundle args) {
if (ibOpenAi != null) chatting = true;
ibOpenAi.setEnabled(false); invalidateOptionsMenu();
} }
@Override @Override
protected void onPostExecute(Bundle args) { protected void onPostExecute(Bundle args) {
if (ibOpenAi != null) chatting = false;
ibOpenAi.setEnabled(true); invalidateOptionsMenu();
} }
@Override @Override
protected OpenAI.Message[] onExecute(Context context, Bundle args) throws Throwable { protected OpenAI.Message[] onExecute(Context context, Bundle args) throws Throwable {
long id = args.getLong("id"); long id = args.getLong("id");
String body = args.getString("body"); String body = args.getString("body");
boolean selection = args.getBoolean("selection");
DB db = DB.getInstance(context); DB db = DB.getInstance(context);
EntityMessage draft = db.message().getMessage(id); EntityMessage draft = db.message().getMessage(id);
if (draft == null) if (draft == null)
return null; return null;
List<EntityMessage> conversation = db.message().getMessagesByThread(draft.account, draft.thread, null, null); List<EntityMessage> inreplyto;
if (conversation == null) if (selection || TextUtils.isEmpty(draft.inreplyto))
return null; inreplyto = new ArrayList<>();
else
inreplyto = db.message().getMessagesByMsgId(draft.account, draft.inreplyto);
if (TextUtils.isEmpty(body) && conversation.size() == 0) List<OpenAI.Message> result = new ArrayList<>();
return null;
EntityFolder sent = db.folder().getFolderByType(draft.account, EntityFolder.SENT);
if (sent == null)
return null;
Collections.sort(conversation, new Comparator<EntityMessage>() {
@Override
public int compare(EntityMessage m1, EntityMessage m2) {
return Long.compare(m1.received, m2.received);
}
});
List<OpenAI.Message> messages = new ArrayList<>();
//messages.add(new OpenAI.Message("system", "You are a helpful assistant.")); //messages.add(new OpenAI.Message("system", "You are a helpful assistant."));
List<String> msgids = new ArrayList<>(); if (inreplyto.size() > 0 && inreplyto.get(0).content) {
for (EntityMessage message : conversation) { Document parsed = JsoupEx.parse(inreplyto.get(0).getFile(context));
if (Objects.equals(draft.msgid, message.msgid)) Document document = HtmlHelper.sanitizeView(context, parsed, false);
continue; Spanned spanned = HtmlHelper.fromDocument(context, document, null, null);
if (msgids.contains(message.msgid)) String[] paragraphs = spanned.toString().split("[\\r\\n]+");
continue;
msgids.add(message.msgid);
String text = HtmlHelper.getFullText(message.getFile(context)); int i = 0;
String[] paragraphs = text.split("[\\r\\n]+");
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (int i = 0; i < 3 && i < paragraphs.length; i++) while (i < paragraphs.length &&
sb.append(paragraphs[i]).append("\n"); sb.length() + paragraphs[i].length() + 1 < 1000)
messages.add(new OpenAI.Message("assistant", sb.toString())); sb.append(paragraphs[i++]).append('\n');
if (msgids.size() >= 3) String role = (MessageHelper.equalEmail(draft.from, inreplyto.get(0).from) ? "assistant" : "user");
break; result.add(new OpenAI.Message(role, sb.toString()));
} }
if (!TextUtils.isEmpty(body)) if (!TextUtils.isEmpty(body))
messages.add(new OpenAI.Message("user", body)); result.add(new OpenAI.Message("assistant", body));
if (messages.size() == 0) if (result.size() == 0)
return null; return null;
return OpenAI.complete(context, messages.toArray(new OpenAI.Message[0]), 1); return OpenAI.completeChat(context, result.toArray(new OpenAI.Message[0]), 1);
} }
@Override @Override
protected void onExecuted(Bundle args, OpenAI.Message[] messages) { protected void onExecuted(Bundle args, OpenAI.Message[] messages) {
if (messages != null && messages.length > 0) { if (messages == null || messages.length == 0)
int start = etBody.getSelectionEnd(); return;
String content = messages[0].getContent();
Editable edit = etBody.getText(); String text = messages[0].getContent()
edit.insert(start, content); .replaceAll("^\\n+", "").replaceAll("\\n+$", "");
int end = start + content.length();
etBody.setSelection(end); Editable edit = etBody.getText();
StyleHelper.markAsInserted(edit, start, end); int start = etBody.getSelectionStart();
} int end = etBody.getSelectionEnd();
int index;
if (etBody.hasSelection()) {
edit.delete(start, end);
index = start;
} else
index = end;
if (index < 0)
index = 0;
if (index > 0 && edit.charAt(index - 1) != '\n')
edit.insert(index++, "\n");
edit.insert(index, text + "\n");
etBody.setSelection(index + text.length() + 1);
StyleHelper.markAsInserted(edit, index, index + text.length() + 1);
} }
@Override @Override

View File

@ -40,7 +40,7 @@ public class OpenAI {
static final String URI_ENDPOINT = "https://api.openai.com/"; static final String URI_ENDPOINT = "https://api.openai.com/";
static final String URI_PRIVACY = "https://openai.com/policies/privacy-policy"; static final String URI_PRIVACY = "https://openai.com/policies/privacy-policy";
private static final int TIMEOUT = 20; // seconds private static final int TIMEOUT = 30; // seconds
static boolean isAvailable(Context context) { static boolean isAvailable(Context context) {
if (BuildConfig.PLAY_STORE_RELEASE) if (BuildConfig.PLAY_STORE_RELEASE)
@ -53,7 +53,7 @@ public class OpenAI {
return (enabled && !TextUtils.isEmpty(apikey)); return (enabled && !TextUtils.isEmpty(apikey));
} }
static Message[] complete(Context context, Message[] messages, int n) throws JSONException, IOException { static Message[] completeChat(Context context, Message[] messages, int n) throws JSONException, IOException {
// https://platform.openai.com/docs/guides/chat/introduction // https://platform.openai.com/docs/guides/chat/introduction
// https://platform.openai.com/docs/api-reference/chat/create // https://platform.openai.com/docs/api-reference/chat/create

View File

@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M18,7H6v12h12V7zM7.5,11.5C7.5,10.67 8.17,10 9,10s1.5,0.67 1.5,1.5S9.83,13 9,13S7.5,12.33 7.5,11.5zM16,17H8v-2h8V17zM15,13c-0.83,0 -1.5,-0.67 -1.5,-1.5S14.17,10 15,10s1.5,0.67 1.5,1.5S15.83,13 15,13z"
android:strokeAlpha="0.3"
android:fillAlpha="0.3"/>
<path
android:fillColor="@android:color/white"
android:pathData="M8,15h8v2h-8z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M20,9V7c0,-1.1 -0.9,-2 -2,-2h-3c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5H6C4.9,5 4,5.9 4,7v2c-1.66,0 -3,1.34 -3,3c0,1.66 1.34,3 3,3v4c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-4c1.66,0 3,-1.34 3,-3C23,10.34 21.66,9 20,9zM18,19H6V7h12V19z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M15,11.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/>
<path
android:fillColor="@android:color/white"
android:pathData="M9,11.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/>
</vector>

View File

@ -15,7 +15,7 @@
<item <item
android:id="@+id/menu_openai" android:id="@+id/menu_openai"
android:icon="@drawable/twotone_question_answer_24" android:icon="@drawable/twotone_smart_toy_24"
android:title="@string/title_openai" android:title="@string/title_openai"
app:showAsAction="always" /> app:showAsAction="always" />