Skip to content

Commit

Permalink
Initial commit of Dumbar
Browse files Browse the repository at this point in the history
  • Loading branch information
JerrySievert committed Aug 26, 2023
0 parents commit 608350a
Show file tree
Hide file tree
Showing 17 changed files with 2,347 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
dist
.DS_Store
7 changes: 7 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Copyright © 2023 Jerry Sievert

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
84 changes: 84 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Dumbar

![dumbar icon](media/dumbar-icon.png)

Dumbar is sort of like a smart bar app, but it's ... dumb.

But seriously, Dumbar is a simple menu bar application that gives you quick access to Ollama and any models you have installed or have created. The goal is to give fast access to LLMs to help you with whatever task you feel you're dumb enough to ask a pseudo-AI for help about.

![dumbar in action](media/dumbar.png)

Dumbar uses the [Ollama](https://ollama.ai) API for any queries to an LLM.

## Requirements

In order to run Dumbar, you must have [Ollama](https://ollama.ai) installed. As of the writing of this README, only Apple Silicon Macs are supported.

### Configuration

There are currently two configuration options for Dumbar:

- Ollama Host - this is typically `127.0.0.1` or `localhost`, but you have the option to override it if needed.
- Ollama Port - typically Ollama runs on port `11434`, but you can override the port as well.

### Troubleshooting

The most common issues are that either Ollama is not running, or you have not yet set up any models for Ollama to use.

You can run `ollama list` in a terminal, and it should give you a list of models installed:

```shell
$ ollama list
NAME SIZE MODIFIED
bartender:latest 7.3 GB 5 weeks ago
chef:latest 7.3 GB 10 minutes ago
codellama:7b-instruct 3.8 GB 33 hours ago
codellama:latest 3.8 GB 33 hours ago
llama2:13b 7.3 GB 5 weeks ago
llama2:7b 3.8 GB 12 hours ago
programmer:latest 3.8 GB 5 minutes ago
```

### Example Modelfiles

Dumbar includes some example Modelfiles that you can use to get started:

- [bartender](examples/bartender.modelfile) - `ollama create bartender -f examples/bartender.modelfile`
- [chef](examples/chef.modelfile) - `ollama create chef -f examples/chef.modelfile`
- [programmer](examples/programmer.modelfile) - `ollama create programmer -f examples/programmer.modelfile`

Each tries to inject a fairly simple prompt to help guide responses from the LLMs.

## Building Locally

Dumbar is an Electron.js app, and requires Node.js in order to run and package.

### Setting Up

You will need to install all packages that are required:

```shell
$ yarn
```

### Running

In order to run locally:

```shell
$ yarn run app
```

This will run Dumbar locally from the checked out source code.

### Packaging

```shell
$ yarn run package
```

Dumbar uses `electron-builder` to prepare and package the application. If you are not familiar with `electron-builder`, you can read documentation at https://electron.build/

When ready to package, you will need to change the `identity` in `build/electron-builder.json` to your own certificate profile, otherwise the package will not be signed. Not being signed is not the end of the world, but other users may have to explicitly allow the installation to take place in their security settings.

Official Dumbar packages are signed for your convenience.
210 changes: 210 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/*
* Dumbar - a simple menu bar for LLM.
*
* Copyright 2023 Jerry Sievert
* https://github.com/JerrySievert/Dumbar
*/

/* Our error message, in case anything goes wrong. */
const error_message = `<div class="error">Unable to connect to Ollama.<br><br>Is it running?</div>`;

/*
* read_chunks
*
* Reads streaming data from a reader, returning an iterator.
*/
const read_chunks = (reader) => {
return {
async *[Symbol.asyncIterator]() {
let readResult = await reader.read();
while (!readResult.done) {
yield readResult.value;
readResult = await reader.read();
}
}
};
};

/*
* get_pref
*
* Reads a preference item from localStorage, returning null if it is
* either unset or blank.
*/
const get_pref = (which) => {
const pref = localStorage.getItem(which);
if (pref == '') {
return null;
}

return pref;
};

/*
* get_models
*
* Retrieve all known models from the Ollama API endpoint. On error,
* write the standard error message into the results.
*/
const get_models = async () => {
const host =
get_pref('prefs:host') !== null ? get_pref('prefs:host') : '127.0.0.1';

const port = get_pref('prefs:port') !== null ? get_pref(prefs.port) : 11434;
const url = `http://${host}:${port}`;

try {
fetch(`${url}/api/tags`)
.then(async (response) => {
const models = await response.json();

const eModel = document.getElementById('model');
eModel.innerHTML = '';

for (let model of models.models) {
const option = document.createElement('option');
option.text = model.name;
option.value = model.name;

eModel.add(option);
}
})
.catch((err) => {
document.getElementById('spinner').classList.add('hidden');
resultsWindow.innerHTML = error_message;
});
} catch (err) {
console.log(err);
}
};

/*
* query
*
* Read the model and prompt from the UI and make a query, writing the results
* into the results element as they arrive. On error, write the standard error
* message.
*/
const query = async () => {
const eModel = document.getElementById('model');
const model = eModel.value;

const ePrompt = document.getElementById('prompt');
const prompt = ePrompt.value;

document.getElementById('spinner').classList.remove('hidden');
const body = {
model,
prompt
};

const resultsWindow = document.getElementById('results');
resultsWindow.innerText = '';

let response;

const host = get_pref('prefs:host') ? get_pref('prefs.host') : '127.0.0.1';
const port = get_pref('prefs:port') ? get_pref(prefs.port) : 11434;
const url = `http://${host}:${port}`;

try {
let utf8decoder = new TextDecoder();

fetch(`${url}/api/generate`, {
method: 'post',
body: JSON.stringify(body),
headers: { 'Content-Type': 'application/json' }
})
.then(async (response) => {
document.getElementById('spinner').classList.add('hidden');

const reader = response.body.getReader();

for await (const chunk of read_chunks(reader)) {
let str = utf8decoder.decode(chunk.buffer);
let obj = JSON.parse(str);

if (obj.done) {
return;
} else if (obj.response) {
resultsWindow.innerText += obj.response;
}
}
})
.catch((err) => {
document.getElementById('spinner').classList.add('hidden');
resultsWindow.innerHTML = `<div class="error">Unable to connect to Ollama.<br><br>Is it running?</div>`;
});
} catch (err) {
console.log(err);
}
};

/*
* show_preferences
*
* Show the preferences dialog pane, set the current values.
*/
const show_preferences = () => {
const host = document.getElementById('host');
host.value = localStorage.getItem('prefs:host');

const port = document.getElementById('port');
port.value = localStorage.getItem('prefs:port');

const prefPane = document.getElementById('preferences');
prefPane.classList.remove('hidden');
};

/*
* prefs_cancel
*
* Cancel out of the preferences pane, setting things back to normal.
*/
const prefs_cancel = () => {
const host = document.getElementById('host');
host.value = '';

const port = document.getElementById('port');
port.value = '';

const prefPane = document.getElementById('preferences');
prefPane.classList.add('hidden');
};

/*
* prefs_save
*
* Save the new preferences, then call for the models again from Ollama.
*/
const prefs_save = () => {
const host = document.getElementById('host');
localStorage.setItem('prefs:host', host.value);

const port = document.getElementById('port');
localStorage.setItem('prefs:port', port.value);

const prefPane = document.getElementById('preferences');
prefPane.classList.add('hidden');

const resultsWindow = document.getElementById('results');
resultsWindow.innerText = '';

get_models();
};

/* Set up click handlers for all of the buttons. */
const searchButton = document.getElementById('query');
searchButton.onclick = query;

const prefsButton = document.getElementById('prefs');
prefsButton.onclick = show_preferences;

const saveButton = document.getElementById('save');
saveButton.onclick = prefs_save;

const cancelButton = document.getElementById('cancel');
cancelButton.onclick = prefs_cancel;

/* Start off by retrieving the models. */
get_models();
Binary file added build/app-icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions build/electron-builder.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"appId": "com.legitimatesounding.dumbar",
"mac": {
"category": "public.app-category.reference",
"icon": "./app-icon.png",
"entitlements": "build/entitlements.mac.plist",
"identity": "Jerry Sievert (8W4GSNA7C5)",
"mergeASARs": true
}
}
8 changes: 8 additions & 0 deletions build/entitlements.mac.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
</dict>
</plist>
6 changes: 6 additions & 0 deletions examples/bartender.modelfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Modelfile for creating a drink from a list of ingredients
# Run `ollama create bartender -f ./bartender.modelfile` and then `ollama run bartender` and feed it lists of ingredients to create recipes around.
FROM llama2:13b
SYSTEM """
The instruction will be a list of ingredients. You should generate a cocktail recipe that can be made with common liquor and mixers. You can also include ingredients that most people will find in their pantry every day. The recipe should be 1 drink and you should include a description of what the cocktail will taste like
"""
6 changes: 6 additions & 0 deletions examples/chef.modelfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Modelfile for creating a recipe from a list of ingredients
# Run `ollama create chef -f ./chef.modelfile` and then `ollama run chef` and feed it lists of ingredients to create recipes around.
FROM llama2:13b
SYSTEM """
The instruction will be a list of ingredients. You should generate a food recipe that can be made with common ingredients that can be found in a regular kitchen. You can also include ingredients that most people will find in their pantry every day. The recipe include a description of what the meal will taste like
"""
6 changes: 6 additions & 0 deletions examples/programmer.modelfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Modelfile for a helpful programmer
# Run `ollama create programmer -f ./programmer.modelfile` and then `ollama run programmer` and tell it what you need help with
FROM codellama:7b-instruct
SYSTEM """
You will be acting as a senior software developer mentoring someone in how to write software. Your responses will consist of source code, be clear, and will contain helpful comments about the code
"""

0 comments on commit 608350a

Please sign in to comment.