Added AI interference to expression conditions

pull/217/head
M66B 11 months ago
parent a2f1febfe7
commit 9effd8a61e

@ -2905,6 +2905,7 @@ The following extra functions are available:
* *Jsoup()* (returns an array of selected strings; since version 1.2179) * *Jsoup()* (returns an array of selected strings; since version 1.2179)
* *Size(array)* (returns the number of items in an array; since version 1.2179) * *Size(array)* (returns the number of items in an array; since version 1.2179)
* *knownContact()* (returns a boolean indicating that the from/reply-to address is in the Android address book or in the local contacts database) * *knownContact()* (returns a boolean indicating that the from/reply-to address is in the Android address book or in the local contacts database)
* *AI(prompt)* (run interference with the configured AI using the specified prompt, returning the result as a string; since version 1.2243)
Example conditions: Example conditions:

@ -26,6 +26,7 @@ import android.text.SpannableStringBuilder;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import org.json.JSONException; import org.json.JSONException;
@ -78,6 +79,11 @@ public class AI {
} }
} }
return completeChat(context, id, body, reply, templatePrompt);
}
@NonNull
static Spanned completeChat(Context context, long id, CharSequence body, String reply, String prompt) throws JSONException, IOException {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (OpenAI.isAvailable(context)) { if (OpenAI.isAvailable(context)) {
@ -94,9 +100,9 @@ public class AI {
new OpenAI.Content(OpenAI.CONTENT_TEXT, systemPrompt)})); new OpenAI.Content(OpenAI.CONTENT_TEXT, systemPrompt)}));
if (reply == null) { if (reply == null) {
if (templatePrompt != null) if (prompt != null)
messages.add(new OpenAI.Message(OpenAI.USER, new OpenAI.Content[]{ messages.add(new OpenAI.Message(OpenAI.USER, new OpenAI.Content[]{
new OpenAI.Content(OpenAI.CONTENT_TEXT, templatePrompt)})); new OpenAI.Content(OpenAI.CONTENT_TEXT, prompt)}));
if (body instanceof Spannable && multimodal) if (body instanceof Spannable && multimodal)
messages.add(new OpenAI.Message(OpenAI.USER, messages.add(new OpenAI.Message(OpenAI.USER,
OpenAI.Content.get((Spannable) body, id, context))); OpenAI.Content.get((Spannable) body, id, context)));
@ -104,10 +110,10 @@ public class AI {
messages.add(new OpenAI.Message(OpenAI.USER, new OpenAI.Content[]{ messages.add(new OpenAI.Message(OpenAI.USER, new OpenAI.Content[]{
new OpenAI.Content(OpenAI.CONTENT_TEXT, body.toString())})); new OpenAI.Content(OpenAI.CONTENT_TEXT, body.toString())}));
} else { } else {
if (templatePrompt == null && body.length() > 0) if (prompt == null && body.length() > 0)
templatePrompt = body.toString(); prompt = body.toString();
messages.add(new OpenAI.Message(OpenAI.USER, new OpenAI.Content[]{ messages.add(new OpenAI.Message(OpenAI.USER, new OpenAI.Content[]{
new OpenAI.Content(OpenAI.CONTENT_TEXT, templatePrompt == null ? defaultPrompt : templatePrompt)})); new OpenAI.Content(OpenAI.CONTENT_TEXT, prompt == null ? defaultPrompt : prompt)}));
messages.add(new OpenAI.Message(OpenAI.USER, new OpenAI.Content[]{ messages.add(new OpenAI.Message(OpenAI.USER, new OpenAI.Content[]{
new OpenAI.Content(OpenAI.CONTENT_TEXT, reply)})); new OpenAI.Content(OpenAI.CONTENT_TEXT, reply)}));
} }
@ -132,14 +138,14 @@ public class AI {
List<Gemini.Message> messages = new ArrayList<>(); List<Gemini.Message> messages = new ArrayList<>();
if (reply == null) { if (reply == null) {
if (templatePrompt != null) if (prompt != null)
messages.add(new Gemini.Message(Gemini.USER, new String[]{templatePrompt})); messages.add(new Gemini.Message(Gemini.USER, new String[]{prompt}));
messages.add(new Gemini.Message(Gemini.USER, messages.add(new Gemini.Message(Gemini.USER,
new String[]{Gemini.truncateParagraphs(body.toString())})); new String[]{Gemini.truncateParagraphs(body.toString())}));
} else { } else {
if (templatePrompt == null && body.length() > 0) if (prompt == null && body.length() > 0)
templatePrompt = body.toString(); prompt = body.toString();
messages.add(new Gemini.Message(Gemini.USER, new String[]{templatePrompt == null ? defaultPrompt : templatePrompt})); messages.add(new Gemini.Message(Gemini.USER, new String[]{prompt == null ? defaultPrompt : prompt}));
messages.add(new Gemini.Message(Gemini.USER, new String[]{reply})); messages.add(new Gemini.Message(Gemini.USER, new String[]{reply}));
} }

@ -115,6 +115,7 @@ public class ExpressionHelper {
JsoupFunction fJsoup = new JsoupFunction(context, message); JsoupFunction fJsoup = new JsoupFunction(context, message);
SizeFunction fSize = new SizeFunction(); SizeFunction fSize = new SizeFunction();
KnownFunction fKnown = new KnownFunction(context, message); KnownFunction fKnown = new KnownFunction(context, message);
AIFunction fAI = new AIFunction(context, doc);
ContainsOperator oContains = new ContainsOperator(false); ContainsOperator oContains = new ContainsOperator(false);
ContainsOperator oMatches = new ContainsOperator(true); ContainsOperator oMatches = new ContainsOperator(true);
@ -130,6 +131,7 @@ public class ExpressionHelper {
configuration.getFunctionDictionary().addFunction("Jsoup", fJsoup); configuration.getFunctionDictionary().addFunction("Jsoup", fJsoup);
configuration.getFunctionDictionary().addFunction("Size", fSize); configuration.getFunctionDictionary().addFunction("Size", fSize);
configuration.getFunctionDictionary().addFunction("knownContact", fKnown); configuration.getFunctionDictionary().addFunction("knownContact", fKnown);
configuration.getFunctionDictionary().addFunction("AI", fAI);
configuration.getOperatorDictionary().addOperator("Contains", oContains); configuration.getOperatorDictionary().addOperator("Contains", oContains);
configuration.getOperatorDictionary().addOperator("Matches", oMatches); configuration.getOperatorDictionary().addOperator("Matches", oMatches);
@ -179,6 +181,17 @@ public class ExpressionHelper {
for (String variable : expression.getUsedVariables()) for (String variable : expression.getUsedVariables())
if ("text".equalsIgnoreCase(variable)) if ("text".equalsIgnoreCase(variable))
return true; return true;
expression.validate();
for (ASTNode node : expression.getAllASTNodes()) {
Token token = node.getToken();
Log.i("EXPR token=" + token.getType() + ":" + token.getValue());
if (token.getType() == Token.TokenType.FUNCTION &&
"AI".equalsIgnoreCase(token.getValue())) {
Log.i("EXPR needs body");
return true;
}
}
} catch (Throwable ex) { } catch (Throwable ex) {
Log.e("EXPR", ex); Log.e("EXPR", ex);
} }
@ -444,6 +457,36 @@ public class ExpressionHelper {
} }
} }
@FunctionParameter(name = "value")
public static class AIFunction extends AbstractFunction {
private final Context context;
private final Document doc;
AIFunction(Context context, Document doc) {
this.context = context;
this.doc = doc;
}
@Override
public EvaluationValue evaluate(
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
String result = null;
try {
if (doc != null && parameterValues.length == 1) {
String prompt = parameterValues[0].getStringValue();
if (!TextUtils.isEmpty(prompt))
result = AI.completeChat(context, -1L, doc.text(), null, prompt).toString();
}
} catch (Throwable ex) {
Log.w(ex);
}
Log.i("EXPR AI()=" + result);
return expression.convertValue(result);
}
}
@InfixOperator(precedence = OPERATOR_PRECEDENCE_COMPARISON) @InfixOperator(precedence = OPERATOR_PRECEDENCE_COMPARISON)
public static class ContainsOperator extends AbstractOperator { public static class ContainsOperator extends AbstractOperator {
private final boolean regex; private final boolean regex;

@ -8,19 +8,10 @@
<style> <style>
code{white-space: pre-wrap;} code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;} span.smallcaps{font-variant: small-caps;}
div.columns{display: flex; gap: min(4vw, 1.5em);} span.underline{text-decoration: underline;}
div.column{flex: auto; overflow-x: auto;} div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
/* The extra [class] is a hack that increases specificity enough to ul.task-list{list-style: none;}
override a similar rule in reveal.js */
ul.task-list[class]{list-style: none;}
ul.task-list li input[type="checkbox"] {
font-size: inherit;
width: 0.8em;
margin: 0 0.8em 0.2em -1.6em;
vertical-align: middle;
}
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style> </style>
<link rel="shortcut icon" href="https://raw.githubusercontent.com/M66B/FairEmail/master/app/src/main/ic_launcher-web.png"> <link rel="shortcut icon" href="https://raw.githubusercontent.com/M66B/FairEmail/master/app/src/main/ic_launcher-web.png">
<meta name="theme-color" content="#006db3"> <meta name="theme-color" content="#006db3">
@ -1602,6 +1593,7 @@ X-Google-Original-From: Somebody &lt;somebody+extra@example.org&gt;</code></pre>
<li><em>Jsoup()</em> (returns an array of selected strings; since version 1.2179)</li> <li><em>Jsoup()</em> (returns an array of selected strings; since version 1.2179)</li>
<li><em>Size(array)</em> (returns the number of items in an array; since version 1.2179)</li> <li><em>Size(array)</em> (returns the number of items in an array; since version 1.2179)</li>
<li><em>knownContact()</em> (returns a boolean indicating that the from/reply-to address is in the Android address book or in the local contacts database)</li> <li><em>knownContact()</em> (returns a boolean indicating that the from/reply-to address is in the Android address book or in the local contacts database)</li>
<li><em>AI(prompt)</em> (run interference with the configured AI using the specified prompt, returning the result as a string; since version 1.2243)</li>
</ul> </ul>
<p>Example conditions:</p> <p>Example conditions:</p>
<p><code>header("X-Mailer") contains "Open-Xchange" &amp;&amp; from matches ".*service@.*"</code></p> <p><code>header("X-Mailer") contains "Open-Xchange" &amp;&amp; from matches ".*service@.*"</code></p>

Loading…
Cancel
Save