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**
Tap on the conversation 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).
Tap on the robot button in the top action bar of the message editor.
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.
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>

View File

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

View File

@ -40,7 +40,7 @@ public class OpenAI {
static final String URI_ENDPOINT = "https://api.openai.com/";
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) {
if (BuildConfig.PLAY_STORE_RELEASE)
@ -53,7 +53,7 @@ public class OpenAI {
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/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
android:id="@+id/menu_openai"
android:icon="@drawable/twotone_question_answer_24"
android:icon="@drawable/twotone_smart_toy_24"
android:title="@string/title_openai"
app:showAsAction="always" />