Skip to content

Commit

Permalink
Manage models (#7)
Browse files Browse the repository at this point in the history
* Manage models
  • Loading branch information
Otacon authored May 6, 2024
1 parent ef94a18 commit e4837eb
Show file tree
Hide file tree
Showing 22 changed files with 714 additions and 31 deletions.
25 changes: 20 additions & 5 deletions blog/_pages/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,33 @@ This is a rough Roadmap and what to expect in the upcoming versions of Olpaka.
- [x] Check Models
- [x] Check CORS

[Blog Post]({{ site.baseurl }}/releases/2024/04/30/release-v0.2)
[Blog Post]({{ site.baseurl }}/releases/2024/05/04/release-v0.2)

## v1.0 - Models galore
## v0.3 - Models galore
- Manage models (list, pull, remove)

## v1.2 - Makeup time!
[Blog Post]({{ site.baseurl }}/releases/2024/05/06/release-v0.3)

## v0.4 - Spring cleaning
- Global state handling
- Streaming HTTP requests
- Refactor project based on what I've learned
- Add analytics
- Add unit tests

## v0.5 - Settings
- Add settings with some basic user preferences

## v1.0 - Stable time!
- First stable release

## v1.1 - Makeup time!
- Customize model parameters

## v1.3 - You're not alone
## v1.2 - You're not alone
- Connect to remote Ollama instances

## v1.4 - Stats for nerds
## v1.3 - Stats for nerds
- Show performance

## Future development - maybe this will come earlier due to CORS restrictions
Expand Down
37 changes: 37 additions & 0 deletions blog/_posts/2024-05-06-release-v0.3.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
title: "Olpaka 0.3: Models Galore"
date: 2024-05-06 11:40:00 +0100
categories: [ releases ]
gallery:
- url: /assets/images/0.3_models_galore_1.webp
image_path: /assets/images/0.3_models_galore_1.webp
title: "New tab in the home screen"
- url: /assets/images/0.3_models_galore_2.webp
image_path: /assets/images/0.3_models_galore_2.webp
title: "Dialog to input the model name"
- url: /assets/images/0.3_models_galore_3.webp
image_path: /assets/images/0.3_models_galore_3.webp
title: "Downloading state for the model"
---

Howdy! After some tinkering and encountering a few hiccups with
[DIO HttpClient and streaming API calls](https://github.com/cfug/dio/issues/1279), I'm excited to
unveil a new tab on the home screen: the Models management tab.

While implementing this feature, I've realized there are a few areas I need to focus on:

- Finding a workaround for the HTTP client issue;
- Enhancing my understanding
of [Ephemeral vs App state management](https://docs.flutter.dev/data-and-backend/state-mgmt/ephemeral-vs-app);
- Deciding whether to keep or revamp the onboarding process;
- Add analytics to understand user's behaviour (before it's too late :P);

Despite the challenges, this version has been a valuable learning experience in Flutter, web HTTP
calls, and more.

My current goal isn't perfection; it's about adding functionality and gaining insights to eventually
deliver a stable and scalable 1.0 version.

{% include gallery caption="Onboarding screenshots from the app" %}

Ready to give it a spin? Check out the "Olpaka web app" link above! 😊
Binary file added blog/assets/images/0.3_models_galore_1.webp
Binary file not shown.
Binary file added blog/assets/images/0.3_models_galore_2.webp
Binary file not shown.
Binary file added blog/assets/images/0.3_models_galore_3.webp
Binary file not shown.
4 changes: 4 additions & 0 deletions lib/app/di.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import 'package:get_it/get_it.dart';
import 'package:olpaka/chat/di.dart';
import 'package:olpaka/generated/l10n.dart';
import 'package:olpaka/home/di.dart';
import 'package:olpaka/models/di.dart';
import 'package:olpaka/ollama/di.dart';
import 'package:olpaka/onboarding/di.dart';

Expand All @@ -11,5 +13,7 @@ void registerModules() {
l.registerFactory(() => S.current);
registerOllama();
registerOnboarding();
registerHome();
registerChat();
registerModels();
}
12 changes: 12 additions & 0 deletions lib/app/http_client.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:dio/dio.dart';

class HttpClient {
Expand All @@ -15,6 +17,16 @@ class HttpClient {
return _handleResponse(response);
}

Future<HttpResponse> delete(String endpoint, {required Object? data}) async {
Response<String> response;
try {
response = await _client.delete(endpoint, data: data);
} on DioException catch (e) {
return _handleException(e);
}
return _handleResponse(response);
}

Future<HttpResponse> get(String endpoint) async {
Response<String> response;
try {
Expand Down
8 changes: 4 additions & 4 deletions lib/app/router.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:go_router/go_router.dart';
import 'package:olpaka/chat/view.dart';
import 'package:olpaka/home/view.dart';
import 'package:olpaka/onboarding/view.dart';

final router = GoRouter(
Expand All @@ -10,9 +10,9 @@ final router = GoRouter(
builder: (_, __) => const OnboardingScreen(),
),
GoRoute(
name: "Chat",
path: "/chat",
builder: (_, __) => const ChatScreen(),
name: "Home",
path: "/home",
builder: (_, __) => const HomeView(),
)
],
);
7 changes: 4 additions & 3 deletions lib/chat/view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class ChatScreen extends StatelessWidget {
title: event.title,
message: event.message,
positive: event.positive,
positiveAction: () => {viewModel.onRefresh()});
positiveAction: () => {});
}
});
viewModel.onCreate();
Expand Down Expand Up @@ -246,8 +246,9 @@ class _AssistantMessage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(S.current.chat_assistant_name,
style: Theme.of(context).textTheme.titleLarge,
Text(
S.current.chat_assistant_name,
style: Theme.of(context).textTheme.titleLarge,
),
MarkdownBlock(
config: _markdownConfig(context),
Expand Down
8 changes: 8 additions & 0 deletions lib/home/di.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

import 'package:get_it/get_it.dart';
import 'package:olpaka/home/view_model.dart';

registerHome(){
final l = GetIt.instance;
l.registerFactory(() => HomeViewModel());
}
57 changes: 57 additions & 0 deletions lib/home/view.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:olpaka/chat/view.dart';
import 'package:olpaka/generated/l10n.dart';
import 'package:olpaka/home/view_model.dart';
import 'package:olpaka/models/view.dart';
import 'package:stacked/stacked.dart';

class HomeView extends StatelessWidget {
final HomeTab tab;

const HomeView({super.key, this.tab = HomeTab.chat});

@override
Widget build(BuildContext context) {
return ViewModelBuilder<HomeViewModel>.reactive(
viewModelBuilder: () => GetIt.I.get(),
onViewModelReady: (viewModel) {
viewModel.events.listen((event) {
switch (event) {}
});
viewModel.onCreate(tab);
},
builder: (context, viewModel, child) {
final content = switch(viewModel.selectedItem){
0 => const ChatScreen(),
1 => ModelsScreen(),
int() => throw UnimplementedError(),
};
return Row(
children: [
NavigationRail(
groupAlignment: 0.0,
selectedIndex: viewModel.selectedItem,
labelType: NavigationRailLabelType.all,
onDestinationSelected: (index) => viewModel.onItemTapped(index),
destinations: <NavigationRailDestination>[
NavigationRailDestination(
icon: const Icon(Icons.chat),
selectedIcon: const Icon(Icons.chat_outlined),
label: Text(S.current.home_tab_name_chat),
),
NavigationRailDestination(
icon: const Icon(Icons.auto_awesome_outlined),
selectedIcon: const Icon(Icons.auto_awesome),
label: Text(S.current.home_tab_name_models),
),
],
),
const VerticalDivider(thickness: 1, width: 1),
Expanded(child: content)
],
);
},
);
}
}
40 changes: 40 additions & 0 deletions lib/home/view_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import 'dart:async';

import 'package:flutter/foundation.dart';

class HomeViewModel with ChangeNotifier {

final _events = StreamController<HomeEvent>.broadcast();

Stream<HomeEvent> get events =>
_events.stream.map((val) {
return val;
});

HomeViewModel();

int selectedItem = 0;

onCreate(HomeTab tab) async {
selectedItem = switch(tab){
HomeTab.chat => 0,
HomeTab.models => 1,
HomeTab.settings => 2,
};
notifyListeners();
}

onItemTapped(int index) async {
selectedItem = index;
notifyListeners();
}
}

sealed class HomeEvent {}


enum HomeTab {
chat,
models,
settings
}
25 changes: 23 additions & 2 deletions lib/l10n/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@
"chat_you_name" : "You",

"chat_missing_model_dialog_title" : "Missing Model",
"chat_missing_model_dialog_message" : "You've got no AI models installed.\nDownload a model by running `ollama run llama3`. Visit https://ollama.com/library to find more.",
"chat_missing_model_dialog_positive" : "Done",
"chat_missing_model_dialog_message" : "You've got no AI models installed.\nDownload a model from the Models tab",
"chat_missing_model_dialog_positive" : "Ok",

"chat_missing_ollama_dialog_title" : "Ollama not found",
"chat_missing_ollama_dialog_message" : "Looks like Ollama is not installed. Visit https://ollama.com/download to install it and try again.",
"chat_missing_ollama_dialog_positive" : "Done",

"home_tab_name_chat": "Chat",
"home_tab_name_models": "Models",
"home_tab_name_settings": "Settings",

"onboarding_title": "Get Started with Olpaka!",
"onboarding_loading": "Loading...",
"onboarding_action_next": "Done",
Expand All @@ -40,6 +44,23 @@
"onboarding_install_model_outro_2": "Not sure which one to choose? Just use llama3!",
"onboarding_install_model_error": "Can't find any model installed. Please install one before you proceed.",

"models_title": "Models",
"models_state_download": "Downloading...",
"models_dialog_download_model_title": "Download new Model",
"models_dialog_download_model_description": "Pick a model from ollama library and add its name here",
"models_dialog_download_model_text_hint": "Model name",
"models_dialog_download_model_action_positive": "Download",
"models_dialog_download_model_action_negative": "Cancel",

"models_dialog_load_models_error_title": "Unable to retrieve models",
"models_dialog_load_models_error_message": "Please check that Ollama is running and configured correctly",

"models_dialog_download_model_error_title": "Unable to download model",
"models_dialog_download_model_error_message": "Please check that Ollama is running, configured correctly, the model exists in the Ollama library and has not been already downloaded",

"models_dialog_remove_model_error_title": "Unable to delete model",
"models_dialog_remove_model_error_message": "Please check that Ollama il running and configured correctly.",

"error_generic_title": "Error",
"error_generic_message": "Whoops...Something didn't work. Maybe try to restart Olpaka.",
"error_generic_positive": "OK"
Expand Down
8 changes: 8 additions & 0 deletions lib/models/di.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

import 'package:get_it/get_it.dart';
import 'package:olpaka/models/view_model.dart';

registerModels() {
final l = GetIt.instance;
l.registerFactory(() => ModelsViewModel(l.get()));
}
Loading

0 comments on commit e4837eb

Please sign in to comment.