mirror of
https://github.com/M66B/FairEmail.git
synced 2025-02-24 23:12:55 +00:00
OpenAI improvements
This commit is contained in:
parent
673897deb5
commit
025c2b752c
5 changed files with 92 additions and 61 deletions
10
FAQ.md
10
FAQ.md
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
24
app/src/main/res/drawable/twotone_smart_toy_24.xml
Normal file
24
app/src/main/res/drawable/twotone_smart_toy_24.xml
Normal 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>
|
|
@ -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" />
|
||||
|
||||
|
|
Loading…
Reference in a new issue