├── .github └── workflows │ └── release-artifacts.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── docker-compose.yaml ├── pom.xml └── src └── main ├── frontend ├── index.html ├── src │ └── prefers-color-scheme.js └── themes │ └── ollama4j-web-ui │ ├── main-layout.css │ ├── styles.css │ ├── theme.json │ └── views │ └── chat-view.css ├── java └── io │ └── github │ └── ollama4j │ └── webui │ ├── Application.java │ ├── components │ └── Badge.java │ ├── config │ └── OllamaConfig.java │ ├── data │ ├── LibraryModelItem.java │ ├── ModelItem.java │ └── ModelListItem.java │ ├── service │ └── ChatService.java │ └── views │ ├── MainLayout.java │ ├── OllamaConnectionView.java │ └── chat │ ├── ChatView.java │ ├── ChatWithImageView.java │ ├── DownloadedModelsView.java │ └── LibraryModelsView.java └── resources ├── META-INF └── resources │ └── icons │ └── icon.png ├── application.properties ├── banner.txt └── static └── images └── ollama-connection.png /.github/workflows/release-artifacts.yml: -------------------------------------------------------------------------------- 1 | name: Release Artifacts 2 | 3 | on: 4 | push: 5 | tags: 6 | - '**' 7 | 8 | permissions: 9 | contents: read 10 | id-token: write 11 | packages: write 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.ref }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | build-and-publish-jar: 19 | runs-on: ubuntu-latest 20 | 21 | permissions: 22 | contents: write 23 | pull-requests: write 24 | repository-projects: write 25 | 26 | steps: 27 | - uses: actions/checkout@v3 28 | - name: Set up JDK 17 29 | uses: actions/setup-java@v3 30 | with: 31 | java-version: '17' 32 | distribution: 'temurin' 33 | server-id: github # Value of the distributionManagement/repository/id field of the pom.xml 34 | settings-path: ${{ github.workspace }} # location for the settings.xml file 35 | 36 | - name: Set env 37 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV 38 | 39 | - name: Build with Maven 40 | run: mvn -B -Drevision=${{ env.RELEASE_VERSION }} clean package --file pom.xml -Pproduction 41 | 42 | - name: Release Assets 43 | uses: softprops/action-gh-release@v1 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | if: startsWith(github.ref, 'refs/tags/') 47 | with: 48 | files: target/*.jar 49 | 50 | build-and-push-docker-image: 51 | runs-on: ubuntu-latest 52 | 53 | needs: 54 | - build-and-publish-jar 55 | 56 | permissions: 57 | contents: write 58 | pull-requests: write 59 | repository-projects: write 60 | 61 | steps: 62 | - uses: actions/checkout@v3 63 | - name: Set up JDK 17 64 | uses: actions/setup-java@v3 65 | with: 66 | java-version: '17' 67 | distribution: 'temurin' 68 | server-id: github # Value of the distributionManagement/repository/id field of the pom.xml 69 | settings-path: ${{ github.workspace }} # location for the settings.xml file 70 | 71 | - name: Set env 72 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV 73 | 74 | - name: Build with Maven 75 | run: mvn -B -Drevision=${{ env.RELEASE_VERSION }} clean package --file pom.xml -Pproduction 76 | 77 | - name: Log in to Docker Hub 78 | run: docker login -u amithkoujalgi -p ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} 79 | 80 | - name: Build and push Docker image - as tag='latest' 81 | uses: docker/build-push-action@v5 82 | if: startsWith(github.ref, 'refs/tags/') 83 | with: 84 | context: . 85 | file: Dockerfile 86 | push: true 87 | tags: amithkoujalgi/ollama4j-web-ui:latest 88 | 89 | - name: Build and push Docker image - as tag with release version number 90 | uses: docker/build-push-action@v5 91 | if: startsWith(github.ref, 'refs/tags/') 92 | with: 93 | context: . 94 | file: Dockerfile 95 | push: true 96 | tags: amithkoujalgi/ollama4j-web-ui:${{ env.RELEASE_VERSION }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | replay_pid* 25 | 26 | .idea/ 27 | target/ 28 | src/main/frontend/generated 29 | .DS_Store 30 | *.DS_Store 31 | **/.DS_Store 32 | 33 | /src/main/bundles/ 34 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Liberica JDK is a lightweight, fully open-source distribution of OpenJDK, free for all users including commercial 2 | # and production use, and verified by TCK for Java SE. 3 | # 4 | # https://hub.docker.com/r/bellsoft/liberica-openjdk-alpine 5 | # https://bell-sw.com/libericajdk-containers/ 6 | 7 | FROM bellsoft/liberica-openjdk-alpine:17.0.12-10 8 | 9 | LABEL maintainer="Amith Koujalgi " 10 | 11 | RUN apk --no-cache add curl 12 | 13 | RUN apk add fontconfig ttf-dejavu 14 | 15 | COPY target/ollama4j-web-ui-*.jar /app/ollama4j-web-ui.jar 16 | 17 | RUN <> /app/application.properties 18 | server.port=\${SERVER_PORT:8080} 19 | logging.level.org.atmosphere=warn 20 | spring.mustache.check-template-location=false 21 | vaadin.launch-browser=true 22 | vaadin.allowed-packages=com.vaadin,org.vaadin,dev.hilla,io.github.ollama4j 23 | spring.jpa.defer-datasource-initialization=true 24 | ollama.url=\${OLLAMA_HOST_ADDR:http://localhost:11434} 25 | ollama.request-timeout-seconds=120 26 | spring.servlet.multipart.max-file-size=50MB 27 | spring.servlet.multipart.max-request-size=50MB 28 | EOF 29 | 30 | ENV JAVA_OPTS="-Xms512m -Xmx1024m" 31 | 32 | ENV APP_CFG_FILE_PATH="/app/application.properties" 33 | 34 | HEALTHCHECK --interval=5s --timeout=30s --retries=30 CMD curl -I -XGET http://localhost:8080/actuator/health || exit 1 35 | 36 | ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -Dserver.port=8080 -Dspring.config.location=file:$APP_CFG_FILE_PATH -jar /app/ollama4j-web-ui.jar"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Ollama4j 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | mvn clean install 3 | 4 | run-dev: 5 | mvn clean spring-boot:run 6 | 7 | prod: 8 | mvn -Drevision=0.0.1 clean package -Pproduction 9 | 10 | image: 11 | docker build -t amithkoujalgi/ollama4j-web-ui:0.0.1 . 12 | 13 | run-image: 14 | docker run -it -p 9090:8080 -e OLLAMA_HOST_ADDR='http://192.168.29.223:11434' amithkoujalgi/ollama4j-web-ui:0.0.1 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ollama4j Web UI 2 | 3 | A web UI for [Ollama](https://ollama.com/) written in Java 4 | using [Spring Boot](https://spring.io/projects/spring-boot/) and [Vaadin](https://vaadin.com/) framework 5 | and [Ollama4j](https://github.com/amithkoujalgi/ollama4j). 6 | 7 | The goal of the project is to enable Ollama users coming from Java and Spring background to have a fully functional web 8 | UI. 9 | 10 | This project focuses on the raw capabilities of interacting with various models running on Ollama servers. 11 | 12 | ## Table of Contents 13 | 14 | - [How does it work?](#how-does-it-work) 15 | - [Requirements](#requirements) 16 | - [Run the app](#running-the-application) 17 | - [Screenshots](#screenshots) 18 | - [Dev Contributions](#get-involved) 19 | 20 | #### How does it work? 21 | 22 | ```mermaid 23 | flowchart LR 24 | owui[Ollama4j Web UI] 25 | o4j[Ollama4j] 26 | o[Ollama Server] 27 | owui -->|uses| o4j 28 | o4j -->|Communicates with| o; 29 | m[Models] 30 | subgraph Ollama Deployment 31 | direction TB 32 | o -->|Manages| m 33 | end 34 | ``` 35 | 36 | #### Requirements 37 | 38 | ![Java](https://img.shields.io/badge/Java-17_+-blue.svg?style=just-the-message&labelColor=gray) 39 | 40 | ## Running the application 41 | 42 | ### Via Docker 43 | 44 | If you already have a Ollama service running, the easiest way to get started is by using Docker by pointing to the host 45 | address of Ollama. Find the image tags [here](https://hub.docker.com/r/amithkoujalgi/ollama4j-web-ui). 46 | 47 | Run the Docker container by issuing this in your terminal: 48 | 49 | ```shell 50 | docker run -it \ 51 | -p 9090:8080 \ 52 | -e OLLAMA_HOST_ADDR='http://192.168.10.1:11434' \ 53 | amithkoujalgi/ollama4j-web-ui 54 | ``` 55 | 56 | ### Via Docker Compose 57 | 58 | If you want to start the Ollama service and the Ollama Web UI as Docker containers, create a 59 | file called `docker-compose.yaml` and add the following contents in it: 60 | 61 | ```yaml 62 | services: 63 | 64 | ollama: 65 | image: ollama/ollama 66 | ports: 67 | - "11434:11434" 68 | volumes: 69 | - ~/ollama:/root/.ollama 70 | shm_size: 512mb 71 | 72 | ollama4j-web-ui: 73 | image: amithkoujalgi/ollama4j-web-ui 74 | ports: 75 | - "9090:8080" 76 | environment: 77 | OLLAMA_HOST_ADDR: 'http://ollama:11434' 78 | ``` 79 | 80 | Then open up your terminal and then type in: 81 | 82 | ```shell 83 | docker-compose -f /path/to/your/docker-compose.yaml up 84 | ``` 85 | 86 | And then you access the Ollama4j Web UI on http://localhost:9090. 87 | 88 | ### As a standalone JAR 89 | 90 | Download the latest version from [here](https://github.com/ollama4j/ollama4j-web-ui/releases). 91 | 92 | Or, you could download it via command-line. 93 | Just make sure to specify the version you want to download. 94 | 95 | ```shell 96 | VERSION=0.0.1; wget https://github.com/ollama4j/ollama4j-web-ui/releases/download/$VERSION/ollama4j-web-ui-$VERSION.jar 97 | ``` 98 | 99 | ### Configure 100 | 101 | Set environment variables. 102 | 103 | ```shell 104 | export SERVER_PORT=8080 105 | export OLLAMA_HOST_ADDR=http://localhost:11434 106 | ``` 107 | 108 | Or, if you would want to override the base config file, create a file `application.properties` and add the following 109 | configuration. 110 | Update the values of `server.port` and `ollama.url` according to your needs. 111 | 112 | ```shell 113 | server.port=8080 114 | logging.level.org.atmosphere = warn 115 | 116 | spring.mustache.check-template-location = false 117 | spring.servlet.multipart.max-file-size=50MB 118 | spring.servlet.multipart.max-request-size=50MB 119 | 120 | vaadin.launch-browser=true 121 | vaadin.whitelisted-packages = com.vaadin,org.vaadin,dev.hilla,io.github.ollama4j 122 | 123 | ollama.url=http://localhost:11434 124 | ollama.request-timeout-seconds=120 125 | ``` 126 | 127 | ### Run the app 128 | 129 | ```shell 130 | java -jar ollama4j-web-ui-$VERSION.jar \ 131 | --spring.config.location=/path/to/your/application.properties 132 | ``` 133 | 134 | Then open http://localhost:8080 in your browser to access the Ollama4j Web UI. 135 | 136 | ### Screenshots 137 | 138 | Chat 139 | Image Chat 140 | Downloaded Models 141 | Models from Ollama 142 | 143 | ### Improvements 144 | 145 | - [ ] Show errors on the UI. For example, 146 | `io.github.ollama4j.exceptions.OllamaBaseException: model "llama3" not found, try pulling it first`. 147 | - [ ] Settings pane for configuring default params such as `top-p`, `top-k`, etc. 148 | 149 | ### Get Involved 150 | 151 | Contributions are most welcome! Whether it's reporting a bug, proposing an enhancement, or helping 152 | with code - any sort 153 | of contribution is much appreciated. 154 | 155 | ### Credits 156 | 157 | The project is inspired by the awesome [ollama4j-ui](https://github.com/AgentSchmecker/ollama4j-ui) project 158 | by [@AgentSchmecker](https://github.com/AgentSchmecker). 159 | 160 | The nomenclature has been adopted from the incredible [Ollama](https://ollama.ai/) 161 | project. 162 | 163 | **Thanks to the amazing contributors** 164 | 165 |

166 | 167 | 168 | 169 |

-------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | ollama: 4 | image: ollama/ollama 5 | ports: 6 | - "11434:11434" 7 | volumes: 8 | - ~/ollama:/root/.ollama 9 | shm_size: 512mb 10 | 11 | ollama4j-web-ui: 12 | image: amithkoujalgi/ollama4j-web-ui 13 | ports: 14 | - "9090:8080" 15 | environment: 16 | OLLAMA_HOST_ADDR: 'http://ollama:11434' -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | io.github.ollama4j 8 | ollama4j-web-ui 9 | ollama4j-web-ui 10 | ${revision} 11 | jar 12 | 13 | 14 | 15 | 17 16 | 24.6.6 17 | 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-parent 22 | 3.2.2 23 | 24 | 25 | 26 | 27 | Vaadin Directory 28 | https://maven.vaadin.com/vaadin-addons 29 | 30 | false 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | com.vaadin 39 | vaadin-bom 40 | ${vaadin.version} 41 | pom 42 | import 43 | 44 | 45 | 46 | 47 | 48 | 49 | github 50 | ollama4j-web-ui 51 | https://github.com/ollama4j/ollama4j-web-ui.git 52 | 53 | 54 | 55 | 56 | 57 | io.github.ollama4j 58 | ollama4j 59 | 1.0.93 60 | 61 | 62 | org.apache.commons 63 | commons-lang3 64 | 3.14.0 65 | 66 | 67 | com.vaadin 68 | 69 | vaadin 70 | 71 | 72 | com.vaadin 73 | vaadin-spring-boot-starter 74 | 75 | 76 | org.parttio 77 | line-awesome 78 | 2.0.0 79 | 80 | 81 | org.springframework.boot 82 | spring-boot-starter-validation 83 | 84 | 85 | org.springframework.boot 86 | spring-boot-devtools 87 | true 88 | 89 | 90 | org.springframework.boot 91 | spring-boot-starter-test 92 | test 93 | 94 | 95 | com.vaadin 96 | vaadin-testbench-junit5 97 | test 98 | 99 | 100 | org.projectlombok 101 | lombok 102 | 1.18.30 103 | 104 | 105 | 106 | 107 | spring-boot:run 108 | 109 | 110 | org.springframework.boot 111 | spring-boot-maven-plugin 112 | 113 | 114 | 115 | com.vaadin 116 | vaadin-maven-plugin 117 | ${vaadin.version} 118 | 119 | 120 | 121 | prepare-frontend 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | production 133 | 134 | 135 | 136 | com.vaadin 137 | vaadin-core 138 | 139 | 140 | com.vaadin 141 | vaadin-dev 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | com.vaadin 150 | vaadin-maven-plugin 151 | ${vaadin.version} 152 | 153 | 154 | 155 | build-frontend 156 | 157 | compile 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | it 167 | 168 | 169 | 170 | org.springframework.boot 171 | spring-boot-maven-plugin 172 | 173 | 174 | start-spring-boot 175 | pre-integration-test 176 | 177 | start 178 | 179 | 180 | 181 | stop-spring-boot 182 | post-integration-test 183 | 184 | stop 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | org.apache.maven.plugins 193 | maven-failsafe-plugin 194 | 195 | 196 | 197 | integration-test 198 | verify 199 | 200 | 201 | 202 | 203 | false 204 | true 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /src/main/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/frontend/src/prefers-color-scheme.js: -------------------------------------------------------------------------------- 1 | window.applyTheme = () => { 2 | const theme = window.matchMedia("(prefers-color-scheme: dark)").matches 3 | ? "dark" 4 | : ""; 5 | document.documentElement.setAttribute("theme", theme); 6 | }; 7 | window 8 | .matchMedia("(prefers-color-scheme: dark)") 9 | .addListener(window.applyTheme); 10 | window.applyTheme(); -------------------------------------------------------------------------------- /src/main/frontend/themes/ollama4j-web-ui/main-layout.css: -------------------------------------------------------------------------------- 1 | vaadin-scroller[slot="drawer"] { 2 | padding: var(--lumo-space-s); 3 | } 4 | 5 | vaadin-side-nav-item vaadin-icon { 6 | padding: 0; 7 | } 8 | 9 | [slot="drawer"]:is(header, footer) { 10 | display: flex; 11 | align-items: center; 12 | gap: var(--lumo-space-s); 13 | padding: var(--lumo-space-s) var(--lumo-space-m); 14 | min-height: var(--lumo-size-xl); 15 | box-sizing: border-box; 16 | } 17 | 18 | [slot="drawer"]:is(header, footer):is(:empty) { 19 | display: none; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/frontend/themes/ollama4j-web-ui/styles.css: -------------------------------------------------------------------------------- 1 | @import url('./main-layout.css'); 2 | @import url('./views/chat-view.css'); -------------------------------------------------------------------------------- /src/main/frontend/themes/ollama4j-web-ui/theme.json: -------------------------------------------------------------------------------- 1 | { 2 | "lumoImports" : [ "typography", "color", "spacing", "badge", "utility" ] 3 | } -------------------------------------------------------------------------------- /src/main/frontend/themes/ollama4j-web-ui/views/chat-view.css: -------------------------------------------------------------------------------- 1 | @media screen and (max-width: 740px) { 2 | .chat-view { 3 | flex-direction: column-reverse; 4 | } 5 | .chat-view aside { 6 | width: 100% !important; 7 | flex-direction: row; 8 | } 9 | .chat-view aside header { 10 | display: none; 11 | } 12 | .chat-view aside vaadin-tabs { 13 | flex: 1 1 auto; 14 | overflow: auto; 15 | } 16 | .chat-view aside vaadin-tabs vaadin-tab.justify-between { 17 | justify-content: flex-start; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/io/github/ollama4j/webui/Application.java: -------------------------------------------------------------------------------- 1 | package io.github.ollama4j.webui; 2 | 3 | import com.vaadin.flow.component.dependency.JsModule; 4 | import com.vaadin.flow.component.page.AppShellConfigurator; 5 | import com.vaadin.flow.component.page.Push; 6 | import com.vaadin.flow.theme.Theme; 7 | import org.springframework.boot.SpringApplication; 8 | import org.springframework.boot.autoconfigure.SpringBootApplication; 9 | 10 | /** 11 | * The entry point of the Spring Boot application. 12 | * 13 | *

Use the @PWA annotation make the application installable on phones, tablets and some desktop 14 | * browsers. 15 | */ 16 | @SpringBootApplication 17 | @Theme(value = "ollama4j-web-ui") 18 | @Push 19 | @JsModule("./src/prefers-color-scheme.js") 20 | public class Application implements AppShellConfigurator { 21 | 22 | public static void main(String[] args) { 23 | SpringApplication.run(Application.class, args); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/io/github/ollama4j/webui/components/Badge.java: -------------------------------------------------------------------------------- 1 | package io.github.ollama4j.webui.components; 2 | 3 | import com.vaadin.flow.component.Component; 4 | import com.vaadin.flow.component.HasTheme; 5 | import com.vaadin.flow.component.html.Span; 6 | import com.vaadin.flow.theme.lumo.LumoUtility.Padding; 7 | import org.vaadin.lineawesome.LineAwesomeIcon; 8 | 9 | import java.util.stream.Collectors; 10 | import java.util.stream.Stream; 11 | 12 | public class Badge extends Span implements HasTheme { 13 | 14 | private Component icon; 15 | 16 | public Badge() { 17 | addThemeName("badge"); 18 | } 19 | 20 | public Badge(String text) { 21 | this(); 22 | add(new Span(text)); 23 | } 24 | 25 | public Badge(String text, BadgeVariant... variants) { 26 | this(text); 27 | addThemeVariants(variants); 28 | } 29 | 30 | public void setIcon(LineAwesomeIcon icon) { 31 | if (this.icon != null) { 32 | remove(this.icon); 33 | } 34 | this.icon = icon.create(); 35 | this.icon.addClassNames(Padding.XSMALL); 36 | addComponentAsFirst(this.icon); 37 | } 38 | 39 | public void addThemeVariants(BadgeVariant... variants) { 40 | getThemeNames().addAll(Stream.of(variants).map(BadgeVariant::getVariantName).collect(Collectors.toList())); 41 | } 42 | 43 | public enum BadgeVariant { 44 | CONTRAST("contrast"), 45 | ERROR("error"), 46 | PILL("pill"), 47 | PRIMARY("primary"), 48 | SMALL("small"), 49 | SUCCESS("success"), 50 | WARNING("warning"); 51 | 52 | private final String variant; 53 | 54 | BadgeVariant(String variant) { 55 | this.variant = variant; 56 | } 57 | 58 | /** 59 | * Gets the variant name. 60 | * 61 | * @return variant name 62 | */ 63 | public String getVariantName() { 64 | return variant; 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/main/java/io/github/ollama4j/webui/config/OllamaConfig.java: -------------------------------------------------------------------------------- 1 | package io.github.ollama4j.webui.config; 2 | 3 | import io.github.ollama4j.OllamaAPI; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | public class OllamaConfig { 10 | @Value("${ollama.url}") 11 | private String ollamaUrl; 12 | 13 | @Value("${ollama.request-timeout-seconds:120}") 14 | private int ollamaRequestTimeoutSeconds; 15 | 16 | @Bean 17 | OllamaAPI getOllamaAPI() { 18 | OllamaAPI ollamaAPI = new OllamaAPI(ollamaUrl); 19 | ollamaAPI.setVerbose(false); 20 | ollamaAPI.setRequestTimeoutSeconds(ollamaRequestTimeoutSeconds); 21 | return ollamaAPI; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/io/github/ollama4j/webui/data/LibraryModelItem.java: -------------------------------------------------------------------------------- 1 | package io.github.ollama4j.webui.data; 2 | 3 | import io.github.ollama4j.models.response.LibraryModel; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | @Data 8 | @EqualsAndHashCode(callSuper = true) 9 | public class LibraryModelItem extends LibraryModel { 10 | private String popularTagsString; 11 | private String link; 12 | 13 | public String getLink() { 14 | return String.format("https://ollama.com/library/%s", getName()); 15 | } 16 | 17 | public String getPopularTagsString() { 18 | StringBuilder tagsString = new StringBuilder(); 19 | getPopularTags().forEach(tag -> { 20 | if (!tagsString.isEmpty()) { 21 | tagsString.append(", "); 22 | } 23 | tagsString.append(tag); 24 | }); 25 | this.popularTagsString = tagsString.toString(); 26 | return this.popularTagsString; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/io/github/ollama4j/webui/data/ModelItem.java: -------------------------------------------------------------------------------- 1 | package io.github.ollama4j.webui.data; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | @Data 7 | @AllArgsConstructor 8 | public class ModelItem { 9 | private String name; 10 | private String version; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/io/github/ollama4j/webui/data/ModelListItem.java: -------------------------------------------------------------------------------- 1 | package io.github.ollama4j.webui.data; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | @Data 7 | @AllArgsConstructor 8 | public class ModelListItem { 9 | private String name; 10 | private String model; 11 | private String modifiedAt; 12 | private String digest; 13 | private String size; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/io/github/ollama4j/webui/service/ChatService.java: -------------------------------------------------------------------------------- 1 | package io.github.ollama4j.webui.service; 2 | 3 | import io.github.ollama4j.models.ps.ModelsProcessResponse; 4 | import io.github.ollama4j.models.response.LibraryModel; 5 | import io.github.ollama4j.webui.data.ModelItem; 6 | import io.github.ollama4j.webui.data.ModelListItem; 7 | import io.github.ollama4j.OllamaAPI; 8 | import io.github.ollama4j.exceptions.OllamaBaseException; 9 | import io.github.ollama4j.models.chat.*; 10 | 11 | import java.io.File; 12 | import java.io.IOException; 13 | import java.io.Serializable; 14 | import java.net.URISyntaxException; 15 | import java.time.ZoneId; 16 | import java.time.format.DateTimeFormatter; 17 | import java.util.ArrayList; 18 | import java.util.Collection; 19 | import java.util.Collections; 20 | import java.util.List; 21 | import java.util.concurrent.CompletableFuture; 22 | import java.util.concurrent.ThreadPoolExecutor; 23 | 24 | import io.github.ollama4j.models.generate.OllamaStreamHandler; 25 | import io.github.ollama4j.models.response.Model; 26 | import org.apache.commons.io.FileUtils; 27 | import org.springframework.beans.factory.annotation.Autowired; 28 | import org.springframework.stereotype.Component; 29 | 30 | @Component 31 | public class ChatService implements Serializable { 32 | 33 | @Autowired 34 | private OllamaAPI ollamaAPI; 35 | private List messages = new ArrayList<>(); 36 | 37 | public void clearMessages() { 38 | messages.clear(); 39 | } 40 | 41 | public List listLibraryModels() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { 42 | return ollamaAPI.listModelsFromLibrary(); 43 | } 44 | 45 | public Collection getModelItems() 46 | throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { 47 | Collection modelItems = new ArrayList<>(Collections.emptyList()); 48 | ollamaAPI 49 | .listModels() 50 | .forEach(x -> modelItems.add(new ModelItem(x.getName(), x.getModelVersion()))); 51 | return modelItems; 52 | } 53 | 54 | public Collection getImageModelItems() 55 | throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { 56 | Collection modelItems = new ArrayList<>(Collections.emptyList()); 57 | modelItems.add(new ModelItem("llava", "latest")); 58 | return modelItems; 59 | } 60 | 61 | public Collection getModels() 62 | throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { 63 | Collection models = ollamaAPI.listModels(); 64 | Collection modelListItems = new ArrayList<>(); 65 | models.forEach( 66 | m -> { 67 | DateTimeFormatter formatter = 68 | DateTimeFormatter.ofPattern("dd MMM, yyyy hh:mm A").withZone(ZoneId.systemDefault()); 69 | modelListItems.add( 70 | new ModelListItem( 71 | m.getModelName(), 72 | m.getModel(), 73 | m.getModifiedAt().format(formatter), 74 | m.getDigest(), 75 | FileUtils.byteCountToDisplaySize(m.getSize()))); 76 | }); 77 | return modelListItems; 78 | } 79 | 80 | public void ask(String message, String model, OllamaStreamHandler streamHandler) { 81 | OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(model); 82 | OllamaChatRequest ollamaChatRequestModel = builder.withMessages(messages).withMessage(OllamaChatMessageRole.USER, message).build(); 83 | try { 84 | OllamaChatResult chat = ollamaAPI.chat(ollamaChatRequestModel, streamHandler); 85 | messages = chat.getChatHistory(); 86 | chat.getResponse(); 87 | } catch (Exception e) { 88 | throw new RuntimeException(e); 89 | } 90 | } 91 | 92 | public void askWithImages( 93 | String message, List imageFiles, String model, OllamaStreamHandler streamHandler) { 94 | OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(model); 95 | OllamaChatRequest ollamaChatRequestModel = builder 96 | .withMessages(messages) 97 | .withMessage(OllamaChatMessageRole.USER, message, null, imageFiles) 98 | .build(); 99 | try { 100 | OllamaChatResult chat = ollamaAPI.chat(ollamaChatRequestModel, streamHandler); 101 | messages = chat.getChatHistory(); 102 | chat.getResponse(); 103 | } catch (Exception e) { 104 | throw new RuntimeException(e); 105 | } 106 | } 107 | 108 | public boolean isConnected() { 109 | try { 110 | return ollamaAPI.ping(); 111 | } catch (Exception e) { 112 | 113 | } 114 | return false; 115 | } 116 | 117 | public ConnectionInfo getConnectionInfo() { 118 | try { 119 | return new ConnectionInfo("Connected", ollamaAPI.ps()); 120 | } catch (IOException | InterruptedException | OllamaBaseException e) { 121 | // Ignored and status is just not available 122 | } 123 | return ConnectionInfo.NOT_AVAILABLE; 124 | } 125 | 126 | public void pullModel(String modelName) { 127 | CompletableFuture.runAsync(() ->{ 128 | try { 129 | ollamaAPI.pullModel(modelName); 130 | } catch (Exception e) { 131 | throw new RuntimeException(e); 132 | } 133 | }); 134 | 135 | } 136 | 137 | public static class ConnectionInfo { 138 | 139 | public static final ConnectionInfo NOT_AVAILABLE = new ConnectionInfo("Not available", null); 140 | 141 | private final String status; 142 | private final ModelsProcessResponse ps; 143 | 144 | 145 | public ConnectionInfo(String statusText, ModelsProcessResponse ps) { 146 | this.status = statusText; 147 | this.ps = ps; 148 | } 149 | 150 | public boolean isAvailable() { 151 | return ps != null; 152 | } 153 | 154 | public List getAvailableModels() { 155 | return ps.getModels(); 156 | } 157 | 158 | public String getHost() { 159 | // TODO: OllamaAPI.host is private. 160 | return "localhost"; 161 | } 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /src/main/java/io/github/ollama4j/webui/views/MainLayout.java: -------------------------------------------------------------------------------- 1 | package io.github.ollama4j.webui.views; 2 | 3 | import com.vaadin.flow.component.UI; 4 | import com.vaadin.flow.component.applayout.AppLayout; 5 | import com.vaadin.flow.component.applayout.DrawerToggle; 6 | import com.vaadin.flow.component.button.Button; 7 | import com.vaadin.flow.component.html.Footer; 8 | import com.vaadin.flow.component.html.H1; 9 | import com.vaadin.flow.component.html.H2; 10 | import com.vaadin.flow.component.html.Header; 11 | import com.vaadin.flow.component.orderedlayout.Scroller; 12 | import com.vaadin.flow.component.sidenav.SideNav; 13 | import com.vaadin.flow.component.sidenav.SideNavItem; 14 | import com.vaadin.flow.dom.ThemeList; 15 | import com.vaadin.flow.router.BeforeEnterEvent; 16 | import com.vaadin.flow.router.BeforeEnterObserver; 17 | import com.vaadin.flow.router.PageTitle; 18 | import com.vaadin.flow.theme.lumo.Lumo; 19 | import com.vaadin.flow.theme.lumo.LumoUtility; 20 | import io.github.ollama4j.webui.service.ChatService; 21 | import io.github.ollama4j.webui.views.chat.ChatView; 22 | import io.github.ollama4j.webui.views.chat.ChatWithImageView; 23 | import io.github.ollama4j.webui.views.chat.DownloadedModelsView; 24 | import io.github.ollama4j.webui.views.chat.LibraryModelsView; 25 | import org.vaadin.lineawesome.LineAwesomeIcon; 26 | 27 | /** The main view is a top-level placeholder for other views. */ 28 | public class MainLayout extends AppLayout implements BeforeEnterObserver { 29 | 30 | private H2 viewTitle; 31 | 32 | private final ChatService service; 33 | 34 | public MainLayout(ChatService service) { 35 | this.service = service; 36 | setPrimarySection(Section.DRAWER); 37 | addDrawerContent(); 38 | addHeaderContent(); 39 | 40 | Button darkModeToggleButton = new Button("◑", click -> { 41 | String toggleScript = """ 42 | const theme = document.documentElement.getAttribute('theme'); 43 | if (theme === 'dark') { 44 | document.documentElement.setAttribute('theme', ''); 45 | } else { 46 | document.documentElement.setAttribute('theme', 'dark'); 47 | } 48 | """; 49 | UI.getCurrent().getPage().executeJs(toggleScript); 50 | }); 51 | darkModeToggleButton.setTooltipText("Switch UI Mode to Light or Dark"); 52 | addToDrawer(darkModeToggleButton); 53 | 54 | String expressionToClearCookies = 55 | """ 56 | function deleteAllCookies() { 57 | const cookies = document.cookie.split(";"); 58 | 59 | for (let i = 0; i < cookies.length; i++) { 60 | const cookie = cookies[i]; 61 | const eqPos = cookie.indexOf("="); 62 | const name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie; 63 | document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT"; 64 | } 65 | } 66 | deleteAllCookies(); 67 | """; 68 | UI.getCurrent().getPage().executeJs(expressionToClearCookies); 69 | } 70 | 71 | private void addHeaderContent() { 72 | DrawerToggle toggle = new DrawerToggle(); 73 | toggle.setAriaLabel("Menu toggle"); 74 | 75 | viewTitle = new H2(); 76 | viewTitle.addClassNames(LumoUtility.FontSize.LARGE, LumoUtility.Margin.NONE); 77 | 78 | addToNavbar(true, toggle, viewTitle); 79 | } 80 | 81 | private void addDrawerContent() { 82 | H1 appName = new H1("Ollama4j Web UI"); 83 | appName.addClassNames(LumoUtility.FontSize.LARGE, LumoUtility.Margin.NONE); 84 | Header header = new Header(appName); 85 | 86 | Scroller scroller = new Scroller(createNavigation()); 87 | 88 | addToDrawer(header, scroller, createFooter()); 89 | } 90 | 91 | private SideNav createNavigation() { 92 | SideNav nav = new SideNav(); 93 | nav.addItem(new SideNavItem("Chat", ChatView.class, LineAwesomeIcon.COMMENTS.create())); 94 | nav.addItem( 95 | new SideNavItem("Image-Based Chat", ChatWithImageView.class, LineAwesomeIcon.COMMENT_MEDICAL_SOLID.create())); 96 | nav.addItem( 97 | new SideNavItem("Downloaded Models", DownloadedModelsView.class, LineAwesomeIcon.BRAIN_SOLID.create())); 98 | nav.addItem( 99 | new SideNavItem("Model Library", LibraryModelsView.class, LineAwesomeIcon.SNOWFLAKE_SOLID.create())); 100 | nav.addItem( 101 | new SideNavItem( 102 | "Give us a Star ⭐", 103 | "https://github.com/ollama4j/ollama4j-web-ui", 104 | LineAwesomeIcon.GITHUB.create())); 105 | return nav; 106 | } 107 | 108 | private Footer createFooter() { 109 | Footer layout = new Footer(); 110 | return layout; 111 | } 112 | 113 | 114 | @Override 115 | protected void afterNavigation() { 116 | super.afterNavigation(); 117 | viewTitle.setText(getCurrentPageTitle()); 118 | } 119 | 120 | private String getCurrentPageTitle() { 121 | PageTitle title = getContent().getClass().getAnnotation(PageTitle.class); 122 | return title == null ? "" : title.value(); 123 | } 124 | 125 | @Override 126 | public void beforeEnter(BeforeEnterEvent event) { 127 | // Make a connection check but allow listing models from website 128 | if (!service.isConnected() && !(this.getContent() instanceof LibraryModelsView)) { 129 | UI.getCurrent().navigate(OllamaConnectionView.class); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/io/github/ollama4j/webui/views/OllamaConnectionView.java: -------------------------------------------------------------------------------- 1 | package io.github.ollama4j.webui.views; 2 | 3 | import com.vaadin.flow.component.Text; 4 | import com.vaadin.flow.component.UI; 5 | import com.vaadin.flow.component.button.Button; 6 | import com.vaadin.flow.component.html.*; 7 | import com.vaadin.flow.component.orderedlayout.VerticalLayout; 8 | import com.vaadin.flow.router.Route; 9 | import com.vaadin.flow.theme.lumo.LumoUtility; 10 | import io.github.ollama4j.webui.service.ChatService; 11 | import io.github.ollama4j.webui.views.chat.LibraryModelsView; 12 | 13 | @Route(value = "/ollama-connection", layout = MainLayout.class) 14 | public class OllamaConnectionView extends VerticalLayout { 15 | 16 | private final ChatService service; 17 | 18 | public OllamaConnectionView(ChatService service) { 19 | this.service = service; 20 | setSpacing(false); 21 | 22 | Image img = new Image("images/ollama-connection.png", "Ollama connection"); 23 | img.setWidth("200px"); 24 | add(img); 25 | 26 | if (service.isConnected()) { 27 | H2 header = new H2("Connected"); 28 | header.addClassNames(LumoUtility.Margin.Top.XLARGE, LumoUtility.Margin.Bottom.MEDIUM); 29 | add(header); 30 | // Show connection details 31 | ChatService.ConnectionInfo info = service.getConnectionInfo(); 32 | Paragraph paragraph = new Paragraph("Running %d local models at %s".formatted(info.getAvailableModels().size(), info.getHost())); 33 | add(paragraph); 34 | if (info.getAvailableModels().size() ==0) { 35 | add(new Button("Find models here", e -> { 36 | UI.getCurrent().navigate(LibraryModelsView.class); 37 | })); 38 | } 39 | } else { 40 | H2 header = new H2("Could not connect to Ollama :("); 41 | header.addClassNames(LumoUtility.Margin.Top.XLARGE, LumoUtility.Margin.Bottom.MEDIUM); 42 | add(header); 43 | ChatService.ConnectionInfo info = service.getConnectionInfo(); 44 | add( 45 | new Paragraph("Please make sure your Ollama is running at %s".formatted(info.getHost())), 46 | new Button("Try again", e -> { 47 | UI.getCurrent().getPage().reload(); 48 | }), 49 | new Paragraph("Here is example how to run Ollama in local Docker without GPU:") 50 | ); 51 | Pre instructions = new Pre(""" 52 | docker run -d -p 11434:11434 \\ 53 | -v ollama:/root/.ollama \\ 54 | --name ollama \\ 55 | ollama/ollama 56 | """); 57 | instructions.getStyle().set("text-align", "left"); 58 | add(instructions); 59 | add(new Anchor("https://github.com/ollama4j/ollama4j", "More instructions")); 60 | } 61 | 62 | setSizeFull(); 63 | setJustifyContentMode(JustifyContentMode.CENTER); 64 | setDefaultHorizontalComponentAlignment(Alignment.CENTER); 65 | getStyle().set("text-align", "center"); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/io/github/ollama4j/webui/views/chat/ChatView.java: -------------------------------------------------------------------------------- 1 | package io.github.ollama4j.webui.views.chat; 2 | 3 | import com.vaadin.flow.component.ScrollOptions; 4 | import com.vaadin.flow.component.combobox.ComboBox; 5 | import com.vaadin.flow.component.messages.MessageInput; 6 | import com.vaadin.flow.component.messages.MessageInputI18n; 7 | import com.vaadin.flow.component.messages.MessageList; 8 | import com.vaadin.flow.component.messages.MessageListItem; 9 | import com.vaadin.flow.component.orderedlayout.VerticalLayout; 10 | import com.vaadin.flow.router.PageTitle; 11 | import com.vaadin.flow.router.Route; 12 | import com.vaadin.flow.router.RouteAlias; 13 | import io.github.ollama4j.webui.data.ModelItem; 14 | import io.github.ollama4j.webui.service.ChatService; 15 | import io.github.ollama4j.webui.views.MainLayout; 16 | import io.github.ollama4j.exceptions.OllamaBaseException; 17 | 18 | import java.io.IOException; 19 | import java.net.URISyntaxException; 20 | import java.time.Instant; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.Optional; 24 | 25 | /** 26 | * The main view contains a text field for getting the user name and a button that shows a greeting 27 | * message in a notification. 28 | */ 29 | @PageTitle("Chat") 30 | @Route(value = "chat", layout = MainLayout.class) 31 | @RouteAlias(value = "", layout = MainLayout.class) 32 | public class ChatView extends VerticalLayout { 33 | 34 | private ChatService chatService; 35 | 36 | private MessageList chat; 37 | private MessageInput input; 38 | private String modelSelected; 39 | private List chatEntries = new ArrayList<>(); 40 | 41 | public ChatView(ChatService chatService) { 42 | this.chatService = chatService; 43 | // H5 header = new H5("Model: " + chatService.getOllamaModel()); 44 | 45 | ComboBox modelsDropdown = new ComboBox<>("Models"); 46 | try { 47 | modelsDropdown.setItems(chatService.getModelItems()); 48 | } catch (OllamaBaseException | IOException | URISyntaxException | InterruptedException e) { 49 | throw new RuntimeException(e); 50 | } 51 | modelsDropdown.setItemLabelGenerator(ModelItem::getName); 52 | modelsDropdown.setWidthFull(); 53 | modelsDropdown.setMaxWidth("1200px"); 54 | 55 | try { 56 | Optional model = chatService.getModelItems().stream().findFirst(); 57 | if (model.isPresent()) { 58 | modelsDropdown.setValue(new ModelItem(model.get().getName(), model.get().getVersion())); 59 | modelSelected = model.get().getName(); 60 | } 61 | } catch (OllamaBaseException | IOException | URISyntaxException | InterruptedException e) { 62 | throw new RuntimeException(e); 63 | } 64 | 65 | modelsDropdown.addValueChangeListener( 66 | event -> { 67 | MessageListItem welcome = 68 | new MessageListItem( 69 | String.format( 70 | "Hi! I'm %s. How may I assist you today?", event.getValue().getName()), 71 | Instant.now(), 72 | "AI"); 73 | welcome.setUserAbbreviation("AI"); 74 | welcome.setUserColorIndex(2); 75 | chat.setItems(welcome); 76 | chatEntries.clear(); 77 | chatService.clearMessages(); 78 | modelSelected = event.getValue().getName(); 79 | // header.setText(modelSelected); 80 | }); 81 | 82 | // add(modelsDropdown); 83 | 84 | chat = new MessageList(); 85 | 86 | MessageListItem welcome = 87 | new MessageListItem( 88 | "Hello there! Select a model to start chatting with AI.", Instant.now(), "AI"); 89 | welcome.setUserAbbreviation("AI"); 90 | welcome.setUserColorIndex(2); 91 | 92 | input = new MessageInput(); 93 | input.setI18n(new MessageInputI18n().setMessage("Ask anything").setSend("Ask")); 94 | 95 | chat.setItems(welcome); 96 | // add(header, chat, input); 97 | add(modelsDropdown, chat, input); 98 | input.addSubmitListener(this::onSubmit); 99 | this.setHorizontalComponentAlignment(Alignment.CENTER, modelsDropdown, chat, input); 100 | this.setPadding(true); 101 | this.setHeightFull(); 102 | chat.setSizeFull(); 103 | input.setWidthFull(); 104 | chat.setMaxWidth("1200px"); 105 | input.setMaxWidth("1200px"); 106 | this.chatService = chatService; 107 | } 108 | 109 | private void onSubmit(MessageInput.SubmitEvent submitEvent) { 110 | MessageListItem question = new MessageListItem(submitEvent.getValue(), Instant.now(), "You"); 111 | question.setUserAbbreviation("You"); 112 | question.setUserColorIndex(1); 113 | chatEntries.add(question); 114 | MessageListItem answer = new MessageListItem("Thinking...", Instant.now(), "AI"); 115 | chatEntries.add(answer); 116 | answer.setUserAbbreviation("AI"); 117 | answer.setUserColorIndex(2); 118 | chat.setItems(chatEntries); 119 | 120 | Thread t = 121 | new Thread( 122 | () -> 123 | chatService.ask( 124 | submitEvent.getValue(), 125 | modelSelected, 126 | (s) -> getUI().ifPresent(ui -> ui.access(() -> { 127 | answer.setText(s); 128 | String jsCode = "document.querySelector('vaadin-message-list vaadin-message:last-child').scrollIntoView({ behavior: 'smooth' });"; 129 | ui.getPage().executeJs(jsCode); 130 | })))); 131 | t.start(); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/io/github/ollama4j/webui/views/chat/ChatWithImageView.java: -------------------------------------------------------------------------------- 1 | package io.github.ollama4j.webui.views.chat; 2 | 3 | import com.vaadin.flow.component.Unit; 4 | import com.vaadin.flow.component.button.Button; 5 | import com.vaadin.flow.component.button.ButtonVariant; 6 | import com.vaadin.flow.component.combobox.ComboBox; 7 | import com.vaadin.flow.component.messages.MessageInput; 8 | import com.vaadin.flow.component.messages.MessageInputI18n; 9 | import com.vaadin.flow.component.messages.MessageList; 10 | import com.vaadin.flow.component.messages.MessageListItem; 11 | import com.vaadin.flow.component.orderedlayout.HorizontalLayout; 12 | import com.vaadin.flow.component.orderedlayout.VerticalLayout; 13 | import com.vaadin.flow.component.upload.Upload; 14 | import com.vaadin.flow.component.upload.receivers.MultiFileMemoryBuffer; 15 | import com.vaadin.flow.router.PageTitle; 16 | import com.vaadin.flow.router.Route; 17 | import com.vaadin.flow.router.RouteAlias; 18 | import com.vaadin.flow.theme.lumo.LumoUtility; 19 | import io.github.ollama4j.webui.data.ModelItem; 20 | import io.github.ollama4j.exceptions.OllamaBaseException; 21 | import io.github.ollama4j.webui.service.ChatService; 22 | import io.github.ollama4j.webui.views.MainLayout; 23 | import java.io.File; 24 | import java.io.FileOutputStream; 25 | import java.io.IOException; 26 | import java.io.InputStream; 27 | import java.net.URISyntaxException; 28 | import java.time.Instant; 29 | import java.util.*; 30 | import java.util.List; 31 | 32 | /** 33 | * The main view contains a text field for getting the user name and a button that shows a greeting 34 | * message in a notification. 35 | */ 36 | @PageTitle("Image-Based Chat") 37 | @Route(value = "image-chat", layout = MainLayout.class) 38 | public class ChatWithImageView extends VerticalLayout { 39 | 40 | private ChatService chatService; 41 | 42 | private MessageList chat; 43 | private MessageInput input; 44 | private String modelSelected; 45 | private List chatEntries = new ArrayList<>(); 46 | private List imageFiles = new ArrayList<>(); 47 | 48 | public ChatWithImageView(ChatService chatService) { 49 | this.chatService = chatService; 50 | // H5 header = new H5("Images: None"); 51 | Button resetButton = new Button("Reset"); 52 | resetButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); 53 | // setHorizontalComponentAlignment(Alignment.END, resetButton); 54 | 55 | ComboBox modelsDropdown = new ComboBox<>("Image Models"); 56 | try { 57 | modelsDropdown.setItems(chatService.getImageModelItems()); 58 | } catch (IOException | URISyntaxException | InterruptedException | OllamaBaseException e) { 59 | throw new RuntimeException(e); 60 | } 61 | modelsDropdown.setItemLabelGenerator(ModelItem::getName); 62 | modelsDropdown.setWidthFull(); 63 | modelsDropdown.setMaxWidth("1200px"); 64 | try { 65 | Optional model = chatService.getImageModelItems().stream().findFirst(); 66 | if (model.isPresent()) { 67 | modelsDropdown.setValue(new ModelItem(model.get().getName(), model.get().getVersion())); 68 | modelSelected = model.get().getName(); 69 | } 70 | } catch (OllamaBaseException | IOException | URISyntaxException | InterruptedException e) { 71 | throw new RuntimeException(e); 72 | } 73 | 74 | modelsDropdown.addValueChangeListener( 75 | event -> { 76 | MessageListItem welcome = 77 | new MessageListItem( 78 | String.format( 79 | "Hi! I'm %s. How may I assist you today?", event.getValue().getName()), 80 | Instant.now(), 81 | "AI"); 82 | welcome.setUserAbbreviation("AI"); 83 | welcome.setUserColorIndex(2); 84 | chat.setItems(welcome); 85 | chatEntries.clear(); 86 | chatService.clearMessages(); 87 | modelSelected = event.getValue().getName(); 88 | // header.setText(modelSelected); 89 | }); 90 | 91 | // add(comboBox); 92 | // add(header); 93 | 94 | HorizontalLayout container = new HorizontalLayout(); 95 | container.addClassNames(LumoUtility.AlignItems.CENTER, LumoUtility.JustifyContent.BETWEEN); 96 | 97 | MultiFileMemoryBuffer buffer = new MultiFileMemoryBuffer(); 98 | Upload upload = new Upload(buffer); 99 | upload.setWidthFull(); 100 | upload.setMaxHeight(70, Unit.PIXELS); 101 | // upload.setHeightFull(); 102 | upload.setMaxFileSize(50 * 1024 * 1024); 103 | 104 | upload.addSucceededListener( 105 | event -> { 106 | String fileName = event.getFileName(); 107 | InputStream inputStream = buffer.getInputStream(fileName); 108 | try { 109 | imageFiles.add(inputStreamToFile(inputStream)); 110 | // header.setText("Images: " + imageFiles.size()); 111 | } catch (IOException e) { 112 | throw new RuntimeException(e); 113 | } 114 | }); 115 | 116 | container.add(upload, resetButton); 117 | 118 | resetButton.addClickListener( 119 | event -> { 120 | imageFiles.clear(); 121 | upload.clearFileList(); 122 | chatEntries.clear(); 123 | // header.setText("Images: None"); 124 | chatService.clearMessages(); 125 | 126 | MessageListItem welcome = 127 | new MessageListItem( 128 | "Hello there! Upload images to start chatting with AI.", Instant.now(), "AI"); 129 | welcome.setUserAbbreviation("AI"); 130 | welcome.setUserColorIndex(2); 131 | chat.setItems(welcome); 132 | }); 133 | 134 | chat = new MessageList(); 135 | 136 | MessageListItem welcome = 137 | new MessageListItem( 138 | "Hello there! Upload images to start chatting with AI.", Instant.now(), "AI"); 139 | welcome.setUserAbbreviation("AI"); 140 | welcome.setUserColorIndex(2); 141 | 142 | input = new MessageInput(); 143 | input.setI18n(new MessageInputI18n().setMessage("Ask anything").setSend("Ask")); 144 | 145 | chat.setItems(welcome); 146 | // add(header, chat, input); 147 | add(modelsDropdown, container, chat, input); 148 | input.addSubmitListener(this::onSubmit); 149 | this.setHorizontalComponentAlignment(Alignment.CENTER, modelsDropdown, container, chat, input); 150 | this.setPadding(true); 151 | this.setHeightFull(); 152 | chat.setSizeFull(); 153 | input.setWidthFull(); 154 | chat.setMaxWidth("1200px"); 155 | input.setMaxWidth("1200px"); 156 | this.chatService = chatService; 157 | } 158 | 159 | private void onSubmit(MessageInput.SubmitEvent submitEvent) { 160 | MessageListItem question = new MessageListItem(submitEvent.getValue(), Instant.now(), "You"); 161 | question.setUserAbbreviation("You"); 162 | question.setUserColorIndex(1); 163 | chatEntries.add(question); 164 | MessageListItem answer = new MessageListItem("Thinking...", Instant.now(), "AI"); 165 | chatEntries.add(answer); 166 | answer.setUserAbbreviation("AI"); 167 | answer.setUserColorIndex(2); 168 | chat.setItems(chatEntries); 169 | 170 | Thread t = 171 | new Thread( 172 | () -> 173 | chatService.askWithImages( 174 | submitEvent.getValue(), 175 | imageFiles, 176 | modelSelected, 177 | (s) -> getUI().ifPresent(ui -> ui.access(() -> answer.setText(s))))); 178 | t.start(); 179 | } 180 | 181 | public static File inputStreamToFile(InputStream inputStream) throws IOException { 182 | File tempFile = File.createTempFile("temp-" + UUID.randomUUID(), ".tmp"); 183 | try (FileOutputStream outputStream = new FileOutputStream(tempFile)) { 184 | byte[] buffer = new byte[1024]; 185 | int bytesRead; 186 | while ((bytesRead = inputStream.read(buffer)) != -1) { 187 | outputStream.write(buffer, 0, bytesRead); 188 | } 189 | } 190 | return tempFile; 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/main/java/io/github/ollama4j/webui/views/chat/DownloadedModelsView.java: -------------------------------------------------------------------------------- 1 | package io.github.ollama4j.webui.views.chat; 2 | 3 | import com.vaadin.flow.component.Component; 4 | import com.vaadin.flow.component.Text; 5 | import com.vaadin.flow.component.button.Button; 6 | import com.vaadin.flow.component.button.ButtonVariant; 7 | import com.vaadin.flow.component.checkbox.CheckboxGroup; 8 | import com.vaadin.flow.component.combobox.MultiSelectComboBox; 9 | import com.vaadin.flow.component.datepicker.DatePicker; 10 | import com.vaadin.flow.component.dependency.Uses; 11 | import com.vaadin.flow.component.grid.Grid; 12 | import com.vaadin.flow.component.grid.GridVariant; 13 | import com.vaadin.flow.component.html.Div; 14 | import com.vaadin.flow.component.html.Span; 15 | import com.vaadin.flow.component.icon.Icon; 16 | import com.vaadin.flow.component.orderedlayout.FlexComponent; 17 | import com.vaadin.flow.component.orderedlayout.FlexLayout; 18 | import com.vaadin.flow.component.orderedlayout.HorizontalLayout; 19 | import com.vaadin.flow.component.orderedlayout.VerticalLayout; 20 | import com.vaadin.flow.component.textfield.TextField; 21 | import com.vaadin.flow.router.PageTitle; 22 | import com.vaadin.flow.router.Route; 23 | import com.vaadin.flow.theme.lumo.LumoUtility; 24 | import io.github.ollama4j.webui.data.ModelItem; 25 | import io.github.ollama4j.webui.data.ModelListItem; 26 | import io.github.ollama4j.webui.service.ChatService; 27 | import io.github.ollama4j.webui.views.MainLayout; 28 | import io.github.ollama4j.exceptions.OllamaBaseException; 29 | 30 | import java.io.IOException; 31 | import java.net.URISyntaxException; 32 | import java.util.Arrays; 33 | import java.util.Collection; 34 | 35 | @PageTitle("Downloaded Models") 36 | @Route(value = "downloaded-models", layout = MainLayout.class) 37 | @Uses(Icon.class) 38 | public class DownloadedModelsView extends Div { 39 | 40 | private Grid grid; 41 | 42 | private Filters filters; 43 | 44 | private ChatService chatService; 45 | 46 | public DownloadedModelsView(ChatService chatService) { 47 | setSizeFull(); 48 | addClassNames("models-view"); 49 | 50 | filters = new Filters(() -> filters.refreshGrid(), grid); 51 | filters.setup(chatService); 52 | 53 | VerticalLayout layout = new VerticalLayout(filters.createGrid()); 54 | layout.setSizeFull(); 55 | layout.setPadding(false); 56 | layout.setSpacing(false); 57 | add(layout); 58 | this.chatService = chatService; 59 | } 60 | 61 | private HorizontalLayout createMobileFilters() { 62 | // Mobile version 63 | HorizontalLayout mobileFilters = new HorizontalLayout(); 64 | mobileFilters.setWidthFull(); 65 | mobileFilters.addClassNames( 66 | LumoUtility.Padding.MEDIUM, LumoUtility.BoxSizing.BORDER, LumoUtility.AlignItems.CENTER); 67 | mobileFilters.addClassName("mobile-filters"); 68 | 69 | Icon mobileIcon = new Icon("lumo", "plus"); 70 | Span filtersHeading = new Span("Filters"); 71 | mobileFilters.add(mobileIcon, filtersHeading); 72 | mobileFilters.setFlexGrow(1, filtersHeading); 73 | mobileFilters.addClickListener( 74 | e -> { 75 | if (filters.getClassNames().contains("visible")) { 76 | filters.removeClassName("visible"); 77 | mobileIcon.getElement().setAttribute("icon", "lumo:plus"); 78 | } else { 79 | filters.addClassName("visible"); 80 | mobileIcon.getElement().setAttribute("icon", "lumo:minus"); 81 | } 82 | }); 83 | return mobileFilters; 84 | } 85 | 86 | public static class Filters extends Div { 87 | 88 | private final TextField name = new TextField("Name"); 89 | private final TextField phone = new TextField("Phone"); 90 | private final DatePicker startDate = new DatePicker("Date of Birth"); 91 | private final DatePicker endDate = new DatePicker(); 92 | private final MultiSelectComboBox occupations = new MultiSelectComboBox<>("Occupation"); 93 | private final CheckboxGroup roles = new CheckboxGroup<>("Role"); 94 | 95 | private ChatService chatService; 96 | private Grid grid; 97 | 98 | public Filters(Runnable onSearch, Grid grid) { 99 | 100 | setWidthFull(); 101 | addClassName("filter-layout"); 102 | addClassNames( 103 | LumoUtility.Padding.Horizontal.LARGE, 104 | LumoUtility.Padding.Vertical.MEDIUM, 105 | LumoUtility.BoxSizing.BORDER); 106 | name.setPlaceholder("First or last name"); 107 | 108 | occupations.setItems("Insurance Clerk", "Mortarman", "Beer Coil Cleaner", "Scale Attendant"); 109 | 110 | roles.setItems("Worker", "Supervisor", "Manager", "External"); 111 | roles.addClassName("double-width"); 112 | 113 | // Action buttons 114 | Button resetBtn = new Button("Reset"); 115 | resetBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY); 116 | resetBtn.addClickListener( 117 | e -> { 118 | name.clear(); 119 | phone.clear(); 120 | startDate.clear(); 121 | endDate.clear(); 122 | occupations.clear(); 123 | roles.clear(); 124 | onSearch.run(); 125 | }); 126 | Button searchBtn = new Button("Search"); 127 | searchBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY); 128 | searchBtn.addClickListener(e -> onSearch.run()); 129 | 130 | Div actions = new Div(resetBtn, searchBtn); 131 | actions.addClassName(LumoUtility.Gap.SMALL); 132 | actions.addClassName("actions"); 133 | 134 | add(name, phone, createDateRangeFilter(), occupations, roles, actions); 135 | } 136 | 137 | private Component createDateRangeFilter() { 138 | startDate.setPlaceholder("From"); 139 | 140 | endDate.setPlaceholder("To"); 141 | 142 | // For screen readers 143 | startDate.setAriaLabel("From date"); 144 | endDate.setAriaLabel("To date"); 145 | 146 | FlexLayout dateRangeComponent = new FlexLayout(startDate, new Text(" – "), endDate); 147 | dateRangeComponent.setAlignItems(FlexComponent.Alignment.BASELINE); 148 | dateRangeComponent.addClassName(LumoUtility.Gap.XSMALL); 149 | 150 | return dateRangeComponent; 151 | } 152 | 153 | public void setup(ChatService chatServiceCfg) { 154 | chatService = chatServiceCfg; 155 | } 156 | 157 | private Component createGrid() { 158 | grid = new Grid<>(ModelListItem.class, false); 159 | grid.addColumn("model").setAutoWidth(true); 160 | grid.addColumn("modifiedAt").setAutoWidth(true); 161 | grid.addColumn("digest").setAutoWidth(true); 162 | grid.addColumn("size").setAutoWidth(true); 163 | 164 | Collection items = null; 165 | try { 166 | items = chatService.getModels(); 167 | items.forEach(item -> { 168 | String[] parts = item.getModifiedAt().split(" "); 169 | if (parts.length > 0) { 170 | String[] newParts = Arrays.copyOf(parts, parts.length - 1); 171 | String modified = String.join(" ", newParts); 172 | item.setModifiedAt(modified); 173 | } 174 | }); 175 | 176 | } catch (OllamaBaseException | IOException | URISyntaxException | InterruptedException e) { 177 | throw new RuntimeException(e); 178 | } 179 | grid.setItems(items); 180 | grid.addThemeVariants(GridVariant.LUMO_NO_BORDER); 181 | grid.addClassNames(LumoUtility.Border.TOP, LumoUtility.BorderColor.CONTRAST_10); 182 | 183 | return grid; 184 | } 185 | 186 | private void refreshGrid() { 187 | grid.getDataProvider().refreshAll(); 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/main/java/io/github/ollama4j/webui/views/chat/LibraryModelsView.java: -------------------------------------------------------------------------------- 1 | package io.github.ollama4j.webui.views.chat; 2 | 3 | import com.vaadin.flow.component.Key; 4 | import com.vaadin.flow.component.Text; 5 | import com.vaadin.flow.component.UI; 6 | import com.vaadin.flow.component.button.Button; 7 | import com.vaadin.flow.component.button.ButtonVariant; 8 | import com.vaadin.flow.component.dependency.Uses; 9 | import com.vaadin.flow.component.dialog.Dialog; 10 | import com.vaadin.flow.component.grid.Grid; 11 | import com.vaadin.flow.component.grid.GridVariant; 12 | import com.vaadin.flow.component.html.*; 13 | import com.vaadin.flow.component.icon.Icon; 14 | import com.vaadin.flow.component.icon.VaadinIcon; 15 | import com.vaadin.flow.component.orderedlayout.FlexComponent; 16 | import com.vaadin.flow.component.orderedlayout.HorizontalLayout; 17 | import com.vaadin.flow.component.orderedlayout.VerticalLayout; 18 | import com.vaadin.flow.router.PageTitle; 19 | import com.vaadin.flow.router.Route; 20 | import io.github.ollama4j.exceptions.OllamaBaseException; 21 | import io.github.ollama4j.models.response.LibraryModel; 22 | import io.github.ollama4j.webui.components.Badge; 23 | import io.github.ollama4j.webui.data.LibraryModelItem; 24 | import io.github.ollama4j.webui.data.ModelListItem; 25 | import io.github.ollama4j.webui.service.ChatService; 26 | import io.github.ollama4j.webui.views.MainLayout; 27 | 28 | import java.io.IOException; 29 | import java.net.URISyntaxException; 30 | import java.util.ArrayList; 31 | import java.util.Collection; 32 | import java.util.List; 33 | import java.util.Set; 34 | import java.util.stream.Collectors; 35 | 36 | @PageTitle("Model Library") 37 | @Route(value = "model-library", layout = MainLayout.class) 38 | @Uses(Icon.class) 39 | public class LibraryModelsView extends Div { 40 | 41 | private Grid grid; 42 | 43 | private ChatService chatService; 44 | 45 | private PullDialog pullDialog; 46 | 47 | private ArrayList items = new ArrayList<>(); 48 | 49 | public LibraryModelsView(ChatService chatService) { 50 | setSizeFull(); 51 | addClassNames("models-view"); 52 | this.chatService = chatService; 53 | this.grid = new Grid<>(LibraryModelItem.class, false); 54 | 55 | // Configure Grid columns 56 | grid.addColumn(LibraryModelItem::getName) 57 | .setHeader("Model") 58 | .setSortable(true) 59 | .setResizable(true); 60 | grid.addColumn(LibraryModelItem::getDescription) 61 | .setHeader("Description") 62 | .setResizable(true); 63 | grid.addColumn(LibraryModelItem::getLastUpdated) 64 | .setHeader("Updated") 65 | .setSortable(true) 66 | .setResizable(true); 67 | grid.addColumn(LibraryModelItem::getPullCount) 68 | .setHeader("Pulls") 69 | .setSortable(true); 70 | grid.addColumn(LibraryModelItem::getTotalTags) 71 | .setHeader("Tags") 72 | .setSortable(true); 73 | 74 | 75 | // Action column 76 | final Collection loadedModels = new ArrayList<>(); 77 | try { 78 | loadedModels.addAll(chatService.getModels()); 79 | } catch (Exception ignored) { } 80 | grid.addComponentColumn(model -> { 81 | Button link = new Button("",new Icon(VaadinIcon.EXTERNAL_LINK), 82 | e -> UI.getCurrent().getPage().open(model.getLink())); 83 | link.setTooltipText("View at ollama.com"); 84 | Button pullButton = new Button("",new Icon(VaadinIcon.DOWNLOAD), 85 | e -> openPullDialog(model)); 86 | pullButton.setEnabled(loadedModels.stream().noneMatch(m -> model.getName().equals(m.getName()))); 87 | pullButton.setTooltipText("Pull model locally"); 88 | return new HorizontalLayout(pullButton,link); 89 | }).setHeader(""); 90 | 91 | grid.addThemeVariants(GridVariant.LUMO_NO_BORDER); 92 | grid.setSizeFull(); 93 | 94 | // Add a listener for item selection 95 | grid.asSingleSelect().addValueChangeListener(event -> { 96 | LibraryModelItem selectedModel = event.getValue(); 97 | if (selectedModel != null) { 98 | } 99 | }); 100 | 101 | add(grid); 102 | refreshGrid(); 103 | } 104 | 105 | private void openPullDialog(LibraryModelItem model) { 106 | // Close if already open 107 | if (pullDialog != null) { 108 | pullDialog.close(); 109 | pullDialog = null; 110 | } 111 | pullDialog = new PullDialog(model); 112 | pullDialog.open(); 113 | } 114 | 115 | private void refreshGrid() { 116 | items = new ArrayList<>(); // Todo: this could be cached 117 | try { 118 | List libraryModels = chatService.listLibraryModels(); 119 | libraryModels.forEach(libraryModel -> { 120 | LibraryModelItem item = new LibraryModelItem(); 121 | item.setName(libraryModel.getName()); 122 | item.setDescription(libraryModel.getDescription()); 123 | item.setPullCount(libraryModel.getPullCount()); 124 | item.setTotalTags(libraryModel.getTotalTags()); 125 | item.setLastUpdated(libraryModel.getLastUpdated()); 126 | item.setPopularTags(libraryModel.getPopularTags()); 127 | items.add(item); 128 | }); 129 | } catch (OllamaBaseException | IOException | URISyntaxException | InterruptedException e) { 130 | throw new RuntimeException(e); 131 | } finally { 132 | grid.setItems(items); 133 | } 134 | } 135 | 136 | /** Dialog for displaying model details before pull. 137 | * 138 | */ 139 | class PullDialog extends Dialog { 140 | 141 | PullDialog(LibraryModelItem model) { 142 | // Dialog for displaying details 143 | setMinWidth("300px"); 144 | setMinHeight("300px"); 145 | setMaxWidth("600px"); 146 | setMaxHeight("600px"); 147 | setCloseOnEsc(true); 148 | setCloseOnOutsideClick(true); 149 | 150 | setHeaderTitle(model.getName()); 151 | VerticalLayout dialogContent = new VerticalLayout(); 152 | dialogContent.setMargin(false); 153 | dialogContent.setPadding(false); 154 | dialogContent.setSizeFull(); 155 | dialogContent.add(new Paragraph(model.getDescription())); 156 | dialogContent.add(new HorizontalLayout(model.getPopularTags() 157 | .stream().map(t -> new Badge(t, Badge.BadgeVariant.SMALL)) 158 | .toList().toArray(new Badge[]{}))); 159 | dialogContent.add(new Span("%s pulls, updated %s".formatted(model.getPullCount(), 160 | model.getLastUpdated()))); 161 | this.add(dialogContent); 162 | 163 | 164 | Button closeButton = new Button("Close", event -> this.close()); 165 | closeButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY); 166 | this.add(closeButton); 167 | closeButton.addClickShortcut(Key.ESCAPE); 168 | Button pullButton = new Button("Pull", new Icon(VaadinIcon.DOWNLOAD), e -> { 169 | chatService.pullModel(model.getName()); 170 | this.close(); 171 | }); 172 | 173 | pullButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); 174 | pullButton.addClickShortcut(Key.ENTER); 175 | HorizontalLayout buttons = new HorizontalLayout(closeButton, pullButton); 176 | buttons.setJustifyContentMode(FlexComponent.JustifyContentMode.END); 177 | buttons.setWidthFull(); 178 | this.getFooter().add(buttons); 179 | 180 | this.addDialogCloseActionListener(l -> { 181 | pullDialog = null; 182 | }); 183 | } 184 | } 185 | } 186 | 187 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollama4j/ollama4j-web-ui/2a156ba37e507039e502cc89323eb4bac61b2bce/src/main/resources/META-INF/resources/icons/icon.png -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=${SERVER_PORT:8080} 2 | logging.level.org.atmosphere = warn 3 | spring.mustache.check-template-location = false 4 | 5 | # Launch the default browser when starting the application in development mode 6 | vaadin.launch-browser=true 7 | # To improve the performance during development. 8 | # For more information https://vaadin.com/docs/flow/spring/tutorial-spring-configuration.html#special-configuration-parameters 9 | vaadin.allowed-packages=com.vaadin,org.vaadin,dev.hilla,io.github.ollama4j 10 | spring.jpa.defer-datasource-initialization = true 11 | 12 | ollama.url=${OLLAMA_HOST_ADDR:http://localhost:11434} 13 | 14 | ollama.request-timeout-seconds=120 15 | 16 | spring.servlet.multipart.max-file-size=50MB 17 | spring.servlet.multipart.max-request-size=50MB -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ___ _ _ _ _ _ __ __ _ _ _ ___ 2 | / _ \| | | __ _ _ __ ___ __ _| || | (_) \ \ / /__| |__ | | | |_ _| 3 | | | | | | |/ _` | '_ ` _ \ / _` | || |_| | \ \ /\ / / _ \ '_ \ | | | || | 4 | | |_| | | | (_| | | | | | | (_| |__ _| | \ V V / __/ |_) | | |_| || | 5 | \___/|_|_|\__,_|_| |_| |_|\__,_| |_|_/ | \_/\_/ \___|_.__/ \___/|___| 6 | |__/ 7 | -------------------------------------------------------------------------------- /src/main/resources/static/images/ollama-connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ollama4j/ollama4j-web-ui/2a156ba37e507039e502cc89323eb4bac61b2bce/src/main/resources/static/images/ollama-connection.png --------------------------------------------------------------------------------