Guardar histórico e melhorar estabilidade do chat
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.example.api;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
@@ -16,6 +17,7 @@ import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
@@ -32,11 +34,20 @@ import java.nio.charset.StandardCharsets;
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
private static final String API_URL = "https://text.pollinations.ai/openai";
|
||||
private static final String PREFS_NAME = "chat_preferences";
|
||||
private static final String HISTORY_KEY = "chat_history";
|
||||
private static final int MAX_CONTEXT_MESSAGES = 24;
|
||||
private static final int MAX_ATTEMPTS = 3;
|
||||
private static final String WELCOME_MESSAGE =
|
||||
"Olá! Sou uma IA ligada a uma API. Faz-me uma pergunta.";
|
||||
|
||||
private LinearLayout messagesContainer;
|
||||
private ScrollView chatScroll;
|
||||
private EditText promptInput;
|
||||
private MaterialButton sendButton;
|
||||
private MaterialButton newChatButton;
|
||||
private SharedPreferences preferences;
|
||||
private JSONArray conversation = new JSONArray();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -54,10 +65,14 @@ public class MainActivity extends AppCompatActivity {
|
||||
chatScroll = findViewById(R.id.chatScroll);
|
||||
promptInput = findViewById(R.id.promptInput);
|
||||
sendButton = findViewById(R.id.sendButton);
|
||||
newChatButton = findViewById(R.id.newChatButton);
|
||||
preferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
|
||||
|
||||
addMessage("Olá! Sou uma IA ligada a uma API. Faz-me uma pergunta.", false);
|
||||
loadConversation();
|
||||
renderConversation();
|
||||
|
||||
sendButton.setOnClickListener(v -> sendPrompt());
|
||||
newChatButton.setOnClickListener(v -> confirmNewChat());
|
||||
promptInput.setOnEditorActionListener((v, actionId, event) -> {
|
||||
if (actionId == EditorInfo.IME_ACTION_SEND) {
|
||||
sendPrompt();
|
||||
@@ -74,17 +89,21 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
promptInput.setText("");
|
||||
addConversationMessage("user", prompt);
|
||||
addMessage(prompt, true);
|
||||
TextView loadingMessage = addMessage("A pensar...", false);
|
||||
setSending(true);
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
String answer = askAi(prompt);
|
||||
runOnUiThread(() -> loadingMessage.setText(answer));
|
||||
String answer = askAiWithRetry();
|
||||
runOnUiThread(() -> {
|
||||
loadingMessage.setText(answer);
|
||||
addConversationMessage("assistant", answer);
|
||||
});
|
||||
} catch (Exception exception) {
|
||||
runOnUiThread(() -> loadingMessage.setText(
|
||||
"Não consegui ligar à API agora. Verifica a internet e tenta novamente."
|
||||
"A API está ocupada ou sem ligação. Tenta enviar novamente dentro de alguns segundos."
|
||||
));
|
||||
} finally {
|
||||
runOnUiThread(() -> setSending(false));
|
||||
@@ -92,11 +111,85 @@ public class MainActivity extends AppCompatActivity {
|
||||
}).start();
|
||||
}
|
||||
|
||||
private String askAiWithRetry() throws Exception {
|
||||
Exception lastError = null;
|
||||
for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
||||
try {
|
||||
return askAi();
|
||||
} catch (Exception exception) {
|
||||
lastError = exception;
|
||||
if (attempt < MAX_ATTEMPTS) {
|
||||
Thread.sleep(attempt * 1200L);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw lastError == null ? new IOException("API unavailable") : lastError;
|
||||
}
|
||||
|
||||
private void setSending(boolean sending) {
|
||||
sendButton.setEnabled(!sending);
|
||||
newChatButton.setEnabled(!sending);
|
||||
sendButton.setText(sending ? "..." : "Enviar");
|
||||
}
|
||||
|
||||
private void loadConversation() {
|
||||
String savedHistory = preferences.getString(HISTORY_KEY, "");
|
||||
if (savedHistory != null && !savedHistory.isEmpty()) {
|
||||
try {
|
||||
conversation = new JSONArray(savedHistory);
|
||||
} catch (Exception ignored) {
|
||||
conversation = new JSONArray();
|
||||
}
|
||||
}
|
||||
|
||||
if (conversation.length() == 0) {
|
||||
addConversationMessage("assistant", WELCOME_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private void renderConversation() {
|
||||
messagesContainer.removeAllViews();
|
||||
for (int i = 0; i < conversation.length(); i++) {
|
||||
JSONObject message = conversation.optJSONObject(i);
|
||||
if (message == null) {
|
||||
continue;
|
||||
}
|
||||
String role = message.optString("role", "");
|
||||
String content = message.optString("content", "");
|
||||
if (!content.isEmpty()) {
|
||||
addMessage(content, "user".equals(role));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addConversationMessage(String role, String content) {
|
||||
try {
|
||||
JSONObject message = new JSONObject();
|
||||
message.put("role", role);
|
||||
message.put("content", content);
|
||||
conversation.put(message);
|
||||
preferences.edit().putString(HISTORY_KEY, conversation.toString()).apply();
|
||||
} catch (Exception ignored) {
|
||||
// The message is still visible even if local storage is unavailable.
|
||||
}
|
||||
}
|
||||
|
||||
private void confirmNewChat() {
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setTitle("Começar um novo chat?")
|
||||
.setMessage("A conversa guardada neste dispositivo será apagada.")
|
||||
.setNegativeButton("Cancelar", null)
|
||||
.setPositiveButton("Apagar", (dialog, which) -> resetConversation())
|
||||
.show();
|
||||
}
|
||||
|
||||
private void resetConversation() {
|
||||
conversation = new JSONArray();
|
||||
preferences.edit().remove(HISTORY_KEY).apply();
|
||||
addConversationMessage("assistant", WELCOME_MESSAGE);
|
||||
renderConversation();
|
||||
}
|
||||
|
||||
private TextView addMessage(String message, boolean fromUser) {
|
||||
LinearLayout row = new LinearLayout(this);
|
||||
row.setGravity(fromUser ? Gravity.END : Gravity.START);
|
||||
@@ -117,50 +210,58 @@ public class MainActivity extends AppCompatActivity {
|
||||
return bubble;
|
||||
}
|
||||
|
||||
private String askAi(String userPrompt) throws Exception {
|
||||
private String askAi() throws Exception {
|
||||
JSONObject systemMessage = new JSONObject();
|
||||
systemMessage.put("role", "system");
|
||||
systemMessage.put(
|
||||
"content",
|
||||
"Responde sempre em português europeu. "
|
||||
+ "Sê natural, curto e claro, como uma IA de chat para um trabalho da escola."
|
||||
"Responde sempre em português europeu. Sê natural, útil e claro. "
|
||||
+ "Mantém o contexto da conversa e responde como uma IA de chat."
|
||||
);
|
||||
|
||||
JSONObject userMessage = new JSONObject();
|
||||
userMessage.put("role", "user");
|
||||
userMessage.put("content", userPrompt);
|
||||
|
||||
JSONArray messages = new JSONArray();
|
||||
messages.put(systemMessage);
|
||||
messages.put(userMessage);
|
||||
|
||||
int firstMessage = Math.max(0, conversation.length() - MAX_CONTEXT_MESSAGES);
|
||||
for (int i = firstMessage; i < conversation.length(); i++) {
|
||||
JSONObject message = conversation.optJSONObject(i);
|
||||
if (message != null) {
|
||||
messages.put(message);
|
||||
}
|
||||
}
|
||||
|
||||
JSONObject requestBody = new JSONObject();
|
||||
requestBody.put("model", "openai");
|
||||
requestBody.put("messages", messages);
|
||||
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(API_URL).openConnection();
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setConnectTimeout(20000);
|
||||
connection.setReadTimeout(60000);
|
||||
connection.setDoOutput(true);
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
try {
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setConnectTimeout(20000);
|
||||
connection.setReadTimeout(70000);
|
||||
connection.setDoOutput(true);
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setRequestProperty("Accept", "application/json");
|
||||
|
||||
byte[] body = requestBody.toString().getBytes(StandardCharsets.UTF_8);
|
||||
try (OutputStream outputStream = connection.getOutputStream()) {
|
||||
outputStream.write(body);
|
||||
byte[] body = requestBody.toString().getBytes(StandardCharsets.UTF_8);
|
||||
try (OutputStream outputStream = connection.getOutputStream()) {
|
||||
outputStream.write(body);
|
||||
}
|
||||
|
||||
int responseCode = connection.getResponseCode();
|
||||
InputStream stream = responseCode >= 200 && responseCode < 300
|
||||
? connection.getInputStream()
|
||||
: connection.getErrorStream();
|
||||
String response = readStream(stream);
|
||||
|
||||
if (responseCode < 200 || responseCode >= 300) {
|
||||
throw new IOException("API error: " + responseCode);
|
||||
}
|
||||
|
||||
return readAiText(response);
|
||||
} finally {
|
||||
connection.disconnect();
|
||||
}
|
||||
|
||||
int responseCode = connection.getResponseCode();
|
||||
InputStream stream = responseCode >= 200 && responseCode < 300
|
||||
? connection.getInputStream()
|
||||
: connection.getErrorStream();
|
||||
String response = readStream(stream);
|
||||
|
||||
if (responseCode < 200 || responseCode >= 300) {
|
||||
throw new IOException("API error: " + responseCode);
|
||||
}
|
||||
|
||||
return readAiText(response);
|
||||
}
|
||||
|
||||
private String readStream(InputStream stream) throws IOException {
|
||||
@@ -200,4 +301,3 @@ public class MainActivity extends AppCompatActivity {
|
||||
return Math.round(value * getResources().getDisplayMetrics().density);
|
||||
}
|
||||
}
|
||||
q
|
||||
@@ -12,30 +12,52 @@
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="150dp"
|
||||
android:layout_height="160dp"
|
||||
android:background="@drawable/bg_chat_header"
|
||||
android:gravity="bottom"
|
||||
android:orientation="vertical"
|
||||
android:gravity="bottom|center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:paddingBottom="24dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:includeFontPadding="false"
|
||||
android:text="Chat IA"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="30sp"
|
||||
android:textStyle="bold" />
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="Pergunta alguma coisa e recebe uma resposta da IA."
|
||||
android:textColor="#EEF9F7"
|
||||
android:textSize="15sp" />
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:includeFontPadding="false"
|
||||
android:text="Chat IA"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="30sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="A conversa fica guardada neste dispositivo."
|
||||
android:textColor="#EEF9F7"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/newChatButton"
|
||||
android:layout_width="104dp"
|
||||
android:layout_height="46dp"
|
||||
android:text="Novo chat"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="13sp"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="#26FFFFFF"
|
||||
app:cornerRadius="18dp"
|
||||
app:strokeColor="#70FFFFFF"
|
||||
app:strokeWidth="1dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
|
||||
Reference in New Issue
Block a user