Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Adaptive UI & new Chat page #23

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
<sub>My take to get privacy friendly, offline AI models accessible to as many people as possible. The cutting edge offline AI models such as [Segment Anything](https://ai.facebook.com/research/publications/segment-anything/), [LLaMA](https://github.com/facebookresearch/llama) and [Whisper](https://github.com/openai/whisper) have been optimised by many other developers in various forms and sizes. The small to medium sizes are the ones I'm interested in.</sub>
</div>

<center>

[Skip to App Preview](#app-preview)

</center>

## Getting Started

Unlike the majority of frontends for AI models, this project aims to be easier than even those with 1-click installers to launch a web app.
Expand Down Expand Up @@ -38,7 +44,7 @@ Stats: mid-70s. Great memory. Always forgets my date of birth, yet somehow remem

That's him 👇

<img alt="Shady's Daddy" src="shady_ai_flutter/assets/dad_the_benchmark.png" height=256>
<img alt="Shady's Daddy" src="shady_ai_flutter/assets/images/dad_cutout.png" height=256>

## Goal

Expand Down Expand Up @@ -90,6 +96,14 @@ Q: Why the name Shady.ai?

A: Because it's shady. It's shady because it's not using the internet. Wait, that doesn't make any sense. I'll think of something better.

## Verified Systems

My daily driver:

- macOS: Ventura - 13.3.1 (22E261)
- Chip: M1
- Memory: 16 GB

## Disclaimer

This project is not affiliated with Facebook, OpenAI or any other company. This is a personal project made by a hobbyist.
Expand All @@ -104,4 +118,6 @@ Feel free to open an issue or a pull request. I'm open to any suggestions.

## App Preview

![shadyai_eval_ok](https://user-images.githubusercontent.com/5500332/234497371-eeb5be33-f884-4a90-b977-0d5f162641da.gif)
### macOS

<image alt="Screenshot of ShadyAI for MacOS - Launchpad" height=256 src="assets/macos_preview_0.png"/>
Binary file added assets/macos_preview_0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 2 additions & 17 deletions lefthook.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,11 @@ pre-push:
flutter-test:
root: shady_ai_flutter/
run: flutter test
fail_text: "Could not pass tests"
fail_text: "Could not pass tests"
flutter-analyze:
root: "shady_ai_flutter/"
run: |
flutter analyze
flutter pub run dart_code_metrics:metrics analyze lib
flutter-check-unused:
root: shady_ai_flutter/
run: |
flutter pub run dart_code_metrics:metrics check-unused-files lib --exclude="{/**.g.dart,/**.freezed.dart}"
flutter pub run dart_code_metrics:metrics check-unused-code lib --exclude="{/**.g.dart,/**.freezed.dart}"

# Note: commit-msg hook takes a single parameter,
# the name of the file that holds the proposed commit log message.
Expand All @@ -38,13 +32,4 @@ pre-commit:
commands:
dart-fix:
root: shady_ai_flutter/
glob: '*.dart' # Find all .dart files
exclude: '*.{g,freezed}.dart' # Exclude generated dart files
run: dart fix --apply "{staged_files}"
stage_fixed: true # Stage the fixed files
dart-format:
root: shady_ai_flutter/
glob: '*.dart' # Find all .dart files
exclude: '*.{g,freezed}.dart' # Exclude generated dart files
run: dart format "{staged_files}" # Format staged files
stage_fixed: true # Stage the fixed files
run: dart fix --apply
1 change: 1 addition & 0 deletions shady_ai_flutter/.mason/bricks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"feature_brick":"/Users/brutalcoding/.mason-cache/hosted/registry.brickhub.dev/feature_brick_0.6.2"}
15 changes: 14 additions & 1 deletion shady_ai_flutter/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
include: package:flutter_lints/flutter.yaml

analyzer:
exclude:
- "**/*.g.dart"
plugins:
- dart_code_metrics
- custom_lint

custom_lint:
rules:
- missing_provider_scope: true
- always_specify_types: true
# Enforce new lines before a return statement
- newline_before_return: true


dart_code_metrics:
metrics:
Expand All @@ -9,6 +21,7 @@ dart_code_metrics:
maximum-nesting-level: 5
metrics-exclude:
- test/**
- generated/**
rules:
- avoid-dynamic
- avoid-passing-async-when-sync-expected
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions shady_ai_flutter/lib/chat/domain/chat_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';

final chatProvider = StateNotifierProvider.autoDispose((ref) {
return ChatProvider();
});

class ChatProvider extends StateNotifier<int> {
ChatProvider() : super(0);
}
32 changes: 32 additions & 0 deletions shady_ai_flutter/lib/chat/presentation/chat_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

import '../domain/chat_provider.dart';

/// {@template chat_page}
/// A page that can be used to chat with AI.
/// {@endtemplate}
class ChatPage extends StatelessWidget {
/// {@macro chat_page}
const ChatPage({super.key});

@override
Widget build(BuildContext context) {
return const Scaffold(
key: Key('chat_page_scaffold'),
body: ChatPageBody(),
);
}
}

class ChatPageBody extends HookConsumerWidget {
const ChatPageBody({super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(chatProvider);
return Text(
count.toString(),
);
}
}
111 changes: 111 additions & 0 deletions shady_ai_flutter/lib/common/helpers/file_helper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Load the model and evaluate it
// Returns true if the evaluation was successful
import 'dart:ffi';
import 'dart:io';

import 'package:ffi/ffi.dart';
import 'package:file_selector/file_selector.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path/path.dart' as p;
import 'package:shady_ai/generated/rwkv/rwkv_bindings.g.dart';

Future<XFile?> openFileCheckpoint() async {
const XTypeGroup typeGroup = XTypeGroup(
label: 'model',
extensions: <String>['bin'],
);
final XFile? file =
await openFile(acceptedTypeGroups: <XTypeGroup>[typeGroup]);

if (file == null) {
// Operation was canceled by the user.
return null;
}

return file;
}

// TODO(BrutalCoding): Finish implementing this
// ignore: unused_element
Future<bool> _loadModel(BuildContext context) async {
RWKV? rwkv;
Pointer<rwkv_context>? ctx;
try {
// Get path to AI model to infer
final fileOfAiModel = await openFileCheckpoint();
if (fileOfAiModel == null) {
return false;
}

// Create a temporary file and write the data to it
await fileOfAiModel.readAsBytes();

// Get basename of the file using the path
final fileNameOfYourModel = p.basename(fileOfAiModel.path);

// Create a temporary file and write the data to it
final tempFile = File("${Directory.systemTemp.path}/$fileNameOfYourModel");

// await tempFile.writeAsBytes(data.buffer.asUint8List());
final modelFilePath = tempFile.path;

// Load the dynamic library
rwkv = RWKV(await loadLibrwkv());
final modelFilePathCStr = modelFilePath.toNativeUtf8().cast<Char>();
ctx = rwkv.rwkv_init_from_file(modelFilePathCStr, 1);

final stateBuffer = rwkv.rwkv_get_state_buffer_element_count(ctx);
final Pointer<Float> stateBufferPtr = malloc.allocate<Float>(stateBuffer);

final logitsBuffer = rwkv.rwkv_get_logits_buffer_element_count(ctx);
final Pointer<Float> logitsBufferPtr = malloc.allocate<Float>(logitsBuffer);

// TODO(BrutalCoding): Get the token from the user (e.g. via a text field or calculate it from the input)
const token = 103;

final result = rwkv.rwkv_eval(
ctx,
token,
stateBufferPtr,
stateBufferPtr,
logitsBufferPtr,
);

return result;
} catch (e) {
// Show an error message with ScaffoldMessenger
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error: $e'),
),
);
}
if (rwkv == null || ctx == null) return false;
rwkv.rwkv_free(ctx);

return false;
}

Future<DynamicLibrary> loadLibrwkv() async {
// Load the dynamic library according to the platform
String libFileName;
if (Platform.isMacOS) {
libFileName = 'librwkv.dylib';
// } else if (Platform.isAndroid) {
// libFileName = 'librwkv.dylib';
} else {
throw UnsupportedError('This platform is not supported yet.');
}

// Get the path of the dynamic library
String libPath = p.join('assets', 'rwkv', libFileName);
final data = await rootBundle.load(libPath);

// Create a temporary file and write the data to it
final tempFile = File("${Directory.systemTemp.path}/$libFileName");
await tempFile.writeAsBytes(data.buffer.asUint8List());

// Load the dynamic library from the temporary file
return DynamicLibrary.open(tempFile.path);
}
Loading