Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .idea/.name

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Einleitung (ImageToText)
Die letzte Aktualisierung hat der App eine wichtige Neuerung hinzugefügt. Jetzt können Nutzer direkt **Bilder** von ihren Handys auswählen. Die App kann dann den **Text aus den ausgewählten Bildern** extrahieren und ihn problemlos an **ChatGPT** weitergeben. Diese Erweiterung macht die App viel besser, da Nutzer nun beispielsweise mathematische Aufgaben von einem Blatt abfotografieren können und sofort die Lösung bekommen.
# Anforderungen
Die neueste Erweiterung, die eingeführt wurde, ermöglicht es nun, **Bilder auszuwählen** und ChatGPT den enthaltenen Text zu senden. Die Benutzer können mit dieser Funktion Textinformationen aus Bildern extrahieren und eine Antwort von ChatGPT erhalten.

# Umsetzung
Mithilfe von **Firebase ML Vision** kann die Erweiterung Bilder aus dem Handy auswählen und den darin enthaltenen Text auslesen. Die Methode **getScanButton().setOnClickListener** startet die Bildauswahl-Intent.

Nachdem das Bild ausgewählt wurde, wird die Methode **processImage(Uri imageUri)** verwendet. In diesem Fall wird das ausgewählte Bild in eine **Bitmap** umgewandelt und der Text wird mit **Firebase ML Vision extrahiert**. Der extrahierte Text wird dann in der Chat-Ansicht als Benutzernachricht angezeigt.

Um eine Antwort zu erhalten, wird der extrahierte Text dann a ChatGPT weitergeleitet. Die Kommunikation mit ChatGPT wird durch eine API-Anfrage mit einem API ermöglicht. ChatGPT-Nachrichten mit den Antworten werden dann angezeigt und vorgelesen.

Zusätzlich zur genannten Funktionalität habe ich im **XML-Layout** des **MainFragment** einen **Button** erstellt. Dieser Button dient dazu, den Auswahlprozess für das Bild zu starten und somit die oben beschriebene Prozedur auszulösen. Darüber hinaus wurde ein **ImageView** hinzugefügt, um das ausgewählte Bild anzuzeigen. Dies bietet den Benutzern eine visuelle Rückmeldung über das ausgewählte Bild, bevor der Text extrahiert und verarbeitet wird. In Kombination mit dem Button ermöglicht dies eine benutzerfreundliche Interaktion mit der Bildverarbeitungsfunktion der App.

### Firebase
Um die App nutzen zu können, muss zunächst Firebase in der Anwendung aktiviert werden. Die Bilder werden in der Datenbank gespeichert.

#### Aktivierung von Firebase
1. Öffnen Sie die **Toolbar**, die sich oben befindet.
2. Wählen Sie **Tools**.
3. Unter **Tools** wählen Sie **Firebase** aus.
4. Es erscheint eine umfangreiche Liste.
5. Suchen Sie nach **Firebase ML** und wählen Sie es aus.
6. Innerhalb von **Firebase ML** wählen Sie **Use Firebase ML to recognize text in image [Java]**.
7. Aktivieren Sie die Schritte **1. Connect your app to Firebase** und **3. Add Firebase ML to your app**.
8. Fertig!


# Probleme

Da das geplante Ziel erfolgreich erreicht wurde, gab es keine Probleme. Die gewünschten Funktionen wurden reibungslos eingeführt, und alle notwendigen Aspekte, wie die Auswahl von Bildern, die Extrahierung von Texten und die Kommunikation mit ChatGPT, wurden erfolgreich integriert. Daher verlief das Projekt ohne große Schwierigkeiten und die gewünschten Features wurden erfolgreich umgesetzt.
3 changes: 2 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
id 'com.android.application'
id 'com.google.gms.google-services'
}

android {
Expand Down Expand Up @@ -29,7 +30,7 @@ android {
}

dependencies {

implementation 'com.google.firebase:firebase-ml-vision:24.0.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
Expand Down
29 changes: 29 additions & 0 deletions app/google-services.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "1093072293771",
"project_id": "chatgpt-242c2",
"storage_bucket": "chatgpt-242c2.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:1093072293771:android:6d84ae93db15a4973f298e",
"android_client_info": {
"package_name": "de.fhdw.app_entwicklung.chatgpt"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyDE7-w8OA55sVON8E54muDETdl4KBh9ulM"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}
2 changes: 2 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

<uses-permission android:name="android.permission.INTERNET" />

<uses-permission android:name="android.permission.CAMERA" />

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
Expand Down
121 changes: 92 additions & 29 deletions app/src/main/java/de/fhdw/app_entwicklung/chatgpt/MainFragment.java
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
package de.fhdw.app_entwicklung.chatgpt;

import static android.app.Activity.RESULT_OK;

import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ScrollView;
import android.widget.TextView;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import com.google.android.gms.tasks.Task;
import com.google.firebase.ml.vision.FirebaseVision;
import com.google.firebase.ml.vision.common.FirebaseVisionImage;
import com.google.firebase.ml.vision.text.FirebaseVisionText;
import com.google.firebase.ml.vision.text.FirebaseVisionTextRecognizer;

import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;

import de.fhdw.app_entwicklung.chatgpt.model.Author;
import de.fhdw.app_entwicklung.chatgpt.model.Chat;
import de.fhdw.app_entwicklung.chatgpt.model.Message;
import de.fhdw.app_entwicklung.chatgpt.openai.IChatGpt;
import de.fhdw.app_entwicklung.chatgpt.openai.MockChatGpt;
import de.fhdw.app_entwicklung.chatgpt.openai.ChatGpt;
import de.fhdw.app_entwicklung.chatgpt.speech.LaunchSpeechRecognition;
import de.fhdw.app_entwicklung.chatgpt.speech.TextToSpeechTool;

Expand All @@ -36,7 +50,7 @@ public class MainFragment extends Fragment {
private final ActivityResultLauncher<LaunchSpeechRecognition.SpeechRecognitionArgs> getTextFromSpeech = registerForActivityResult(
new LaunchSpeechRecognition(),
query -> {
Message userMessage = new Message(Author.User, query);
Message userMessage = new Message(Author.User,"Text: \n"+ query);
chat.addMessage(userMessage);
if (chat.getMessages().size() > 1) {
getTextView().append(CHAT_SEPARATOR);
Expand All @@ -46,10 +60,10 @@ public class MainFragment extends Fragment {

MainActivity.backgroundExecutorService.execute(() -> {
String apiToken = prefs.getApiToken();
IChatGpt chatGpt = new MockChatGpt(apiToken);
ChatGpt chatGpt = new ChatGpt(apiToken);
String answer = chatGpt.getChatCompletion(chat);

Message answerMessage = new Message(Author.Assistant, answer);
Message answerMessage = new Message(Author.Assistant,"ChatGPT: \n"+ answer);
chat.addMessage(answerMessage);

MainActivity.uiThreadHandler.post(() -> {
Expand All @@ -61,12 +75,18 @@ public class MainFragment extends Fragment {
});
});

public MainFragment() {
}
private final ActivityResultLauncher<Intent> pickImageLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
Uri imageUri = result.getData().getData();
getImageView().setImageURI(imageUri);
processImage(imageUri);
}
});

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_main, container, false);
}

Expand All @@ -83,37 +103,76 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat

getAskButton().setOnClickListener(v ->
getTextFromSpeech.launch(new LaunchSpeechRecognition.SpeechRecognitionArgs(Locale.GERMAN)));

getResetButton().setOnClickListener(v -> {
chat = new Chat();
updateTextView();

});

getScanButton().setOnClickListener(v -> {
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
pickImageLauncher.launch(intent);

});

updateTextView();
}

@Override
public void onPause() {
super.onPause();

textToSpeech.stop();
}
private void processImage(Uri imageUri) {
try {
Bitmap bitmap = MediaStore.Images.Media.getBitmap(requireActivity().getContentResolver(), imageUri);

@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(EXTRA_DATA_CHAT, chat);
}
FirebaseVisionImage firebaseVisionImage = FirebaseVisionImage.fromBitmap(bitmap);

@Override
public void onDestroy() {
textToSpeech.destroy();
textToSpeech = null;
FirebaseVisionTextRecognizer textRecognizer = FirebaseVision.getInstance().getOnDeviceTextRecognizer();


Task<FirebaseVisionText> result = textRecognizer.processImage(firebaseVisionImage);

super.onDestroy();
result.addOnSuccessListener(firebaseVisionText -> {
String extractedText = firebaseVisionText.getText();
Message imageMessage = new Message(Author.User, "Text: \n" + extractedText);
chat.addMessage(imageMessage);

if (chat.getMessages().size() > 1) {
getTextView().append(CHAT_SEPARATOR);
}

getTextView().append(toString(imageMessage));
scrollToEnd();

MainActivity.backgroundExecutorService.execute(() -> {
String apiToken = prefs.getApiToken();
ChatGpt chatGpt = new ChatGpt(apiToken);

String answer = chatGpt.getChatCompletion(chat);
Message answerMessage = new Message(Author.Assistant, "ChatGPT: \n" + answer);
chat.addMessage(answerMessage);

MainActivity.uiThreadHandler.post(() -> {
getTextView().append(CHAT_SEPARATOR);
getTextView().append(toString(answerMessage));
scrollToEnd();
textToSpeech.speak(answer);
});
});
});

} catch (Exception e) {
e.printStackTrace();
}
}



private void updateTextView() {
getTextView().setText("");
List<Message> messages = chat.getMessages();
getImageView().setImageURI(null);
List<Message> messages = chat.getMessages().stream()
.filter(message -> message.author == Author.User || message.author == Author.Assistant)
.collect(Collectors.toList());
if (!messages.isEmpty()) {
getTextView().append(toString(messages.get(0)));
for (int i = 1; i < messages.size(); i++) {
Expand All @@ -124,6 +183,7 @@ private void updateTextView() {
scrollToEnd();
}


private void scrollToEnd() {
getScrollView().postDelayed(() -> getScrollView().fullScroll(ScrollView.FOCUS_DOWN), 300);
}
Expand All @@ -133,23 +193,26 @@ private CharSequence toString(Message message) {
}

private TextView getTextView() {
//noinspection ConstantConditions
return getView().findViewById(R.id.textView);
}

private Button getAskButton() {
//noinspection ConstantConditions
return getView().findViewById(R.id.button_ask);
}

private Button getResetButton() {
//noinspection ConstantConditions
return getView().findViewById(R.id.button_reset);
}

private Button getScanButton() {
return getView().findViewById(R.id.camera);
}

private ScrollView getScrollView() {
//noinspection ConstantConditions
return getView().findViewById(R.id.scrollview);
}

private ImageView getImageView() {
return getView().findViewById(R.id.image);
}
}
8 changes: 8 additions & 0 deletions app/src/main/res/drawable/baseline_app_shortcut_24.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M17,18H7V6h10v1h2V3c0,-1.1 -0.9,-2 -2,-2H7C5.9,1 5,1.9 5,3v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2v-4h-2V18z"/>
<path android:fillColor="@android:color/white" android:pathData="M20.38,9.62l0.62,1.38l0.62,-1.38l1.38,-0.62l-1.38,-0.62l-0.62,-1.38l-0.62,1.38l-1.38,0.62z"/>
<path android:fillColor="@android:color/white" android:pathData="M16,8l-1.25,2.75l-2.75,1.25l2.75,1.25l1.25,2.75l1.25,-2.75l2.75,-1.25l-2.75,-1.25z"/>
<path android:fillColor="@android:color/white" android:pathData="M21,13l-0.62,1.38l-1.38,0.62l1.38,0.62l0.62,1.38l0.62,-1.38l1.38,-0.62l-1.38,-0.62z"/>
</vector>
9 changes: 9 additions & 0 deletions app/src/main/res/drawable/document_scanner_black_24dp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M7,3H4v3H2V1h5V3zM22,6V1h-5v2h3v3H22zM7,21H4v-3H2v5h5V21zM20,18v3h-3v2h5v-5H20zM19,18c0,1.1 -0.9,2 -2,2H7c-1.1,0 -2,-0.9 -2,-2V6c0,-1.1 0.9,-2 2,-2h10c1.1,0 2,0.9 2,2V18zM15,8H9v2h6V8zM15,11H9v2h6V11zM15,14H9v2h6V14z"
android:fillColor="#000000"/>
</vector>
9 changes: 9 additions & 0 deletions app/src/main/res/drawable/mic_none_black_24dp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,14c1.66,0 2.99,-1.34 2.99,-3L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zM10.8,4.9c0,-0.66 0.54,-1.2 1.2,-1.2 0.66,0 1.2,0.54 1.2,1.2l-0.01,6.2c0,0.66 -0.53,1.2 -1.19,1.2 -0.66,0 -1.2,-0.54 -1.2,-1.2L10.8,4.9zM17.3,11c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11L5,11c0,3.41 2.72,6.23 6,6.72L11,21h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"
android:fillColor="#000000"/>
</vector>
6 changes: 6 additions & 0 deletions app/src/main/res/drawable/radius_btn.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>

<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="30dp"/>
<solid android:color="@android:color/darker_gray" />
</shape>
12 changes: 12 additions & 0 deletions app/src/main/res/drawable/restart_alt_black_24dp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,5V2L8,6l4,4V7c3.31,0 6,2.69 6,6c0,2.97 -2.17,5.43 -5,5.91v2.02c3.95,-0.49 7,-3.85 7,-7.93C20,8.58 16.42,5 12,5z"
android:fillColor="#000000"/>
<path
android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02C8.17,18.43 6,15.97 6,13z"
android:fillColor="#000000"/>
</vector>
Loading