├── .github └── workflows │ └── android.yml ├── .gitignore ├── .idea ├── .gitignore ├── .name ├── compiler.xml ├── deploymentTargetDropDown.xml ├── gradle.xml ├── migrations.xml ├── misc.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── lvnvceo │ │ └── ollamadroid │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── lvnvceo │ │ │ └── ollamadroid │ │ │ ├── ChatActivity.java │ │ │ ├── ChatAdapter.java │ │ │ ├── ChatMessage.java │ │ │ ├── MainActivity.java │ │ │ ├── SettingsActivity.java │ │ │ └── ollama │ │ │ ├── ChatRequest.java │ │ │ ├── ChatResponse.java │ │ │ ├── Messages.java │ │ │ └── OllamaModels.java │ └── res │ │ ├── drawable │ │ ├── baseline_account_circle_24.xml │ │ ├── check_fill0_wght700_grad200_opsz48.xml │ │ ├── close_fill0_wght700_grad200_opsz48.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── settings_fill0_wght200_grad0_opsz24.xml │ │ ├── settings_fill1_wght100_grad_25_opsz24.xml │ │ └── settings_fill1_wght100_grad_25_opsz40.xml │ │ ├── layout │ │ ├── activity_chat.xml │ │ ├── activity_main.xml │ │ ├── activity_settings.xml │ │ ├── appbar.xml │ │ └── chat_message_item.xml │ │ ├── mipmap-anydpi │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── com │ └── lvnvceo │ └── ollamadroid │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: set up JDK 17 17 | uses: actions/setup-java@v3 18 | with: 19 | java-version: '17' 20 | distribution: 'temurin' 21 | cache: gradle 22 | 23 | - name: Grant execute permission for gradlew 24 | run: chmod +x gradlew 25 | - name: Build with Gradle 26 | run: ./gradlew build 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | /app/release/ 17 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | OllamaDroid -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # OllamaDroid 3 | 4 | Ollama + Android = OllamaDroid 5 | 6 | 7 | # THIS IS NO LONGER MAINTAINED 8 | 9 | context: I've been busy with irl stuff and work. I sadly am moving on to other projects. If you'd like make your own OllamaDroid project, fork it, recreate it, doesnt matter. Please, if you have an alternative to OllamaDroid, create an issue and ill post a link to that GitHub to redirect others. 10 | | | | | 11 | |--|--|--| 12 | | ![Image](https://i.imgur.com/NnB4wmY.png) | ![Image](https://i.imgur.com/Db7DAke.png) | ![Image](https://i.imgur.com/1MpnV8a.png) | 13 | 14 | ## Features 15 | 16 | - [x] Text streaming responses 17 | - [ ] Profile customization 18 | - [x] Custom username 19 | - [ ] Custom profile picture 20 | - [ ] System prompt implementation 21 | - [x] Model selection 22 | - [ ] Dark theme / Light theme 23 | 24 | ## Installation 25 | 26 | 1. Clone the repository: `git clone https://github.com/ollamadroid.git` 27 | 2. Open the project in Android Studio. 28 | 3. Build and run the application on your Android device or emulator. 29 | 30 | Alternatively, you can download the APK file from the [releases](https://github.com/lvnvceo/ollamadroid/releases) section and install it directly on your Android device. 31 | 32 | ## Support 33 | 34 | For any inquiries or issues, please [open an issue](https://github.com/lvnvceo/ollamadroid/issues/new) on GitHub. 35 | 36 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | namespace 'com.lvnvceo.ollamadroid' 7 | compileSdk 34 8 | 9 | defaultConfig { 10 | applicationId "com.lvnvceo.ollamadroid" 11 | minSdk 28 12 | targetSdk 34 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | } 31 | 32 | dependencies { 33 | implementation 'com.squareup.okhttp3:okhttp:4.9.0' 34 | implementation 'com.google.code.gson:gson:2.10.1' 35 | implementation 'androidx.appcompat:appcompat:1.6.1' 36 | implementation 'com.google.android.material:material:1.9.0' 37 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 38 | testImplementation 'junit:junit:4.13.2' 39 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 40 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 41 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/lvnvceo/ollamadroid/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.lvnvceo.ollamadroid; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.lvnvceo.ollamadroid", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/lvnvceo/ollamadroid/ChatActivity.java: -------------------------------------------------------------------------------- 1 | package com.lvnvceo.ollamadroid; 2 | 3 | import android.content.Intent; 4 | import android.content.SharedPreferences; 5 | import android.os.Bundle; 6 | import android.os.NetworkOnMainThreadException; 7 | import android.text.Editable; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.Button; 11 | import android.widget.EditText; 12 | import android.widget.ImageButton; 13 | import android.widget.TextView; 14 | 15 | import androidx.appcompat.app.AppCompatActivity; 16 | import androidx.recyclerview.widget.LinearLayoutManager; 17 | import androidx.recyclerview.widget.RecyclerView; 18 | 19 | import com.google.gson.Gson; 20 | import com.lvnvceo.ollamadroid.ollama.ChatRequest; 21 | import com.lvnvceo.ollamadroid.ollama.ChatResponse; 22 | import com.lvnvceo.ollamadroid.ollama.Messages; 23 | 24 | import java.io.IOException; 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | import okhttp3.Call; 29 | import okhttp3.Callback; 30 | import okhttp3.MediaType; 31 | import okhttp3.OkHttpClient; 32 | import okhttp3.Request; 33 | import okhttp3.RequestBody; 34 | import okhttp3.Response; 35 | import okhttp3.ResponseBody; 36 | 37 | public class ChatActivity extends AppCompatActivity { 38 | 39 | private RecyclerView recyclerView; 40 | private ImageButton settingsButton; 41 | private final OkHttpClient okHttpClient = new OkHttpClient(); 42 | private ChatAdapter adapter; 43 | private final Messages llamaMessages = new Messages(); 44 | private final Gson gson = new Gson(); 45 | 46 | // Constants 47 | private static final String SETTINGS_KEY = "settings"; 48 | private static final String MODEL_KEY = "model"; 49 | private static final String OLLAMA_URL_KEY = "ollama_url"; 50 | private static final String SYSTEM_PROMPT = "system_prompt"; 51 | private List messages =new ArrayList<>(); 52 | private TextView modelTextView; 53 | 54 | @Override 55 | protected void onResume() { 56 | super.onResume(); 57 | SharedPreferences sharedPreferences = getApplicationContext().getSharedPreferences(SETTINGS_KEY, MODE_PRIVATE); 58 | modelTextView.setText(sharedPreferences.getString(MODEL_KEY,"")); 59 | messages.clear(); 60 | llamaMessages.messages.clear(); 61 | 62 | } 63 | 64 | @Override 65 | protected void onCreate(Bundle savedInstanceState) { 66 | super.onCreate(savedInstanceState); 67 | setContentView(R.layout.activity_chat); 68 | 69 | recyclerView = findViewById(R.id.recycler_view); 70 | settingsButton = findViewById(R.id.buttonSettings); 71 | recyclerView.setLayoutManager(new LinearLayoutManager(this)); 72 | modelTextView = findViewById(R.id.modelName); 73 | 74 | SharedPreferences sharedPreferences = getApplicationContext().getSharedPreferences(SETTINGS_KEY, MODE_PRIVATE); 75 | adapter = new ChatAdapter(messages); 76 | recyclerView.setAdapter(adapter); 77 | Button sendButton = findViewById(R.id.button_send); 78 | Button stopButton = findViewById(R.id.button_stop); 79 | 80 | // Settings button click listener 81 | settingsButton.setOnClickListener(v -> { 82 | Intent settingsIntent = new Intent(ChatActivity.this, SettingsActivity.class); 83 | startActivity(settingsIntent); 84 | }); 85 | 86 | // Stop button click listener 87 | stopButton.setOnClickListener(v -> { 88 | stopButton.setVisibility(View.INVISIBLE); 89 | sendButton.setVisibility(View.VISIBLE); 90 | }); 91 | 92 | sendButton.setOnClickListener(v -> { 93 | stopButton.setVisibility(View.VISIBLE); 94 | sendButton.setVisibility(View.INVISIBLE); 95 | 96 | EditText editTextMessage = findViewById(R.id.edit_text_message); 97 | Editable newTextMessage = editTextMessage.getText(); 98 | adapter.addMessage(new ChatMessage(R.drawable.baseline_account_circle_24, sharedPreferences.getString("username", "John Doe"), newTextMessage.toString())); 99 | adapter.addMessage(new ChatMessage(R.drawable.ic_launcher_foreground,"AI", "")); 100 | adapter.notifyItemInserted(messages.size() - 1); 101 | recyclerView.smoothScrollToPosition(messages.size() - 1); 102 | llamaMessages.messages.add(new Messages.Message("user", editTextMessage.getText().toString())); 103 | ChatRequest chatRequest = new ChatRequest(sharedPreferences.getString(MODEL_KEY, ""), llamaMessages.messages,true); 104 | RequestBody body = RequestBody.create(gson.toJson(chatRequest), MediaType.parse("application/json")); 105 | Request request = new Request.Builder() 106 | .url(sharedPreferences.getString(OLLAMA_URL_KEY, "")+"/api/chat") 107 | .post(body) 108 | .build(); 109 | 110 | okHttpClient.newCall(request).enqueue(new Callback() { 111 | @Override 112 | public void onFailure(Call call, IOException e) { 113 | e.printStackTrace(); 114 | } 115 | 116 | @Override 117 | public void onResponse(Call call, Response response) throws IOException { 118 | if (!response.isSuccessful()) { 119 | handleResponseError(sendButton, stopButton); 120 | } 121 | 122 | try (ResponseBody responseBody = response.body()) { 123 | if (responseBody != null) { 124 | stopButton.setOnClickListener(v -> { 125 | try { 126 | response.close(); 127 | } catch (NetworkOnMainThreadException ignored) {} 128 | handleResponseError(sendButton,stopButton); 129 | }); 130 | processResponseBody(responseBody, messages, sendButton, stopButton); 131 | } 132 | } 133 | } 134 | }); 135 | editTextMessage.setText(""); 136 | }); 137 | } 138 | 139 | private void handleResponseError(Button sendButton, Button stopButton) { 140 | runOnUiThread(() -> { 141 | stopButton.setVisibility(View.INVISIBLE); 142 | sendButton.setVisibility(View.VISIBLE); 143 | }); 144 | } 145 | 146 | private void processResponseBody(ResponseBody responseBody, List messages, Button sendButton, Button stopButton) { 147 | TextView textViewMessageContent = recyclerView.getLayoutManager().getChildAt(((LinearLayoutManager) recyclerView.getLayoutManager()).getChildCount() - 1).findViewById(R.id.message_content); 148 | ChatResponse chatResponse = new ChatResponse(); 149 | String line; 150 | StringBuilder fullResponse = new StringBuilder(); 151 | 152 | try { 153 | while ((line = responseBody.source().readUtf8Line()) != null) { 154 | 155 | chatResponse = gson.fromJson(line, ChatResponse.class); 156 | fullResponse.append(chatResponse.message.content); 157 | runOnUiThread(() -> { 158 | textViewMessageContent.setText(fullResponse); 159 | recyclerView.smoothScrollToPosition(recyclerView.getScrollBarSize()); 160 | }); 161 | } 162 | 163 | runOnUiThread(() -> { 164 | textViewMessageContent.setText(fullResponse.toString()); 165 | recyclerView.smoothScrollToPosition(recyclerView.getScrollBarSize()); 166 | }); 167 | 168 | if (chatResponse.done) { 169 | addMessage(chatResponse,fullResponse.toString(),stopButton,sendButton); 170 | } 171 | } catch (IOException | NumberFormatException | IllegalStateException e) { 172 | addMessage(chatResponse,fullResponse.toString(),stopButton,sendButton); 173 | e.printStackTrace(); 174 | } 175 | } 176 | private void addMessage(ChatResponse finalChatResponse, String fullResponse,Button stopButton, Button sendButton) { 177 | runOnUiThread(() -> { 178 | adapter.removeMessage(adapter.getItemCount() - 1); 179 | adapter.addMessage(new ChatMessage(R.drawable.ic_launcher_foreground, "AI", fullResponse)); 180 | llamaMessages.messages.add(new Messages.Message(finalChatResponse.message.role, fullResponse)); 181 | stopButton.setVisibility(View.INVISIBLE); 182 | sendButton.setVisibility(View.VISIBLE); 183 | }); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /app/src/main/java/com/lvnvceo/ollamadroid/ChatAdapter.java: -------------------------------------------------------------------------------- 1 | package com.lvnvceo.ollamadroid; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.ImageView; 8 | import android.widget.TextView; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.recyclerview.widget.RecyclerView; 12 | 13 | import java.util.List; 14 | 15 | public class ChatAdapter extends RecyclerView.Adapter { 16 | 17 | private final List messages; 18 | 19 | public ChatAdapter(List messages) { 20 | this.messages = messages; 21 | } 22 | 23 | @NonNull 24 | @Override 25 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 26 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_message_item, parent, false); 27 | return new ViewHolder(view); 28 | } 29 | 30 | @Override 31 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 32 | ChatMessage message = messages.get(position); 33 | holder.profileImage.setImageResource(message.getProfileImage()); 34 | holder.profileName.setText(message.getProfileName()); 35 | holder.messageContent.setText(message.getMessageContent()); 36 | } 37 | 38 | @Override 39 | public int getItemCount() { 40 | return messages.size(); 41 | } 42 | 43 | public static class ViewHolder extends RecyclerView.ViewHolder { 44 | ImageView profileImage; 45 | TextView profileName; 46 | TextView messageContent; 47 | 48 | public ViewHolder(@NonNull View itemView) { 49 | super(itemView); 50 | profileImage = itemView.findViewById(R.id.profile_image); 51 | profileName = itemView.findViewById(R.id.profile_name); 52 | messageContent = itemView.findViewById(R.id.message_content); 53 | } 54 | } 55 | @SuppressLint("NotifyDataSetChanged") 56 | public void addMessage(ChatMessage message) { 57 | messages.add(message); 58 | notifyDataSetChanged(); // Notify adapter of dataset change 59 | } 60 | @SuppressLint("NotifyDataSetChanged") 61 | public void removeMessage(int pos){ 62 | messages.remove(pos); 63 | notifyDataSetChanged(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/lvnvceo/ollamadroid/ChatMessage.java: -------------------------------------------------------------------------------- 1 | package com.lvnvceo.ollamadroid; 2 | 3 | public class ChatMessage { 4 | private final int profileImage; 5 | private final String profileName; 6 | private final String messageContent; 7 | 8 | public ChatMessage(int profileImage, String profileName, String messageContent) { 9 | this.profileImage = profileImage; 10 | this.profileName = profileName; 11 | this.messageContent = messageContent; 12 | } 13 | 14 | public int getProfileImage() { 15 | return profileImage; 16 | } 17 | 18 | public String getProfileName() { 19 | return profileName; 20 | } 21 | 22 | public String getMessageContent() { 23 | return messageContent; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/lvnvceo/ollamadroid/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.lvnvceo.ollamadroid; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.os.AsyncTask; 9 | import android.os.Bundle; 10 | import android.os.Handler; 11 | import android.os.HandlerThread; 12 | import android.view.View; 13 | import android.widget.Button; 14 | import android.widget.EditText; 15 | import android.widget.ImageButton; 16 | import android.widget.ImageView; 17 | import android.widget.TextView; 18 | 19 | import com.google.android.material.snackbar.Snackbar; 20 | 21 | import java.io.IOException; 22 | import java.net.MalformedURLException; 23 | import java.net.URL; 24 | import java.util.Objects; 25 | 26 | import okhttp3.Call; 27 | import okhttp3.Callback; 28 | import okhttp3.OkHttp; 29 | import okhttp3.OkHttpClient; 30 | import okhttp3.Request; 31 | import okhttp3.Response; 32 | 33 | public class MainActivity extends AppCompatActivity { 34 | private OkHttpClient client = new OkHttpClient(); 35 | ImageView connectionStatusImage; 36 | TextView connectionStatusText; 37 | Button goToChatButton; 38 | private EditText editOllamaURL; 39 | @Override 40 | protected void onResume() { 41 | super.onResume(); 42 | getConnectionStatus(); 43 | } 44 | @Override 45 | protected void onCreate(Bundle savedInstanceState) { 46 | super.onCreate(savedInstanceState); 47 | setContentView(R.layout.activity_main); 48 | ImageButton settingsButton = findViewById(R.id.buttonSettings); 49 | connectionStatusImage = findViewById(R.id.imageConnectionStatus); 50 | connectionStatusText = findViewById(R.id.textConnectionStatus); 51 | goToChatButton = findViewById(R.id.goToChatButton); 52 | settingsButton.setOnClickListener(v -> { 53 | Intent settingsIntent = new Intent(MainActivity.this, SettingsActivity.class); 54 | startActivity(settingsIntent); 55 | }); 56 | goToChatButton.setOnClickListener(v -> { 57 | Intent chatIntent = new Intent(MainActivity.this,ChatActivity.class); 58 | startActivity(chatIntent); 59 | }); 60 | getConnectionStatus(); 61 | 62 | } 63 | private void displayError() { 64 | connectionStatusImage.setImageResource(R.drawable.close_fill0_wght700_grad200_opsz48); 65 | connectionStatusText.setText(R.string.connection_test_failed); 66 | goToChatButton.setVisibility(View.INVISIBLE); 67 | } 68 | private void displayOK() { 69 | connectionStatusImage.setImageResource(R.drawable.check_fill0_wght700_grad200_opsz48); 70 | connectionStatusText.setText(R.string.connection_test_successful); 71 | goToChatButton.setVisibility(View.VISIBLE); 72 | 73 | } 74 | private void getConnectionStatus() { 75 | String ollamaURLStr = getApplicationContext().getSharedPreferences("settings", Context.MODE_PRIVATE).getString("ollama_url", null); 76 | 77 | URL ollamaURL; 78 | try { 79 | ollamaURL = new URL(ollamaURLStr); 80 | } catch (MalformedURLException ignored) { 81 | Snackbar.make(findViewById(R.id.textConnectionStatus),"Invalid URL",2) 82 | .show(); 83 | return; 84 | } 85 | if(ollamaURL.getHost().equals("")) { 86 | return; 87 | } 88 | Request request = new Request.Builder() 89 | .url(ollamaURL) 90 | .build(); 91 | client.newCall(request).enqueue(new Callback() { 92 | @Override 93 | public void onFailure(@NonNull Call call, @NonNull IOException e) { 94 | runOnUiThread(() -> { 95 | displayError(); 96 | e.printStackTrace(); 97 | }); 98 | } 99 | 100 | @Override 101 | public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { 102 | if(!response.isSuccessful()) { 103 | runOnUiThread(() -> { 104 | displayError(); 105 | }); 106 | throw new IOException("Unexpected code " + response); 107 | } else { 108 | runOnUiThread(() -> { 109 | displayOK(); 110 | }); 111 | } 112 | } 113 | }); 114 | 115 | } 116 | } -------------------------------------------------------------------------------- /app/src/main/java/com/lvnvceo/ollamadroid/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package com.lvnvceo.ollamadroid; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.graphics.Color; 6 | import android.os.Bundle; 7 | import android.text.Editable; 8 | import android.text.TextWatcher; 9 | import android.view.View; 10 | import android.widget.AdapterView; 11 | import android.widget.ArrayAdapter; 12 | import android.widget.Button; 13 | import android.widget.EditText; 14 | import android.widget.Spinner; 15 | import android.widget.TextView; 16 | 17 | import androidx.annotation.NonNull; 18 | import androidx.appcompat.app.AppCompatActivity; 19 | 20 | import com.google.android.material.snackbar.Snackbar; 21 | import com.google.gson.Gson; 22 | import com.lvnvceo.ollamadroid.ollama.OllamaModels; 23 | 24 | import java.io.IOException; 25 | import java.net.MalformedURLException; 26 | import java.net.URL; 27 | import java.sql.Array; 28 | import java.util.ArrayList; 29 | import java.util.Map; 30 | import java.util.stream.Stream; 31 | 32 | import okhttp3.Call; 33 | import okhttp3.Callback; 34 | import okhttp3.OkHttpClient; 35 | import okhttp3.Request; 36 | import okhttp3.Response; 37 | 38 | public class SettingsActivity extends AppCompatActivity { 39 | private EditText ollamaAPIURL; 40 | private EditText systemPrompt; 41 | private EditText username; 42 | private Spinner modelSpinner; 43 | private OkHttpClient okHttpClient; 44 | private String model; 45 | private SharedPreferences sharedPreferences; 46 | 47 | 48 | 49 | @Override 50 | public void onCreate(Bundle savedInstance) { 51 | super.onCreate(savedInstance); 52 | setContentView(R.layout.activity_settings); 53 | Button saveButton = findViewById(R.id.buttonSave); 54 | okHttpClient = new OkHttpClient(); 55 | systemPrompt = findViewById(R.id.editSystemPrompt); 56 | ollamaAPIURL = findViewById(R.id.editOllamaAPIURL); 57 | username = findViewById(R.id.editUsername); 58 | modelSpinner = findViewById(R.id.spinnerModels); 59 | 60 | sharedPreferences = getApplicationContext().getSharedPreferences(getString(R.string.settings_key), Context.MODE_PRIVATE); 61 | systemPrompt.setText(sharedPreferences.getString(getString(R.string.system_prompt_key), null)); 62 | ollamaAPIURL.setText(sharedPreferences.getString(getString(R.string.ollama_url_key),null)); 63 | username.setText(sharedPreferences.getString(getString(R.string.username_key), null)); 64 | 65 | System.out.println(sharedPreferences.getString(getString(R.string.ollama_url_key), "")); 66 | saveButton.setOnClickListener(v -> { 67 | 68 | SharedPreferences.Editor editor = sharedPreferences.edit(); 69 | editor.putString(getString(R.string.ollama_url_key), ollamaAPIURL.getText().toString()); 70 | editor.putString(getString(R.string.system_prompt_key), systemPrompt.getText().toString()); 71 | editor.putString(getString(R.string.username_key), username.getText().toString().equals("") ? "John Doe":username.getText().toString()); 72 | editor.putString(getString(R.string.model_key), model); 73 | editor.apply(); 74 | finish(); 75 | }); 76 | ollamaAPIURL.addTextChangedListener(new TextWatcher() { 77 | @Override 78 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 79 | 80 | } 81 | 82 | @Override 83 | public void onTextChanged(CharSequence s, int start, int before, int count) { 84 | 85 | } 86 | 87 | @Override 88 | public void afterTextChanged(Editable s) { 89 | try { 90 | getModels(new URL(ollamaAPIURL.getText().toString())); 91 | } catch (MalformedURLException e) { 92 | e.printStackTrace(); 93 | } 94 | } 95 | }); 96 | 97 | modelSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 98 | @Override 99 | public void onItemSelected(AdapterView parent, View view, int position, long id) { 100 | model = parent.getItemAtPosition(position).toString(); 101 | ((TextView) parent.getChildAt(0)).setTextColor(getColor(com.google.android.material.R.color.design_default_color_on_primary)); 102 | } 103 | @Override 104 | public void onNothingSelected(AdapterView parent) { 105 | } 106 | }); 107 | try { 108 | getModels(new URL(sharedPreferences.getString(getString(R.string.ollama_url_key),""))); 109 | } catch (MalformedURLException e) { 110 | e.printStackTrace(); 111 | } 112 | } 113 | private void getModels(URL ollamaURL) { 114 | if(ollamaURL.getHost().equals("")) { 115 | return; 116 | } 117 | Request request = new Request.Builder() 118 | .url(ollamaURL+"/api/tags") 119 | .build(); 120 | 121 | okHttpClient.newCall(request).enqueue(new Callback() { 122 | @Override 123 | public void onFailure(@NonNull Call call, @NonNull IOException e) { 124 | Snackbar.make(findViewById(R.id.editSystemPrompt), "Failed to pull models", 2) 125 | .show(); 126 | } 127 | 128 | @Override 129 | public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { 130 | if(!response.isSuccessful()) { 131 | return; 132 | } 133 | Gson gson = new Gson(); 134 | OllamaModels ollamaModels = gson.fromJson(response.body().string(), OllamaModels.class); 135 | ArrayList ollamaStrings = new ArrayList<>(); 136 | for (OllamaModels.OllamaModel ollamaModel : ollamaModels.models) { 137 | ollamaStrings.add(ollamaModel.name); 138 | } 139 | ArrayAdapter arrayAdapter = new ArrayAdapter<>(getApplicationContext(), android.R.layout.simple_spinner_item, ollamaStrings); 140 | runOnUiThread(() -> { 141 | modelSpinner.setAdapter(arrayAdapter); 142 | arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 143 | modelSpinner.setSelection(arrayAdapter.getPosition(sharedPreferences.getString(getString(R.string.model_key), ""))); 144 | }); 145 | } 146 | }); 147 | 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /app/src/main/java/com/lvnvceo/ollamadroid/ollama/ChatRequest.java: -------------------------------------------------------------------------------- 1 | package com.lvnvceo.ollamadroid.ollama; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class ChatRequest { 6 | public String model; 7 | public ArrayList messages; 8 | public boolean stream; 9 | public ChatRequest(String model, ArrayList message, boolean stream){ 10 | this.model = model; 11 | this.messages = message; 12 | this.stream = stream; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/lvnvceo/ollamadroid/ollama/ChatResponse.java: -------------------------------------------------------------------------------- 1 | package com.lvnvceo.ollamadroid.ollama; 2 | 3 | import java.util.Date; 4 | 5 | public class ChatResponse { 6 | public String model; 7 | public Date created_at; 8 | public Messages.Message message; 9 | public boolean done; 10 | public long total_duration; 11 | public int load_duration; 12 | public int prompt_eval_count; 13 | public int prompt_eval_duration; 14 | public int eval_count; 15 | public long eval_duration; 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/lvnvceo/ollamadroid/ollama/Messages.java: -------------------------------------------------------------------------------- 1 | package com.lvnvceo.ollamadroid.ollama; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class Messages { 6 | public ArrayList messages = new ArrayList<>(); 7 | public static class Message { 8 | public String role; 9 | public String content; 10 | public Message(String role, String string) { 11 | this.role = role; 12 | this.content = string; 13 | 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/lvnvceo/ollamadroid/ollama/OllamaModels.java: -------------------------------------------------------------------------------- 1 | package com.lvnvceo.ollamadroid.ollama; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class OllamaModels { 6 | public ArrayList models; 7 | public class OllamaModel { 8 | 9 | public String name; 10 | public String modified_at; 11 | public Object size; 12 | public String digest; 13 | public Details details; 14 | 15 | private class Details { 16 | public String format; 17 | public String family; 18 | public Object families; 19 | public String parameter_size; 20 | public String quantization_level; 21 | } 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_account_circle_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/check_fill0_wght700_grad200_opsz48.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/close_fill0_wght700_grad200_opsz48.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/settings_fill0_wght200_grad0_opsz24.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/settings_fill1_wght100_grad_25_opsz24.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/settings_fill1_wght100_grad_25_opsz40.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_chat.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 21 | 29 | 37 | 38 | 39 | 40 | 48 | 49 | 55 | 56 | 66 | 67 |