Skip to content

Commit

Permalink
ChatGPT Clone on web (#25)
Browse files Browse the repository at this point in the history
* wip

* basic web chat

* delete unused packages and update readme

* remove stale comment

* update options
  • Loading branch information
depombo authored Jan 29, 2025
1 parent 221b109 commit ece5853
Show file tree
Hide file tree
Showing 60 changed files with 195 additions and 4,190 deletions.
30 changes: 15 additions & 15 deletions .metadata
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.

version:
revision: "d211f42860350d914a5ad8102f9ec32764dc6d06"
revision: "17025dd88227cd9532c33fa78f5250d548d87e9a"
channel: "stable"

project_type: app
Expand All @@ -13,26 +13,26 @@ project_type: app
migration:
platforms:
- platform: root
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
- platform: android
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
- platform: ios
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
- platform: linux
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
- platform: macos
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
- platform: web
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
- platform: windows
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a
base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a

# User provided section

Expand Down
68 changes: 5 additions & 63 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,69 +1,11 @@
# nimbus
# Nimbus, ChatGPT-like Flutter App

Nimbus gives Google's Gemini LLM access to your desktop so it can easily read files and run code for you.
ChatGPT-like interface built on Flutter to target multiple platforms.

# architecture
## Architecture

- Flutter desktop app that targets MacOS and Linux with a single codebase.
- Flutter app
- Google Gemini LLM
- Firebase Firestore is used to preserve chats and messages with security rules to ensure that only authenticated users can read or write their data.
- Firebase Authentication is used to let users log in using email or Google.
- There is no backend. The Flutter client uses a pass-through server proxy to directly call the Google AI Gemini API.
- Firebase Hosting for the landing page

# next

- [ ] Firebase App Check to increase security
- [ ] Use Firebase Storage when files exceed the size limit for direct upload to Google AI Gemini API

# macos app release

1. Build, sign and notarize app in Xcode. Select `Product > Archive` => `Distribute App` => `Direct Distribution` and verify it

```bash
spctl -a -vvv -t install Nimbus.app
```

2. Zip it and upload it to GCP

https://console.cloud.google.com/storage/browser/nimbus-d5268.appspot.com

## macos dmg release

We needed a Developer ID Installer certificate added to our identity via Xcode > Preferences > Accounts

1. Create DMG from App

/~https://github.com/sindresorhus/create-dmg

```bash
create-dmg path/to/Nimbus.app
```

Verify it

```bash
spctl -a -vvv -t install Nimbus\ 1.0.1.dmg
```

2. Notarize and staple DMG with Apple's `notarytool`

https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow#3087734

https://wiki.lazarus.freepascal.org/Notarization_for_macOS_10.14.5%2B

```bash
xcrun notarytool submit Nimbus\ 1.0.1.dmg --keychain-profile "notarytool-password" --wait
xcrun notarytool history --keychain-profile "notarytool-password"
xcrun stapler staple Nimbus\ 1.0.1.dmg
```

Verify it

```bash
spctl -a -vvv -t install Nimbus.app
spctl -a -vvv -t install Nimbus\ 1.0.1.dmg
```

3. Zip it and upload it to GCP

https://console.cloud.google.com/storage/browser/nimbus-d5268.appspot.com
28 changes: 28 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.

# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml

linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule

# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
71 changes: 0 additions & 71 deletions lib/files.dart

This file was deleted.

34 changes: 0 additions & 34 deletions lib/functions.dart

This file was deleted.

56 changes: 19 additions & 37 deletions lib/gemini.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import 'dart:io';
import 'package:google_generative_ai/google_generative_ai.dart';
import 'package:google_generative_ai/src/model.dart'
show createModelWithBaseUri;
import 'package:nimbus/files.dart';
import 'package:nimbus/functions.dart';
import 'package:nimbus/logger.dart';
import 'package:nimbus/user_store.dart';

Expand All @@ -14,19 +11,6 @@ final API_VERSION = 'v1beta';
final BASE_URL =
'https://edge.backmesh.com/v1/proxy/PyHU4LvcdsQ4gm2xeniAFhMyuDl2/aUxjzrA9w7K9auXp6Be8';

final sysMessage = """
You are a world-class programmer and system administrator that can complete any goal by executing code.
When you execute code, it will be executed **on the user's machine**. The user has given you **full and complete permission** to execute any code necessary to complete the task. Execute the code.
You can access the internet. Run **any code** to achieve the goal, and if at first you don't succeed, try again and again.
You can install new packages.
You can process and understand files with the following mimes: ${SUPPORTED_GEMINI_MIMES.join(', ')}.
The user can reference and upload files in their machine which is typically what they reference when they talk about filenames.
Write messages to the user in Markdown.
You are capable of **any** task.
User's OS: ${Platform.operatingSystem}
""";

class GeminiClient {
late GenerativeModel client;

Expand All @@ -43,24 +27,30 @@ class GeminiClient {
_instance = GeminiClient._();
Uri uri = Uri.parse('$BASE_URL/$API_VERSION');
_instance!.client = createModelWithBaseUri(
model: model,
apiKey: token,
baseUri: uri,
systemInstruction: Content.system(sysMessage),
safetySettings: [
SafetySetting(HarmCategory.dangerousContent, HarmBlockThreshold.high)
],
tools: [
Tool(functionDeclarations: getGeminiFnDefs())
]);
model: model,
apiKey: token,
baseUri: uri,
);
return _instance!;
}

chatComplete(List<Message> history, Message userMessage) async {
List<Content> contents = [];
for (var msg in history) {
contents.add(await msg.toGemini());
}
final chat = client.startChat(history: contents);
final content = await userMessage.toGemini();
final resps = await Future.wait(
[chat.sendMessage(content), chat.sendMessage(content)]);
resps.forEach((r) {
print(r.text);
});
}

Stream<ChatResult> chatCompleteStream(
List<Message> history, Message userMessage) async* {
// add the sysmessage again here because otherwise it gets ignored often
// not great because we are using up a lot of the context window
List<Content> contents = [Content.text(sysMessage)];
List<Content> contents = [];
for (var msg in history) {
contents.add(await msg.toGemini());
}
Expand All @@ -71,14 +61,6 @@ class GeminiClient {
await for (var response
in chat.sendMessageStream(await userMessage.toGemini())) {
res.content += response.text ?? '';
List<FunctionCall> functionCalls = response.functionCalls.toList();
if (functionCalls.isNotEmpty) {
for (final functionCall in functionCalls)
res.fnCalls.add(FnCall(
fnArgs: functionCall.args,
fnName: functionCall.name,
fnOutput: {}));
}
Logger.debug('ON chat result for message ${userMessage.docKey()}');
// Logger.debug('Response: ${res.content}');
yield res;
Expand Down
Loading

0 comments on commit ece5853

Please sign in to comment.